读完本文,你能够去力扣拿下以下题目:java
1.两数之和算法
-----------数据结构
Two Sum 系列问题在 LeetCode 上有好几道,这篇文章就挑出有表明性的几道,介绍一下这种问题怎么解决。优化
这个问题的最基本形式是这样:给你一个数组和一个整数 target
,能够保证数组中存在两个数的和为 target
,请你返回这两个数的索引。设计
好比输入 nums = [3,1,3,6], target = 6
,算法应该返回数组 [0,2]
,由于 3 + 3 = 6。指针
这个问题如何解决呢?首先最简单粗暴的办法固然是穷举了:code
int[] twoSum(int[] nums, int target) { for (int i = 0; i < nums.length; i++) for (int j = i + 1; j < nums.length; j++) if (nums[j] == target - nums[i]) return new int[] { i, j }; // 不存在这么两个数 return new int[] {-1, -1}; }
这个解法很是直接,时间复杂度 O(N^2),空间复杂度 O(1)。排序
能够经过一个哈希表减小时间复杂度:索引
int[] twoSum(int[] nums, int target) { int n = nums.length; index<Integer, Integer> index = new HashMap<>(); // 构造一个哈希表:元素映射到相应的索引 for (int i = 0; i < n; i++) index.put(nums[i], i); for (int i = 0; i < n; i++) { int other = target - nums[i]; // 若是 other 存在且不是 nums[i] 自己 if (index.containsKey(other) && index.get(other) != i) return new int[] {i, index.get(other)}; } return new int[] {-1, -1}; }
这样,因为哈希表的查询时间为 O(1),算法的时间复杂度下降到 O(N),可是须要 O(N) 的空间复杂度来存储哈希表。不过综合来看,是要比暴力解法高效的。
我以为 Two Sum 系列问题就是想教咱们如何使用哈希表处理问题。咱们接着日后看。
这里咱们稍微修改一下上面的问题。咱们设计一个类,拥有两个 API:
class TwoSum { // 向数据结构中添加一个数 number public void add(int number); // 寻找当前数据结构中是否存在两个数的和为 value public boolean find(int value); }
如何实现这两个 API 呢,咱们能够仿照上一道题目,使用一个哈希表辅助 find
方法:
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; } }
进行 find
的时候有两种状况,举个例子:
状况一:add
了 [3,3,2,5]
以后,执行 find(6)
,因为 3 出现了两次,3 + 3 = 6,因此返回 true。
状况二:add
了 [3,3,2,5]
以后,执行 find(7)
,那么 key
为 2,other
为 5 时算法能够返回 true。
除了上述两种状况外,find
只能返回 false 了。
对于这个解法的时间复杂度呢,add
方法是 O(1),find
方法是 O(N),空间复杂度为 O(N),和上一道题目比较相似。
可是对于 API 的设计,是须要考虑现实状况的。好比说,咱们设计的这个类,使用 find
方法很是频繁,那么每次都要 O(N) 的时间,岂不是很浪费费时间吗?对于这种状况,咱们是否能够作些优化呢?
是的,对于频繁使用 find
方法的场景,咱们能够进行优化。咱们能够参考上一道题目的暴力解法,借助哈希集合来针对性优化 find
方法:
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
的场景。
对于 TwoSum 问题,一个难点就是给的数组无序。对于一个无序的数组,咱们彷佛什么技巧也没有,只能暴力穷举全部可能。
通常状况下,咱们会首先把数组排序再考虑双指针技巧。TwoSum 启发咱们,HashMap 或者 HashSet 也能够帮助咱们处理无序数组相关的简单问题。
另外,设计的核心在于权衡,利用不一样的数据结构,能够获得一些针对性的增强。
最后,若是 TwoSum I 中给的数组是有序的,应该如何编写算法呢?答案很简单,前文「双指针技巧汇总」写过:
int[] twoSum(int[] nums, int target) { int left = 0, right = nums.length - 1; while (left < right) { int sum = nums[left] + nums[right]; if (sum == target) { return new int[]{left, right}; } else if (sum < target) { left++; // 让 sum 大一点 } else if (sum > target) { right--; // 让 sum 小一点 } } // 不存在这样两个数 return new int[]{-1, -1}; }