【刷题笔记】n数之和

不知道大家是否还能够想起自己刷leetcode的第一道题,反正我能清晰的记得——两数之和。当时用两个for循环,暴力地得到了结果。然后,一看答案,TMD hash 是什么鬼,接着看那个答案的讲解(脑中瞬间想起那个声音),看完视频,遂放弃......

其实,像两数之和、三数之和以及四数之和在校招面试中还是很可能出现的,一定不能做不出来,否则就可以去买块豆腐撞shi自己了。

不多废话,今天一次性把这个n数之和给大家总结一下:

1. 两数之和

就不要两个for循环去做了吧,会被鄙视的。

这道题的前提是每种输入只会对应一个答案,所以找到结果就可以立即返回。

我们要利用一个HashMap,其中map的key为target - nums[i],value为当前位置下标i。

以下图为例:

当我们走到4的时候,发现之前hashmap中存过4,那么我们就找到了下标0和下标2的位置加起来是得target的。 

    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        map.put(target - nums[0], 0);
        int[] res = new int[2];
        for (int i = 1; i < nums.length; i++) {
            if (map.containsKey(nums[i])) {
                res[0] = map.get(nums[i]);
                res[1] = i;
                break;
            } 
            map.put(target - nums[i], i);
        }
        return res;
    }

 15. 三数之和

 这道题也不能用三个for循环去做了吧?太暴力了,还是用双指针吧。

其实这道题的思路也很清晰,题目不是说让我们找三个数的和吗?那我们把第一个数固定,让另外两个数变不就好了,当另外两个数把数组都遍历完了,我们再移动第一个数,再让另外两个数变。

图解如下:

通过这种方法遍历下去,就能获取到所有解。但是如果只是这样的话,还是不能获取到最终的正确答案,因为题目说了答案中不可以包含重复的三元组,所以我们还得给获得的结果去重。比如说,当我们移动到如下位置时,会得到相同的结果,所以在我们写代码的时候,可以通过以下代码跳过去:

if (i > 0 && nums[i] == nums[i - 1]) {
    continue;
}

这样就可以获得正确答案了。

但是,细心的小伙伴就发现了,在这道题的第一幅图中,第一步和第二步,也是重复的:

左边指针在两个-1处获得的所有组合都是一致的,除了{-4, -1, -1},但是这种情况包含在左边的-1的所有结果内,因此可以说,右边-1的所有结果是左边-1所有结果的子集。由此,我们可以得出一个结论,就是只要我的左指针向右移动时,碰到和我相邻的前一个数一样,就可以直接跳过他直接继续向右移动,直到下一个值与之前的值都不一样。同理,我们的右指针向左移动也要遵循这个原则。这段代码如下:

while (left < right && nums[left] == nums[left + 1]) {
    left++;
}
while (left < right && nums[right] == nums[right - 1]) {
    right--;
}

最后,就是这道题要求解的前提是,必须给这个数组排序,否则是找不出全正确答案的。直接调用Arrays.sort(nums)就行,你也可以自己写个排序(但是我们掉的这个sort方法要比我们自己写的快,包括快排、堆排等,之后我也会写出一篇关于排序算法的介绍)。

    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 2; i++) {
            if (nums[i] > 0) {
                return res;
            }
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.length - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum < 0) {
                    left++;
                } else if (sum > 0) {
                    right--;
                } else {
                    res.add(Arrays.asList(nums[i], nums[left], nums[right]));
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    left++;
                    right--;
                }
            }
        }
        return res;
    }

 16. 最接近的三数之和

这道题和上面的三数之后非常相似,逻辑是一样的,只不过这道题是找最接近目标值target的数,那么其实我们找到和target相等的数就可以立刻返回。如果找不到,那么和target最接近的数就一定在nums[i] + nums[left] + nums[right] 和 nums[i] + nums[left + 1] + nums[right]之间,或者在nums[i] + nums[left] + nums[right] 和 nums[i] + nums[left] + nums[right - 1]。也就是说,当到了某个临界值的时候,当left所在位置的三数和小于target,而当left向右移动一个位置,三数和就大于target了,之后时候就要取这两个位置和的最接近target的值。right也是同样的道理。

所以有了如下代码:

    res = Math.abs(res - target) <= Math.abs(sum - target) ? res : sum;
    if (sum < target) {
        while (left < right && nums[left] == nums[left + 1]) {
            left++;
        }
        left++;
    } else {
        while (left < right && nums[right] == nums[right - 1]) {
            right--;
        }
        right--;
    }

上述whlie中代码是用来去重的,跟上题原理一致。

最终题解如下:

    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int res = nums[0] + nums[1] + nums[2];
        for (int i = 0; i < nums.length - 2; i++) {
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int left = i + 1;
            int right = nums.length - 1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == target) {
                    return sum;
                }
                res = Math.abs(res - target) <= Math.abs(sum - target) ? res : sum;
                if (sum < target) {
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    left++;
                } else {
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    right--;
                }
            }
        }
        return res;
    }

18. 四数之和

四数之和,就是一个数组中的四个数相加,让其等于一个目标值(没完了是吧)。解法跟三数之和一样的思路,只要三数之和你弄明白了,四数之和不看题解就可以自己做出来。三数之和是固定一个值,让两个指针去遍历数组剩余部分。

那四数之和呢?

让三个指针呗。

开什么玩笑,三个指针咋遍历,那不乱套了。

so,就固定两个值,然后让两个指针去遍历剩余部分,不就好了。

就这样(举个例子):

剩下的就和三数之和大差不差了。

public List<List<Integer>> fourSum(int[] nums, int target) {
    List<List<Integer>> res = new ArrayList<>();
    if (nums.length < 4) {
        return res;
    }
    Arrays.sort(nums);
    for (int i = 0; i < nums.length - 3; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        for (int j = i + 1; j < nums.length - 2; j++) {
            if (j > i + 1 && nums[j] == nums[j - 1]) {
                continue;
            }
            int left = j + 1;
            int right = nums.length - 1;
            while (left < right) {
                long sum = (long)nums[i] + nums[j] + nums[left] + nums[right];
                if (sum < target) {
                    left++;
                } else if (sum > target) {
                    right--;
                } else {
                    res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                    while (left < right && nums[left] == nums[left + 1]) {
                        left++;
                    }
                    while (left < right && nums[right] == nums[right - 1]) {
                        right--;
                    }
                    left++;
                    right--;
                }
            }
        }
    }
    return res;
}

其实还有很多类似题,之后如果遇到的话我再补充进来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值