算法 系列 twoSum

基础数据结构...

Posted by lichao modified on June 20, 2020

设计的核⼼在于权衡,利⽤不同的数据结构,可以得到⼀些针对性的加强。

⼀般情况下,我们会⾸先把数组排序再考虑双指针技巧。受 TwoSum 启发,HashMap 或者 HashSet 也可以帮助处理⽆序数组相关的简单问题。

twoSum I

给定⼀个数组和⼀个整数 target ,可以保证数组中存在两个数的和为 target ,请返回这两个数的索引

利用哈希表解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
 public int[] twoSum(int[] nums, int target) {
    int n = nums.length;
    Map<Integer, Integer> index = new HashMap<>();
    // 构造⼀个哈希表:元素映射到相应的索引
    for (int i = 0; i < n; i++) {
        int other = target - nums[i];
        // 如果 other 存在且不是 nums[i] 本⾝
        if (index.containsKey(other))
            return new int[]{i, index.get(other)};
        index.put(nums[i], i);
    }
    return new int[]{-1, -1};
}

由于哈希表的查询时间为 O(1),算法的时间复杂度降低到 O(N),但是需要 O(N) 的空间复杂度来存储哈希表。

twoSum II

设计⼀个类,拥有两个 API:

1
2
3
4
5
6
class TwoSum {
    // 向数据结构中添加⼀个数 number
    public void add(int number);
    // 寻找当前数据结构中是否存在两个数的和为 value
    public boolean find(int value);
}

实现一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class TwoSum {
    Map<Integer, Integer> freq = new HashMap<>();
    public void add(int number) {
        // 记录 number 出现的次数
        freq.put(number, freq.getOrDefault(number, 0) + 1);
    }
    public boolean find(int value) {
        for (Integer key : freq.keySet()) {
            int other = value - key;

        // 情况⼀
        if (other == key && freq.get(key) > 1)
            return true;

        // 情况⼆
        if (other != key && freq.containsKey(other))
            return true;
        }
        return false;
    }
}

对于这个解法的时间复杂度呢, add ⽅法是 O(1), find ⽅法是 O(N),空间复杂度为 O(N)。

实现二:

1
2
3
4
5
6
7
8
9
10
11
12
13
class TwoSum {
    Set<Integer> sum = new HashSet<>();
    List<Integer> nums = new ArrayList<>();
    public void add(int number) {
        // 记录所有可能组成的和
        for (int n : nums)
            sum.add(n + number);
        nums.add(number);
    }
    public boolean find(int value) {
        return sum.contains(value);
    }
}

这样 sum 中就储存了所有加⼊数字可能组成的和,每次 find 只要花费 O(1) 的时间在集合中判断⼀下是否存在就⾏了,显然⾮常适合频繁使⽤ find 的场景。