文章目录
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;
}
随想录代码(优化)
- 可以用
增强for循环
遍历数组中的数字 - 可以把
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)
思路
代码
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 --;
}
}
}
}