代码随想录算法训练营|Day 7
Day 7
今日任务
● 454.四数相加II
● 383. 赎金信
● 15. 三数之和
● 18. 四数之和
● 总结
详细布置
454.四数相加II
建议:本题是使用map巧妙解决的问题,好好体会一下哈希法如何提高程序执行效率,降低时间复杂度,当然使用哈希法会提高空间复杂度,但一般来说我们都是舍空间换时间,工业开发也是这样。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0454.四数相加II.html
暴力解法:四个for循环 O(n^4)
使用map: 将a+b作为map的key,统计出现次数作为value
在遍历cd数组的时候,判断0-(a+b)有没有在c与d的集合中出现过
先只遍历ab数组→O(n^2)
再只遍历cd数组→O(n^2)
总的时间复杂度就是O(n^2)
class Solution:
def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
count = {}
for i in range(len(nums1)):
for j in range(len(nums2)):
count[nums1[i]+nums2[j]] = count.get(nums1[i]+nums2[j], 0)+1
res = 0
for i in range(len(nums3)):
for j in range(len(nums4)):
res += count.get(-nums3[i] - nums4[j], 0)
return res
使用defaultdict
from collections import defaultdict
class Solution:
def fourSumCount(self, nums1: list, nums2: list, nums3: list, nums4: list) -> int:
rec, cnt = defaultdict(lambda : 0), 0
for i in nums1:
for j in nums2:
rec[i+j] += 1
for i in nums3:
for j in nums4:
cnt += rec.get(-(i+j), 0)
return cnt
defaultdict 是 Python 的一种特殊字典,来自 collections 模块。它和普通字典最大的区别是:
访问不存在的 key 时会自动生成一个默认值,不会报错。
基本语法
from collections import defaultdict
d = defaultdict(工厂函数)
- 工厂函数:用来生成默认值,比如
int,list,set等。
常用工厂函数示例
自动初始化为 0(适合统计计数)
from collections import defaultdict
count = defaultdict(int) # int() 默认返回 0
count['apple'] += 1
count['apple'] += 1
count['banana'] += 1
print(count)
# 输出: defaultdict(<class 'int'>, {'apple': 2, 'banana': 1})
等价于:
count = {}
count['apple'] = count.get('apple', 0) + 1
自动初始化为空列表(适合分组数据)
from collections import defaultdict
group = defaultdict(list)
group['A'].append(1)
group['A'].append(2)
group['B'].append(3)
print(group)
# 输出: defaultdict(<class 'list'>, {'A': [1, 2], 'B': [3]})
等价于:
group = {}
group['A'] = group.get('A', []) + [1]
但 defaultdict(list) 自动帮你准备好空列表。
自动初始化为空集合(适合去重分组)
from collections import defaultdict
s = defaultdict(set)
s['x'].add(1)
s['x'].add(2)
s['y'].add(1)
print(s)
# 输出: defaultdict(<class 'set'>, {'x': {1, 2}, 'y': {1}})
自定义默认值
d = defaultdict(lambda: 'default value')
print(d['missing'])
# 输出: default value
383. 赎金信
建议:本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题
题目链接/文章讲解:https://programmercarl.com/0383.赎金信.html
map思路:需要的元素是否出现
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
count = [0] * 26
for l1 in ransomNote:
count[ord(l1) - ord('a')] += 1
for l2 in magazine:
count[ord(l2) - ord('a')] -= 1
for l1 in ransomNote:
if count[ord(l1) - ord('a')] > 0:
return False
return True
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
for char in ransomNote:
if char in magazine and ransomNote.count(char) <= magazine.count(char):
continue
else:
return False
return True
from collections import Counter
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
return not Counter(ransomNote) - Counter(magazine)
Counter(ransomNote)
统计 ransomNote 里每个字母出现的次数。
例如:
ransomNote = 'aab'
Counter(ransomNote)
# 输出: Counter({'a': 2, 'b': 1})
Counter(magazine)
统计 magazine 里每个字母出现的次数。
例如:
magazine = 'aaabc'
Counter(magazine)
# 输出: Counter({'a': 3, 'b': 1, 'c': 1})
Counter(ransomNote) - Counter(magazine)
这个是 Counter 的减法操作:
- 对每个字母,把
ransomNote的数量减去magazine的数量。 - 如果某个字母减完之后 ≤ 0,它会从结果里消失。
- 最终结果是:缺的字母及其缺的数量。
例子:
Counter('aab') - Counter('aaabc')
= Counter({'a':2, 'b':1}) - Counter({'a':3,'b':1,'c':1})
= Counter({})
# 都能满足需求,没有缺的字母
再看一个不能构造的例子:
Counter('aab') - Counter('abc')
= Counter({'a':2,'b':1}) - Counter({'a':1,'b':1,'c':1})
= Counter({'a':1})
# 缺一个 'a'
not Counter(...)
- 如果结果是
Counter({})(空),说明没有缺的字母。 not空 Counter 是True,表示可以构造。- 如果结果不是空的,说明有缺字母,返回
False。
15. 三数之和
建议:本题虽然和 两数之和 很像,也能用哈希法,但用哈希法会很麻烦,双指针法才是正解,可以先看视频理解一下 双指针法的思路,文章中讲解的,没问题 哈希法很麻烦。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0015.三数之和.html
去重:
nums[i]去重:
nums[i] == nums[i-1],说明目前的nums[i]曾是i=i-1是left的位置,需要跳过循环
nums[left]与nums[right]去重:
收割到一个合适的结果后,可能left+1或right-1位置的元素仍是我们收割过的这个结果集的元素,需要跳过
当left ≠ left +1 或者 right ≠ right-1,说明可以开始新的判断
再移动left,right指针到可以重新开始判断的位置(即各自的下一位)
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
for i in range(len(nums)):
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return res
if i > 0 and nums[i] == nums[i-1]:
continue
left, right= i+1,len(nums)-1
while left < right:
if nums[i] + nums[left] + nums[right] > 0:
right -= 1
elif nums[i] + nums[left] + nums[right] < 0:
left += 1
else:
res.append((nums[i],nums[left],nums[right]))
while right > left and nums[left] == nums[left+1]:
left += 1
while right > left and nums[right] == nums[right-1]:
right -= 1
right -= 1
left += 1
return res
Java版本:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);
// 找出a + b + c = 0
// a = nums[i], b = nums[left], c = nums[right]
for (int i = 0; i < nums.length; i++) {
// 排序之后如果第一个元素已经大于零,那么无论如何组合都不可能凑成三元组,直接返回结果就可以了
if (nums[i] > 0) {
return result;
}
if (i > 0 && nums[i] == nums[i - 1]) { // 去重a
continue;
}
int left = i + 1;
int right = nums.length - 1;
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
if (sum > 0) {
right--;
} else if (sum < 0) {
left++;
} else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
right--;
left++;
}
}
}
return result;
}
}
18. 四数之和
建议: 要比较一下,本题和 454.四数相加II 的区别,为什么 454.四数相加II 会简单很多,这个想明白了,对本题理解就深刻了。 本题 思路整体和 三数之和一样的,都是双指针,但写的时候 有很多小细节,需要注意,建议先看视频。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0018.四数之和.html
target可以是负数 两个负数相加会变得更小
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
res = []
nums.sort()
for i in range(len(nums)):
# 剪枝(可省)
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, len(nums)):
#剪枝(可省)
if nums[j] > target and nums[j] > 0 and target > 0:
break
# 去重
if j > i+1 and nums[j] == nums[j-1]:
continue
left,right = j+1, len(nums)-1
while left < right:
if nums[i] + nums[j] + nums[left] + nums[right] > target:
right -= 1
elif nums[i] + nums[j] + nums[left] + nums[right] < target:
left += 1
else:
res.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
return res

浙公网安备 33010602011771号