当前位置: 首页 > news >正文

每日算法-两数之和

双指针法高效解决有序数组两数之和问题

引言

在算法面试中,数组操作类问题一直是热门考点。今天我们来探讨一个经典问题:如何在两个有序数组中找到所有和为特定值的元素组合,要求算法复杂度尽可能优化。这个问题不仅考察对数组特性的理解,还能很好地体现算法设计的精妙之处。

问题描述

给定两个非递减排序的整数数组 nums1nums2,以及一个目标和 m。请找出所有满足 a + b = m 的元素组合,其中 a 来自 nums1b 来自 nums2。要求算法的时间复杂度尽可能低,最好能做到线性时间复杂度。

问题分析

暴力解法思路

最直观的思路是采用双层循环:遍历 nums1 中的每个元素 a,然后遍历 nums2 查找是否存在 b = m - a。这种方法的时间复杂度为 O(n×m),其中 n 和 m 分别是两个数组的长度。当数组规模较大时,这种解法效率极低,显然不符合优化要求。

优化思路

由于数组是有序的,我们可以利用这个特性来优化查找过程。有序数组最常用的优化手段包括二分查找和双指针法。对于这个问题,双指针法是更优的选择。

双指针法解题思路

双指针法的核心思想是设置两个指针,分别从两个数组的特定位置开始遍历,根据当前元素和与目标值的比较结果来移动指针,从而减少不必要的比较。

具体到这个问题,我们可以:

  1. 设置指针 inums1 的起始位置(索引 0)开始
  2. 设置指针 jnums2 的末尾位置(索引 nums2.length - 1)开始
  3. 计算当前指针指向的元素和 sum = nums1[i] + nums2[j]
  4. 根据 sum 与目标值 m 的关系移动指针:
    • 如果 sum == m:找到一组解,记录下来,同时移动两个指针(i++,j–)
    • 如果 sum < m:当前和太小,需要增大,移动 nums1 的指针(i++)
    • 如果 sum > m:当前和太大,需要减小,移动 nums2 的指针(j–)
  5. 继续上述过程,直到任一指针超出数组范围

算法步骤详解

让我们通过一个流程图来理解算法的执行步骤:

初始化 i = 0, j = nums2.length - 1
┌─────────────┐
│  i=0, j=n-1 │
└──────┬──────┘│▼
┌─────────────────────┐
│ 计算 sum = nums1[i] + nums2[j] │
└───────────┬─────────┘│┌───────┼───────┐▼       ▼       ▼
┌────────┐ ┌────────┐ ┌────────┐
│ sum < m │ │ sum = m │ │ sum > m │
└───┬────┘ └───┬────┘ └───┬────┘│          │          │▼          ▼          ▼
┌────────┐ ┌────────┐ ┌────────┐
│  i++   │ │记录结果│ │  j--   │
│        │ │ i++,j--│ │        │
└───┬────┘ └───┬────┘ └───┬────┘│          │          │└──────────┼──────────┘│┌──────────┴──────────┐│                      │
┌───▼────┐            ┌───▼────┐
│i越界或j越界?  ──────►│继续循环  │
└───┬────┘            └────────┘│▼
┌─────────────┐
│  返回结果集  │
└─────────────┘

Java代码实现

下面是使用双指针法解决这个问题的Java实现:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;public class TwoSumPairs {/*** 查找两个有序数组中和为目标值的所有元素对* @param nums1 第一个有序数组* @param nums2 第二个有序数组* @param target 目标和* @return 包含所有符合条件的元素对的列表*/public List<List<Integer>> findPairs(int[] nums1, int[] nums2, int target) {List<List<Integer>> result = new ArrayList<>();// 边界条件处理if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0) {return result;}int i = 0; // nums1的起始指针int j = nums2.length - 1; // nums2的末尾指针while (i < nums1.length && j >= 0) {int sum = nums1[i] + nums2[j];if (sum == target) {// 找到一对解,记录下来List<Integer> pair = new ArrayList<>();pair.add(nums1[i]);pair.add(nums2[j]);result.add(pair);// 移动两个指针,继续寻找其他可能的组合i++;j--;} else if (sum < target) {// 当前和太小,需要增大,移动nums1的指针i++;} else {// 当前和太大,需要减小,移动nums2的指针j--;}}return result;}/*** 查找两个有序数组中和为目标值的所有元素对(去重版本)* @param nums1 第一个有序数组* @param nums2 第二个有序数组* @param target 目标和* @return 包含所有符合条件且不重复的元素对的列表*/public List<List<Integer>> findPairsWithDeduplication(int[] nums1, int[] nums2, int target) {List<List<Integer>> result = new ArrayList<>();if (nums1 == null || nums2 == null || nums1.length == 0 || nums2.length == 0) {return result;}int i = 0, j = nums2.length - 1;while (i < nums1.length && j >= 0) {int sum = nums1[i] + nums2[j];int currentNum1 = nums1[i];int currentNum2 = nums2[j];if (sum == target) {// 记录结果result.add(Arrays.asList(currentNum1, currentNum2));// 跳过nums1中的重复元素while (i < nums1.length && nums1[i] == currentNum1) {i++;}// 跳过nums2中的重复元素while (j >= 0 && nums2[j] == currentNum2) {j--;}} else if (sum < target) {// 跳过nums1中的重复元素while (i < nums1.length && nums1[i] == currentNum1) {i++;}} else {// 跳过nums2中的重复元素while (j >= 0 && nums2[j] == currentNum2) {j--;}}}return result;}public static void main(String[] args) {TwoSumPairs solution = new TwoSumPairs();// 测试用例1:基本情况int[] nums1 = {1, 3, 5, 7, 9};int[] nums2 = {2, 4, 6, 8, 10};int target = 10;List<List<Integer>> result1 = solution.findPairs(nums1, nums2, target);System.out.println("测试用例1结果: " + result1);// 预期输出: [[1, 9], [3, 7], [5, 5]]// 测试用例2:无符合条件的组合int[] nums3 = {1, 2, 3};int[] nums4 = {4, 5, 6};int target2 = 10;List<List<Integer>> result2 = solution.findPairs(nums3, nums4, target2);System.out.println("测试用例2结果: " + result2);// 预期输出: [] (因为3+7不存在,2+8不存在,1+9不存在)// 测试用例3:包含重复元素int[] nums5 = {1, 2, 2, 3};int[] nums6 = {3, 4, 4, 5};int target3 = 6;List<List<Integer>> result3 = solution.findPairs(nums5, nums6, target3);System.out.println("测试用例3结果(不去重): " + result3);// 预期输出: [[1,5], [2,4], [2,4], [3,3]]// 测试用例4:包含重复元素(去重版本)List<List<Integer>> result4 = solution.findPairsWithDeduplication(nums5, nums6, target3);System.out.println("测试用例4结果(去重): " + result4);// 预期输出: [[1,5], [2,4], [3,3]]}
}

代码解析

上面的代码实现了两个版本的解决方案:

  1. 基本版本(findPairs)

    • 双指针遍历两个数组
    • 找到符合条件的元素对并记录
    • 不处理重复元素,适用于无重复元素的数组
  2. 去重版本(findPairsWithDeduplication)

    • 在基本版本的基础上增加了去重逻辑
    • 当遇到重复元素时,跳过相同的元素
    • 适用于可能包含重复元素的数组

两个版本都遵循双指针法的核心思想,但去重版本在处理重复元素时更加健壮。

复杂度分析

  • 时间复杂度:O(n + m),其中n和m分别是两个数组的长度。每个数组最多被遍历一次。
  • 空间复杂度:O(1)(不考虑存储结果所需的空间)。算法本身只使用了常数级别的额外空间。

示例演示

让我们通过一个具体的例子来演示算法的执行过程:

示例:nums1 = [1, 3, 5, 7], nums2 = [2, 4, 6, 8], target = 9

执行步骤

  1. 初始状态:i=0, j=3 (nums1[0]=1, nums2[3]=8)
    sum = 1+8=9,等于target,记录(1,8),i=1, j=2

  2. 当前状态:i=1, j=2 (nums1[1]=3, nums2[2]=6)
    sum = 3+6=9,等于target,记录(3,6),i=2, j=1

  3. 当前状态:i=2, j=1 (nums1[2]=5, nums2[1]=4)
    sum = 5+4=9,等于target,记录(5,4),i=3, j=0

  4. 当前状态:i=3, j=0 (nums1[3]=7, nums2[0]=2)
    sum = 7+2=9,等于target,记录(7,2),i=4, j=-1

  5. 此时i越界,循环结束

结果:[(1,8), (3,6), (5,4), (7,2)]

结果顺序说明:由于我们从nums1的开头和nums2的末尾开始遍历,结果中的元素对顺序会呈现nums1递增、nums2递减的特点。这是算法的自然结果,算法保证找到所有组合,但不保证特定的顺序。如果需要按特定顺序输出,可以在收集完所有结果后进行排序。

边界情况处理

在实际应用中,我们需要考虑各种边界情况:

  1. 数组为空:如果任一数组为空,直接返回空结果
  2. 无符合条件的组合:算法会自然结束,返回空列表
  3. 数组中有重复元素:可以使用去重版本的算法(findPairsWithDeduplication)
  4. 目标值超出可能范围:如果目标值小于两个数组的最小元素之和,或大于两个数组的最大元素之和,直接返回空结果
  5. 数组长度为1:算法同样适用,不会出现问题

算法优化与扩展

优化方向

  1. 提前终止:如果nums1[i]已经大于target(假设数组元素为正),可以提前终止循环
  2. 二分查找结合:对于某些特殊情况,可以结合二分查找进一步优化
  3. 并行处理:在大规模数据处理时,可以考虑并行化处理

扩展问题

  1. 三数之和:在一个数组中找到所有和为目标值的三元组
  2. 四数之和:类似三数之和,但需要找到四个元素的组合
  3. 两数之和II:设计一个函数,找出数组中和为目标值的两个元素的索引
  4. 最接近的三数之和:找出与目标值最接近的三个元素的和

总结

双指针法是解决有序数组问题的高效技术,通过合理设置和移动指针,可以将时间复杂度从O(n×m)优化到O(n+m)。本文详细介绍了双指针法在两个有序数组两数之和问题中的应用,包括基本思路、Java实现、复杂度分析和边界情况处理。

我们提供了两个版本的实现:基本版本和去重版本,以适应不同的应用场景。通过多个测试用例验证了算法的正确性,并对结果顺序和可能的优化方向进行了讨论。

掌握双指针技术不仅能帮助我们解决这类特定问题,更重要的是培养我们利用数据结构特性进行算法优化的思维方式。在实际开发中,这种思维方式对于提高程序性能至关重要。

希望本文能帮助你深入理解双指针法的应用,如果有任何问题或建议,欢迎在评论区留言讨论!

http://www.dtcms.com/a/295340.html

相关文章:

  • Go基础教程 从零到英雄:30分钟掌握Go语言核心精髓
  • Leetcode—1035. 不相交的线【中等】
  • 独家|百度副总裁尚国斌即将离职,此前统筹百度地图;行业搜索及智能体业务总经理谢天转岗IDG
  • MongoDB 和 Elasticsearch(ES)区别
  • 项目重新发布更新缓存问题,Nginx清除缓存更新网页
  • MAC包头、IP包头 、UDP包头中的长度含义是啥?三者之间有啥区别?
  • Node.js 版本兼容问题:minimatch@10.0.3和minio@7.0.28 冲突的解决
  • Node.js 全局对象
  • Ubuntu-安装Asyn教程
  • 造成服务器内存不足的原因有什么
  • Node.js 中的内置模板path
  • Node.js特训专栏-实战进阶:18.密码加密与安全传输
  • node.js中的fs与path模块
  • 04.建造者模式的终极手册:从快餐定制到航天飞船的组装哲学
  • React+threejs两种3D多场景渲染方案
  • STM32 HAL库 HAL_TIM_OC_Stop函数详细解释
  • 期待更好的发展
  • 文件管理困境如何破?ZFile+cpolar打造随身云盘新体验
  • 亲测有效:EPLAN导出CAD图纸,DWG文件里的图纸复制粘贴出错和无法编辑的解决方法
  • EIDE 创建基于STM32-HD的项目快速创建流程
  • 精通Python PDF裁剪:从入门到专业的三重境界
  • 回调后门 函数
  • 微信格式插件 建的文件位置
  • 边缘智能体:轻量化部署与离线运行
  • MIT线性代数02_矩阵消元
  • C语言实现BIOS定义的WMI调用
  • NumPy 线性代数
  • 分布式推客系统开发全解:微服务拆分、佣金结算与风控设计
  • Sklearn 机器学习 数值标准化
  • 变量和函数底层工作原理