LeetCode算法(和中等打的有来有回)——两、三、四数之和、最接近的三数之和

leetcode第1题,两数之和

在这里插入图片描述

很容易能够让人想到通过二重循环的方式依次组合数组所有数字组合情况,但是时间复杂度为O(n*n)。
优化:

  1. 通过HashMap记录当前每个数字信息
    key: 当前数字value: 当前数字的位置
  2. 重新遍历数组,计算当前数字atarget的差值target-a,然后通过在上面构建的HashMap中通过key查找与该差值target-a相同的数字b,这样就可以获取b的位置,此时a+b=target并且两者的索引均可以获取到。

需要注意题目要求:你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。因此构建条件hash_map.get(target-a)!=index

代码

class Solution {
    public int[] twoSum(int[] nums, int target) {
    	// 结果集
        int[] res = new int[2];
        // 构建HashMap
        HashMap<Integer,Integer> num_map = new HashMap<>();
        int index=0;
        for(int item : nums){
        	// `key: 当前数字`,`value: 当前数字的位置`
            num_map.put(item,index);
            index++;
        }
        // 重新遍历
        index=0;
        for(int item : nums){
        	// 如果存在两数之和=target,并且该数并不是使用两次构成的target
            if(num_map.get(target-item)!=null&&num_map.get(target-item)!=index){
            	// 题目已知只有一种答案,如果找到了直接输出即可
                 res[0] = index;
                 res[1]=num_map.get(target-item);
                return res;
            }
            index++;
        }

        return res;
    }
}

leetcode第15题,三数之和

在这里插入图片描述

HashMap法

可以采用与两数之和相同的方式。

  1. 首先对数组进行排序,这样在之后遍历过程中便于去重
  2. 通过HashMap记录每个数字出现的位置。
  3. 再次通过双指针模拟另外两个数字,然后从HashMap中寻找剩余数字的位置。
  4. 如果位置存在,那么直接收集结果。

这种方案去重及其麻烦,正常情况不采用。
1. 指针i,指向的位置存放的数字如果和上次相同,那么直接向后移
2. 指针j,指向的位置存放的数字如何和上次相同,也直接向后移动
3. 在hashmap中查到对应目标值的下标后,要求下标必须大于指针j,这样避免产生与前面相同的组合。

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> rst = new ArrayList();
        // 构建HashMap
        HashMap<Integer,Integer> num_map = new HashMap<>();
        for(int i=0;i<nums.length;i++){
        	// `key: 当前数字`,`value: 当前数字的位置`
            num_map.put(nums[i],i);
        }
        for(int i=0;i<nums.length-1;i++){
            // 当前最小值已经是正数了
            if(nums[i]>0)return rst;
            // 第一个值与上次相同
            if(i>0&&nums[i]==nums[i-1])continue;
            // 第二个值
            for(int j=i+1;j<nums.length;j++){
                // 第二个值与上次相同
                if(j-1>i&&nums[j]==nums[j-1])continue;
                // 目标值
                int target = -(nums[i]+nums[j]);
                // 从hashmap中获取目标值的下标
                Integer find_index = num_map.get(target);
                // 如果是在第二个数后面,才进行收集,避免重复
                if(find_index!=null&&find_index>j)
                {
                    rst.add(Arrays.asList(
                        nums[i],
                        nums[j],
                        target
                    ));
                }
            }
        }      

        return rst;
    }
}

双指针法

如果采用双指针分别指向最左侧和最右侧的话,可以不需要构建HashMap,并且去重操作更为简单,

  1. 仍然先进行排序操作
  2. 遍历目标数组,计算当前剩余两数所需要构成的目标和target_sum
  3. 分别使用left,right指向剩余内容的最左侧和最右侧的数据
    • 假设nums[left]+nums[right]>target_sum,那么只需要将右侧的指针向左移动
    • 假设nums[left]+nums[right]<target_sum,那么只需要将左侧的指针向右移动
    • 假设nums[left]+nums[right]==target_sum,那么就是当前结果

去重操作:

  1. 遍历数组的时候,如果与上次相同,那么就不用查找了,因为已经收集过结果了
  2. 在收集结果时,将左右侧指针一直指向下一个不相同的数字的位置。这样在之后就避免的重复收集。
    (注意:也可以在左右侧移动后,查看与上一个位置的数字是否相同
    不推荐
    因为这样需要判断跳过的相同的数字是与最外侧i相同的还是与自己上次相同的,例如(-1,-1,-1,2),假设left在指向第二个-1时,发现与上个相同,然后就会跳过,一直跳到2,显然是不符合的,需要单独判断当前的left是不是i之后的第一个——如果是相同但是是i后的第一个,那么就不需要跳过),
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
        List<List<Integer>> rst = new ArrayList();
    
        for(int i=0;i<nums.length;i++){
            // 当前最小值已经是正数了
            if(nums[i]>0)return rst;
            // 与上次相同
            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];
                int sum_ = nums[right]+nums[left];
                if(sum_>sum){
                    right--;
                }else if(sum_<sum){
                    left++;
                }else{
                    List<Integer> k = Arrays.asList(nums[i],nums[left],nums[right]); 
                    rst.add(k);
                    while(left<right&&nums[left+1]==nums[left])left++;
                    while(left<right&&nums[right-1]==nums[right])right--;
                    right--;
                    left++;
                }
            }
        }

        return rst;
    }
}

比较

第一种和第二种时间复杂度均为O(n*n),但是由于方法一有查询HashMap的操作在,单次的时间耗费要大于方法二
第二种方式的空间复杂度为O(1),但是第一种的空间复杂度为O(n)

leetcode第18题,四数之和

在这里插入图片描述
这道题就是对三数之和的应用,依次遍历数组,然后寻找剩余三数之和 = target-nums[i],然后加入结果集中。
但是这道题比较坑的就是需要转换为长整型,因为可能会出现溢出。
这道题没什么好做的,看看就行

class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {

        Arrays.sort(nums);
        List<List<Integer>> rst = new ArrayList();
        if(nums[0]>0&&nums[0]>target)return rst;
        if(nums[nums.length-1]<0&&target>0) return rst;
        
        for(int i=0;i<nums.length-3;i++){
            if(nums[i]>0&&nums[i]>target)continue;
            List<List<Integer>> three_rst = 
            threeSum(Arrays.copyOfRange(nums, i+1,nums.length),(long)target-nums[i]);
            for(List<Integer> item:three_rst){
                rst.add(new ArrayList<>(Collections.singletonList(nums[i])) {{
                    addAll(item);
                }});
            }
            while(i<nums.length-3&&nums[i]==nums[i+1]){
                i++;
            }
        }
        return rst;
    }

	
	/**
	三数之和
	**/
    public List<List<Integer>> threeSum(int[] nums, long target){
        
        List<List<Integer>> rst = new ArrayList();
        int length = nums.length;
        for(int i=0;i<length-2;i++){
            if(i>0&&nums[i]==nums[i-1])continue;
            int left = i+1;
            int right = length-1;
            long two_target  = target-nums[i];
            while(left<right){
                if(nums[left]>two_target&&nums[left]>0)return rst;
                long two_sum = (long)(nums[left]+nums[right]);
                if(two_sum>two_target)right--;
                else if(two_sum<two_target)left++;
                else if(two_sum==two_target){
                    rst.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    while(left<right&&nums[left+1]==nums[left])left++;
                    while(left<right&&nums[right-1]==nums[right])right--;
                    right--;
                    left++;
                }
            }
        }
        return rst;

    }
}

leetcode第16题:最接近的三数之和

与上述的方案都很类似,都是通过双指针夹紧的方案,收集与当前目标值相距最近的结果,也就是与目标值差的绝对值的最小值
只要学会了三数之和,这道题就很简单了

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        Arrays.sort(nums);
        int rst = nums[0]+nums[1]+nums[2];
        for(int i=0;i<nums.length;i++){
            int left = i+1, right = nums.length-1;
            while(left<right){
                // 比较目标值和当前收集结果的距离
                int divide1 = Math.abs(rst-target);
                // 当前三个数的和
                int sum = nums[i]+nums[left]+nums[right];
                // 比较目标值和当前本次遍历总和的距离
                int divide2 = Math.abs(sum-target);
                // 如果本次遍历的总和,与target距离更近,就修改结果
                if(divide1>divide2)rst = sum;    
                // 如果收集的结果小于目标值,将左侧指针右移
                if(sum<target)left++;
                // 如果收集的结果大于目标值,将有侧指针左移
                else if(sum>target)right--;
                // 如果等于就可以直接返回结果了。
                else return target;  
            }
        }

        return rst;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值