1.两数之和
解题思路:
-
哈希 ,每次循环将 元素值 和对应 下标 放入 map 中,每次更新 map 之前先判断一下,如果 map 中已经包含 target - nums[i] 的 key ,则找到答案,返回 当前下标 和之前的 key 对应的 下标 。
167. 两数之和 II - 输入有序数组
解题思路:
-
1. 对撞指针 , L 从 0 开始, R 从 N - 1 开始,搜索 nums[L] + nums[R] 的和,如果 等于 target ,就返回 L + 1 和 R + 1 , 否则如果 小于 target 就让 L++ ,如果 大于 target 就让 R-- 。
解题思路:
-
2. 二分查找 ,既然数组是有序的,那么在一个有序数组中查找固定目标值,自然想到的就是二分查找,对于 [0, N - 1] 区间的每个元素 nums[i] , 到剩余区间 [i + 1, N - 1] 中 二分查找 元素为 target - nums[i] 的下标 index ,如果找到就返回 i + 1 和 index + 1 。
170. 两数之和 III - 数据结构设计
解题思路:
-
1. 数组 + 排序 + 对撞指针 ,参考167. 创建一个 足够大的数组 来存储数字,设置一个标志位 isSorted 表示是否排序,每次调用 find() 先判断有没有排序,如果未排序就先排序,将 isSorted 标记为 true , 然后进行 对撞指针 查找两数之和等于 value 的,每次调用 add() 时将 isSorted 标记为 false .
注意这里的对撞指针的L指针使用 nums.length - i 即可,因为 i 表示数组当前实际使用的大小,排序后已使用的部分一定是从 nums.length - i 开始到数组的末尾,形如 [-100001,-100001,-100001,.........,3, 7, 8, 12, 23, 94]
当然,上面代码你也可以选择在add方法中进行排序,效果是一样的,就看add和find方法哪个调用的频率高了。
解题思路:
-
2. 哈希 ,在 add() 方法中使用 Map 记录每个元素 出现的次数 ,在 find() 方法中,每次遍历 map 的所有 key , 判断 value - key 这个数是否存在于 map 中,如果存在,那么:
-
1)如果该数和 key 的值 不同 ,则一定存在解,
-
2)如果该数和 key 的值 相同 ,则该数在 map 中必须至少出现 2 次才有解。
解释一下上面代码中关键的部分,即当 map 中包含 num = value - key 时:
1)如果 num 和 key 不是同一个数,肯定有解,这很好理解,比如 value = 10, key = 3,那么 num = 10 - 3 = 7,只要 7 存在于 map 中,且 7 != 3 则一定有解
2)如果 num 和 key 相同,例如 value = 10, key = 5,那么 num = 10 - 5 = 5,也就是 value 此时是由两个相同的数字之和构成,因此,map 中必须至少有 2 个 5 才行
653. 两数之和 IV - 输入 BST
解题思路:
-
1. 中序遍历 + 对撞指针 ,先对二叉搜索树进行 中序遍历 ,可以得到一个 有序数组 ,再用 对撞指针 在有序数组中查找。
解题思路:
-
2. DFS + 哈希 ,对二叉搜索树进行 DFS遍历 ,每次访问一个节点就将该节点值加入到HashSet 中,但是在这之前先判断一下 target - 当前节点值 是否已经包含在 HashSet 集合中,如果已经存在,则存在解,返回 true 。否则将 递归调用左右子树 的返回结果作为当前递归函数的返回值,左右子树有一个返回 true 即可。
-
递归终止:空树返回 false 表示不存在解。
15. 三数之和
解题思路:
-
排序 + 对撞指针 ,第一步先 排序 ,然后外层循环中 i 枚举 [0, N - 3] ,固定数字 nums[i] ,在内层循环中使用 对撞指针 求解, L 从 i + 1 开始, R 从 N - 1 开始,找 nums[i] + nums[L] + nums[R] == 0 的。
-
如果三数之和等于 0,就将三个数收集到答案结果集中,然后 L++,R-- ,此时 L 和 R 两头都要通过 while循环去重 ;
-
如果三数之和 小于 0 ,让 L++ ;
-
如果三数之和 大于 0 ,让 R-- ;
-
优化:外层循环中首先判断一下如果 当前元素大于 0 , 直接跳出 ,不用进行内层循环了,因为是排序后的,后面的肯定都 大于 0 ,不可能得到三数之和 等于 0 的。
-
注意:进入内层循环的对撞指针之前,外层循环的 nums[i] 和 nums[i - 1] 需要去重判断,去重判断的方法:比较当前的跟前一个的值,如果相等则跳过。
解释一下上面代码中的3处去重的作用:
- “跳过重复元素 ①” :例如 [-3, -3, -3, -3, -3, 1, 2],如果选择了第一个 -3 和 1, 2 组成和为 0 的三元组 [-3, 1, 2],那么后面的连续相同的 -3 就需要跳过,否则就会得到若干个重复的三元组[-3, 1, 2],这跟题目要求不符
- “去重②” 和 “去重③” 处的代码,其实等价于下面代码的写法:
while (L < R && nums[L] == nums[L + 1]) L++;
L++;
while (L < R && nums[R] == nums[R - 1]) R--;
R--;
- 比如 [-2, -1, -1, -1, 3, 3, 3],当 i = 0, L = 1, R = 6,即将[-2, -1, 3]收集答案后,L 和 R 就需要排除相邻重复的 -1 和 3,否则就会得到若干个重复的三元组[-2, -1, 3],这跟题目要求不符
注意:本题在LeetCode上最新版本的提示条件改了 3 <= nums.length <= 3000,否则还需要添加一个特判 N < 3 时,return res。
18. 四数之和
解题思路:
-
同15. 三数之和,也是 先排序 ,只不过 外面多套一层for循环 ,第一层 i 从 [0, N - 4] 遍历,第二层 j 从 [i + 1, N - 3] 遍历,最内层用 对撞指针 L 从 j + 1 开始, R 从 N - 1 开始,搜索 nums[i] + nums[j] + nums[L] + nums[R] == target 的。
-
注意点:第一层的 i 和第二层的 j 都需要 去重 操作, 等于target 时收集完答案之后,移动 L 和 R 时也需要 去重 判断。
16. 最接近的三数之和
解题思路:
-
类似15.三数之和,这题求的不是三个元素的集合,而是三个数的和,当 sum == target 时,直接返回 sum 即可,否则就更新最接近 target 的答案,并移动 L 和 R 指针。
-
初始时设一个变量 closest 表示最接近 target 的和,开始时设置为一个比数组中任意三数之和还小的值,需要更新 closest 时,看如果 abs(sum - target) < abs(closest - target) ,就将 closest 更新为 sum 。
31. 下一个排列
解题思路:
-
先从 右往左 找 第一个 出现的【 降序点 】(当前的小于其右边的),找到后记住这个位置 i ,然后再从 右往左 找 第一个 比之前找到的 【 降序点 】大 的位置,记为 j , 然后 交换 i 和 j 位置的值,最后 反转 i 之后的所有元素 ,即得到下一个全排列。
-
如果第一步中没有找到 【 降序点 】 ,也就是 i == -1 ,那么说明从右往左都是升序的,也就是 从左往右 是一个 降序序列 ,如54321,则 下一个排列 应该返回 字典序最小的排列 。这通过反转整个数组即可得到。
-
反转 操作通过 对撞指针 可以实现,从两头往中间逼近,不断的交换两头指针的元素。
下图是上面算法的执行动画过程:
输入: 1 5 8 4 7 6 5 3 1
第一步:1 5 8 <
第一步:1 5 8 <