LintCode 57. 三数之和

原题

第一步,万年不变的查错。如果给的array是null或不够三个数,直接return 空的result。因为return type是list,一般情况下绝不return null,而是return empty list。

    public List> threeSum(int[] nums) {
        List> results = new ArrayList<>();
        if (nums == null || nums.length < 3) {
            return results;
        }
        ...
    }

我第一个思路是,跟twosum一样。把所有的数放到HashMap里面去,然后遍历一遍,以每一个数字为目标,做一遍twosum。但是这样的话,不能有重复的数字。想要用重复的数字,那就得value里面放个count,还要保证在做twosum的时候,及时更新这个count,还要在最后重置回原来的count,太麻烦。
第二个思路,源于twosum的特殊情况下的简便解法。如果twosum给的array是排好序的,那么可以直接用两个pointer来做。鉴于这个题,无论什么方法,最少都是O(n^2)了,所以我们可以先把它排序,然后再用pointer来做。好处是节省空间,还比较容易implement。
所以,一开始先排序。

        Arrays.sort(nums);

然后,遍历整个array,以每一个数字为target,对它后面的数字做一个twosum。这里只需要遍历到倒数第三个数字,因为后面就不够三个数字了(而前面的都访问过了)。关键点是要去重。如果不是第一个数字,那么只要这个数字和前面一样,我们就直接跳过,因为前面已经做过了。

        for (int i = 0; i < nums.length - 2; i++) {
            if (i != 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            twoSum(nums, i + 1, -nums[i], results);
        }
        return results;

然后是具体的twoSum。首先需要知道从哪开始,即startIndex。然后需要知道target,最后需要一个能不断被更新的结果。

        private void twoSum(int[] nums, int startIndex, int target, List> results) {
            ....
        }

因为array已经排好序了,所以左右同时进行,如果一样就找到了,如果比目标小就左边往右移,如果比目标大就右边往左移。关键点还是去重。如果找到了答案,那么就要跳过所有跟答案一样的数字。第二个关键点是,以往排序时,是left <= right,这里必须是left < right,不然的话会重复用到一个数字。

            while (left < right) {
                int sum = nums[left] + nums[right];
                if (sum == target) {
                    List triplet = new ArrayList<>();
                    triplet.add(-target);
                    triplet.add(nums[left]);
                    triplet.add(nums[right]);
                    results.add(triplet);

                    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--;
                }
            }

完整的code

public class Solution {
    /*
     * @param numbers: Give an array numbers of n integer
     * @return: Find all unique triplets in the array which gives the sum of zero.
     */
    public List> threeSum(int[] nums) {
        List> results = new ArrayList<>();
        if (nums == null || nums.length < 3) {
            return results;
        }
        Arrays.sort(nums);
        for (int i = 0; i < nums.length - 2; i++) {
            if (i != 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            
            twoSum(nums, i + 1, -nums[i], results);
        }
        
        return results;
    }
    
    private void twoSum(int[] nums, int startIndex, int target, List> results) {
        int left = startIndex;
        int right = nums.length - 1;
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum == target) {
                List triplet = new ArrayList();
                triplet.add(-target);
                triplet.add(nums[left]);
                triplet.add(nums[right]);
                results.add(triplet);
                
                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--;
            }
        }
    }
}

分析

时间复杂度

不可避免的O(n2)。因为twoSum最好的情况也是O(n),threeSum的话,遍历一遍以每一个数字当target,每一个数字做一个twoSum,就是O(n2)。

空间复杂度

不考虑results的空间的话(因为这事需要return的东西),应该是O(1)。因为全程都在用pointer。这也是在做twoSum的时候,用pointer比用hashSet好的地方,当然前提是sorted。

这种题一般是作为twoSum的followup吧,感觉不是很难,就是感觉有点长。

你可能感兴趣的:(LintCode 57. 三数之和)