代码随想录算法训练营|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
posted @ 2025-06-18 17:46  ForeverEver333  阅读(5)  评论(0)    收藏  举报