LeetCode刷题之1,15,16,18-n数之和

一、两数之和

描述:

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。你可以按任意顺序返回答案。

示例:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

输入:nums = [3,2,4], target = 6
输出:[1,2]

输入:nums = [3,3], target = 6
输出:[0,1]

思路一暴力法

最简单的思路就是枚举每一个数,找到nums[i] + nums[j] = target

   private static int[] twoSum(int[] nums, int target) {
        for(int i = 0; i < nums.length; i++){
            for(int j = i + 1; j < nums.length; j++){
                if(nums[i] + nums[j] == target){
                    return new int[]{i, j};
                }
            }
        }
        return new int[]{-1, -1};
    }

思路二:哈希表

思路一中在进行target查找的时间复杂度为O(n^{2}),那么如何进行优化呢?

在学习查找算法的时候,哈希表的时间复杂度可以为1,所以我们可以创建一个哈希表,对于每一个 x,我们首先查询哈希表中是否存在 target - x,然后将 x 插入到哈希表中,即可保证不会让 x 和自己匹配

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

二、三数之和

描述:

给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]

输入:nums = []
输出:[]

输入:nums = [0]
输出:[]

思路

题目中要求要求三元组不能有重复值,这样就不能简单的使用三重循环计算,那么我们能不能考虑加入哈希表进行去重操作呢?显然是可以的,但是这样一来时间复杂度和空间复杂度都相当高。

那么我们就要考虑如何优化了,首先我们可以考虑固定一个数,那么如何找到另外两个数并且时间复杂度为O(N)呢?很容易我们就可以想到双指针的方法。

当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从O(n^{_{2}})减少至 O(N)。为什么是 O(N) 呢?这是因为在枚举的过程每一步中,「左指针」会向右移动一个位置(也就是题目中的 b),而「右指针」会向左移动若干个位置,这个与数组的元素有关,但我们知道它一共会移动的位置数为 O(N),均摊下来,每次也向左移动一个位置,因此时间复杂度为 O(N)。

    public static List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> list = new ArrayList<>();
        if(nums.length<3||nums==null) return list;
        Arrays.sort(nums);
        for(int i = 0;i< nums.length-2;i++){
            if(nums[0]>0) break;
            if (i == 0 || (i > 0 && nums[i] != nums[i - 1])) {
                int left = i+1;
                int right = nums.length - 1;
                while (left < right){
                    int sum = nums[i] + nums[left] + nums[right];
                    if(sum == 0){
                        list.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--;

                    }
                    else if(sum < 0)  left++;
                    else right--;
                }
            }
        }

        return list;
    }

三、最接近的三数之和

描述:

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。

示例:

输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 。

思路

我们可以借鉴三数之和的思路,借助双指针,可以固定一个数,然后用left和right分别指向i+1和n-1,如果三者之和等于target直接返回该和,如果之和大于target则right--,否则就left++。

    private static int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int n = nums.length, res = Integer.MAX_VALUE;
        for(int i = 0; i < n; i++){
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }
            int left = i + 1, right = n - 1;
            while (left < right){
                int sum = nums[i] + nums[left] + nums[right];
                if(sum == target){
                    return sum;
                }
                if(Math.abs(sum - target) < Math.abs(res - target)){
                    res = sum;
                }
                if(sum > target){
                    //去重
                    while (left < right && nums[right] == nums[right - 1]){
                        right--;
                    }
                    right--;
                }else {
                    //去重
                    while (left < right && nums[left] == nums[left + 1]){
                        left++;
                    }
                    left++;
                }
            }
        }
        return res;
    }

四、四数之和

描述:

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:答案中不可以包含重复的四元组。

示例:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

输入:nums = [], target = 0
输出:[]

思路:

本题与三数之和类似,最朴素的想法就是四重循环+哈希表去重,这样一来时间复杂度和空间复杂度都极高,因此我们可以考虑使用排序+双指针的方法来解决。

使用两重循环分别枚举前两个数,然后在两重循环枚举到的数之后使用双指针枚举剩下的两个数。假设两重循环枚举到的前两个数分别位于下标 i和 j,其中 i<j。初始时,左右指针分别指向下标 j+1 和下标 n-1。每次计算四个数的和,并进行如下操作:

  • 如果和等于target,则将枚举到的四个数加到答案中,然后将左指针右移直到遇到不同的数,将右指针左移直到遇到不同的数;
  • 如果和小于target,则将左指针右移一位;
  • 如果和大于target,则将右指针左移一位。
    public static List<List<Integer>> fourSum(int[] nums, int target){
        List<List<Integer>> res = new ArrayList<>();
        int n = nums.length;
        if(n < 4){
            return res;
        }
        Arrays.sort(nums);
        for(int i = 0; i < n - 3; i++){
            /**
             * 具体实现时,还可以进行一些剪枝操作:
             *
             * 在确定第一个数之后,如果nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target,说明此时剩下的三个数无论取什么值,四数之和一定大于target,因此退出第一重循环;
             * 在确定第一个数之后,如果 nums[i] + nums[j] + nums[n - 1] + nums[n - 2] < target,说明此时剩下的三个数无论取什么值,四数之和一定小于target,因此第一重循环直接进入下一轮,枚举nums[i+1];
             * 在确定前两个数之后,如果 nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target,说明此时剩下的两个数无论取什么值,四数之和一定大于target,因此退出第二重循环;
             * 在确定前两个数之后,如果 nums[i] + nums[j] + nums[n - 1] + nums[n - 2] < target,说明此时剩下的两个数无论取什么值,四数之和一定小于target,因此第二重循环直接进入下一轮,枚举nums[j+1]。
             */
            if(i > 0 && nums[i] == nums[i - 1]){
                continue;
            }
            if(nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target){
                break;
            }

            if(nums[i] + nums[n - 1] + nums[n - 2] + nums[n - 3] < target){
                continue;
            }

            for(int j = i + 1; j < n - 2; j++){
                if(j > i + 1 && nums[j] == nums[j - 1]){
                    continue;
                }
                if(nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target){
                    break;
                }
                if(nums[i] + nums[j] + nums[n - 1] + nums[n - 2] < target){
                    continue;
                }
                int left = j + 1, right = n - 1;
                while (left < right){
                    int sum = nums[i] + nums[j] + nums[left] + nums[right];
                    if(sum == target){
                        res.add(Arrays.asList(nums[i], nums[j], nums[left], nums[right]));
                        while (left < right && nums[left] == nums[left + 1]){
                            left++;
                        }
                        left++;
                        while (left < right && nums[right] == nums[right - 1]){
                            right--;
                        }
                        right--;
                    }else if(sum < target){
                        left++;
                    }else {
                        right--;
                    }
                }
            }
        }
        return res;
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值