GuilinDev

Lc0001

05 August 2008

1 - 2Sum

原题概述

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution, and you may not use the same element twice.

Example:

1
2
3
4
Given nums = [2, 7, 11, 15], target = 9,

Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

题意和分析

Leetcode的第一道题,被翻来覆去做过很多遍了;一般就是HashMap存入值和相对应index的信息,回过来再对之前存入的键值对进行查找。

Time: O(n), Space: O(n);

另外还有一个办法是可以先对数组排序,然后用两个指针,头尾开始查找,直到找到结果或者两个指针相遇。因为Arrays.sort()使用的排序方法是quick sort和优化过的merge sort,为什么使用了两种排序方法?《Java编程艺术》上的解释是quick sort对基本数据类型包括int,short,long,float,double等的排序,merge sort则是对对象类型进行排序,对比下两个排序办法:

1
2
3
4
5
6
7
8
merge sort 
1)最坏时间复杂度是 O(nlogn);
2)平均时间复杂度是 O(nlogn);
3)空间复杂度如果用Array实现为O(n),如果用LinkedList来实现为O(logn)。
quick sort 
1)最坏时间复杂度是 O(n^2);
2)平均时间复杂度是 O(nlogn);
3)空间复杂度是 O(n)。

因此,这个办法的时间复杂度Arrays.sort()的O(nlogn)+后面两个指针的的O(n) = O(nlogn);另外因为本题是返回两个indices,而不是返回两个值,所以还需要记住之前每个元素的位置,复制一个数组(延伸:Java中常用有几种复制数组的办法?)进行排序,原数组则保持原来顺序。

扩展下知识:quick sort 在统计意义上效率比 merge sort 高,为何不都采用 quick sort ?

使用两个不同类型的排序算法主要是由于 quick sort 是不稳定的,而 merge sort 是 稳定(stable)的。这里的 stable 是指比较相等的数据在排序之后仍然按照排序之前的前后顺序排列(保序性)。对于基本数据类型,稳定性没有意义。而对于对象类型,稳定性比较重要,因为对象相等的判断可能只是判断关键属性,因此最好保持相等对象的非关键属性的顺序与排序前一直;另外一个原因是由于merge sort相对而言比较(compare)的次数比快速排序要少,移动(对象引用的移动,move)次数比快速排序多,而对于对象来说,compare一般比move耗时。

代码

HashMap的办法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] result = new int[2];
        Map<Integer, Integer> map = new HashMap<>();
        
        for (int i = 0; i < nums.length; i++) {
            if (map.containsKey(target - nums[i])) {
                //注意存到result的顺序
                result[1] = i;//第1位为当前的i
                result[0] = map.get(target - nums[i]);//第0位为跟当前i匹配之前存入到map中的值和相对应的index
                
                break;
            }
            map.put(nums[i], i);//值和其相对应的index
        }
        return result;
    }
}

两个指针的办法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
    public int[] twoSum(int[] nums, int target) {
        int[] sorted = new int[nums.length]; //一个新数组来排序
        System.arraycopy(nums, 0, sorted, 0, nums.length);//deep clone数组
        Arrays.sort(sorted);
        
        int left = 0, right = sorted.length - 1;//新数组里面目标的两个数的indices
        while (left < right) {
            if (sorted[left] + sorted[right] < target) {
                left++;
            } else if (sorted[left] + sorted[right] > target) {
                right--;
            } else {//找到新数组里面的两个数了
                break;
            }
        }
        
        int index1 = -1, index2 = -1; //初始值为-1,不为0,这样知道两个indices是否找到原来数组的对应两个数的索引从而改变过
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == sorted[left] || nums[i] == sorted[right]) {//只可能一个解
                if (index1 == -1) {
                    index1 = i;
                } else {
                    index2 = i;
                }
            }
        }
        
        int [] result = {index1, index2};
        Arrays.sort(result);//之前不知道index1和index2哪个在前,所以需要sort一下
        
        return result;
     }
}