两数之和

0 阅读4分钟

HOT100 第一题,也是力扣第一题
链接:1. 两数之和 - 力扣(LeetCode)
时间:2026年4月10日20:22:47
思路:这个问题本质上是在找数对,第一想法是枚举每个数对(i,j),第一个迭代变量迭代i从0到n,第二个迭代变量迭代j从i+1到n,这样就可以不重复的枚举不同位置的数对,然后通过判断其和是否等于target。由于本题问题规模n上限为10410^4,这种方法的时间复杂度为O(n2n^2),也可以过。
写法一:

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 暴力解法:枚举所有可能的两个数
        // 看它们的和是否等于 target

        int n = nums.length; // 数组长度

        // 第一层循环:固定第一个数 nums[i]
        for (int i = 0; i < n; ++i) {

            // 第二层循环:从 i 的后一个位置开始找第二个数 nums[j]
            // 这样可以避免重复枚举,例如 (0,1) 和 (1,0) 只算一次
            for (int j = i + 1; j < n; ++j) {

                // 如果当前这两个数的和正好等于目标值
                if (nums[i] + nums[j] == target) {

                    // 返回这两个数的下标
                    return new int[] { i, j };
                }
            }
        }

        // 如果遍历完所有数对都没有找到
        // 按题意一般不会发生,这里只是为了代码完整性
        return new int[] { -1, -1 };
    }
}

写法二:我可以用另一种枚举写法:先枚举第二个数,迭代变量j从0到n-1,然后再枚举第一个数,迭代变量i从0到i-1,这样也可以覆盖到每一种数对<i,j>

class Solution {
    public int[] twoSum(int[] nums, int target) {
        // 暴力解法:枚举所有可能的两个数(i,j)
        // 看它们的和是否等于 target

        int n = nums.length; // 数组长度

        // 第一层循环:固定第二个数 nums[j]
        for (int j = 0; j < n; ++j) {

            // 第二层循环:从0到j-1 枚举第一个数nums[i]
            // 这样可以避免重复枚举,例如 (0,1) 和 (1,0) 只算一次
            for (int i = 0; i < j; ++i) {

                // 如果当前这两个数的和正好等于目标值
                if (nums[i] + nums[j] == target) {

                    // 返回这两个数的下标
                    return new int[] { j, i };
                }
            }
        }

        // 如果遍历完所有数对都没有找到
        // 按题意一般不会发生,这里只是为了代码完整性
        return new int[] { -1, -1 };
    }
}

时间复杂度O(n2n^2),空间复杂度(O(1))。 image.png


考虑进一步优化:用O($n^2$)去枚举数对的过程中发现【特别是从写法二可以看出】,对于某下标而言,我其实只需要看他之前的元素【或者他之后的元素即可】,比如说写法二中,我看当固定第二个迭代变量j后,其实我只需要看j之前包不包含`target-nums[j]`即可,而这种枚举在每次固定j后都会查询诸如j=2时需要查询nums[0]+nums[2],nums[1]+nums[2], j=3时候需要查nums[0] + nums[3],nums[1] + nums[3],nums[2]+nums[3],相当于每次都要去数组中查询nums[0],nums[1],nums[2],所以造成了这些元素一共要在数组中被访问n次、n-1次、n-2次……,但是如果我们此时使用哈希表记录访问过的数,那么就可以将这时的时间复杂度压缩到O(1),相当于数组中只访问1次每个元素,访问后记录到哈希表中,利用空间换时间。于是通过这种方法可以使时间复杂度压缩到O(n)。
class Solution {
    /**
     * 两数之和问题:给定一个整数数组 nums 和一个整数目标值 target,
     * 请你在该数组中找出和为目标值 target 的那两个整数,并返回它们的数组下标。
     * 你可以假设每种输入只会对应一个答案,且数组中同一个元素在答案里不能重复出现。
     *
     * 人话:
     * 其实就是要检测数对,因为对于某一个数而言我们有明确的目标,
     * 所以我们可以通过哈希表的方式进行前值的存储,便于后值的查询。
     * 
     * @param nums   输入的整数数组
     * @param target 目标和值
     * @return 两个整数的索引组成的数组,按出现顺序返回(前一个索引小于后一个)
     */
    public int[] twoSum(int[] nums, int target) {
        int n = nums.length; // 获取数组长度,用于循环遍历
        // 创建哈希表,键为数组元素值,值为该元素对应的索引,用于快速查找已遍历过的元素
        Map<Integer, Integer> mp = new HashMap<>();

        // 遍历数组中的每个元素
        for (int i = 0; i < n; ++i) {
            int x = nums[i]; // 将当前元素赋值给变量x,减少数组访问次数,优化性能

            // 计算目标值与当前元素的差值,即需要找到的另一个元素值
            int complement = target - x;
            // 检查哈希表中是否存在该差值:若存在,说明之前已遍历过该元素,直接返回两个索引
            if (mp.containsKey(complement)) {
                return new int[] { mp.get(complement), i };
            }

            // 若哈希表中不存在该差值,则将当前元素及其索引存入哈希表,供后续元素查找
            mp.put(x, i);
        }

        // 题目假设存在唯一解,此处为默认返回(实际不会执行到)
        return new int[] { -1, -1 };
    }
}

时间复杂度O(nn),空间复杂度(O(n))

image.png