1 概述
该博客结合leetcode原题介绍了如何利用哈希表降低程序的时间复杂度。
1.1 HashMap实现方式
将key通过一个function:变成一个数字(429),存入大小为30的数组中(取余数得到数组中的下标)。
1.2 Hash碰撞
Function的设计有可能算出重复的数组下标(尽量避免),有许多方法可以解决。此处介绍“拉链法”,即在同一个数组下标处存一个链表,将重复的key链起来。
1.3 更多实现方式
set和map类似,可以用Hash和Tree两种方式实现。
Hash的时间复杂度O(1),Tree的时间复杂度O(log(N)),但是Tree是有序排列,Hash是无序排列。
2 例题
2.1 有效的字母异位词
#Leetcode 242 有效的字母异位词
该题是让我们判断两个字符串是否可以通过字母顺序变换得到一样的结果。
(1)暴力解法
两个字符串分别排序,然后看是否相等。
*时间复杂度:O(NlogN)
*空间复杂度:O(1)
# coding = utf-8
class Solution(object):
def isAnagram(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
return sorted(s)==sorted(t)
(2)利用hashmap
分别遍历两个字符串,将对应字母以及出现的总次数记录在字典中,然后比较字典是否相同。
*时间复杂度:O(N)
*空间复杂度:O(N)
# coding = utf-8
class Solution(object):
def isAnagram(self, s, t):
"""
:type s: str
:type t: str
:rtype: bool
"""
if len(s)!=len(t):
return False
s_map, t_map = {}, {}
for c in s:
if c in s_map:
s_map[c] += 1
else:
s_map[c] = 1
for c in t:
if c in t_map:
t_map[c] += 1
else:
t_map[c] = 1
if s_map==t_map:
return True
else:
return False
2.2 数字之和
#Leetcode 1,15 两数之和,三数之和(高频题)
该题是让我们判断给定的数组中是否可以找到数字的组合满足求和等于target(该题target是0),并返回所有不重复的组合,下面我们以三数之和为例展开。
(1)暴力解法
对数组做三层循环,i从0开始,j从i+1开始,k从j+1开始,遍历完,找到所有的组合,最后去重。(此处使用了一个小技巧,先将数组排序,用set存储结果,避免最后去重的时间复杂度过大)
*时间复杂度:O(N^3)
*空间复杂度:O(1)
def threeSum(self, nums):
if len(nums)<3:
return []
nums.sort()
res = set()
for i in range(0, len(nums)):
for j in range(i+1, len(nums)):
for k in range(j+1, len(nums)):
if nums[i]+nums[j]+nums[k]==0:
res.add((nums[i],nums[j],nums[k]))
return map(list, res)
(2)使用hashmap
该解法考虑到遍历第1,2层时用target减去这两个数即所找的数,因此使用hashmap存储数组可以节省一层循环
*时间复杂度:O(N^2)
*空间复杂度:O(N)
def threeSum(self, nums):
if len(nums)<3:
return []
d = {}
res = set()
for i, v in enumerate(nums):
d[v] = 1
for i in range(0, len(nums)):
for j in range(i+1, len(nums)):
if -nums[i]-nums[j] in d:
res.add(tuple(sorted([nums[i],nums[j],-nums[i]-nums[j])))
return map(list, res)
(3)两边往中间夹
比较反人类的做法,首先排序,依次遍历每个元素,此次观察后续数组的首尾,大于0则尾元素减一位,小于0则首元素加一位。
*时间复杂度:O(N^2)
*空间复杂度:O(1)
def threeSum(self, nums):
if len(nums) < 3:
return []
nums.sort()
res = set()
for i, v in enumerate(nums):
if i>=len(nums)-1: break
j = i + 1
k = len(nums) - 1
while j < k:
if nums[i] + nums[j] + nums[k] > 0:
k -= 1
elif nums[i] + nums[j] + nums[k] < 0:
j += 1
else:
res.add((nums[i], nums[j], nums[k]))
j += 1
k -= 1
return map(list, res)
2.3 K数之和
该题是面试中经常遇到,不在leetcode中出现,原则是将此问题抽象为递归,最小问题就是2-sum。
public void kSum(int[] nums, int k, int target, int from, int end, List<Integer> cur, List<List<Integer>> result){
if(k == 2) {
int left = from;
int right = end;
while(left < right){
int diff = target- nums[left] - nums[right];
if (diff == 0){
List<Integer> r = new ArrayList<>();
r.add(nums[left]);
r.add(nums[right]);
r.addAll(cur);
result.add(r);
while(left < right && nums[left] == nums[left + 1]) left++;
while(left < right && nums[right] == nums[right - 1]) right--;
left++;
right--;
}else if (diff > 0){
left++;
}else {
right--;
}
}
}else { // k>2时
for(int i = from; i < end - k + 2; i++){
if(i > from && nums[i] == nums[i - 1]) continue;
cur.add(nums[i]);
kSum(nums, k - 1, target - nums[i], i + 1, end, cur, result);
cur.remove(cur.size() - 1);
}
}
}