文章目录
- 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