给你一个整数数组,一个目标值,在数组中,找到两个数 || 三个数 || 四个数 之和 为 目标值 的数字下标
一. 2数之和
for 循环 2层 遍历
1. 我们可以将所有的数字 放入map中,(值--下标)
2. 然后遍历数组,算出对应差值,看map中是否存在 (这个数的下标不能是 当前 遍历的下标)
1. 在遍历的时候,放入map中
2. (一边遍历查找 ,一边放入)
// 每一次 ,找的是 当前位置 之前的数字 是否 存在 与 当前数字 匹配
// 存在 ,返回结果
// 不存在 ,put进map, 给之后查找
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int cha = target - nums[i];
if(map.containsKey(cha) ){
return new int[]{i,map.get(cha)};
}
map.put(nums[i], i);
}
return null;
}
}
这是一个 互补的过程
假设 满足的 下标为 x,y (x < y )
过程: 遍历到 x 时候,没有满足的 ,put进map
遍历到 y 时候, map包含x ,返回结果
二. 3数之和
不可重复问题 :往往需要排序
1. 排序
2. 遍历集合 ,
3. 因为 是求 3 数之和 ,所以固定 一个下标,往后遍历找到
class Solution {
// 3指针,两个夹逼
public List<List<Integer>> threeSum(int[] nums) {
// 1. 排序
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
// i 就是标志指针 ,在它的后面找满足的 2 个数字 所以 i 的后面至少 有 2 个数字
for (int i = 0; i < nums.length-2; i++) {
// 大于0,结束,后面的和,一定大于0
if (nums[i] > 0) break;
// 非第一个重复,下一个 ,
// 【1,0, 0, 0】 对于连续重复 ,将第一个作为标志位,后面相同的 continue
if (i > 0 && nums[i] == nums[i-1]) continue;
// 2指针 ,首尾 夹逼 ,不会跳过 可行解
int a = i+1, b = nums.length - 1;
while (a < b) {
int sum = nums[i] + nums[a] + nums[b];
if (sum < 0){
// 低指针往后移 , 之和 变大,
while ( a < b && nums[a] == nums[++a]);
}else if(sum > 0) {
// 高指针往前移 ,之和 变小
while ( a < b && nums[b] == nums[--b]);
}else {
list.add(new ArrayList<Integer>(Arrays.asList(nums[i],nums[a],nums[b])));
// 低,高指针往后,前移动,进行下一次while,即同时往 大,小变化,抵消
while ( a < b && nums[a] == nums[++a]);
while ( a < b && nums[b] == nums[--b]);
}
}
}
return list;
}
}
[-1 , 0 , 1 , 2 , -1 , -4]
模拟过程:
1. 排序: [-4 , -1 , -1 ,0 , 1 , 2 ]
2. 固定 -4 , 在 [-1 , -1 ,0 , 1 , 2 ] 范围中 双指针夹逼 找满足的下标
3. 固定 -1 , 在 [-1 ,0 , 1 , 2 ] 范围中 双指针夹逼 找满足的下标
4. ....
5. 在这个过程中,首先要 去重:
对于 第一个指针的去重 : if (i > 0 && nums[i] == nums[i-1]) continue;
例如情况:【1 , 1 , 1 , 2 , 2 , 2】 在将 下标为 0 的1 作为 标志位指针 后,要直接移动到 下标为 3 的2 位置,进行 下一次计算
固定 下标为 0 的 1 搜索的范围 : 【 , 1 , 1 , 2 , 2 , 2】
固定 下标为 1 的 1 搜索的范围 : 【 , , 1 , 2 , 2 , 2】
固定 下标为 2 的 1 搜索的范围 : 【 , , , 2 , 2 , 2】
很显然,后面的搜索范围 , 在第一次,已经搜索过了,得到的结果集: 必然与之前重复
6. 对于双指针的去重及加速:
对于 while ( a < b && nums[a] == nums[++a]); 类似代码的解释
当我们的 左右指针满足 或 不满足条件后 ,都需要移动指针
以 sum < 0 为例:
双指针的值应该增加,让左指针 往 左 移动,并且 移动后 的值不等于之前的值 ,并满足左右指针原则 :明了代码
while (a < b){
a++; //左移一位
if (nums[a] != nums[a-1]){ // 当前位置 与 之前位置 的值是否 不等
break;
}
}
7. 为什么这样做可以 完全搜索 满足的 下标?
这与 https://leetcode-cn.com/problems/container-with-most-water/ 11. 盛最多水的容器 的原理类似
为什么这样做可以 完全搜索 满足的 下标? 两数和的解
- 可以先将问题简化 ,将左右指针的范围的元素 首先
去重
,(不是 将[1,1,1,2,2] 变为 [1,2] 去重) - 这是 代码的一部分 功能 ,
while代码的功能 - 对于
if ,else
的解释,按照暴力解法,双重for循环, - 但是数组现在已经
有序(升序)
,以一种情况为例:[ a , b ],
当sum < 0
, 左右指针和,应该变大, - 直接将范围 变为
[ a+1 , b ]
, 我们在这一步里,直接将 暴力遍历 中的
【a,a+1】 , 【a,a+2】 , 【a,a+3】, ..... 【a,b-1】
这些情况 剪枝
, 因为a+1,…,b-1 ,都 小于 b, 不满足解
为什么 可以 完全 搜索 3 数之和 的解 ,不会有遗漏
在排序后 , 所有 满足条件的三元组 下标都可以写 为 [x , y , z ] (0 < x < y < z < len-1)
要证明的是 代码对其 没有任何遗漏
- 代码的 遍历顺序,从前到后
X 肯定会作为 指针标志位 ,Y,Z 也一定会 作为 左右指针可行解
- 在结合 遍历出 左右指针 所有可行解,没有遗漏
三. 4数之和
与三数之和一样的思路,霸王硬上弓
class Solution {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> list = new ArrayList<>();
// 倒数第四个
for (int i = 0; i < nums.length-3; i++) {
// 与自己的起点 0 相比较
if (i > 0 && nums[i] == nums[i-1]) continue;
// 倒数第 三 个
for (int j = i+1; j < nums.length-2; j++) {
// 与自己的起点 i+1 相比较
if (j > i+1 && nums[j] == nums[j-1]) continue;
// 后面双指针 要凑成 的数字 和
int flag = target-nums[i]-nums[j];
// 左右指针,夹逼
int a = j+1 , b = nums.length-1;
while(a < b){
if(nums[a] + nums[b] > flag){
while(a < b && nums[b] == nums[--b]) ;
}else if(nums[a] + nums[b] < flag){
while(a < b && nums[a] == nums[++a]) ;
}else{
list.add(Arrays.asList(nums[i],nums[j],nums[a],nums[b]));
while(a < b && nums[b] == nums[--b]) ;
while(a < b && nums[a] == nums[++a]) ;
}
}
}
}
return list;
}
}
原理与3数之和,一模一样