Hot 100 刷题笔记 · 数组与哈希专题(3 题)

Hot 100 刷题笔记 · 数组与哈希专题(3 题)

收录:1. 两数之和、238. 除自身以外数组的乘积、41. 缺失的第一个正数;每题按 ① 分析思路 → ② 解法讲解 → ③ 知识点总结 组织。


一、1. 两数之和

① 题目分析 & 解题思路

给定 nums 与 target,找两个相加等于 target 的元素并返回下标;不能重复使用同一元素,保证只有一组答案。

  • 暴力:两层循环枚举组合判断和,能通过但不满足进阶。
  • 哈希表(优化) :找互补数 complement = target - nums[i];哈希表存【数值→下标】,先查互补数,命中即返回下标,否则存入当前数与下标。

② 常见解法及讲解

解法 1:暴力双循环(O(n2)O(n^2)O(n2),仅用于理解)

java 复制代码
class Solution {
    public int[] twoSum(int[] nums, int target) {
        for(int i = 0;i < nums.length ; i++){
            for(int j = i + 1; j < nums.length; j++){
                if(nums[i] + nums[j] == target){
                    return new int[] {i,j};
                }
            }
        }
        return new int[0];
    }
}

好理解、不占额外空间;大数据下慢,非最优解。

解法 2:HashMap 哈希表(最优,面试首选)

java 复制代码
class Solution {
    public int[] twoSum(int[] nums, int target) {
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < nums.length; i++) {
            int complement = target - nums[i];
            if (map.containsKey(complement)) {
                // HashMap 哈希表 (数值,下标)
                //// 先看互补数是否已经存在map中
                return new int[] { map.get(complement), i };
            }

            // 不存在,把当前数和下标放入map
            map.put(nums[i], i);
        }

        return new int[0];

    }
}

推演 nums=[3,2,4], target=6:i=0 互补数 3 不在表中 → put(3,0);i=1 互补数 4 不在 → put(2,1);i=2 互补数 2 在表中(下标 1)→ 返回 [1,2]。

先查再 put ,避免自己匹配自己([3,3] target=6:先存入第一个 3,第二个 3 查到它,返回 0,1)。

复杂度总结

  • 解法 1(暴力双循环):时间 O(n2)O(n^2)O(n2),空间 O(1)O(1)O(1);
  • 解法 2(HashMap):时间 O(n)O(n)O(n),空间 O(n)O(n)O(n)。

③ 知识点总结

常见易错变种:

  1. j 从 0 开始并写 i==j 判断,容易重复使用同一元素;
  2. 返回 {nums[i], nums[j]}(返回数值而非下标);
  3. 忘记 return,或返回 null。

补充:不推荐排序 + 双指针------排序打乱下标,需额外保存原始下标;该法适合返回值而非下标的题目。


二、238. 除自身以外数组的乘积

① 题目分析 & 解题思路

返回 answer,answer[i] = 除 nums[i] 外所有元素的乘积; 不能用除法(含 0 时总乘积 / numsi 会出错),⏱️ 时间 O(n)O(n)O(n),进阶额外空间 O(1)O(1)O(1)(输出数组不计)。

前缀积 + 后缀积 :prefix[i] 为 i 左边元素乘积,suffix[i] 为 i 右边元素乘积,answer[i] = prefix[i] × suffix[i]。进阶:复用输出数组存前缀,用变量滚动记录后缀,从后往前相乘,不开 suffix 数组。

② 常见解法及讲解

解法 1:前缀 + 后缀数组(空间 O(n)O(n)O(n))

java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] prefix = new int[n];
        int[] suffix = new int[n];
        int[] ans = new int[n];

        // 前缀:prefix[i] = i左边所有乘积
        // 原数组里第一个的前缀记为1
        prefix[0] = 1;
        // 从前往后累积
        for (int i = 1; i < n; i++) {
            prefix[i] = prefix[i - 1] * nums[i - 1];

        }

        // 后缀:suffix[i] = i右边所有乘积
        // 原数组最后一个的后缀记为1
        suffix[n - 1] = 1;
        // 从后往前累积
        for (int j = n - 2; j >= 0; j--) {
            suffix[j] = suffix[j + 1] * nums[j + 1];
        }

        // 左右相乘
        for (int k = 0; k < n; k++) {
            ans[k] = prefix[k] * suffix[k];

        }
        return ans;
    }
}

逻辑直观、不易写错;缺点是开两个辅助数组,空间 O(n)O(n)O(n)。

解法 2:进阶版 O(1)O(1)O(1) 额外空间(面试优先写这个)

java 复制代码
class Solution {
    public int[] productExceptSelf(int[] nums) {
        int n = nums.length;
        int[] ans = new int[n];

        // 第一步:ans数组先存前缀乘积
        ans[0] = 1;
        for (int i = 1; i < n; i++) {
            ans[i] = ans[i - 1] * nums[i - 1];
        }

        // 第二步:从后往前,用一个变量保存后缀乘积,直接乘进ans
        int suffix = 1;
        for (int j = n - 1; j >= 0; j--) {
            ans[j] = ans[j] * suffix;
            suffix = suffix * nums[j];// 更新后缀,给左边下一个用
        }

        return ans;
    }
}

推演 [1,2,3,4]:前缀遍历后 ans=[1,1,2,6],suffix 数组 [24,12,4,1];从后往前:i=3 ans=6×1=6、suffix=4;i=2 ans=2×4=8、suffix=12;i=1 ans=1×12=12、suffix=24;i=0 ans=1×24=24,结果 [24,12,8,6]。

复杂度总结

  • 解法 1(前后缀数组):时间 O(n)O(n)O(n),空间 O(n)O(n)O(n);
  • 解法 2(进阶版):时间 O(n)O(n)O(n),空间 O(1)O(1)O(1)(不计输出数组)。

③ 知识点总结

易错点:

  1. 前缀第一个、后缀最后一个初始值是 1,不是 0;
  2. 前缀用 nums[i-1]、后缀用 nums[i+1],下标容易写反;
  3. 不能用除法,遇到 0 的样例除法方案直接失效;
  4. 数组长度最大 10510^5105,暴力双重循环 O(n2)O(n^2)O(n2) 会直接超时。

三、41. 缺失的第一个正数

① 题目分析 & 解题思路

给无序整数数组,找未出现的最小正整数 ;时间 O(n)O(n)O(n)、额外空间 O(1)O(1)O(1),不能开哈希表。

关键结论 :长度为 n,答案必在 1,n+11, n+11,n+1:1~n 全出现则返回 n+1,否则返回最小缺失数;负数、0、大于 n 的数可忽略。

原地哈希 (把数组本身当哈希表):合法正数 xxx(1≤x≤n1\le x\le n1≤x≤n)放到下标 x−1x-1x−1(3→2,1→0)。流程:① 遍历交换归位;② 再遍历,第一个不满足 nums[i]==i+1 的位置,i+1 即答案;③ 全部满足返回 n+1。

② 常见解法及讲解

解法 1:原地哈希(推荐,面试标准,O(n)O(n)O(n) 时间、O(1)O(1)O(1) 空间)

java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            // nums[i]值 的目标位置上的值是 nums[nums[i]-1]
            // 满足三个条件才交换:是合法正数、不在正确位置、目标位置的值和它不一样
            while (nums[i] >= 1 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                // 交换 nums[i] 和 nums[nums[i]-1]
                int temp = nums[nums[i] - 1];
                nums[nums[i] - 1] = nums[i];
                nums[i] = temp;
            }
        }

        // 找第一个 nums[i] != i+1
        for (int i = 0; i < n; i++) {
            if (nums[i] != i + 1) {
                return i + 1;
            }
        }

        // 1~n全都有
        return n + 1;
    }
}

推演 nums=[3,4,-1,1],n=4:i=0,3 合法,交换下标 0、2 → [-1,4,3,1],-1 退出 while;i=1,4 合法,交换下标 1、3 → [-1,1,3,4],1 继续交换到下标 0 → [1,-1,3,4];i=2 的 3、i=3 的 4 已就位。第二轮:i=0 的 1==1 ✔,i=1 的 -1≠2 → 返回 2 ✔。

解法 2:标记法(原地哈希另一种实现,不交换)

先把负数、0 改成 n+1;再把下标 x-1 的值改成负数标记;最后找第一个正数下标。

java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        // 第一步:把<=0的数都改成n+1
        for (int i = 0; i < n; i++) {
            if (nums[i] <= 0) nums[i] = n + 1;
        }
        // 第二步:标记
        for (int i = 0; i < n; i++) {
            int x = Math.abs(nums[i]);
            if (x <= n) {//限定范围<=n里的整数,结合上一步排除了<=0的值
                // 把下标x-1的值nums[x - 1] 都改成负数做标记
                nums[x - 1] = -Math.abs(nums[x - 1]);
            }
        }
        // 第三步:找第一个正数
        for (int i = 0; i < n; i++) {
            // nums[i] > 0 说明值(i+1)不属于原数组里的正数,nums[i]没在上一步被改为负数
            if (nums[i] > 0) return i + 1;
        }
        return n + 1;
    }
}

不用 while 交换、不易死循环;缺点是理解稍绕。

解法 3:HashSet(简单但不满足空间要求,适合初学理解)

java 复制代码
class Solution {
    public int firstMissingPositive(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for (int x : nums) set.add(x);
        int res = 1;
        // 从正数1开始依次判断出第一个不在原数组里的正数
        while (set.contains(res)) {
            res++;
        }
        return res;
    }
}

好写好理解;空间 O(n)O(n)O(n),面试会被追问优化。

复杂度总结

  • 原地交换 / 标记法:O(n)O(n)O(n) 时间、O(1)O(1)O(1) 额外空间;
  • HashSet:O(n)O(n)O(n) 时间、O(n)O(n)O(n) 空间。

③ 知识点总结

常见出错点:

  1. 只交换一次不用 while:[3,4,-1,1] 中 3 归位后,该位置的 -1 还要处理;
  2. 不判断 nums[i]==nums[nums[i]-1] 会死循环(重复数字);
  3. 漏判 nums[i]<=n,大于 n 的数不应交换;
  4. 下标换算错:x 对应下标是 x-1,不能直接用 x;
  5. 让负数、0 也参与交换;
  6. 用 HashSet 空间 O(n)O(n)O(n),不满足进阶要求。
相关推荐
Hardworking6661 小时前
2026年10月24日系统集成项目管理工程师上午考试内容
1024程序员节·系统集成项目管理工程师
豆豆1 小时前
网站漏洞应急处置 + 安全加固指南,附安全 CMS 推荐
web安全·网站安全·1024程序员节·漏洞修复·漏洞处置·cms 选型·网站漏洞修复
fpcc1 小时前
并行编程实战—CUDA中的Tile编程之八优化提示
cuda·1024程序员节·并行编程
小豆庄1 小时前
视频隐私脱敏:从逐帧标注到识别追踪的工程取舍
目标跟踪·视频·敏捷流程·python3.11·1024程序员节·qt6.3
quantdash_cc1 小时前
如何利用标的信息为研究股票池补充名称和交易所信息?
开发语言·python·pandas·量化交易·1024程序员节·股票数据·quantdash
2601_962885721 小时前
AkShare 报错 / 返回空 DataFrame 怎么修?
1024程序员节
乔旭伟1 小时前
部署形态选型笔记:多租户 SaaS 与单机本地部署的运维模型对比
运维·服务器·1024程序员节
数字化转型分享点滴1 小时前
机加工企业上大数据分析系统有哪些容易踩的坑?
python·1024程序员节
NamoAmitabha1281 小时前
AXI-DMA-VIVADO2022.2-终版-含跑通main.c--deepseek
1024程序员节·zynq--ps