当前位置: 首页 > news >正文

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

15. 三数之和

建议:本题虽然和 两数之和 很像,也能用哈希法,但用哈希法会很麻烦,双指针法才是正解,可以先看视频理解一下 双指针法的思路,文章中讲解的,没问题 哈希法很麻烦。

题目链接/文章讲解/视频讲解:代码随想录

和之前我们遇到的两数之和不同,两数之和可以很快的通过哈希表完成作答

两层for循环就可以确定 两个数值,可以使用哈希法来确定 第三个数 0-(a+b) 或者 0 - (a + c) 是否在 数组里出现过,其实这个思路是正确的,但是我们有一个非常棘手的问题,就是题目中说的不可以包含重复的三元组。

把符合条件的三元组放进set中,然后再去重,这样是非常费时的,很容易超时,也是这道题目通过率如此之低的根源所在。

所以我们在这里优先考虑双指针算法

我们首先先对数组进行排序,使其变成升序的方式,然后从第一位元素开始遍历数组,并设置两个指针

拿这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。

依然还是在数组中找到 abc 使得a + b +c =0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。

接下来如何移动left 和right呢, 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。

如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

我们来看代码

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;
    }
}

这些代码里面有很多值得思考的地方

a的去重

说到去重,其实主要考虑三个数的去重。 a, b ,c, 对应的就是 nums[i],nums[left],nums[right]

a 如果重复了怎么办,a是nums里遍历的元素,那么应该直接跳过去。

但这里有一个问题,是判断 nums[i] 与 nums[i + 1]是否相同,还是判断 nums[i] 与 nums[i-1] 是否相同。

有同学可能想,这不都一样吗。

其实不一样!

都是和 nums[i]进行比较,是比较它的前一个,还是比较它的后一个。

如果我们的写法是 这样:

if (nums[i] == nums[i + 1]) { // 去重操作
    continue;
}

那我们就把 三元组中出现重复元素的情况直接pass掉了。 例如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。

我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!

那么应该这么写:

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

这么写就是当前使用 nums[i],我们判断前一位是不是一样的元素,在看 {-1, -1 ,2} 这组数据,当遍历到 第一个 -1 的时候,只要前一位没有-1,那么 {-1, -1 ,2} 这组数据一样可以收录到 结果集里。

前面有-1了,那这样第一个数为-1的所有组合都找到了,然后再将第一个数的移到更大的一个数,再进行寻找,其实就是第一个数的去重,要将i设置为不同的数字才可以

b与c的去重

当我们找到了一组符合条件的a,b,c的时候我们需要对b,c进行去重,这里可以举个例子

在这个情况下,我们看到确定了i之后,如果移动left或者right会造成重复,

我们来看去重逻辑

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++;

18. 四数之和

建议: 要比较一下,本题和 454.四数相加II 的区别,为什么 454.四数相加II 会简单很多,这个想明白了,对本题理解就深刻了。 本题 思路整体和 三数之和一样的,都是双指针,但写的时候 有很多小细节,需要注意,建议先看视频。

题目链接/文章讲解/视频讲解:代码随想录

这个题目的思路和之前的三数之和基本一致,只是在剪枝的时候判断更加复杂一点,在三数之和中,target=0,所以我们在进行判断的时候,基于升序排序的数组,,如果当前位置的值的大小已经>0,那么直接break即可,

四数之和这道题目 target是任意值。比如:数组是[-4, -3, -2, -1]target-10,不能因为-4 > -10而跳过。但是我们依旧可以去做剪枝,逻辑变成nums[k] > target && (nums[k] >=0 || target >= 0)就可以了。

我们直接来看代码

import java.util.*;

public class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);  // 排序数组
        List<List<Integer>> result = new ArrayList<>();  // 结果集
        for (int k = 0; k < nums.length; k++) {
            // 剪枝处理
            if (nums[k] > target && nums[k] >= 0) {
                break;	// 此处的break可以等价于return result;
            }
            // 对nums[k]去重
            if (k > 0 && nums[k] == nums[k - 1]) {
                continue;
            }
            for (int i = k + 1; i < nums.length; i++) {
                // 第二级剪枝
                if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
                    break;	// 注意是break到上一级for循环,如果直接return result;会有遗漏
                }
                // 对nums[i]去重
                if (i > k + 1 && nums[i] == nums[i - 1]) {
                    continue;
                }
                int left = i + 1;
                int right = nums.length - 1;
                while (right > left) {
                    long sum = (long) nums[k] + nums[i] + nums[left] + nums[right];
                    if (sum > target) {
                        right--;
                    } else if (sum < target) {
                        left++;
                    } else {
                        result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
                        // 对nums[left]和nums[right]去重
                        while (right > left && nums[right] == nums[right - 1]) right--;
                        while (right > left && nums[left] == nums[left + 1]) left++;
                        right--;
                        left++;
                    }
                }
            }
        }
        return result;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {1, 0, -1, 0, -2, 2};
        int target = 0;
        List<List<Integer>> results = solution.fourSum(nums, target);
        for (List<Integer> result : results) {
            System.out.println(result);
        }
    }
}

383. 赎金信

建议:本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题

题目链接/文章讲解:代码随想录

这道题思路和有效的字母移位词比较像,还是先遍历,后判断出现频率是否为0,在遍历的时候,我们可以先做判断,如果需要组成的字段的长度>给定的字段,我们直接返回false即可,最后判断的条件,也只需要是数组里的所有元素都大于等于0即可,如果小于0,说明给点字段里面存在不包含生成字段所需字母的情况。

我们来看代码

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        // shortcut
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        // 定义一个哈希映射数组
        int[] record = new int[26];

        // 遍历
        for(char c : magazine.toCharArray()){
            record[c - 'a'] += 1;
        }

        for(char c : ransomNote.toCharArray()){
            record[c - 'a'] -= 1;
        }
        
        // 如果数组中存在负数,说明ransomNote字符串中存在magazine中没有的字符
        for(int i : record){
            if(i < 0){
                return false;
            }
        }

        return true;
    }
}

454.四数相加II

建议:本题是 使用map 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使用哈希法 会提高空间复杂度,但一般来说我们都是舍空间 换时间, 工业开发也是这样。

题目链接/文章讲解/视频讲解:代码随想录

这道题思路和之前一样,因为之前几道题需要考虑去重,使用哈希表的话会非常复杂,

双指针技术可以在有序数组中高效地找到满足条件的元素对,而在哈希表中,这样的操作并不直观。

哈希表主要用于快速判断元素是否存在,而不是用于去重。虽然可以通过哈希表来检查元素是否已经出现过,但在去重时,我们通常需要知道元素出现的次数或者需要移除重复的元素,这并不是哈希表直接提供的功能。

因为四个数组,使用哈希表我们可以分成两组,遍历前两组数组,然后将每次遍历的结果储存起来,如果结果出现不止一次,我们通过

map.put(sum, map.getOrDefault(sum, 0) + 1);

将它的次数增加,便于我们直接返回元组个数 

我们来看代码:

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        int res = 0;
        Map<Integer, Integer> map = new HashMap<Integer, Integer>();
        //统计两个数组中的元素之和,同时统计出现的次数,放入map
        for (int i : nums1) {
            for (int j : nums2) {
                int sum = i + j;
                map.put(sum, map.getOrDefault(sum, 0) + 1);
            }
        }
        //统计剩余的两个元素的和,在map中找是否存在相加为0的情况,同时记录次数
        for (int i : nums3) {
            for (int j : nums4) {
                res += map.getOrDefault(0 - i - j, 0);
            }
        }
        return res;
    }
}

相关文章:

  • 【编程实践】pymeshlab的meshing_close_holes参数设置
  • 达梦数据库系列之Mysql项目迁移为达梦项目
  • 代码随想录算法训练营 | 图论 | 孤岛总面积、沉没孤岛
  • GPTs+RPA赋能智慧校园:构建下一代教育智能体的技术实践
  • RK3588开发笔记-fiq_debugger: cpu 0 not responding, reverting to cpu 3问题解决
  • Dify 开源大语言模型应用开发平台使用(二)
  • 软考高项笔记 1.1.1 信息
  • Raven: 2靶场渗透测试
  • ​Unity插件-Mirror使用方法(八)组件介绍(​Network Behaviour)
  • 【Linux】http 协议
  • 3427. 变长子数组求和
  • 【YOLO V5】目标检测 WSL2 AutoDL VScode SSH
  • 基于编译器特性浅析C++程序性能优化
  • Vue基础
  • 【Linux】自定协议和序列化与反序列化
  • 跨域-告别CORS烦恼
  • 【C++设计模式】第七篇:桥接模式(Bridge)
  • @PostConstruct注解的作用
  • 基于websocket的多用户网页五子棋 --- 测试报告
  • 小微企业友好方案:低成本智能客服系统如何落地
  • 新华时评:让医德医风建设为健康中国护航
  • “免签圈”扩容,旅游平台:今年以来巴西等国入境游订单显著增加
  • 陕西榆林:全力推进榆林学院升格榆林大学
  • GDP逼近五千亿,向海图强,对接京津,沧州剑指沿海经济强市
  • 夜读|尊重生命的棱角
  • 影子调查丨三名“淘金客”殒命雪峰山:千余废弃金矿洞的监管难题