算法题 两数之和

1. 两数之和

问题描述

给定一个整数数组 nums 和一个整数目标值 target,在数组中找出两个数,使它们的和等于 target,并返回这两个数的索引(任意顺序)。假设每种输入只有唯一解,且同一个元素不能使用两次。

示例

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]

示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

算法思路

哈希表(字典)优化法

  1. 核心思想
    使用哈希表存储遍历过的数字及其索引,实现 O(1) 时间复杂度的差值查询。
  2. 遍历过程
    对于每个数字 nums[i],计算补数 complement = target - nums[i]
    • 若补数在哈希表中,直接返回结果
    • 否则将当前数字存入哈希表
  3. 避免自用
    先查询后存储,确保不会使用同一个元素两次

代码实现

import java.util.HashMap;
import java.util.Map;

class Solution {
    /**
     * 查找两数之和等于目标值的索引
     * 
     * @param nums   整数数组
     * @param target 目标值
     * @return 包含两个索引的数组
     */
    public int[] twoSum(int[] nums, int target) {
        // 创建哈希表:键-数字值,值-索引
        Map<Integer, Integer> numMap = new HashMap<>();
        
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            
            // 检查补数是否存在于哈希表中
            if (numMap.containsKey(complement)) {
                return new int[]{numMap.get(complement), i};
            }
            
            // 将当前数字存入哈希表
            numMap.put(nums[i], i);
        }
        
        // 根据题目假设,此处不会执行
        return new int[0];
    }
}

代码注释

代码部分说明
Map<Integer, Integer> numMap哈希表:存储数字值与索引的映射关系
int complement = target - nums[i]计算当前数字所需的补数
numMap.containsKey(complement)检查补数是否在已遍历的数字中
new int[]{numMap.get(complement), i}找到结果时返回两个索引
numMap.put(nums[i], i)将当前数字存入哈希表(先查询后存储)

算法过程

nums = [2,7,11,15], target = 9

  1. i=0
    nums[0]=2complement=7
    哈希表为空 → 存入 {2:0}
  2. i=1
    nums[1]=7complement=2
    哈希表中有 2 → 返回 [0,1]

复杂度分析

  • 时间复杂度:O(n)
    只需遍历数组一次,哈希表查询和插入均为 O(1)
  • 空间复杂度:O(n)
    最坏情况下需要存储所有元素

关键点

  1. 哈希表优化查询:将查找时间从 O(n) 降为 O(1)
  2. 先查询后存储:避免同一个元素被错误匹配
  3. 一次遍历完成:同时进行查询和数据存储
  4. 结果唯一性:题目保证解唯一,找到即可返回

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 示例测试
    int[] nums1 = {2,7,11,15};
    int[] result1 = solution.twoSum(nums1, 9);
    System.out.println("Test 1: " + Arrays.toString(result1)); // [0,1]
    
    // 有负数的测试
    int[] nums2 = {-3,4,3,90};
    int[] result2 = solution.twoSum(nums2, 0);
    System.out.println("Test 2: " + Arrays.toString(result2)); // [0,2]
    
    // 重复元素测试
    int[] nums3 = {3,2,4};
    int[] result3 = solution.twoSum(nums3, 6);
    System.out.println("Test 3: " + Arrays.toString(result3)); // [1,2]
    
    // 相同元素不同位置
    int[] nums4 = {3,3};
    int[] result4 = solution.twoSum(nums4, 6);
    System.out.println("Test 4: " + Arrays.toString(result4)); // [0,1]
}

常见问题

  1. 为什么用哈希表不用暴力法?
    暴力法时间复杂度 O(n²),哈希表优化至 O(n),效率更高。

  2. 遇到重复元素怎么办?
    哈希表存储最新索引,当出现重复元素时,新索引会覆盖旧索引。由于题目要求唯一解,且先查询后存储,不会影响结果(如 [3,3], target=6)。

  3. 哈希表存储什么内容?
    存储数字值(键)和对应的索引(值),便于快速查询补数位置。

  4. 为什么先查询后存储?
    避免同一个元素被匹配(如 target=6,当前元素为3时,若先存储会错误匹配自己)。

167. 两数之和 II - 输入有序数组

问题描述

给定一个已按非递减顺序排列的整数数组 numbers,从数组中找出两个数,使它们的和等于目标值 target。返回这两个数的下标(下标从 1 开始)。假设每个输入只对应唯一的答案,且不能重复使用相同元素。

示例

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。

示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

算法思路

双指针法

  1. 指针初始化
    • 左指针 left 指向数组起始位置(下标 0)
    • 右指针 right 指向数组末尾位置(下标 n-1
  2. 指针移动规则
    • numbers[left] + numbers[right] > target:右指针左移(减小总和)
    • numbers[left] + numbers[right] < target:左指针右移(增大总和)
    • 若相等:返回下标(注意下标从 1 开始)
  3. 终止条件left < right(确保使用两个不同元素)

代码实现

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        // 初始化双指针
        int left = 0;
        int right = numbers.length - 1;
        
        while (left < right) {
            int sum = numbers[left] + numbers[right];
            
            if (sum > target) {
                // 和过大,右指针左移
                right--;
            } else if (sum < target) {
                // 和过小,左指针右移
                left++;
            } else {
                // 找到目标,返回下标(从1开始)
                return new int[]{left + 1, right + 1};
            }
        }
        
        // 根据题目保证有解,此处不会执行
        return new int[0];
    }
}

代码注释

代码部分说明
int left = 0;左指针初始指向数组起始位置
int right = numbers.length - 1;右指针初始指向数组末尾位置
while (left < right)循环条件:确保使用两个不同元素
int sum = numbers[left] + numbers[right];计算当前两数之和
sum > target → right--和过大时,右指针左移(减小总和)
sum < target → left++和过小时,左指针右移(增大总和)
return new int[]{left+1, right+1}找到目标时返回下标(从1开始)

算法过程

numbers = [2,7,11,15], target = 9

  1. 初始状态
    left=0 → 2, right=3 → 15, sum=17 >9
    
  2. 右指针左移
    left=0 → 2, right=2 → 11, sum=13 >9
    
  3. 右指针再次左移
    left=0 → 2, right=1 → 7, sum=9 ==9
    
  4. 返回结果[1,2]

复杂度分析

  • 时间复杂度:O(n)
    最坏情况下遍历整个数组一次
  • 空间复杂度:O(1)
    仅使用常数空间

关键点

  1. 有序数组特性:利用非递减特性,通过指针移动高效调整总和
  2. 双指针移动规则
    • 总和过大 → 左移右指针
    • 总和过小 → 右移左指针
  3. 下标转换:结果下标从 1 开始,返回时需 +1
  4. 唯一解保证:题目保证有唯一解,无需处理多解情况

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 示例测试
    int[] nums1 = {2,7,11,15};
    int[] result1 = solution.twoSum(nums1, 9);
    System.out.println("Test 1: " + Arrays.toString(result1)); // [1,2]
    
    // 存在负数测试
    int[] nums2 = {-5,-3,0,1,5};
    int[] result2 = solution.twoSum(nums2, -2);
    System.out.println("Test 2: " + Arrays.toString(result2)); // [2,4]
    
    // 目标值较大测试
    int[] nums3 = {1,2,3,4,5};
    int[] result3 = solution.twoSum(nums3, 8);
    System.out.println("Test 3: " + Arrays.toString(result3)); // [3,5]
    
    // 最小长度数组
    int[] nums4 = {1,2};
    int[] result4 = solution.twoSum(nums4, 3);
    System.out.println("Test 4: " + Arrays.toString(result4)); // [1,2]
}

常见问题

  1. 为什么双指针法有效?
    数组有序性保证:

    • 右指针左移必然减小总和
    • 左指针右移必然增大总和
      通过系统性的指针移动,不会错过有效组合。
  2. 如何处理重复元素?
    题目要求不能重复使用元素,但双指针天然使用不同位置的元素(left < right),不会重复使用同一元素。

  3. 为什么返回下标从 1 开始?
    题目要求,注意结果需转换为 1-based 索引。

  4. 无序数组能用此方法吗?
    不能,必须先排序(但会破坏原始索引)。无序数组建议使用哈希表法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值