算法江湖的「两数之和」三式破阵法

183 阅读4分钟

题目:两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。你可以按任意顺序返回答案。

示例

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

方法一:暴力法

解题思路

通过两层循环穷举所有可能的数字组合,逐一检查它们的和是否等于目标值。
外层循环固定左手元素,想象你左手依次抓住数组中的每个数字(先抓第一个数2,再抓第二个数7...);内层循环寻找匹配元素,右手从左手抓取元素的后一位开始向右尝试所有数字;检查当前左右手数字之和是否等于target,一旦找到符合条件的组合,立即返回索引并终止函数。

代码实现

var twoSum = function(nums, target) {
    // 外层循环,从第一个元素开始遍历
    for(let i = 0; i < nums.length; i++) {
        let left = nums[i]; // 左手握着当前元素(比如第一个元素2)
        
        // 内层循环,从外层循环当前元素的下一个元素开始遍历
        for(let j = i + 1; j < nums.length; j++) {
            let right = nums[j]; // 右手握着后面的元素
            
            // 检查两手握的数的和是否等于目标值
            if(left + right == target) {
                return [i, j]; // 如果等于,返回两个索引
            }
        }
    }
};
console.log(twoSum(nums, target));

复杂度分析

  • 时间复杂度为 O(n²) ,其中n是数组中的元素数量。最坏情况下数组中任意两个数都要被匹配一次。
       外层循环        内层查找
        O(n)       ×    O(n)    = O(n²)
  • 空间复杂度为 O(1) ,仅用常数级变量ijleftright,无额外数据结构。

方法二:数组中的 indexOf 方法

解题思路

通过计算补数来优化两数之和问题,但本质上仍属于顺序查找策略。
对于当前元素 nums[i],能与之配对的和等于 target 的元素必然是 target - nums[i],即补数。通过 indexOf 方法在数组中线性搜索补数,注意避免同一元素被使用两次。

代码实现

var twoSum = function(nums, target) {  
   for (let i = 0; i < nums.length; i++) {
     // 计算当前元素所需的补数(即目标值减去当前元素)
     let item = target - nums[i];
     
     // 在数组中查找这个补数是否存在
     let j = nums.indexOf(item);
     
     // 检查补数是否存在且不是当前元素本身(避免重复使用同一个元素)
     if (j !== -1 && j !== i) {
       return [i, j]; // 返回两个索引
     }
   }
};

复杂度分析

  • 时间复杂度为 O(n²) ,虽然代码看似单层循环,但 indexOf 的内部遍历使其时间复杂度仍为 O(n²),与暴力双循环相同。
  • 空间复杂度为 O(1) ,仅使用常数级变量。

方法三:哈希表法

解题思路

利用哈希表实现补数查找,空间换时间策略。
创建空对象作为哈希表,用于存储已遍历元素的值与其索引位置的映射关系,通过键名item检查补数是否已存在哈希表中,当补数存在于哈希表,说明之前已遍历过该元素,直接返回两者索引;若未找到补数,将当前元素存入哈希表,供后续元素查找。后出现的重复元素会覆盖前值。

代码实现

var twoSum = function(nums, target) {
    let diff = {}; // 创建一个空对象来存储数字和它们的索引
    for(var i = 0; i < nums.length; i++) {
        let item = target - nums[i]; // 计算当前数字与目标的差值
        
        // 检查这个差值是否在diff对象中存在
        if(diff[item] !== undefined) {
            return [diff[item], i]; // 如果存在,返回两个索引
        }
        
        // 如果不存在,将当前数字和它的索引存入diff对象
        diff[nums[i]] = i;
    }
};

执行示例

步骤inums[1]补数(item)diff内容操作
1027{}存入 diff[2]=0
2172{2:0}发现diff[2]存在 → 返回[0,1]

复杂度分析

  • 时间复杂度为 O(n) ,仅需一次遍历,n为数组长度。
  • 空间复杂度为 O(n) ,最坏情况需存储所有元素。

题目链接

leetcode.cn/problems/tw…