【代码随想录算法训练营Day07】454.四数相加II; 383. 赎金信; 15. 三数之和; 18. 四数之和

文章讲述了如何利用哈希表和双指针技巧解决LeetCode中的四数之和和赎金信问题,强调了哈希法在优化时间复杂度中的作用,以及与传统方法如双指针的区别。
摘要由CSDN通过智能技术生成

Day 7 第三章 哈希表part02

  • 今日任务
    • 454.四数相加II; 383. 赎金信; 15. 三数之和; 18. 四数之和; 总结

454.四数相加II

  • 建议:本题是 使用map 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使用哈希法会提高空间复杂度,但一般来说我们都是舍空间 换时间, 工业开发也是这样。
  • 题目链接:https://leetcode.cn/problems/4sum-ii/
  • 视频讲解:https://www.bilibili.com/video/BV1Md4y1Q7Yh/
  • 文章讲解:https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html

同样自己没有思路

思路(哈希表 - Map)

与上一题两数相加思路类似,只不过就是把四个数组分成两组先遍历前两个数组将a+b存入Map再遍历后两个数组看Map中有没有target-(c+d)

自己的代码(✅通过)

public static int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {  
    int result = 0;  
    //用Key记录相加的值,用value记录出现的次数  
	HashMap<Integer, Integer> map = new HashMap<>();  
    for(int i = 0; i < nums1.length; i++){  
        for(int j = 0; j < nums2.length; j++){  
            if(map.containsKey(nums1[i] + nums2[j])){  
                //如果已经有这个值了,次数++  
				map.put(nums1[i] + nums2[j], map.get(nums1[i] + nums2[j])+1);  
            }else map.put(nums1[i] + nums2[j], 1);  
        }  
    }  
    //System.out.println(map);  
    for(int i = 0; i < nums3.length; i++){  
        for(int j = 0; j < nums4.length; j++){  
            //Map里可能有好几种  
		    if(map.containsKey(0 - (nums3[i] + nums4[j]))){  
                result += map.get(0 - (nums3[i] + nums4[j]));  
            }  
        }  
    }  
    return result;  
}

随想录代码(优化)

  1. 可以用增强for循环遍历数组中的数字
  2. 可以把Map.get换成Map.getOrDefault就不需要用if-else判断了

383. 赎金信

  • 建议:本题 和 242.有效的字母异位词 是一个思路 ,算是拓展题
  • 题目链接:https://leetcode.cn/problems/ransom-note/
  • 文章讲解:https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html

自己的思路(✅通过)

想到用Map没想到用数组TT

public static boolean canConstruct(String ransomNote, String magazine) {  
    HashMap<String, Integer> map = new HashMap<>();  
    for(int i = 0; i < magazine.length(); i++){  
        String a = String.valueOf(magazine.charAt(i));  
	}  
    for(int i = 0; i < ransomNote.length(); i++){  
        String b = String.valueOf(ransomNote.charAt(i));  
        if(map.containsKey(b) && map.get(b) != 0){  
            map.put(b, map.getOrDefault(b, 0)-1);  
        }else{  
            return false;  
        }  
    }   
    return true;  
}

随想录代码(数组)

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

15. 三数之和

  • 建议:本题虽然和 两数之和 很像,也能用哈希法,但用哈希法会很麻烦,双指针法才是正解,可以先看视频理解一下
  • 双指针法的思路,文章中讲解的,没问题 哈希法很麻烦。
  • 题目链接:https://leetcode.cn/problems/3sum/
  • 视频讲解:https://www.bilibili.com/video/BV1GW4y127qo/
  • 文章讲解:https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html
  • 参考视频:https://b23.tv/R0vtQ1f

区别说明

  • 本题与之前的两数之和(Leetcode 1 和 Leetcode 167)相比,区别在于
    • 两数之和里明确说了,只有一个答案,而本题要找出所有答案
    • 本题要虑去重
  • 本题类似于 组合总和 II (Leetcode 40),区别在于
    • 40题要求列出任意数之和等于 target 的所有组合,而本题要求三数之和等于 target 的所有组合
    • 40题使用回溯的办法时间复杂度是O(2n × \times ×n),而本题的三数限制了递归次数仅有一次,并且每次递归终点是求两数之和时间复杂度为O(n),因此总时间复杂度为O(n2)

思路

  1. 先将数组的元素从小到大排序(方便去重)
  2. 先固定一个值,然后将后面的数转化成两数之和
    pF8iAV1.png

代码

public List<List<Integer>> threeSum(int[] nums){
	List<List<Integer>> result = new LinkedList<>();
	Arrays.sort(nums);  //对数组进行排序
	for(int i = 0; i < nums.length; i ++){
		if(nums[i] > 0) return result;  //如果大于01怎么也不可能相加为0
		//第一步去重,确定固定的数和之前不一样并防止越界
		if(i > 0 && nums[i] == nums[i - 1]) continue;
		int left = i + 1;
		int right = nums.length - 1;
		while(left < right){
			int sum = nums[left] + nums[right] + nums[i];
			if(sum < 0){
				left ++;
			}else if(sum > 0){
				right --;
			}else{
				result.add(Arrays.asList(nums[i], nums[left], nums[right]));
				//继续查找其他解,同时需要去重
				left ++;
				while( left < right && nums[left] == nums[left - 1]){
					left ++;
				}
				right --;
				while( left < right && nums[right] == nums[right + 1]){
					right --;
				}
			}
		}
	}
	return result;
}

18. 四数之和

  • 建议: 要比较一下,本题和 454.四数相加II 的区别,为什么 454.四数相加II 会简单很多,这个想明白了,对本题理解就深刻了。
  • 本题思路整体和 三数之和一样的,都是双指针,但写的时候 有很多小细节,需要注意,建议先看视频。
  • 题目链接:https://leetcode.cn/problems/4sum/
  • 视频讲解:https://www.bilibili.com/video/BV1DS4y147US/
  • 文章讲解:https://programmercarl.com/0018.%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.html

思路

四数之和固定一个数就转换成三数之和了,与三数之和合并,写一个递归的代码

非递归代码

public static List<List<Integer>> fourSum(int[] nums, int target){
	//准备:创建存放结果的二维数组
	List<List<Integer>> result = new ArrayList<>();
	//第一步:先给数组进行排序
	Arrays.sort(nums);
	System.out.println(Arrays.toString(nums));
	//第二步:固定nums[k](最少剩3个数)
	for(int k = 0; k < nums.length - 3; k ++){
		//对固定的数和target进行判断
		if(nums[k] > target && target > 0) return result;
		//去重操作
		if(k > 0 && nums[k] == nums[k - 1]) continue;
		//第三步:固定nums[i](最少剩2个数)
		for(int i = k + 1; i < nums.length - 2; i++){
			//去重操作
			if(i > k + 1 && nums[i] == nums[i - 1]) continue;
			//❗❗❗重点一
			//对固定的k和i的总和进行判断,这里不是target > 0了!需要nums[i]>0!!!
			//且不是直接return是break,上一层可以直接return
			if(nums[i] + nums[k] > target && nums[i] > 0) break;
			//第四部:进行二数相加
			int left = i + 1;
			int right = nums.length - 1;
			//注意类型!
			while(left < right){
				//❗❗❗重点二
				//需要把sum放到循环里,防止溢出
				long sum = (long) nums[k] + nums[i] + nums[left] + nums[right];
				if(sum < target) left ++;
				else if(sum > target) right --;
				else{
					result.add(Arrays.asList(nums[k], nums[i], nums[left], nums[right]));
					//继续查找其他解,同时需要去重
					left ++;
					while( left < right && nums[left] == nums[left - 1]){
						left ++;
					}
					right --;
					while( left < right && nums[right] == nums[right + 1]){
						right --;
					}
				}
			}
		}
	}
	return result;
}

递归代码

public static List<List<Integer>> fourSum(int[] nums, int target) {
	Arrays.sort(nums);  //对数组进行排序
	List<List<Integer>> result = new LinkedList<>();
	dfs(4,0,nums.length-1, target, nums, new LinkedList<>(), result);
	return result;
}
//使用递归的方法,设计一个用来递归的函数
//n代表数字的个数,刚开始是4,n为2时就可套用两数之和
//i,j表示起始和终止的索引
//stack是用来收集一组结果的
//result用来存放最终结果
public static void dfs(int n, int i, int j, long target, int nums[], LinkedList<Integer> stack, List<List<Integer>> result){
	if(n == 2){
		//套用两数之和求解(Leetcode167)
		twoSum(i, j, target, nums, stack, result);
		return;
	}
	//当剩余数不够时就不需要固定了,所以缩小范围至 j - (n - 2)
	for(int k = i; k < j - (n - 2); k ++){
		//检查重复数(防止越界)
		if(k > i && nums[k] == nums[k - 1]) continue;
		//当target不为0时不可以这样判断!因为target可能是负数
		//❌if(nums[k] > target) break;✅增加target>0的判断
		if(nums[k] > target && target > 0) break;
		//先固定一个数字,将其先放在栈里
		stack.push(nums[k]);
		//再尝试n-1个数字之和
		dfs(n-1, k + 1, j, target - nums[k], nums, stack, result);
		//如果固定后没找到解要回溯,取消固定
		stack.pop();
	}
}
public static void twoSum(int i, int j, long target, int nums[], LinkedList<Integer> stack, List<List<Integer>> result){
	while(i < j){
		int sum = nums[i] + nums[j];
		if(sum < target){
			i ++;
		}else if(sum > target){
			j --;
		}else{
			//先把stack中的数添加到list里
			ArrayList<Integer> list = new ArrayList<>(stack);
			list.add(nums[i]);
			list.add(nums[j]);
			result.add(list);
			//继续查找其他解,同时需要去重
			i ++;
			while( i < j && nums[i] == nums[i - 1]){
				i ++;
			}
			j --;
			while( i < j && nums[j] == nums[j + 1]){
				j --;
			}
		}
	}
}
  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值