LeetCode - 15. 3Sum

方法一:

这一道题目直接的想法就是Brute Force,通过三重循环来寻找目标,但是这样的时间复杂度非常高,同时又需要检测来避免重复,所以这种方法虽然能够得到正确的答案,但是在leetcode上会因为time limit exceeded而无法通过

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> resultList = new ArrayList<List<Integer>>();
        
        for(int i = 0; i < nums.length; i++){
            for(int j = i + 1; j < nums.length; j++){
                for(int k = j + 1; k < nums.length; k++){
                    if(nums[i] + nums[j] + nums[k] == 0){
                        List<Integer> list = new ArrayList<Integer>();
                        list.add(nums[i]);
                        list.add(nums[j]);
                        list.add(nums[k]);

                        if(!resultList.contains(list)){
                            resultList.add(list);
                        }
                    }
                }
            }
        }
        return resultList;
    }
}

方法二:

回想前面所做的1. Two Sum这一道题目,在这一题目中采用了一种思想:先固定一个数,通过查找的方式找出第二个数,在Two Sum题目中这一思想得到了很好的应用。所以在思考更快速的方法来解决3Sum的时候,可以在外围使用两个二重循环分别固定两个数,而采用查找的方式来找出第三个数字,这样时间复杂度就从原来的O(n^3)变成了O(n^2logn)。可惜这种改进方法还是无法在leetcode上面通过,同样会出现time limit exceeded的错误

public List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> result = new ArrayList<List<Integer>>();
        
    //sort array
    java.util.Arrays.sort(nums);
        
    //find complement
    int complement;
    for(int i = 0; i < nums.length; i++){
        for(int j = i + 1; j < nums.length; j++){
            complement = 0 - nums[i] - nums[j];
                
            int index = java.util.Arrays.binarySearch(nums, complement);
            if(index >= 0 && nums[index] > nums[j]){
                List<Integer> list = new ArrayList<Integer>();
                list.add(nums[i]);
                list.add(nums[j]);
                list.add(nums[index]);
                    
                if(!result.contains(list)){
                    result.add(list);
                }
            }
        }
    }
    return result;
}

方法三:

正在一筹莫展的时候,搜索到了这篇文章http://www.sigmainfy.com/blog/summary-of-ksum-problems.html,是对k-sum问题的一个总结,看下来之后又有了新的想法。首先,在2Sum问题当中,首先对数组进行排序,接着通过一个指向头部,一个指向尾部的指针向中间移动,来寻找目标数字,这样就成功地把2Sum问题的时间复杂度降低到了O(n)级别,非常有效。因此对于2Sum问题的变体-3Sum,也可以采用同样的思路,即通过一个外层的循环分别固定数组中的每一个数字,将3Sum问题变成多个2Sum问题,同时,题目要求没有重复,所以在添加结果之前,还要检测是否有重复

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        int pfront;
        int pback;
        
        //sort array
        java.util.Arrays.sort(nums);
        
        for(int i = 0; i < nums.length - 2; i++){
            int complement = 0 - nums[i];
            
            //set pointers
            pfront = i + 1;
            pback = nums.length - 1;
            
            //search complement
            while(pfront < pback){
                if(nums[pfront] + nums[pback] == complement){
                    //construct answer, nondescending order
                    List<Integer> list = java.util.Arrays.asList(nums[i], nums[pfront], nums[pback]);
                    if(!result.contains(list)){
                        result.add(list);
                    }
                    
                    /*
                    List<Integer> list = new ArrayList<Integer>();
                    list.add(nums[i]);
                    list.add(nums[pfront]);
                    list.add(nums[pback]);
                    result.add(list);
                    */
                    
                    //move pointers and avoid duplicate 
                    pfront++;
                    pback--;
                    
                }else if(nums[pfront] + nums[pback] < complement){
                    pfront++;
                }else{
                    pback--;
                }
            }
        }
        return result;
    }
}
方法四:

方法三在leetcode上面得到的结果竟然还是time limit exceeded,此时我的内心几乎是崩溃的....还能有其他的方法减少时间吗?其实是有的,注意到在方法三中,检测重复时使用了list的内置函数contains,但是我们可以通过对指针的正确操作来避免对contains的使用。要了解怎么操作指针,首先我们应该想到在什么情况下会有重复,当然就是给出的目标数组中有相等数字的情况,而在这一算法中,外层循环中的i,内层循环中的两个指针pfront, pback可能会碰到相同的数字进而导致重复,所以在每次外层循环的时候,我们都要检测nums[i]和nums[i - 1]是否一样,如果一样的话,很可能会造成重复;在内层循环中,当找到目标数字的时候,需要进行pfront++和pback--来跳过临近的相同数字。总的来说,第四种方法和第三种方法并没有本质上的区别,只是优化了检测重复的机制,这样就终于可以在leetcode上accept了

public class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        
        //Attention: alaways consider extreme situation
        if(nums == null || nums.length < 3) return result;
        
        int pfront;
        int pback;
        int complement;
        
        //sort array
        Arrays.sort(nums);
        
        for(int i = 0; i < nums.length - 2; i++){
            //avoid duplicate
            if(i > 0 && nums[i] == nums[i - 1]) continue;
            
            complement = 0 - nums[i];
            //set pointers
            pfront = i + 1;
            pback = nums.length - 1;
            
            while(pfront < pback){
                if(nums[pfront] + nums[pback] == complement){
                    List<Integer> list = Arrays.asList(nums[i], nums[pfront], nums[pback]);
                    result.add(list);
                    
                    //move pointers
                    while(pfront < pback && nums[pfront] == nums[pfront + 1]) pfront++;
                    while(pfront < pback && nums[pback] == nums[pback - 1]) pback--;
                    pfront++;
                    pback--;
                }else if(nums[pfront] + nums[pback] < complement){
                    pfront++;
                }else{
                    pback--;
                }
            }
        }
        return result;
    }
}
知识点:

1. List<List<Integer>>的正确实例化是new ArrayList<List<Integer>>()或new ArrayList<>(),而不是new ArrayList<ArrayList<Integer>>()或new List<List<Integer>>,前者的解释在http://stackoverflow.com/questions/5763750/why-we-cant-do-listparent-mylist-arraylistchild,而后者List是接口,不能实例化

2. Array与List的相互转换:java.util.Arrays.asList(nums[i], nums[pfront], nums[pback])

3. Array排序:Arrays.sort(nums)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值