力扣100/01

108 阅读5分钟

LC100_01

1. 两数之和 - 力扣(LeetCode)

q: 给定一个整数数组 nums 和一个整数目标值 target,你需要从数组中找出 两个 数字,它们的和等于 target。然后返回这两个数字在原数组中的 下标

E: Given an integer array nums and an integer target value target, you need to find two numbers from the array whose sum is equal to target and return the indices of those two numbers in the original array

Key constraints:

  1. 恰好一个答案: 题目保证只有一对解,不用担心有多个或没有解的情况
  2. 不能重复使用相同元素: 意味着你不能用 nums[i]nums[i] 自身相加
  3. 返回下标: 而不是数字本身

方案一:暴力枚举 (Brute Force)

思想: 最直接的想法就是,既然要找到两个数,那我就把数组中所有可能的“两数组合”都试一遍,看看它们的和是不是等于 target

算法步骤:

  1. 选择第一个数 nums[i]。
  2. 然后遍历剩下的所有数 nums[j] (其中 j 必须大于 i,避免重复计算和使用同一个元素两次)。
  3. 检查 nums[i] + nums[j] 是否等于 target。
  4. 如果相等,就找到了,返回 [i, j]
// 暴力解法 (Brute Force)
class Solution {
public:
    std::vector<int> twoSum(std::vector<int>& nums, int target) {
        int n = nums.size();
        // 外层循环,选择第一个数
        for (int i = 0; i < n; ++i) {
            // 内层循环,选择第二个数,确保j > i 避免重复和使用同一元素
            for (int j = i + 1; j < n; ++j) {
                if (nums[i] + nums[j] == target) {
                    return {i, j}; // 找到解,返回下标
                }
            }
        }
        // 根据题目,题目保证有且只有一个解,所以理论上不会运行到这里
        return {}; 
    }
};

时间复杂度分析:

  • 外层循环 i 运行了 n 次。
  • 内层循环 j 大约运行了 n 次 (从 i+1n-1)。
  • 所以总的操作次数大约是 n * n = n^2 (n 的平方)。
  • 用大 O 表示法来表示,时间复杂度是 O(n2)

空间复杂度分析:

  • 只使用了几个额外的变量来存储 i, j, n,以及返回结果的 vector
  • 这些空间不随输入 nums 的大小而变化,所以空间复杂度是 O(1) (常数空间)

方案二:哈希表/字典优化 (Hash Table Optimization)

遍历数组时,对于每个数字 nums[i],计算出它“需要”的另一个数字 complement (或称“补数”),即 complement = target - nums[i]。然后,我们去检查这个 complement 是否已经在我们之前遍历过的数字中出现过。

  1. 如果 complement 出现过,并且它不是 nums[i] 本身 (因为不能重复使用同一元素),那么我们就找到了答案。
  2. 如果 complement 没出现过,或者我们正在查找 nums[i],那么我们就把当前的 nums[i] 和它的下标 i 存入哈希表,以便后续的数字可以查找它

哈希表的作用: 哈希表能够以平均 O(1) (常数时间) 的速度进行查找、插入和删除操作

算法步骤:

  1. 创建一个空的哈希表,用于存储 (数值, 下标) 对。
  2. 遍历数组 nums,对于每一个 nums[i]: a. 计算 complement = target - nums[i]。 b. 在哈希表中查找 complement 是否存在。 * 如果存在,说明我们找到了匹配的另一个数字。哈希表中存储的 complement 的下标就是第一个数字的下标,当前的 i 是第二个数字的下标。返回这两个下标。 * 如果不存在,将当前的 nums[i] 和它的下标 i 存入哈希表,以供后续的数字查找
class Solution {
public:
    std::vector<int> twoSum(std::vector<int>& nums, int target) {
        // 创建一个哈希表,键是数字,值是它的下标
        std::unordered_map<int, int> num_map; 
        
        int n = nums.size();
        for (int i = 0; i < n; ++i) {
            int current_num = nums[i];
            // 计算需要的补数
            int complement = target - current_num;
            
            // 在哈希表中查找补数是否存在
            // map.count(key) 返回键出现的次数,0 或 1
            // map.find(key) 返回指向元素的迭代器,如果不存在则返回 map.end()
            if (num_map.count(complement)) { 
                // 如果补数存在,并且补数对应的下标不是当前元素的下标 (避免自己和自己相加,虽然这题可以通过题目限制隐性避免,但好习惯)
                // 这里其实不需要额外判断是否是自己,因为是边遍历边存入,如果找到的complement是当前元素,
                // 那它的下标一定是之前存进去的,而不是当前的i。
                // 比如 target=6, nums=[3,3],当i=0, num=3时,complement=3,hashmap里还没3。
                // 当i=1, num=3时,complement=3,hashmap里有3,下标是0。所以可以返回 [0,1]。
                // 如果是 [2,4,7,11,15], target=8, 找到4时,complement=4,hashmap里还没4。
                // 所以不会出现自己和自己相加的情况。
                return {num_map[complement], i}; // 返回找到的补数的下标和当前数字的下标
            }
            
            // 如果补数不存在,将当前数字和它的下标存入哈希表,以供后续查找
            num_map[current_num] = i; 
        }
        
        // 题目保证有解,所以此处不会被执行到
        return {}; 
    }

时间复杂度分析:

  1. 只遍历了一次数组 nums。
  2. 在循环的每一步中,我们执行了:
  3. 计算 complement (常数时间)。
  4. 在哈希表中查找 (平均 O(1) 时间)。
  5. 在哈希表中插入 (平均 O(1) 时间)。

因此,总的时间复杂度是 O(n) (线性时间)

空间复杂度分析:

  • 使用了一个哈希表来存储最多 n(数值, 下标) 对。
  • 在最坏情况下 (所有数字都不匹配,直到最后一个才找到),哈希表会存储 n 个元素。
  • 所以,空间复杂度是 O(n)