代码随想录算法训练营第七天 | 454. 四数相加II、383. 赎金信、15. 三数之和、18.四数之和

代码随想录算法训练营第七天 | 454. 四数相加II、383. 赎金信、15. 三数之和、18.四数之和

今日学习的文章链接和视频链接

参考代码随想录

自己看到题目的第一想法

今天的题目前几天才写过,希望都可以不看笔记写出来

自己实现过程中遇到哪些困难

  • 454. 四数相加II秒了,尝试使用dict.setdefault()函数,没有写出来,还是踏踏实实写if判断吧
  • 383. 赎金信也秒了,字母异位词的高级版,感觉对于字符串中用数组做哈希表更熟练了
  • 15. 三数之和18.四数之和思路基本一致,用了双指针降低时间复杂度,都是先遍历,循环内双指针,然后判断;难点在于判断去重和剪枝操作,都是看了笔记才想出来,还需要多练习

今日收获,记录一下自己的学习时长

  • 应打卡7月4日,7月5日补打卡,学习时长2hr
  • 栈和队列*1,232.用栈实现队列
  • 因为笨蛋操作,一不小心第四天的打卡没了,懒得再写一遍了

0454. 四数相加II 4Sum II

Leetcode 题目链接

1. 题目描述

给你四个整数数组nums1nums2nums3nums4,数组长度都是n,请你计算有多少个元组(i, j, k, l)能满足:

  • 0 <= i, j, k, l < n
  • nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0

示例2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

2. 解题思路

  • 可以使用暴力枚举法,时间复杂度为O(n^4),超出时间限制
  • 因为题目给出四个独立数组nums1nums2nums3nums4,只用考虑nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0,不用考虑在同一个数组中有重复的四个元素相加等于0的情况,所以适合使用 哈希法
  • 时间复杂度O(n^2)
  • 空间复杂度O(n^2)

具体算法:

  • 定义map做哈希表,先遍历nums1nums2数组;哈希表中keynums1nums2两数之和,即key = nums1[i] + nums2[j],而value为两数之和出现的次数
  • 定义变量count,统计nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0出现的次数
  • 再遍历nums3nums4数组,如果在map中找到相匹配的元素,即此时key = 0 - nums3[k] - nums4[l],就用计数器count统计mapkey所对应的次数value,即count = map[key]

3. 具体实现

3.1. Python - 使用字典

class Solution:
    def fourSumCount(self, nums1: List[int], nums2: List[int], nums3: List[int], nums4: List[int]) -> int:
        # 字典做哈希表
        map = dict()

        # 先遍历 nums1 + nums2
        for n1 in nums1:
            for n2 in nums2:
                # 统计 nums1 + nums2 出现的次数
                if n1 + n2 in map:
                    map[n1 + n2] += 1
                else: 
                    map[n1 + n2] = 1
        
        count = 0

        # 再遍历 nums3 + nums4
        for n3 in nums3:
            for n4 in nums4:
                # 寻找匹配的 nums1 + nums2
                # nums1 + nums2 + nums3 + nums4 = 0
                diff = 0 - n3 - n4
                if diff in map:
                    count += map[diff]
                
        return count

3.2 Java

  • hashmap.getDefault(Object key, V defaultValue): 如果mapkey,返回相对应的value;如果没有,就返回初始值defaultValue
class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int count = 0;

        // map哈希表
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();

        // nums1,nums2和的出现次数
        for (int n1: nums1) {
            for (int n2: nums2) {
                int sum = n1 + n2;
                // key - n1 + n2; value - 出现次数
                map.put(sum, map.getOrDefault(sum, 0) + 1); 
            }
        }

        // nums3, nums4寻找匹配元素
        for (int n3: nums3) {
            for (int n4: nums4) {
                // n1 + n2 + n3 + n4 = 0
                int diff = 0 - n3 - n4;
                count += map.getOrDefault(diff, 0);
            }
        }

        return count;
    }
}

0383. 赎金信 Ransom Note

Leetcode 题目链接

1. 题目描述

给你两个字符串:ransomNotemagazine,判断ransomNote能不能由magazine里面的字符构成。

如果可以,返回true;否则返回false

magazine中的每个字符只能在ransomNote中使用一次。

示例1:

输入:ransomNote = "a", magazine = "b"
输出:false

示例2:

输入:ransomNote = "aa", magazine = "ab"
输出:false

示例3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

提示:

字符串`magazing`和`ransomNote`只包含英文小写字母

2. 解题思路

  • 本题跟0242_有效的字母异构词很像,区别在判断字符串magazine中的字符能否组成字符串ransomNote,注意题目要求字符串magazine中的每个字符不可以重复利用
  • 可以使用暴力枚举法,时间复杂度为O(n^2)
  • 使用哈希法定义哈希表记录字符串magazine中每个字符出现的次数
  • 哈希表的选择:选择 数组 做哈希表,因为本题中哈希表的大小固定,有26个英文字母,所以定义一个长度为26的数组做哈希表,比使用map更加省时间
  • 时间复杂度O(n)
  • 空间复杂度O(1)

具体算法:

  • 定义一个大小为 26 的数组record,因为从小写字母az一共有26个字符;key为英文小写字母,valuemagazine每个字符出现次数
  • 把字符映射到哈希表的索引下标上,因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25。
  • 先遍历字符串magazine;遍历时,将magazine[i] - 'a'所在的元素做+1操作,不需要记住a的ASCII,只要求出一个相对值就可以
  • 再遍历字符串ransomNote;同样,在遍历时将ransomNote[i] - 'a'所在的元素做-1操作
  • 最后判断在哈希表中,ransomNote每个字符所对应的出现次数;如果次数小于0,说明magazine中的字符不够组成ransomNote,返回false,否则返回true

3. 算法实现

3.1 Python - 数组

class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        # 数组做哈希表
        record = [0] * 26

        for m in magazine:
            record[ord(m) - ord('a')] += 1

        for r in ransomNote:
            record[ord(r) - ord('a')] -= 1
            if record[ord(r) - ord('a')] < 0: # 说明magazine字符不够组成ransomNote
                return False

        return True

3.2 Python - 字典

  • Python字典get(Object key, V defaultValue):: 如果字典中key,返回相对应的value;如果没有,就返回初始值defaultValue
class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        record = dict()

        for m in magazine:
            record[m] = record.get(m, 0) + 1

        for r in ransomNote:
            record[r] = record.get(r, 0) - 1
            if record[r] < 0:
                return False
        
        return True

3.3 Java - 数组

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        // magazine长度小于ransomNote
        if (magazine.length() < ransomNote.length()) {
            return false;
        }

        // 数组做哈希表
        int[] record = new int[26];

        for (int i = 0; i < magazine.length(); i++) {
            record[magazine.charAt(i) - 'a'] ++;
        }

        for (int j = 0; j < ransomNote.length(); j++) {
            record[ransomNote.charAt(j) - 'a'] --;
            if (record[ransomNote.charAt(j) - 'a'] < 0) {
                return false;
            }
        }

        return true;   
    }
}

0015. 三数之和 3Sum

Leetcode 题目连接

1. 题目描述

给你一个整数数组nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != ji != kj != k,同时还满足nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

2. 解题思路

  • 本题可以使用 哈希法,类似0454_四数相加,用两层循环遍历数组nums确定nums[i]nums[j]的值,存储在哈希表里,然后判断0 - nums[i] - nums[j]是否出现过。
  • 哈希法局限:题目要求不能出现重复的三元组,所以需要去重,时间复杂度为O(n^2),非常费时间
  • 本体也可以使用双指针法

2.1. 双指针法

  • 首先将数组nums排序,然后用第一层for循环遍历,i从下标为0的地方开始
  • 定义left指针为i + 1的位置上,定义right指针为数组结尾的位置上,即len(nums) -1
  • 如果i + left + right > 0,说明此时的三数之和 大于 条件,因为数组nums已经排序过,所以把right向左移动一位;如果i + left + right < 0,说明此时的三数之和 小于 条件,所以把left向右移动一位;如此循环直到leftright相遇为止
  • 去重:题目要求不能出现重复的三元组,但是三元组内的元素可以重复
    • 对于i的去重来说,为了避免跳过元素导致错过匹配的三元组,应该把ii - 1相比较,如果ii - 1相同,跳过i(而不是比较ii + 1而跳过i + 1
    • 对于leftright的去重:类似于i的去重,在双指针更新后,rightright - 1做判断,如果重复跳过rightleftleft + 1做判断,如果重复跳过left
  • 注意:使用双指针法一定要先对数组 排序,会改变数组的下标,所以如果题目要求的是返回符合条件元素的下标(例如0001_双数之和),就不能使用双指针法,而应该使用哈希法

3. 算法实现

  • 时间复杂度O(n^2)
  • 空间复杂度O(1)

3.1 Python - 双指针法

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = [] # 存储返回的结果
        nums.sort() # 排序

        for i in range(len(nums)):
            # 如果i>0, 不需要继续检查
            if nums[i] > 0:
                return res
            
            # i的去重
            if i > 0 and nums[i] == nums[i - 1]:
                continue

            # 双指针
            left = i + 1
            right = len(nums) - 1

            # 出界条件 left = right, 双指针相遇
            while left < right:
                sum = nums[i] + nums[left] + nums[right]
                if sum > 0:
                    right -= 1

                elif sum < 0:
                    left += 1

                else: # i + left + right == 0
                    res.append([nums[i], nums[left], nums[right]])

                    # 去重逻辑出现在找到第一个三元组后
                    # right去重
                    while left < right and nums[right] == nums[right - 1]:
                        right -= 1

                    # left去重
                    while left < right and nums[left] == nums[left + 1]:
                        left += 1

                    # 双指针更新,继续遍历
                    left += 1
                    right -= 1

        return res

3.2 Java - 双指针法

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<> (); // return result

        Arrays.sort(nums);

        for (int i = 0; i < nums.length; i++) {
            // 排序后,如果i>0, 不需要继续检查
            if (nums[i] > 0) {
                return res;
            }

            // i去重
            if (i > 0 && nums[i] == nums[i-1]) {
                continue;
            }

            // 双指针
            int left = i + 1;
            int right = nums.length - 1;

            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];

                if (sum > 0) {
                    right --;
                } else if (sum < 0) {
                    left ++;
                } else {
                    // sum == 0
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    // 去重逻辑出现在找到第一个三元组后
                    // right 去重 
                    while (left < right && nums[right] == nums[right - 1]) {
                        right --;
                    }
                    // left 去重 
                    while (left < right && nums[left] == nums[left + 1]) {
                        left ++;
                    }

                    left ++;
                    right --;
                }
            }
        }

        return res;
    }
}

0018. 四数之和 4Sum

Leetcode 题目链接

1. 题目描述

给你一个由n个整数组成的数组nums,和一个目标值target。请你找出并返回满足下述全部条件且不重复的四元组[nums[a], nums[b], nums[c], nums[d]](若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target
    你可以按 任意顺序 返回答案 。

示例1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

示例2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

2. 解题思路

  • 基本思路和0013_三数之和一致,使用双指针法
  • 三数之和中先for循环遍历nums[i]作为确定值,然后循环内定义双指针循环,找到nums[i] + nums[left] + nums[right] == 0;本题中多加一个循环,食用双重for循环确定nums[i] + nums[j],然后循环内定义双指针找到nums[i] + nums[j] + nums[left] + nums[right] == target
  • 三数之和原本三重循环,时间复杂度为O(n^3),使用双指针后降低为O(n^2);本题中原本四重循环,时间复杂度为O(n^4),使用双指针后降低为O(n^3)
  • 去重与剪枝
    • 一级剪枝:在数组nums排序之后,不能判断nums[i] > target就返回,因为target可能是任意值;但是可以使用剪枝处理,判断nums[i] > target && (nums[i]> 0 || target >= 0)
    • 二级剪枝:nums[i] + nums[j] > target && nums[i] + nums[j] >= 0可以优化为nums[i] + nums[j] > target && nums[j] >= 0,因为如果nums[j]大于0的话,后面的数都一定大于nums[j],所以不符合条件

3. 算法实现

  • 时间复杂度O(n^3)
  • 空间复杂度O(1)

3.1 Python - 双指针

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        res = []
        n = len(nums)
        nums.sort()

        for i in range(n):
            # 一级剪枝
            if nums[i] > target and (nums[i] > 0 or 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:
                    sum = nums[i] + nums[j] + nums[left] + nums[right]
                    if sum > target:
                        right -= 1
                    elif sum < target:
                        left += 1
                    else:
                        # sum == target
                        res.append([nums[i], nums[j], nums[left], nums[right]])

                        # right去重
                        while left < right and nums[right] == nums[right-1]:
                            right -= 1

                        # right去重
                        while left < right and nums[left] == nums[left+1]:
                            left += 1

                        left += 1
                        right -= 1

        return res

3.2 Java - 双指针

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        int n = nums.length;
        Arrays.sort(nums);
        List<List<Integer>> res = new ArrayList<>();

        for (int i = 0; i < n; i++) {
            if (nums[i] > target && (nums[i] > 0 || target > 0)) {
                break;
            }

            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }

            for (int j = i + 1; j < n; j++) {
                if (nums[i] + nums[j] > target && nums[j] > 0) {
                    break;
                }

                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }

                int left = j + 1;
                int right = n - 1;

                while (left < right) {
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if (sum > target) {
                        right --;
                    } else if (sum < target) {
                        left ++;
                    } else {
                        res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));

                        while (left < right && nums[right] == nums[right - 1]) {
                            right --;
                        }

                        while (left < right && nums[left] == nums[left + 1]) {
                            left ++;
                        }

                        right --;
                        left ++;
                    }
                }
            }
        }

        return res;   
    }
}
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值