代码随想录算法训练营第六天 | 哈希表 Part02


每日任务

一,leetcode 454 四数相加 II
二,leetcode 383 赎金信
三,leetcode 015 三数之和
四,leetcode 018 四数之和
五,总结

一、leetcode 454 四数相加 II

1、原题链接

leetcode 454 四数相加 II
在这里插入图片描述
在这里插入图片描述

2、解题思路及代码展示

本题是使用哈希法的经典题目,虽然本题看着与“三数之和 ”和“四数之和 ”两道题目类似,但是后两者并不适合用哈希法,因为这两题使用哈希法在不超时的情况下做到对结果去重是很困难的,很有多细节需要处理。

而这道题目是四个独立的数组,只要找到A[i] + B[j] + C[k] + D[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况,所以相对于题目18. 四数之和,题目15.三数之和,还是简单了不少!

如果本题想难度升级:就是给出一个数组(而不是四个数组),在这里找出四个元素相加等于0,答案中不可以包含重复的四元组。

本题解题步骤:
(1)首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数
(2)遍历A和B数组,统计两个数组元素之和,和出现的次数,放到map中。
(3)定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
(4)再遍历C和D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
最后返回统计值 count 就可以了

具体代码实现:

class Solution {
    public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
        // 定义一个result,用来存最后返回的结果(元组的个数)
        int result = 0 ;
        // 使用HashMap结构存储本题数据
        // {key:a + b对应的值 , value a + b这个值出现的次数}
        Map<Integer,Integer> map = new HashMap<Integer,Integer>();

        // 首先遍历数组1和数组2,将a + b 的情况先取出来
        for(int num1 : nums1){
            for(int num2 : nums2){
                // sum 记录 a + b
                int sum = num1 + num2 ;
                // 将 a + b 的值和对应出现的次数存入到map中
                // map.getOrDefault(sum,0)  该方法是在map中,根据key(a+b的值)得到其对应的value(出现次数),没找到就返回默认0,那么对于第一次出现的元素,所以加上个1,表明出现了一次
                map.put(sum , map.getOrDefault(sum,0) + 1);
            }
        }

        // 再遍历数组3和数组4,看map中有没有出现 (0 - c - d)的情况 ,即 a + b = 0 -(c - d),因为map中的key就是存的 a + b 的值
        // 0 - c - d = a + b  ==>  a + b + c + d = 0
        for(int num3 : nums3){
            for(int num4 : nums4){
                result += map.getOrDefault(0 - num3 - num4,0);
            }
        }
        return result;
    }
}

有一个小细节,传入的s和t都是字符串,而我们的record是一个int类型的数组:
(1)字符串的 length() 方法:当你有一个字符串变量,比如 s 或 t,你可以通过调用 s.length() 或 t.length() 来获取字符串的长度。这是因为 String 类提供了一个名为 length 的方法,它返回字符串的长度。
(2)数组的 length 属性:对于数组,比如 int[] record,你不需要调用方法来获取其长度。数组有一个内置的属性 length,它是一个 int 类型的值,直接表示数组的长度。

二、leetcode 383(赎金信)

1、原题链接

leetcode 383 (赎金信)

在这里插入图片描述

2、解题思路及代码展示

有效的字母异位词 那一题相当于求字符串a 和字符串b 是否可以相互组成 ,而这道题目是求字符串a能否组成字符串b,而不用管字符串b能不能组成字符串a,是一个单向的操作。

再度明确题意,本题判断第一个字符串ransom(赎金信)能不能由第二个字符串magazines(杂志)里面的字符构成,需要注意两点:
(1)为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思,这里说明杂志里面的字母不可重复使用,即 magazine 中的每个字符只能在 ransomNote 中使用一次。
(2)两个字符串均只含有小写字母,题目明确指出了说只有小写字母。

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {

        // 判断ransomNote能不能由magazine中的字符组成

        // 特殊情况的直接判断,如果ransomNote的长度比magazine的长度还长,肯定是组不成的。
        // 传入的ransomNote和magazine是字符串String,用的是length()是一个方法
        if(ransomNote.length() > magazine.length()){
            return false;
        }

        // 下来是选择具体的哈希表结构来实现本题,还是选择数组,记录a-z每一个字符出现的次数
        // 初始化容量为 26 ,因为题目中说了只有小写字母
        int[] record = new int[26];

        //首先遍历magazine数组,统计每一个数组出现的情况
        //使用加强for循环,还需要将字符串转换为字符数组
        for(char m : magazine.toCharArray()){
            record[m -'a']++;
        }

        //获取magazine中所有元素之后,就再遍历一下ransomNote数组
        for(char r : ransomNote.toCharArray()){
            record[r - 'a']--;
        }

        // 最后再遍历record数组,如果小于零说明ransomNote里出现的字符,magazine没有,其余情况都是能组成的
        for(int i : record){
            if(i < 0){
                return false;
            }
        }

        return true;

    }
}

三、leetcode 015 三数之和

1、原题链接

leetcode 015 三数之和
在这里插入图片描述
在这里插入图片描述

2、解题思路及代码展示

本题的哈希表解法,先通过两层for循环确定 a 和b 的数值了,然后最后,使用哈希法来确定 0-(a+b) 是否在数组里出现过,思路正确,但是本题有一个关键点在于“不可以包含重复的三元组”,把符合条件的三元组放进结果集中,然后再去重,这样是非常费时的,很容易超时,而且去重的过程不好处理,有很多小细节,整体来看使用哈希法比较难。所以提出另一种解法——双指针法。

双指针法
整体思路如下:
(1)首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
(2)依然还是在数组中找到 a,b和c,使得a + b +c = 0,我们这里相当于 a = nums[i],b = nums[left],c = nums[right]。
(3)如何移动 left 和 right? 如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
(4)如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。

PS:
所谓的三元组重复,是指的是两个三元组的元素一模一样,但是一个元组内的元素时刻一样的,比如【-1,-1,2】这种情况,有两个-1。

直接上代码:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
    	// 定义一个List,其中每一个元素也是List,List中每一个元素是Intrger类型的
    	// result 是结果的集合,也就是元组的集合: [[],[],[]]
        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;
            }
			
			// 完成对a的去重操作
			// nums[i] == nums[i - 1] 还是 nums[i] == nums[i + 1],后者对于
            if (i > 0 && nums[i] == nums[i - 1]) {  // 去重a
                // 如果这两个数数值相同,说明目前这个数与之前的数得到的结果是一样,直接跳过这个数的循环,让a直接到nums中的下一个数。
                continue; 
            }
			// 定义左指针为i的邻近右元素
            int left = i + 1;
            // 右指针指向数组的最后一个元素
            int right = nums.length - 1;
            while (right > left) {
            	// 根据a + b +c 的值判断左右指针该如何移动,相对而行,直到相遇
                int sum = nums[i] + nums[left] + nums[right];
                if (sum > 0) {
                	// 比0大,说明数值大了,让右指针往左移一位,因为数组是排过序的,从左到右依次递增
                    right--;
                } else if (sum < 0) {
               		// 比0小,说明数值小了,让左指针指针往右移一位,因为数组是排过序的,从左到右依次递增
                    left++;
                } else {
                	// 找到满足三数和为0的情况了,添加到结果集中
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));
		    		// 去重逻辑应该放在找到一个三元组之后,对b 和 c去重
		    		// 而且此时的a,也就是第一个元素nums[i]时已经去重了的
		    		// b,c去重原理也是一样的,两个值是一样的话,肯定之前的那次就已经把这次的给算上了
		    		// 对于b,也就是nums[left],left是从左往右移动的
		    		// 对于c,也就是nums[right],right是从右往左移动的
                    while (right > left && nums[right] == nums[right - 1]) right--;
                    while (right > left && nums[left] == nums[left + 1]) left++;
                    
                    // 找到目标值以后,左右指针同时收缩
                    right--; 
                    left++;
                }
            }
        }
        return result;
    }
}

四、leetcode 018 (四数之和)

1、原题链接

leetcode 018 四数之和
在这里插入图片描述
在这里插入图片描述

2、解题思路及代码展示

本题和上一题三数之和的区别就在于,再加一层for循环:
在这里插入图片描述

三数之和 (opens new window)的双指针解法是一层for循环num[i]为确定值,然后循环内有left和right下标作为双指针,找到nums[i] + nums[left] + nums[right] == 0。

四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况。那么一样的道理,五数之和、六数之和等等都采用这种解法。

import java.util.*;
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++){

            // 先做剪枝处理
            // target可以为负值,nums中也可能有负值,两个负数相加可能为负数
            // [-4,-1,0,0] target=-5的话,-4比target-5大
            if(nums[k] > target && nums[k]>=0){
                break; // 直接退出循环
            }

            // 对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;
                }
                // 对nums[i]去重 i > k + 1 的目的是为了让nums[i -1]下标不为负值  
                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;
    }
}

提示:这里对文章进行总结:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值