总结
一、while问题
1.自己while()用的不熟,总出错
while() 一定加加边界判断,之后再是条件判断
在while()里,不像for()循环,开始就定义好了下一个
在while()里,要自己记得加上,下一个是什么 ,L++,R++
2. 列举所有情况时,总是冗杂在一起考虑,应该单独针对一个数,一个数的单独考虑
双层for()循环遍历两个数的所有组合 固定一个,动另一个
三层for()循环遍历三个数的所有组合
三数之和,四数之和,分别对应两层循环,三层循环
最后两个数用while()场景的特殊性
遍历数组,求满足要求的所有组合,就解决两个问题
// 1. 遍历所有情况:
// 2. 去重问题
// 顺带注意遍历时,边界问题
二、没有想到hash表解法问题
三、去重问题
为什么if(sum == 0) 要去重,else if(sum < 0 or sum > 0)不去重,因为sum == 0 若nums[L] == nums[L+!], nums[R] == nums[R-1],下一次循环,又会被添加到sum里面。
而sum < 0 或 sum > 0 , 若nums[L] == nums[L+1] nums[R] == nums[R-1], 进入下次循环,依然会继续移动指针,不会被加入到结果中。所以不需要去重。
本题知识点
-
1.遍历数组寻找若干个满足条件的数怎么做:比如说三个数和
- 1.1 数组排序,不让数组杂乱无章
- 1.2 for 固定一个数,作为base
- 1.3 while(L < R) 作用逼近
-
2.怎么实现数组的下一个要访问的元素和上一个元素不相等
- 2.1 while(越界条件 && 下一个要访问的元素 == 上一个访问过的元素) 指针移动
- eg while(i > 0 && i < nums.length - 2 && nums[i] == nums[i-1]) i++;
- eg while(L < R && nums[L+1] == nums[L]) L++;
四数之和:
// 1. 遍历所有情况:
// 先固定第一个数,for(int i = 0; i < nums.length - 3; i++) 去重
// 在固定第二个数, for(int j = i + 1; j < nums.length - 2; j++) 去重
// 两层循环,列举出了第一个数,第二数的所有情况
// 第三层循环,寻找满足条件的两个数(为什么一层循环就可以遍历两个数的所有情况)
// 因为如果两层循环:固定以三个数,遍历跟新第四个数,四数之和肯定会变。
// 所以最后遍历第三个,第四个数的所有情况只需要1层循环,同时变!
// 2. 去重问题
// 即将要访问的数 ?= 刚刚访问的数
// 对于第一个数,第二数
// for() 循化 nums[i] == nums[i-1] nums[i]就是即将要访问的数,nums[i-1]就是访问过的数
// 对于第三个,第四个数:
// while() 循环,要自己负责自增 nums[L+1] == nums[L] 从左到右,nums[L+1]是下一个即将要访问的元素,nums[L]是刚刚访问过的元素
// nums[R-1] == nums[R] 从右到左, nums[R-1]是下一个即将要访问的元素,nums[R]是刚刚访问过的元素
1.题目leetcode_15_三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
2.思路
在数组中寻找满足某一条件的所有组合数。
2.1关键:怎么既能列举出所有情况,又不重复。
2.2 一般做法:遍历数组,一种深度优先搜索,回溯,双指针,
这里采用双指针法:
定下一个数,再去用指针去遍历剩下的数;
for(int i = 0; i < nums.length - 2; i++) { // 第一个数的所有情况
int num = nums[i]; // 第一个数
int L = i + 1, R = nums.length - 1; // 在(L,R)中寻找剩下的两个数
while(L < R) {
}
}
2.3 去重思路:单独对每一个数进行考虑,不要融在一起考虑:
针对第一个数的重复,从左到右,若有重复,跳过
for(int i = 0; i < nums.length - 2; i++) {
while(i > 0 && i < data.length - 2 && nums[i] == nums[i-1]) i++;
}
确定好第一个数后,确定第二个数,第三个数 int L = i+1, R = nums.length - 1;
访问:第一个数,第二个数,第三个数 int sum = nums[i] + nums[L] + nums[R];
针对第二个数重复
L: 从左到右,如果下一个要访问nums[L+1]的和已经访问过的nums[L]一样,继续向右L++
当下一个要访问nums[L+1] != 已经访问过的nums[L], 跳出循环,后面还要L++;
while(L < R && nums[L+1] == nums[L]) L++;
针对第三个数重复
R:从右到左,如果下一个要访问的nums[R-1]和已经访问过的nums[R]一样,继续向左R-- ;
当下一个要访问nums[R-1 != 已经访问过的nums[R], 跳出循环,后面还要R–;
while(L < R && nums[R-1] == nums[R]) R--;
注意点:跳出循环的状态
当下一个要访问nums[L+1] != 已经访问过的nums[L], 跳出循环,后面还要L++;
当下一个要访问nums[R-1 != 已经访问过的nums[R], 跳出循环,后面还要R–;
3.出错
当输入为[0, 0, 0, 0]时,nums[3] == nums[2] i++, i = 4 , nums[4] 越界
修改:
while中一定要判断 i 的取值范围,要不然很容易数组越界
L = i+1, R = nums.length - 1;
while(L < R) 这里只判断 L < R; 是因为 L, R 都是往数组中间走,而不是两边走,所以判断
L < R 的情况。
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
// 确定第一个数 for(int i = 0; i < nums.length - 1; i++) 去重
// 确定第二,三二个数
// 判断是否满足条件
// 寻找下两个新的左右元素,
// 去重: 即将要访问的是否等于刚刚访问的,针对第一个的元素,即将要访问的i 刚刚访问的i-1
// 第二个,第三个,L+1 L R-1 R
// 为什么第二,三个数同时去重,固定第一个元素,固定第二个元素,遍历第三个元素,第三个元素去重;相当于三层遍历,才是能全部列举出来
// 因为这里的是三数之和,第一个元素不变,第二个元素不变,只有第三个元素判断去重更新,最后结果肯定不满足,这里是第二,三个元素一起变
// 四数之和是:固定一个元素,固定第二个元素,相当于两层循环,在最后一层循环中,同时更新第三,四个元素
// whlile()判断,先写边界条件,再写跳出循环条件
List<List<Integer>> res = new ArrayList<>();
if(nums == null || nums.length < 3) return res;
Arrays.sort(nums);
for(int i = 0; i < nums.length - 2; i++) {
if(nums[i] > 0) return res;
while(i > 0 && (i < nums.length - 2) && nums[i] == nums[i-1]) i++;
int L = i+1, R = nums.length - 1;
while(L < R) {
int sum = nums[i] + nums[L] + nums[R];
if(sum < 0) L++;
else if(sum > 0) R--;
else if(sum == 0) {
res.add(Arrays.asList(nums[i], nums[L], nums[R]));
while(L < R && nums[L+1] == nums[L]) L++;
while(L < R && nums[R-1] == nums[R]) R--;
L++;
R--;
}
}
}
return res;
}
}
leetcode18_四数之和
遍历数组,求满足要求的所有组合,就解决两个问题
// 1. 遍历所有情况:
// 2. 去重问题
// 顺带注意遍历时,边界问题
// 1. 遍历所有情况:
// 先固定第一个数,for(int i = 0; i < nums.length - 3; i++) 去重
// 在固定第二个数, for(int j = i + 1; j < nums.length - 2; j++) 去重
// 两层循环,列举出了第一个数,第二数的所有情况
// 第三层循环,寻找满足条件的两个数(为什么一层循环就可以遍历两个数的所有情况)
// 因为如果两层循环:固定以三个数,遍历跟新第四个数,四数之和肯定会变。
// 所以最后遍历第三个,第四个数的所有情况只需要1层循环,同时变!
// 2. 去重问题
// 即将要访问的数 ?= 刚刚访问的数
// 对于第一个数,第二数
// for() 循化 nums[i] == nums[i-1] nums[i]就是即将要访问的数,nums[i-1]就是访问过的数
// 对于第三个,第四个数:
// while() 循环,要自己负责自增 nums[L+1] == nums[L] 从左到右,nums[L+1]是下一个即将要访问的元素,nums[L]是刚刚访问过的元素
// nums[R-1] == nums[R] 从右到左, nums[R-1]是下一个即将要访问的元素,nums[R]是刚刚访问过的元素
for() {
a
for() {
b
while(L,R)
}
}
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
// 确定第一个数 for(int i = 0; i < nums.length - 3; i++) 去重 while(nums[i] == nums[i-1])
// 确定第二个数 for(int j = i + 1; j < nums.length - 2; j++) 去重
// 确定第三,四个数 int L = j + 1, R = nums.length - 1;
// 判断4个数是否满足条件 nums[i] + nums[j] + nums[L] + nums[R] == 0;
// 第三,四个数去重
List<List<Integer>> res = new ArrayList();
if(nums == null || nums.length < 4) return res;
Arrays.sort(nums);
for(int i = 0; i < nums.length - 3; i++) {
while(i > 0 && (i < nums.length - 3) && nums[i] == nums[i-1]) i++;
for(int j = i + 1; j < nums.length - 2; j++) {
while(j > i + 1 && (j < nums.length - 2) && nums[j] == nums[j-1]) j++;
int L = j + 1, R = nums.length - 1;
while(L < R) {
int sum = nums[i] + nums[j] + nums[L] + nums[R];
if(sum < target) L++;
else if(sum > target) R--;
else if(sum == target) {
res.add(Arrays.asList(nums[i], nums[j], nums[L], nums[R]));
while(L < R && nums[L+1] == nums[L]) L++;
while(L < R && nums[R-1] == nums[R]) R--;
L++;
R--;
}
}
}
}
return res;
}
}
出错:
target >= 0时, if(nums[i] > target) return res; 整数加越大,第一个元素nums[i] > 0, 和后面两个元素相加,sun肯定大于 > 0,不满足;
但当targer < 0时,不能添加,因为负数越加越小,第一个元素nums[i] > target, 和后面两个元素相加,sum有可能 == target
if(nums[i] > target) return res;
二、hash表解决
private List<List<Integer>> hashSolution(int[] nums) {
if (nums == null || nums.length <= 2) {
return Collections.emptyList();
}
Set<List<Integer>> result = new LinkedHashSet<>();
for (int i = 0; i < nums.length - 2; i++) {
int target = -nums[i];
Map<Integer, Integer> hashMap = new HashMap<>(nums.length - i);
for (int j = i + 1; j < nums.length; j++) {
int v = target - nums[j];
Integer exist = hashMap.get(v);
if (exist != null) {
List<Integer> list = Arrays.asList(nums[i], exist, nums[j]);
list.sort(Comparator.naturalOrder());
result.add(list);
} else {
hashMap.put(nums[j], nums[j]);
}
}
}
return new ArrayList<>(result);
}
三数之和最接近
思路
- 遍历数组寻找所有组合,for固定第一个,while第二,三个
- 遍历寻找组合的和值最接近目标值,排好序后的数组,L , R左右夹击,和小于目标值,往L++, 和大于目标值,R–
- 找最小,先确定一个res,将中途计算的所有sum和res相比,替换。
class Solution {
public int threeSumClosest(int[] nums, int target) {
// for() 确定第一个数
// while() 确定第二,三个数
if(nums == null || nums.length < 3) return Integer.MIN_VALUE;
Arrays.sort(nums);
int res = nums[0] + nums[1] + nums[2];
for(int i = 0; i < nums.length - 2; i++) {
while(i > 0 && i < nums.length - 2 && nums[i] == nums[i-1]) i++;
int L = i + 1, R = nums.length - 1;
while(L < R) {
int sum = nums[i] + nums[L] + nums[R];
if(Math.abs(sum - target) < Math.abs(res - target)) res = sum;
if(sum - target == 0) return res;
else if(sum - target < 0) L++;
else if(sum - target > 0) R--;
}
}
return res;
}
}