字典类算法
字典所涉及到的题目有如下:
- 两数之和
- 字母异位词分组
- 数独
- 设计LRU
字典使用技巧:
字典在某些场景下很方便:
- 判断重复,某一个元素是否重复出现
- in操作的时间复杂度为常数
- 需要使用复杂数据结构,
key-->[]
- 消灭判断,使用key直接获取value
两数之和
题目:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
方法:
常规思路是可以两次循环找到数组中任意两个不重复元素的和,时间复杂度为O(2)。
高效的做法是将两个数相加等于target,变成判断b --> target-a 在不在字典中。如果在就说明有a + b 等于 target。同时,判断b在不在字典中是常数级别的操作,所以将两层循环变成一层循环。
知识点
:使用了字典的查找高效这一特点
def twoSum(nums, target):
hashmap={}
for i,num in enumerate(nums):
if hashmap.get(target - num) is not None:
return [i,hashmap.get(target - num)]
hashmap[num] = i #这句不能放在if语句之前,解决list中有重复值或target-num=num的情况
字母异位词分组
题目:
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
所有输入均为小写字母。
不考虑答案输出的顺序。
方法:给出的字符串数组,将每一个字符串重新排列之后可以分成三类。进一步说就是将排序之后相同的字符串加入到一个数组中。使用字典的key:value。value为数组,这样的结构能够完成,key就是排序后的字符串。因为排序之后的字符串是数组,所以不能直接作为key值,将其合并成字符串。
知识点
: 字典的复杂格式 key:[]
的使用。要熟练使用collections中的基础数据增强功能
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
from collections import defaultdict # 构造一个key --> []的数据结构
strs_dict = defaultdict(list)
for i in strs:
strs_dict[''.join(sorted(i))].append(i)
return list(strs_dict.values())
有效的数独
题目:
判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。
数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
上图是一个部分填充的有效的数独。
数独部分空格内已填入了数字,空白格用 '.' 表示。
示例 1:
输入:
[
["5","3",".",".","7",".",".",".","."],
["6",".",".","1","9","5",".",".","."],
[".","9","8",".",".",".",".","6","."],
["8",".",".",".","6",".",".",".","3"],
["4",".",".","8",".","3",".",".","1"],
["7",".",".",".","2",".",".",".","6"],
[".","6",".",".",".",".","2","8","."],
[".",".",".","4","1","9",".",".","5"],
[".",".",".",".","8",".",".","7","9"]
]
输出: true
方法:关键点在于判断当前的数字在同行、同列、同一个区域有没有重复。将每一个行中数字保存为一个字典,key是数字,value是出现次数。如果有重复,则value会大于1。同样,每一个列,每一个区域都需要以数字为key,建立字典。最后判断行、列、区域中哪一块有value大于1,则表明数独不存在
知识点
:使用字典判断重复,比起循环查找要高效很多。同时字典不断添加还有一个先后循序,有些场合可以避免重复(两数之和)
def isValidSudoku(self, board):
"""
:type board: List[List[str]]
:rtype: bool
"""
# init data
rows = [{} for i in range(9)]
columns = [{} for i in range(9)]
boxes = [{} for i in range(9)]
# validate a board
for i in range(9):
for j in range(9):
num = board[i][j]
if num != '.':
num = int(num)
box_index = (i // 3 ) * 3 + j // 3
# keep the current cell value
rows[i][num] = rows[i].get(num, 0) + 1
columns[j][num] = columns[j].get(num, 0) + 1
boxes[box_index][num] = boxes[box_index].get(num, 0) + 1
# check if this value has been already seen before
if rows[i][num] > 1 or columns[j][num] > 1 or boxes[box_index][num] > 1:
return False
return True
设计LRU
题目:
设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
set(key, value):将记录(key, value)插入该结构
get(key):返回key对应的value值
要求:
- set和get方法的时间复杂度为O(1)
- 某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
- 当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案
方法:题目中要求set或get的时间复杂度是O(1),这就是意味着添加或者查询元素是不能使用循环,所以必须是字典的数据结构。而set或get某一个元素同时还要让该元素热点最大,结合时间复杂度来考虑就不能用循环解决,只能用插入的顺序。但是总所周知通常字典是一个无序的,所以这里要使用有序字典。从collections
中导入OrderedDict
这种有序字典。有序字典顾名思义就是能够按照插入的顺序保存元素。
知识点
:collections中增强数据结构,结合对时间复杂的要求
#
# lru design
# @param operators int整型二维数组 the ops
# @param k int整型 the k
# @return int整型一维数组
#
class Solution:
def LRU(self , operators , k ):
from collections import OrderedDict
res = []
dic = OrderedDict()
for item in operators:
oper = item[0]
if oper == 1:
key = item[1]
value = item[2]
if key in dic:
dic.pop(key)
else:
if k > 0:
k -= 1
else:
dic.popitem(False)
dic[key] = value
elif oper == 2:
key = item[1]
res.append(dic.get(key, -1))
if key in dic:
dic.move_to_end(key)
return res
总结
字典在算法中出镜的频率很高,这和字典本身的高效性能有关。通常来说一个题如果有一下特征,可以考虑使用字典来解决:
- 相加之和等于多少
- 判断元素是否重复出现
- 对元素分类操作