文章目录
- 1. [LeetCode01:two sum](https://leetcode.com/problems/two-sum/)
- 2. [LintCode607. Two Sum III - Data structure design](https://www.lintcode.com/problem/two-sum-iii-data-structure-design/my-submissions)
- 3. 167. [Two Sum II - Input array is sorted](https://leetcode.com/problems/two-sum-ii-input-array-is-sorted/)
- 4. LintCode587.Two Sum - Unique pairs
- 5. [LeetCode15. 3Sum](https://leetcode.com/problems/3sum/)
- 题意
- 思路
- 代码
- 6. [LintCode-382. Triangle Count](https://www.lintcode.com/problem/triangle-count/description)
- 题意
- 思路
- 代码
- 7. [LintCode59. 3Sum Closest](https://www.lintcode.com/problem/3sum-closest/description)
- [8. LeetCode18-4Sum](https://leetcode.com/problems/4sum/)
这类题目都是套路非常明显的题目,掌握下述的题目后应该对这类问题没有任何问题。
1. LeetCode01:two sum
题意
- 给定数组和一个目标数,要求在数组中找到两个数,其和等于目标数,返回该两数的下标
思路1:map法
- 建立从
value->index
的映射 - 遍历数组
- 依次检验当前
map
的key
中是否包含target - num[i]
- 如果包含,那么说明一定存在
nums[j] = target - nums[i]
已经在map
中,且一定j<i
,那么只需要通过targte - nums[i]
在map
中找到下标j
,那么j和i
就是结果 - 如果不包含,则把当前的
nums[i]和i
添加到map
中即可
- 如果包含,那么说明一定存在
- 依次检验当前
代码1:
public int[] twoSum(int[] nums, int target) {
int[] res = new int[2];
if (nums == null || nums.length == 0){
return null;
}
//0. num->index
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])){
res[0] = map.get(target - nums[i]);
res[1] = i;
return res;
}
map.put(nums[i],i);
}
return res;
}
思路2:双指针解法
- 在有序数组中可以利用两个相向双指针,一个从左往右,一个从右往左的遍历数组,然后求出题目要求(两数之和等于/大于/小于/最接近于某个数)的答案即可
- 所以双指针解法的第一步永远是排序
- 如果题目要求返回的值是索引,此时如果直接对数组进行排序,那么就会丢失掉题目给定数组中数组元素和索引的对应关系,此时通用的解决办法是利用一个
Pair
来存储value和index
的对应关系,然后对Pair
的value
进行排序 - 当得到有序数组后,双指针的逻辑就很简单了(见代码中的注释部分)
代码2
class Solution {
class Pair{
int val;
int index;
public Pair(int val, int index){
this.val = val;
this.index = index;
}
}
public int[] twoSum(int[] nums, int target) {
if (nums == null || nums.length == 0){
return null;
}
int[] res = new int[2];
Pair[] pairs = new Pair[nums.length];
//0. 将数组元素值和index进行绑定
for (int i = 0; i < nums.length; i++) {
pairs[i] = new Pair(nums[i],i);
}
//1. 双指针解法的第一步永远是排序
Arrays.sort(pairs,new Comparator<Pair>(){
@Override
public int compare(Pair o1, Pair o2) {
return o1.val - o2.val;
}
});
int left = 0;
int right = nums.length - 1;
while (left < right){
if (pairs[left].val + pairs[right].val == target){
res[0] = pairs[left].index;
res[1] = pairs[right].index;
return res;
}else if (pairs[left].val + pairs[right].val > target){
//2. 当前的最大值加上最小值 都还是大于目标值,说明最大值太大了
right--;
}else {
//3.当前的最小值加上最大值 都还是小于目标值,说明最小值太小了
left++;
}
}
return null;
}
}
小结
- 上述两种思路是解决两数之和这类问题的通用解决手段
2. LintCode607. Two Sum III - Data structure design
题意
- 实现一个数据结构,完成如下两个功能
void add(num)
:可以增加给定的数据boolean find(value)
:查找当前数据集中是否存在两数之和等于value
思路
- 首先,该数据结构需要存储数据,在设计数据结构的时候,底层的数据存储要么用链表要么用动态数组
- 此题中并没有说明要存储数据的具体长度,那么就只能用动态数组,在
Java
中最常用的就是list
- 其次,第二个功能的实现,就是一个两数之和问题,而且此题不要求返回两数的索引,只需要判断是否存在,那么显然考虑用
map
法,因为完全不需要考虑索引,完全可以把map
简化为用一个set
来做判断
代码
class Solution {
//0. 用list来存数据
List<Integer> list = new ArrayList<>();
public void add(int number) {
// write your code here
list.add(number);
}
public boolean find(int value) {
// write your code here
Set<Integer> set = new HashSet<>();
for (Integer num : list) {
if (set.contains(value - num)){
return true;
}
set.add(num);
}
return false;
}
}
3. 167. Two Sum II - Input array is sorted
题意
给定有序数组,和一目标数,在数组中找到两数之和等于目标数,返回两数在数组中的次序(注意不是基0的索引,所以最后需要+1)
思路
- 是第1题的简化版
- 因为已经有序了,不需要再排序,自然也不需要处理排序后数组值和索引无法对应的问题
- 直接用双指针来处理即可
代码
public static int[] twoSum(int[] A, int target) {
int[] res = new int[2];
int left = 0;
int right = A.length - 1;
while (left < right){
if (A[left] + A[right] == target){
res[0] = left + 1;
res[1] = right + 1;
return res;
}else if (A[left] + A[right] > target){
//1. 和大了 要减小
right--;
}else {
//2. 和小了 要增大
left++;
}
}
return null;
}
4. LintCode587.Two Sum - Unique pairs
此题是vip,无法提供链接
题意
- 给定一个数组和目标数,找到数组中有多少组不同的元素对,其和都等于给定的目标数,返回不同的元素对的对数
- 例如:
nums=[1,1,2,45,46,46],target = 47
- 返回
2
- 因为:
1 + 46 = 47、2 + 45 = 47
思路
- 仍然是两数之和问题,且不用返回下标,所以无需考虑数值和下标的问题
- 关键在于去重
- 双指针和去重的一个首要操作都是排序
- 去重的一般手段就是利用
while
来找到不再重复的元素(具体见代码和注释)
代码
public int twoSum6(int[] nums, int target) {
if (nums == null || nums.length == 0){
return 0;
}
//0. 首先排序,因为此题不需要返回索引,所以无需考虑数值与下标的关系
//0.1 此处还有一个重要的目的在于为后续去重做准备,(数组中去重问题一般都需要先排序)
Arrays.sort(nums);
int left = 0;
int right = nums.length - 1;
int cnt = 0;//计数
while (left < right){
int v = nums[left] + nums[right];
if (v == target){
//1. 如果找到一对数,那么肯定计数+1,指针移动
cnt++;
left++;
right--;
//2. 去重,为了避免一对同样的数的和再组成target
while (left < right && nums[right] == nums[right+1]){
right--;
}
while (left < right && nums[left] == nums[left-1]){
left++;
}
}else if (v > target){
//3. 大了 要减小
right--;
}else {
//小了 要增大
left++;
}
}
return cnt;
}
5. LeetCode15. 3Sum
题意
- 给定数组,要求找到满足
a +b + c = 0
的三元组,且无重复
思路
-
a + b + c = 0-> a +b = -c
-
遍历数组,将每一个元素当作
-c = target
-
然后在数组剩余元素中寻找无重复的两数之和等于target的组合
-
那么这时候问题就退化成了上面第4题
-
同时要注意对
target
也要去重- 去重的一个通用的思路是用
set
- 当然数组中可以有更简单的方式
if (i > 0 && nums[i] == nums[i-1]){ continue; }
- 去重的一个通用的思路是用
代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> results = new ArrayList<>();
List<Integer> item = new ArrayList<>();
if (nums == null || nums.length < 3){
return results;
}
//0. 先排序,无需返回index,所以直接排序即可
Arrays.sort(nums);
//1.遍历nums,把每一个元素当作target,然后在数组剩下的部分寻找无重复的两数之和等于target
// 问题退化成了two sum
int n = nums.length;
for (int i = 0; i < n; i++) {
//2.1 避免重复的target
if (i > 0 && nums[i] == nums[i-1]){
continue;
}
//2.2 a + b + c = 0 -> a + b = -c
int target = -nums[i];
//2.3 保证数组中剩下的元素至少有两个
if (n - i - 1 >= 2) {
twoSum(i+1,target,nums,results);
}
}
return results;
}
private void twoSum(int startIndex, int target, int[] nums, List<List<Integer>> results) {
int left = startIndex;
int right = nums.length - 1;
while (left < right){
if (nums[left] + nums[right] == target){
List<Integer> item = new ArrayList<>();
item.add(-target);
item.add(nums[left]);
item.add(nums[right]);
results.add(item);
//指针移动
left++;
right--;
while (left < right && nums[left] == nums[left-1]){
left++;
}
while (left < right && nums[right] == nums[right +1]){
right--;
}
}else if (nums[left] + nums[right] < target){
//小了 要增大
left++;
}else {
//大了 要减小
right--;
}
}
}
}
6. LintCode-382. Triangle Count
题意
给定数组,要求找出能够构成三角形的三元组的数量
思路
-
需要知道三条边能够构成一个三角形的充要条件
- 两边之和大于第三边或两边之差小于第三边(任意一个都是充要的)
-
那么这道题就只需要遍历数组元素,将每一个元素都当作
target
-
然后寻找两数之和大于
target
的数量,所以仍然是一个两数之和的问题 -
既然是两数之和问题,所以仍然是先排序
-
一个比较关键的地方就是在于计数的问题
- 如果我们固定
nums[i]
,然后在[i+1,n-1]
这个区间内去找满足这样的一个不等式的数量,那么就会出现求一个的组合数问题 - 所以为了规避这个问题,我们从前面看,也就是固定
nums[i]
,然后在区间[0,i-1]
这个区间内去找满足这个不等式的两数数量 - 具体的实现细节查看代码中的注释
- 如果我们固定
代码
public int triangleCount(int[] S) {
// write your code here
if ( S == null || S.length == 0){
return 0;
}
Arrays.sort(S);
int count = 0;
for (int i = 0; i < S.length; i++) {
//0. 以S[i] = target 在 区间[0,i-1]中寻找两数之和大于 target
int left = 0;
int right = i - 1;
while (left < right){
if (S[left] + S[right] > S[i]){
//1. 因为有序,所以 left 到 right区间内的任意两数之和都大于 S[i]
//1.1 例如 3 4 5 6 7
// S[left] = 3 S[right] = 6 S[i] = 7
// 这里本质上是把 S[right] 和S[i]都固定了,然后偶 left 到 right中的任一个数加上S[right]和S[i]
// 都满足条件,所以数量就是从left 到 right-1 直接的数量,也就是right - left
count += right - left;
//2. 现在改变right,让if中的左边的和小一点,看能否找到更多的解
right -=1;
}else {
//3. 两数之和<=target,所以需要增大
left++;
}
}
}
return count;
}
7. LintCode59. 3Sum Closest
题意
给定数组和目标数,要求在数组中找到一组三元组,其和离给定的目标数最小,返回这个和
思路
- 三数之和的题目,一定都是先固定一个数,然后降问题退化为两数之和
- 所以仍然是两数之和的题目,所以先排序
- 这里是求全局最接近,凡是求最值型问题,一定需要维护一个或多个全局变量,与临时变量进行比较,然后更新
- 所以,这道题目只需要依次遍历数组,固定
nums[i]
,然后从区间[i+1,n-1]
中寻找可能的离目标数最小的三个数 - 相关细节在代码中
代码
public int threeSumClosest(int[] numbers, int target) {
if (numbers == null || numbers.length == 0){
return 0;
}
Arrays.sort(numbers);
//0. 定义全局变量(凡是求最值型问题,都一定需要定义这样的全局变量来和一些临时变量进行比较更新)
int closestSum = Integer.MAX_VALUE;
int closestDiff = Integer.MAX_VALUE;
//1. 遍历数组,先固定每个元素, -2是因为从 i开始到最后至少需要三个元素 n -3 n-2 n-1
for (int i = 0; i < numbers.length - 2; i++) {
int e1 = numbers[i];
int left = i+1;
int right = numbers.length - 1;
while (left < right){
//2. 先求出当前的和以及和target的距离
int tempSum = e1 + numbers[left] + numbers[right];
int tempDiff = Math.abs(tempSum - target);
//3. 如果当前距离更短则更新
//3.1 这里有一个细节是,进入了if之后没有移动指针
// 这样在下次的while循环中,一定会进入else分支,然后移动指针
if (tempDiff < closestDiff){
closestDiff = tempDiff;
closestSum = tempSum;
}else {
//4. 如果当前距离没有更短,则需要缩短tempSum和target的距离
//4.1 具体怎么缩短就需要看当前的具体情况了
if (tempSum < target){
left++;
}else {
right--;
}
}
}
}
return closestSum;
}
8. LeetCode18-4Sum
题意
给定数组和一个目标数,找到4个数之和等于该目标数的组合,返回这些组合
思路
- 遍历数组,依次固定两个数,然后把问题转换为两数之和
- 注意对每一个数的去重
代码
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> results = new ArrayList<>();
if (nums == null || nums.length == 0){
return results;
}
Arrays.sort(nums);
//1. i后面至少还需要3个数 n-4 n-3 n-2 n-1
for (int i = 0; i < nums.length - 3; i++) {
//去重
if (i > 0 && nums[i] == nums[i-1]){
continue;
}
int e1 = nums[i];
//1.1 j后面至少还要2个数 n-3 n-2 n-1
for (int j = i+1; j < nums.length - 2; j++) {
//去重 e1 和e2 是可以相等的
if (j != i + 1 && nums[j] == nums[j-1]){
continue;
}
int e2 = nums[j];
int left = j + 1;
int right = nums.length - 1;
while (left < right){
int sum = e1 + e2 + nums[left] + nums[right];
if (sum == target){
List<Integer> item = new ArrayList<>();
item.add(e1);
item.add(e2);
item.add(nums[left]);
item.add(nums[right]);
results.add(item);
left++;
right--;
//2. 去重
while (left < right && nums[left] == nums[left-1]){
left++;
}
while (left < right && nums[right] == nums[right+1]){
right--;
}
}else if (sum > target){
right--;
}else {
left++;
}
}
}
}
return results;
}
来源:CSDN
作者:小胖头
链接:https://blog.csdn.net/fat_cai_niao/article/details/103688772