题一:第454题.四数相加II
2023/1/30 22:00
链接
视频总结
关键点
- 思路:分成两组,分别遍历相加之后然后再对两部分采用哈希法
- 注意点:数值很大,数组不可用,只能用set或者map,而哈希表需要储存和值和次数,而set只能储存是否出现过这一信息,所以用map,而python里都是用字典来实现
编程思路
Me:
只想到了暴力解法,恩的四方复杂度
卡尔:
- 对一二数组元素遍历相加,结果和出现的次数储存在哈希表里,key是和值,value是次数
- 对三四数组元素遍历相加,结果的负数判断是否在哈希表中,若在,最终的计数加上哈希表中那个数对应的value,也就是次数
- 最终返回总计数
力扣实战
思路一:
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
count = 0 # 记录最终元组的个数
hash = {}
for i in nums1: # 第一个for循环建立哈希表,key为一二数组任意两个数字和的结果,value是结果的次数
for j in nums2:
if i + j not in hash:
hash[i+j] = 1
else:
hash[i+j] +=1
for i in nums3: # 第二个for循环先对三四数组元素遍历相加,然后判断结果的相反数是否在哈希表中,若在则计数count加上key为相反数时对应的value
for j in nums4:
if -(i+j) in hash:
count += hash[-(i+j)]
return count
# 反思1:
文档总结
1. 注意调用collections的写法
题二:383. 赎金信
链接
编程思路
Me:
- 用数组即可拿下,和day6的第一题很像,ord函数获得字符串地址,遍历字符串一时建立哈希表,次数加一,遍历字符串二时次数减一,最后遍历values,若有值为1则不是,负责是。
卡尔:
退出条件书写时发生了错误,不只等于一时可以退出,也可以是其他的正数。
力扣实战
思路一:
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
hash = [0]*26
for i in ransomNote:
hash[ord(i)-ord("a")] += 1
for j in magazine:
hash[ord(j)-ord("a")] -= 1
# if 1 in hash: # 也可能是2 3 4...
# return False
# else:
# return True
for i in hash:
if i>0:
return False
return True
# 反思1:写法可以优化,第二个for循环可以直接开始判断!思路类似于经典的map法
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
arr = [0] * 26
for x in magazine: # 记录 magazine里各个字符出现次数
arr[ord(x) - ord('a')] += 1
for x in ransomNote: # 在arr里对应的字符个数做--操作
if arr[ord(x) - ord('a')] == 0: # 如果没有出现过直接返回
return False
else:
arr[ord(x) - ord('a')] -= 1
return True
题三:15. 三数之和
链接
视频总结
关键点
- 去重细节
编程思路
Me:
- 只想到了暴力法,三层for循环找出所有可能,再判断是否为零
卡尔:
- 用传统的map法,分成两组,第一组建立哈希表,两个for循环,但是因为实在一个数组上需要去重,所以用哈希法比较繁琐,去重细节较多
- 双指针法较好,但是也要注意去重环节。continue语句的作用是跳过本次循环体中余下尚未执行的语句,立即进行下一次的循环条件判定,可以理解为仅结束本次循环。
- 边界问题:r>l还是>=,遇到边界问题时可以采用假设带入法,若等于时,则两个数一样,而我们要求的是一个三元组。故只能写大于
- 去重问题:同一个二元组,如[-3,2,1]可能会出现多次
所以在排序之后,第一个for循环里首次去重,发现和前一个数一样,就可以直接结束这次循环,跳到下一次循环里。 - 第二次去重是在移动l和r指针时,此时也可能会出现重复的元祖,同样r指针若和前一个数相等,说明r需要向后再移动一个数(因为和前一个相等时,第三个数就已经是确定了,l指针无论怎么移动,一定会和此前的元组相同)
- 去重逻辑要保证至少先获得一个元组结果,所以不可放在while条件里,否则当数组元素全为0时,一个结果也没有
力扣实战
思路一:
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort() # 数组排序
for i in range(len(nums)):
l = i + 1 # 定义左右指针
r = len(nums)-1
if nums[i]>0: #若a大于0,则bc大于0,abc之和不可能为0,故结束此次循环
continue
elif i>0 and nums[i]==nums[i-1]: # 对a去重 和前一个比较,而不是后一个
continue
while l<r: # 边界问题,只能是小于,不能是≤,因为b的索引不能等于c的索引
if nums[i] +nums[l] +nums[r] ==0:
res.append([nums[i],nums[l],nums[r]])
l +=1
r -=1
# if nums[l]==nums[l-1]: #对b和c去重
# l +=1
# elif nums[r]==nums[r+1]:
# r -=1
# while nums[l]==nums[l-1]: #对b和c去重 #错误:指针越限
# l +=1
# while nums[r]==nums[r+1]:
# r -=1
while l<r and nums[l]==nums[l-1]: #对b和c去重 #错误:指针越限
l +=1
while l<r and nums[r]==nums[r+1]:
r -=1
elif nums[i] +nums[l] +nums[r] >0: #移动左右指针
r -= 1
else:
l +=1
return res
# 反思:编程时出现两次错误,都是在去重时,第一次是对bc去重,用了if语句,导致只做了一次去重,去重不彻底应该改用while;第二次错误是while条件没加l<r,导致指针最后越界了
思路二:
# 哈希表法代码简洁的多,去除了繁琐的去重操作
hashmap = dict()
target = 0
for n in nums: # 建立哈希表,key为数值 value为数值在数组中出现的次数
if n in hashmap:
hashmap[n] += 1
else:
hashmap[n] = 1
ans = set()
for i in range(len(nums)):
for j in range(i + 1, len(nums)): # 哈希也是把暴力的恩三方减少到恩平方的时间复杂度,在第二层循环里,用前两个数表示第三个数,然后在哈希表里查询是否存在,从而在这里用空间换取了时间
val = target - (nums[i] + nums[j])
if val in hashmap:
count = (nums[i] == val) + (nums[j] == val) #计数原理和四数值和里思路一致,每一个括号里都为bool的True或者False,但他们加减运算时被视为0或1
if hashmap[val] > count:
ans_tmp = tuple(sorted([nums[i], nums[j], val]))
ans.add(ans_tmp)
else:
continue
return list(ans) # 改进思路,直接使用数组来存储,不需要调用内部的函数或许更快×××,数组操作则需要大量去重,如
#把[[-1,0,1],[-1,1,0],[-1,2,-1],[-1,-1,2],[0,1,-1],[0,-1,1],[1,-1,0],[2,-1,-1]]去重为[[-1,-1,2],[-1,0,1]]
# 而set为什么可以呢?这个问题有待研究一下,研究思路:研究set函数的用法
文档总结
1. 第一个continue最好改成break,表示这个a>0后,那么后面的循环都不要考虑了,循环终止。而continue只是结束了本轮循环,虽然在后面的循环里,a全部都是大于零,但是执行的时间白白增加了
2. 对bc去重有两种写法,先自加再去重,或者先去重再自加
题四:18. 四数之和
链接
视频总结
编程思路
Me:
- 排序,然后第一层for循环遍历数组,然后子问题变为三数之和等于target-nums[i]。(漏掉了剪枝的操作,最后耗时会长一点)
卡尔:
- 排序,遍历数组,第一个参数i对应a
- 剪枝,即确认部分情况可以直接终止本次及后面的循环,即break
- 去重对a
- 对b的参数j遍历,从i+1开始
- 剪枝去重
- 再进行左右指针的操作,和三数之和类似
力扣实战
思路一:
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
# 把四数之和想象成a+b+c+d
nums.sort() # 数组排序,便于移动l和r
n = len(nums)
res = [] #存放四元组数组
for i in range(n): # 外层遍历数组,针对参数a
if nums[i]>target and nums[i]>0: # 剪枝操作:此时后面的数一定不满足要求,无论bcd是什么,所以结束本轮和后面的循环
break
elif i>0 and nums[i]== nums[i-1]: # 去重操作,和前一个比,因为i的初始是0,所以从0+1才开始考虑去重
continue
for j in range(i+1,n): # 内存遍历i后面的数组元素,针对参数b
if nums[i]+nums[j]>target and nums[j]>0: #剪枝:注意是i+j,这样的范围比j更大,剪掉的更多
break
elif j>i+1 and nums[j] == nums[j-1]: # 去重:注意起始处是i+1!而不是0,如果是零的话,b的j有可能和a的i相同,导致去掉了不该去的部分
continue
l = j+1 #其余的大体和三数之和一致
r = n-1
while l<r:
if nums[i]+nums[j]+nums[l]+nums[r]>target:
r -= 1
elif nums[i]+nums[j]+nums[l]+nums[r]<target:
l +=1
else:
res.append([nums[i],nums[j],nums[l],nums[r]])
while l<r and nums[l] == nums[l+1]:
l += 1
while l<r and nums[r] == nums[r-1]:
r -= 1
l +=1
r -=1
return res
# 总结1:第二层for循环的剪枝和去重小细节很有意思
文档总结
1. 剪枝,逻辑变成nums[i] > target && (nums[i] >=0 || target >= 0)就可以了
2. 三数之和的时间复杂度是O(n^2),四数之和的时间复杂度是O(n^3) 。
3. 一样的道理,五数之和、六数之和等等都采用这种解法
4. 对于三数之和 双指针法就是将原本暴力O(n^3)的解法,降为O(n^2)的解法,四数之和的双指针解法就是将原本暴力O(n^4)的解法,降为O(n^3)的解法。
5. 双指针法将时间复杂度:O(n^2)的解法优化为 O(n)的解法。也就是降一个数量级
思路二
# 哈希表法
class Solution(object):
def fourSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[List[int]]
"""
# use a dict to store value:showtimes
hashmap = dict()
for n in nums:
if n in hashmap:
hashmap[n] += 1
else:
hashmap[n] = 1
# good thing about using python is you can use set to drop duplicates.
ans = set()
# ans = [] # save results by list()
for i in range(len(nums)):
for j in range(i + 1, len(nums)):
for k in range(j + 1, len(nums)):
val = target - (nums[i] + nums[j] + nums[k])
if val in hashmap:
# make sure no duplicates.
count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
if hashmap[val] > count:
ans_tmp = tuple(sorted([nums[i], nums[j], nums[k], val]))
ans.add(ans_tmp)
# Avoiding duplication in list manner but it cause time complexity increases
# if ans_tmp not in ans:
# ans.append(ans_tmp)
else:
continue
return list(ans)
#很有意思的哈希表思路,关键步骤是count和value比大小!且value必须大于count,若等于,说明有一个数被使用了两次,若是小于,说明有一个数至少被使用了三次。使用哈希表的写法就直接不需要考虑去重的操作了,这是优点!按这样算的话,三数之和也是可以使用哈希表法的
2023/1/31 16:41