leetcode_15_三数之和_leetcode18_四数之和_leetcode16_三数之和最接近

总结

一、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;
    } 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值