【题型总结】x数之和 | 双指针 哈希表

x数之和

当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法;或者通过哈希表判断是否有目标元素存在

两数之和 II - 输入有序数组【LC167】

给定一个已按照 非递减顺序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length 。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

作者:力扣 (LeetCode)
链接:https://leetcode-cn.com/leetbook/read/array-and-string/cnkjg/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 我的题解

    • 暴力

      • 2021/11/15
      class Solution {
          public int[] twoSum(int[] numbers, int target) {
              int[] index = {0,0};
              for(int i =0;i<numbers.length;i++){
                  for(int j=i+1;j<numbers.length;j++){
                      if(numbers[i]+numbers[j] == target){
                          index[0] = i+1;
                          index[1] = j+1;
                      }
                  }
              }
              return index;
      
      
          }
      }
      
  • 复杂度

    • 时间复杂度: O ( n 2 ) O(n^2) O(n2),其中 n是数组的长度。
    • 空间复杂度: O ( 1 ) O(1) O(1)
双指针

2022/10/18

由于数组从小到大排列, 设置双指针分别指向数组的首部(left)和尾部(right)

  • 若首部尾部相加等于目标值,返回结果集

  • 若首部尾部相加小于目标值,右移left,使和变大

  • 若首部尾部相加大于目标值,左移right,使和减小

  • 代码

    class Solution {
        public int[] twoSum(int[] numbers, int target) {
            int[] res = new int[2];
            int len = numbers.length;
            int left = 0;
            int right = len-1;
            while(left < right){
                if (numbers[left] + numbers[right] == target){
                    res[0] = left;
                    res[1] = right;
                    return res;
                }else if(numbers[left] + numbers[right] > target){
                    right--;
                }else {
                    left++;
                }
            }
            return res;
        }
    }
    
  • 复杂度

    • 时间复杂度:O(n),其中 n是数组的长度。两个指针移动的总次数最多为 n次。
    • 空间复杂度:O(1)
二分查找

2023/7/8

依次遍历数组中的元素,然后使用二分法查找数组中是否存在元素的数值为targe-numbers[i]

  • 若存在,则返回结果
  • 若不存在,则继续遍历
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int n = numbers.length;
        for (int i = 0; i < n; i++){
            int index = search(numbers, target - numbers[i], i + 1, n - 1);
            if (index != -1){
                return new int[]{i + 1, index + 1};
            }
        }
        return null;
    }
    public int search(int[] nums, int target, int l, int r){
        while (l <= r){
            int mid = (l + r) >> 1;
            if (nums[mid] == target){
                return mid;
            }else if (nums[mid] > target){
                r = mid - 1;
            }else{
                l = mid + 1;
            }
        }
        return -1;
    }
}
  • 复杂度
    • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)
    • 空间复杂度: O ( 1 ) O(1) O(1)
二分查找+双指针
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int i = 0, j = numbers.length - 1;
        while (i < j) {
            int m = (i + j) >>> 1;
            if (numbers[i] + numbers[m] > target) {
                j = m - 1;
            } else if (numbers[m] + numbers[j] < target) {
                i = m + 1;
            } else if (numbers[i] + numbers[j] > target) {
                j--;
            } else if (numbers[i] + numbers[j] < target) {
                i++;
            } else {
                return new int[]{i + 1, j + 1};
            }
        }
        return new int[]{0, 0};
    }
}

https://leetcode-cn.com/problems/two-sum-ii-input-array-is-sorted/solution/liang-shu-zhi-he-ii-shu-ru-you-xu-shu-zu-by-leet-2/502315
  • 复杂度
    • 时间复杂度:最好情况 O ( l o g n ) O(logn) O(logn),最坏 O ( n ) O(n) O(n)
    • 空间复杂度: O ( 1 ) O(1) O(1)

三数之和【LC15】

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != kj != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

**注意:**答案中不可以包含重复的三元组。

排序+双指针
  • 思路

    • 首先对数组进行排序,然后遍历数组,在确定nums[i]的情况下,在数组nums[i+1,n-1]中使双指针法(前后指针),确定另外两个元素,判断这三个元素之和sumtarget的大小:
      • 如果 s u m > t a r g e t sum>target sum>target,左移左指针,使sum减小
      • 如果 s u m < t a r g e t sumsum<target,右移右指针,使sum增大
      • 如果 s u m = = t a r g e t sum==target sum==target,添加三元组至结果集
    • 去重
      • 在找到符合条件的三元组后,移动指针r、l跳过相同元素,以便过滤掉重复的三元组
      • 如果nums[i]==nums[i-1] n u m s [ i ] nums[i] nums[i]的三元组是 n u m s [ i − 1 ] nums[i-1] nums[i1]的子集,需要跳过
      • 在遇到不符合条件的左右边界时也可以移动双指针去重,但是由于根据sum和target的大小关系,双指针也会被移动,所以此时是否去重不是必须的
  • 实现

    1. 首先将数组排序;

    2. 然后有一层for循环, i i i从下表0的地方开始,同时定义下标left在i+1位置上,定义下标right在数组末尾的位置上

    3. 如果nums[i]>0,不可能存在三元组,直接退出循环,返回结果集

    4. 去重:如果(i > 0 && nums[i] == nums[i - 1]) n u m s [ i ] nums[i] nums[i]的三元组是 n u m s [ i − 1 ] nums[i-1] nums[i1]的子集,需要跳过

    5. 移动left和right

      • 如果nums[i] + nums[left] + nums[right] > 0,说明right下标应该向左移动

        去重:while (left < right && nums[right] == nums[right + 1]) right--;【非必须】

      • 如果nums[i] + nums[left] + nums[right] < 0,说明left下标应该向右移动

        去重: while (left < right && nums[left] == nums[left - 1]) left++;【非必须】

      • 如果nums[i] + nums[left] + nums[right] == 0,说明找到了三元组,双指针同时收缩,right--;left++

        去重: while (right > left && nums[right] == nums[right - 1]) right--;
        while (right > left && nums[left] == nums[left + 1]) left++;

      class Solution {
          public List<List<Integer>> threeSum(int[] nums) {
              List<List<Integer>> res = new ArrayList<>();
              Arrays.sort(nums);
              for (int i = 0; i < nums.length - 2; i++){
                  if (nums[i] > 0){
                      return res;
                  }
                  if (i > 0 && nums[i] == nums[i-1] ){
                      continue;
                  }
                  int left = i + 1;
                  int right = nums.length - 1;
                  while (left < right){
                      int sum = nums[i] + nums[left] + nums[right];
                      if (sum > 0){
                          right--;
                          // 非必须
                          while(left < right && nums[right] == nums[right+1]){
                              right--;
                          }
                      }else if (sum < 0){
                          // 非必须
                          left++;
                          while (left < right && nums[left-1] == nums[left]){
                              left++;
                          }
                      }else{
                          res.add(Arrays.asList(nums[i],nums[left],nums[right]));
                          while(left < right && nums[right] == nums[right-1]){
                              right--;
                          }
                          while (left < right && nums[left+1] == nums[left]){
                              left++;
                          }
                          right--;
                          left++;
                      }
                  }
              }
              return res;
          }
      }
      
      class Solution {
          public List<List<Integer>> threeSum(int[] nums) {
              Arrays.sort(nums);
              List<List<Integer>> res = new ArrayList<>();
              int len = nums.length;
              int i  = 0;
              while (i < len - 2){
                  twoSum(nums,i,res);
                  int temp = nums[i];
                  while (i < len && temp == nums[i]){
                      i++;
                  }
              }
              return res;
          }
          public void twoSum(int[] numbers, int i, List<List<Integer>> res ) {
              int len = numbers.length;
              int left = i + 1;
              int right = len - 1;
              while(left < right){
                  if ( numbers[i] + numbers[left] + numbers[right] == 0){
                      res.add(Arrays.asList(numbers[i],numbers[left],numbers[right]));
                      int temp = numbers[left];
                      while (numbers[left] == temp && left < right){
                          left++;
                      }
                  }else if(numbers[i] + numbers[left] + numbers[right] > 0){
                      right--;
                  }else {
                      left++;
                  }
              }
          }
      }
      
      • 复杂度

        • 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)
        • 空间复杂度: O ( 1 ) O(1) O(1)

最接近的三数之和【LC16】

给你一个长度为 n 的整数数组 nums 和 一个目标值 target。请你从 nums 中选出三个整数,使它们的和与 target 最接近。

返回这三个数的和。

假定每组输入只存在恰好一个解。

2023/1/14

  • 思路:

    • 搜索三数之和的过程与LC15相同:首先对数组进行排序,然后遍历数组,在确定nums[i]的情况下,在数组nums[i+1,n-1]中使双指针法(前后指针),确定另外两个元素,判断这三个元素之和sumtarget的大小:
      • 如果 s u m > t a r g e t sum>target sum>target,左移左指针,使sum减小
      • 如果 s u m < t a r g e t sumsum<target,右移右指针,使sum增大
      • 如果 s u m = = t a r g e t sum==target sum==target,添加三元组至结果集
    • 不同之处为更新结果的条件变为如果sum更接近target那么更新sum。如果sum==target,那么可以直接返回target。本题不去重也不影响结果
    • 本题只需要找到最接近的和,因此去重不是必须的
  • 实现

    // 2023/7/10
    class Solution {
        public int threeSumClosest(int[] nums, int target) {
            Arrays.sort(nums);
            int n = nums.length ;
            int res = Integer.MAX_VALUE;
            for (int i = 0; i < n - 2; i++){
                int l = i + 1, r = n - 1;
                if (i > 0 && nums[i] == nums[i - 1]){
                    continue;
                }
                while (l < r){
                    int sum = nums[i] + nums[l] + nums[r];
                    if (Math.abs(sum - target) < Math.abs(res - target)){
                        res = sum;
                    }
                    if (sum == target){
                        return target;
                    }else if(sum > target){
                        r--;
                    }else{
                        l++;
                    }
                }
            }
            return res;
        }
    
    }
    
    • 复杂度

      • 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)
      • 空间复杂度: O ( 1 ) O(1) O(1)

较小的三数之和【LC259】

给定一个长度为 n 的整数数组和一个目标值 target ,寻找能够使条件 nums[i] + nums[j] + nums[k] < target 成立的三元组 i, j, k 个数(0 <= i < j < k < n)。

2023/1/14

双指针
  • 思路:首先对数组排序,然后固定 n u m s [ i ] nums[i] nums[i],使用双指针在 n u m s [ i + 1 , n − 1 ] nums[i+1,n-1] nums[i+1,n1]内搜索左指针最小右指针最大的二元组,。左指针起始位置为 i + 1 i+1 i+1,右指针起始位置为 n − 1 n-1 n1,假设三数之和为sum

    • 如果 s u m > t a r g e t sum>target sum>target,那么需要左移右指针,减小sum
    • 如果 s u m < t a r g e t sumsum<target,那么此时的二元组满足要求,并且固定il时,右指针r的可取位置为 [ l + 1 , r ] [l+1,r] [l+1,r],此时的三元组之和均小于target,对结果的贡献为 r − l r-l rl。然后右移左指针,寻找新的二元组

    本题不要求去重,因为统计的是下标的个数

  • 实现

    class Solution {
        public int threeSumSmaller(int[] nums, int target) {
            int count = 0, n = nums.length;
            Arrays.sort(nums);
            for (int i = 0; i < n - 2; i++){
                int l = i + 1, r = n - 1;
                while (l < r){
                    int sum = nums[i] + nums[l] + nums[r];
                    if (sum < target){
                        count += r - l;
                        l++;
                    }else{
                        r--;
                    }
                }
            }
            return count;
    
        }
    }
    
    • 复杂度

      • 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 ) O(n^2) O(n2),因此时间复杂度为 O ( n 2 ) O(n^2) O(n2)
      • 空间复杂度: O ( 1 ) O(1) O(1)
二分查找
  • 思路:我们从小到大枚举 i i i j j j,规定 i < j ii<j,随后在数组上二分查找,找出最大的满足 n u m s [ i ] + n u m s [ j ] + n u m s [ k ] < t a r g e t nums[i]+nums[j]+nums[k]nums[i]+nums[j]+nums[k]<target k k k,那么对于当前的 i i i j j j,有 k − j k−j kj 对三元组是满足要求的。

  • 实现

    public int threeSumSmaller(int[] nums, int target) {
        Arrays.sort(nums);
        int sum = 0;
        for (int i = 0; i < nums.length - 2; i++) {
            sum += twoSumSmaller(nums, i + 1, target - nums[i]);
        }
        return sum;
    }
    
    private int twoSumSmaller(int[] nums, int startIndex, int target) {
        int sum = 0;
        for (int i = startIndex; i < nums.length - 1; i++) {
            int j = binarySearch(nums, i, target - nums[i]);
            sum += j - i;
        }
        return sum;
    }
    
    private int binarySearch(int[] nums, int startIndex, int target) {
        int left = startIndex;
        int right = nums.length - 1;
        while (left < right) {
            int mid = (left + right + 1) / 2;
            if (nums[mid] < target) {
                left = mid;
            } else {
                right = mid - 1;
            }
        }
        return left;
    }
    
    作者:力扣 (LeetCode)
    链接:https://leetcode.cn/problems/3sum-smaller/solutions/11601/jiao-xiao-de-san-shu-zhi-he-by-leetcode/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    
    • 复杂度

      • 时间复杂度: O ( n l o g n + n 2 l o g n ) O(nlogn+n^2logn) O(nlogn+n2logn),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 2 l o g n ) O(n^2logn) O(n2logn)
      • 空间复杂度: O ( 1 ) O(1) O(1)

四数之和【LC18】

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

  • 0 <= a, b, c, d < n
  • abcd 互不相同
  • nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

排序+双指针
  • 思路

    类似三数之和,先将数组排序,然后固定两个数 n u m s [ a ] , n u m s [ b ] nums[a],nums[b] nums[a],nums[b],然后在区间 [ b + 1 , n − 1 ] [b+1,n-1] [b+1,n1]使用双指针搜索是否有两数之和为 t a r g e t − n u m s [ a ] − n u m s [ b ] target-nums[a]-nums[b] targetnums[a]nums[b],如果有则记录答案;否则根据大小,右移左指针或者左移右指针。

    • 注意去重以及溢出
    • 优化:
      • 判断最小四元组是否大于target,是则break
      • 判断最大四元组是否小于target,实则continue
  • 实现

    class Solution {
        public List<List<Integer>> fourSum(int[] nums, int target) {
            List<List<Integer>> res = new ArrayList<>();
            int n = nums.length;
            Arrays.sort(nums);
            for (int i = 0; i < n - 3; i++){
                if (i > 0 && nums[i] == nums[i - 1]) continue;
                long x = nums[i];
                if (x + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
                if (x + nums[n - 1] + nums[n - 2] + nums[n - 3] < target) continue;
                for (int j = i + 1; j < n - 2; j++){
                    if (j > i + 1 && nums[j] == nums[j - 1]) continue;
                    long sum2 = nums[i] + nums[j];
                    int l = j + 1, r = n - 1;
                    long num = target - sum2;
                    while (l < r){
                        if (nums[l] + nums[r] == num){
                            res.add(Arrays.asList(nums[i], nums[j], nums[l], nums[r]));                       
                            l++;
                            while (l < n && nums[l - 1] == nums[l]){
                                l++;
                            }
                            r--;
                            while (r > l && nums[r + 1] == nums[r]){
                                r--;
                            }
                        }else if (nums[l] + nums[r] > num){
                            r--;
                        }else{
                            l++;
                        }
                    }
                }
            }
            return res;
        }
    }
    
    • 复杂度

      • 时间复杂度: O ( n l o g n + n 2 ) O(nlogn+n^2) O(nlogn+n2),其中 n是数组的长度。排序所需的时间复杂度一般为 O ( n l o g n ) O(nlogn) O(nlogn),查找三元组的时间复杂度为 O ( n 3 ) O(n^3) O(n3),因此时间复杂度为 O ( n 3 ) O(n^3) O(n3)
      • 空间复杂度: O ( 1 ) O(1) O(1)

两数之和【LC1】

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

哈希表
  • 题解

    注意:先在map中寻找是否存在target-num[i],若存在则返回下标,若不存在再将nums[i],i放入map中,避免返回相同坐标或者坐标覆盖的情况

    class Solution {
        public int[] twoSum(int[] nums, int target) {
            int[] res = new int[2];
            if(nums == null || nums.length == 0){
                return res;
            }
            Map<Integer, Integer> map = new HashMap<>();
            for(int i = 0; i < nums.length; i++){
                int temp = target - nums[i];
                if(map.containsKey(temp)){
                    res[1] = i;
                    res[0] = map.get(temp);
                }
                map.put(nums[i], i);
            }
            return res;
        }
    }
    

四数之和【LC454】

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/4sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

哈希表
  • 题解

    2022/4/20

    1. 首先求两数之和后,将nums1[i]+nums2[j],nums3[k]和nums4[l]中的数字对应相加,将和以及出现的次数依次放入map1和map2中
    2. 然后遍历map1,在map2中寻找是否存在-nums1[i]-nums2[j],如果存在则将对应的value值相乘后累加至res
    class Solution {
        public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
            int len = nums1.length;
            Map<Integer,Integer> sum1 = new HashMap<>();
            Map<Integer,Integer> sum2 = new HashMap<>();
            for (int i = 0; i < len; i++){
                for (int j = 0; j < len; j++){
                    sum1.put(nums1[i] + nums2[j], sum1.getOrDefault(nums1[i] + nums2[j],0) + 1);
                    sum2.put(nums3[i] + nums4[j], sum2.getOrDefault(nums3[i] + nums4[j],0) + 1);
                }
            }
            Set<Map.Entry<Integer,Integer>> set1 = sum1.entrySet();
            int res = 0;
            for (Map.Entry<Integer,Integer> node:set1 ){
                int target = -1 * node.getKey().intValue();
                if(sum2.containsKey(target)){
                    res += node.getValue().intValue() * sum2.get(target);
                }
            }
            return res;
        }
        
    }
    
  • 改进

    只用一个map记录nums1[i]+nums2[j],多使用一个双重for循环在map中寻找nums3[k]和nums4[l]

    class Solution {
        public int fourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
            int len = nums1.length;
            Map<Integer,Integer> sum1 = new HashMap<>();
            for (int i = 0; i < len; i++){
                for (int j = 0; j < len; j++){
                    sum1.put(nums1[i] + nums2[j], sum1.getOrDefault(nums1[i] + nums2[j],0) + 1);
                }
            }
            int res = 0;
            for (int i = 0; i < len; i++){
                for (int j = 0; j < len; j++){
                    int target = -nums3[i] - nums4[j];
                    if(sum1.containsKey(target)){
                        res += sum1.get(target);
                    }
                }
            }
            return res;
        }
        
    }
    

你可能感兴趣的:(算法总结,散列表,双指针)