FTPrep, 18 4Sum

首先回忆了一下3Sum。每次看到xSum的题,有种总会有bug的感觉,主要是这几个点:

1,3Sum用2Sum的方法,2Sum收集两个数,list里面的item都是两个元素,如果收集3元素的item?嗯,应该在每次完成一个index时,把该index对应的数字添加到2Sum list中的每一个2-tuple,形成了3-tuple,加入最终finalList

结构应该是:

for( i: 0 -> len-3){

*

for ( j: i+1-> len-1){

**

} // generating 2Sum result, 这个for loop就用 helper function 取代。

// 写成: 2SumList= helper( nums, i, ...);

for (2-tuple : 2SumList ) {

finalList.add.(2-tuple.add(nums[i]))

}

}

以上的抽象结构看得很清楚:第一层for loop里面,包含了2个 第二层 for loop,姑且说第一层是大loop,第二层的小loop,第一个小loop其实是简化成了helper function,因为大部分代码复用了,只有几个参数不同;第二个小loop就是把helper function 形成的结果,都加上nums[i],添加到最终结果。有了这个结构之后,再加上两个关于 不包括 duplicate 的处理就可以了,在 ** 处。

* 这个地方要加continue的判断条件,但i的移动前后所指的数相同的话,就skip掉后面的code

** 在2Sum过程中,如果==,那就添加item,同时要记得做 移动后相邻两个数相等是 要进行 skip,通过while()来实现


哈哈,把3Sum的过程在头脑中有个清晰的结构了,和记忆的pattern了。2层loop,N * 2N 复杂度,每一层都有一个 处理duplicate的点,第一层用的是continue,第二层用的while().

3Sum link: 点击打开链接

过来几天回来把代码补上,思路是:4sum->3sum->2sum ,但是实现起来还是要细心。

首先一点,2sum才是核心kernel;外面的3,4都是外加的层数,只是一个简单的遍历,再遍历。所以,实现起来还是不一样的。

再者,既然有了上面的总结,就可以总结一些3,4层的套路,在循环的时候要注意的地方。比如3sum其实就是定一个index start,然后start+1就是2sum内核开始的位置。然后这个3sum的interface还有一个参数就是 target,这必须的,在判断的时候要用 target-nums[start] 作为传入2sum interface作为 target,这样就可以各层之间target的传递。另一个参数显然就是位置index。对比3sum的原题的interface,就会发现少了2个args,第一个是start这个参数,第二个是 target,其实target是给定了的0,真正重要的是这个 start index作为一个参数出现了。为什么?因为3sum是从0->len-1 这个范围的遍历。这个是固定不变的,给任何一个nums都这样。但是如果外面还有一层,那么这个开始的点就可以变化了,虽然所有的组合都在之前出现过,但是这时的target就是不同的数了,之前的三个index组合的数的sum是5,可能之前不match,现在可能match了,这个时候就需要加入。也就是说 target根据4sum的第一个index所指的数不同而发生了变化。所以3sum的interface,必须要加入target和start这两个index来配合。

同时要记得上面的pattern,一个大for loop,里面两个小loop,小loop开始前有一个重复element的condition check来避免duplicate。这一条在下面的4sum里也会实现。

在第4层实现时,和3sum有同样的思路,因为现在的3sum的interface有了start这个参数,那么也是一个大loop,里面两个小loop。大loop是遍历0->len-3,而第一个小loop是遍历3sum interface可以取到的起始点,传入3sum。这样就可以完整的实现了3层遍历了。

不妨把画面感在头脑中实现一下:定第一个点,第二个点在其后,然后2sum 核心kernel在剩下的 空地array空间里夹逼,从两头往中间跑。跑完之后,第一个点不动,第二个点后移一位,这样target就变了,2sum 核心kernel又开始夹逼跑起来了,顺便说一句2sum的target一直是:4sum的target-nums[4sum index] - nums[3sum index]; 也就是说由外面两层的index来确定的。

最后还是要题型要做 重复数字的condition check,此时要continue。

写了这么多分析是为了有一个非常明确的思路,同时要把关键点,易错点抓住,找出pattern和套路,才容易一次做出来:

public class Solution {
    public List<List<Integer>> fourSum(int[] nums, int target) {
        List<List<Integer>> list = new ArrayList<List<Integer>>();
        if(nums.length<4) return list;
        Arrays.sort(nums);
        int len=nums.length;
        for(int j=0; j<len-3; ++j){
            if(j!=0 && nums[j]==nums[j-1]) continue;
            List<List<Integer>> subList = threeSum(nums, j+1, target-nums[j]);
            for(List<Integer> item:subList) item.add(nums[j]);
            list.addAll(subList);
        }
        return list;
    }
    
    private List<List<Integer>> threeSum(int[] nums, int start, int target) {
        List<List<Integer>> list = new ArrayList<List<Integer>>();
        int len=nums.length;
        for(int i=start; i<len-2; ++i){
            if(i!=start && nums[i]==nums[i-1]) continue;
            List<List<Integer>> subList = twoSum(nums, i+1, len-1, target-nums[i]);
            for(List<Integer> item:subList) item.add(nums[i]);
            list.addAll(subList);
        }
        return list;
    }

    private List<List<Integer>> twoSum(int[] nums, int left, int right, int target) {
        List<List<Integer>> list = new ArrayList<List<Integer>>();
        while(left<right){
            int sum=nums[left]+nums[right];
            if(sum==target){
                list.add(new ArrayList<Integer>(Arrays.asList(nums[left], nums[right])));
                left++;
                right--;
                while(left<right&&nums[left]==nums[left-1]) left++;
                while(left<right&&nums[right]==nums[right+1]) right--;          
            }
            else if(sum<target) left++;
            else right--;
        }
        return list;
    }
}

最后,时间复杂度是N的3次方,cube。空间是非常大的,因为在不断的使用list,一个循环就一个list,所以最坏的情况也是n cube,这个是根据有多少match的组合存在了。




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值