1. 四数之和(454)
这道题考查四个数组中, 有几组组合可以组成和为0. 考虑把四个数组划分为两个, 两两一组, 如果划分为一三的话, 复杂度就是O(n**3)了, 所以不推荐
然后就是把a+b的结果存到字典中, key是a+b的数值, value是a+b出现的次数, 然后再遍历c+d, 看是否有需要的a+b值(即-c-d的值)已经出现在字典中. 如果有的话, count加上value值(这里注意是加value值而不是1), 因为要计算一共有多少组组合
res = dict()
n = len(nums1)
for i in range(n):
for j in range(n):
key = nums1[i] + nums2[j]
if key not in res:
res[key] = 0
res[key] += 1
result = 0
for a in range(n):
for b in range(n):
key1 = -nums3[a] - nums4[b]
result += res.get(key1, 0)
return result
这里稍微注意一下这一行
if key not in res:
res[key] = 0
res[key] += 1
这里的res[key]是无条件为key的值➕1, 所以初始化的值适合设置为0. 之前把res[key]初始化为1就出错了, 可以改为:
if key not in res:
res[key] = 1 # key第一次出现,计数为1
else:
res[key] += 1
还可以用defaultdict:
from collections import defaultdict
res = defaultdict(int) # 默认值为0
for i in range(n):
for j in range(n):
key = nums1[i] + nums2[j]
res[key] += 1
defaultdict还有一种写法
rec = defaultdict(lambda : 0)
for i in nums1:
for j in nums2:
rec[i+j] += 1
或者用get函数:
hashmap = dict()
for n1 in nums1:
for n2 in nums2:
hashmap[n1+n2] = hashmap.get(n1+n2, 0) + 1
最后提一下, 这道题我的解法并不好, 可以直接遍历num中的数值, 我是基于每个数组长度都是n才能写出的, 不好
2. 赎金信(383)
就是说信中有字符, magazine中有字符, 判断前者的字符能不能由后者组成, 即后者是否包含前者
list1 = [0]*26
list2 = [0]*26
for i in ransomNote:
list1[ord(i) - ord("a")] += 1
for j in magazine:
list2[ord(j) - ord("a")] += 1
for i in range(26):
if not list1[i] <= list2[i]:
return False
return True
这题用数组不难 , 但是要注意一点, 就是写的时候我也是考虑怎么写对于所有的list1中的对应值要小于等于list2中的, 答案是如果不小于就return false就行
就是如何判断1有的2一定有还有其他方法, 比如在2的数组中, 1的就减1, 如果出现小于0则说明ransoNote中出现的字符, magazine中没有
还可以用count, 小的减去大的, counter减法中, 如果计数结果为0或者负数, 不会出现在count中, 如果结果为空会返回一个false, return的就是true, 如果不能完全覆盖最后返回false
from collections import Counter
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
return not Counter(ransomNote) - Counter(magazine)
或者是, 对于ransomnote中的每个唯一字符c, 计算字符c在ransomnote和magazine中的出现次数, 检查是否magazine中的次数是否都是不少于ransomnote中的次数
return all(ransomNote.count(c) <= magazine.count(c) for c in set(ransomNote))
3. 三数之和(15)
三数之和问题就是要看一个数组中是否存在三个数和为0, 三个数的值可以重复, 但是一个数不能用两次, 并且同一个组合只需要记录一次
用双指针法解决这个问题, 首先先将数组排序, 确定i, 遍历整个数组, 这里要注意一点就是i如果和前一个数字相同就跳过这个数. 那么到底是nums[i] == nums[i+1] 还是nums[i] == nums[i-1]
答案是后者, 因为不要忘了left是i的下一个数, 而i和left同样取值是允许的. 如果出现这种情况, 就continue, 跳过后面的步骤, 继续执行for循环, 改变i的值, 直到和前面一个不相等
设定两个指针, 一个是left, 一个是right, left从i后面一个数开始, right从最后一个数开始
也就是 left = i+1, right = len(num) - 1
紧接着考虑循环条件是right >= left还是right > left, 因为不能出现一个数使用两次的情况, 所以right和left不能取等, 所以是选择后者
然后就是记录三个指针指向数的和, 如果大于0, right向左移动, 如果小于0, 则left向右移动
找到等于0 的情况就添加入result中
找到一个有效三元组后执行内层的while, 跳过所有与当前num[right]以及nun[left]相同的元素, 防止下次迭代中再使用相同的元素
def threeSum(self, nums):
"""
:type nums: List[int]
:rtype: List[List[int]]
"""
result = []
nums.sort()
for i in range(len(nums)):
if nums[i] > 0:
return result
if i>0 and nums[i] == nums[i-1]:
continue
left = i+1
right = len(nums) - 1
while right>left:
sum1 = nums[i] + nums[left] + nums[right]
if sum1 < 0 :
left += 1
elif sum1 > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
4. 四数之和(18)
4.1 双指针
四数之和就是在三数的基础上再加上一个循环, 最后的复杂度是O(n**3)
这道题的重点在于剪枝和去重, 注意不能用num[k] > target的方式来剪枝, 这是因为虽然k是最小的元素, 但是后面的也有可能是负数, 这样相加可能缩小值, 从而得到target
要剪枝的话, 要用if nums[i] > target and nums[i] > 0 and target > 0:
去重要用 if i > 0 and nums[i] == nums[i-1]:
后来剪枝将nums[k] + nums[i]看作一个整体
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
result = []
for i in range(n):
if nums[i] > target and nums[i] > 0 and target > 0:# 剪枝(可省)
break
if i > 0 and nums[i] == nums[i-1]:# 去重
continue
for j in range(i+1, n):
if nums[i] + nums[j] > target and target > 0: #剪枝(可省)
break
if j > i+1 and nums[j] == nums[j-1]: # 去重
continue
left, right = j+1, n-1
while left < right:
s = nums[i] + nums[j] + nums[left] + nums[right]
if s == target:
result.append([nums[i], nums[j], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]:
left += 1
while left < right and nums[right] == nums[right-1]:
right -= 1
left += 1
right -= 1
elif s < target:
left += 1
else:
right -= 1
return result
4.2 字典
首先建立一个字典freq, 用来记录数组nums中每个数字出现的次数
freq = {}
for num in nums:
freq[num] = freq.get(num, 0) + 1
接着用一个集合set来存储答案, 用三重循环遍历数组nums中每一个可能的三元组合, 并且i<j<k保证组合的唯一性
接着看需要的第四个数val是否在字典freq中, 如果在那么判断是否能用
即nums i,j,k使用的val的数量是否小于字典中记录的数量, 如果的确小于说明这个字典中存放的val是可以用的, 将这个四元组加入答案中
用tuple(sorted([nums[i], nums[j], nums[k], val]))确保四元组是有序的, 从而避免重复,
而此时仍然是一个列表, 没有办法存入set中去重, 所以要先转化为元组即用tuple(sorted([nums[i], nums[j], nums[k], val])), 然后再存入set中, 因为排序了又在set中去重, 得到的就是符合题意的元组
例如,四元组 (1, 3, 2, 4)
和 (1, 2, 3, 4)
在排序后都会变为 (1, 2, 3, 4)
ans = set()
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 freq:
count = (nums[i] == val) + (nums[j] == val) + (nums[k] == val)
if freq[val] > count:
ans.add(tuple(sorted([nums[i], nums[j], nums[k], val])))
所以最后要从元组转化为列表
return [list(x) for x in ans]