力扣Hot100系列6(Java)——[普通数组]总结(下)(除自身以外数组的乘积,缺失的第一个整数)


前言

本文记录力扣Hot100里面关于普通数组的剩下的两道题,包括常见解法和一些关键步骤理解,也有例子便于大家理解


一、除自身以外数组的乘积

1.题目

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。
不要使用除法,且在 O(n) 时间复杂度内完成此题。

示例 1:
输入: nums = [1,2,3,4]输出: [24,12,8,6]
示例 2:
输入: nums = [-1,1,0,-3,3]
输出: [0,0,9,0,0]

2.代码

步骤:

  1. 初始化数组长度和结果数组;
  2. 计算每个元素的左侧乘积,首先定义索引为0的左侧乘积为1,再进行for循环遍历(从前往后),计算当前元素的左侧乘积,其实也就是上一个元素的左侧乘积乘上上一个元素本身
  3. 计算右侧乘积并和左侧乘积相乘,先定义右侧乘积为1,再进行for循环(从后往前),先计算最终结果,即之前计算出的左侧乘积乘上右侧乘积(因为是从后往前,最后一个元素右侧乘积为1,直接乘上就行),再更新右侧乘积,右侧乘积就是当前遍历到的元素乘上之前的右侧乘积
  4. 最后返回结果数组。
class Solution {
    public int[] productExceptSelf(int[] nums) {
        // 1. 初始化基础变量
        int length = nums.length; // 初始化数组长度
        int[] answer = new int[length]; // 初始化结果数组
        // 2. 第一步:计算每个元素的「左侧乘积」(左→右遍历)
        // 索引0的元素左边没有元素,所以左侧乘积为1
        answer[0] = 1;
        // 从索引1开始遍历(因为索引0已经初始化)
        for (int i = 1; i < length; i++) {
            // 核心逻辑:当前元素的左侧乘积 = 前一个元素 × 前一个元素的左侧乘积
            answer[i] = nums[i - 1] * answer[i - 1];
        }
        // 3. 第二步:计算「右侧乘积」并和左侧乘积相乘(右→左遍历)
        int R = 1; // R表示当前元素右侧所有元素的乘积,初始时最右侧元素右边无元素,所以R=1
        for (int i = length - 1; i >= 0; i--) {
            // ① 当前元素的最终结果 = 左侧乘积(answer[i]) × 右侧乘积(R)
            answer[i] = answer[i] * R;
            // ② 更新R:把当前元素加入右侧乘积,供左边的元素使用
            R *= nums[i];
        }
        return answer;
    }
}

3.关键步骤

        answer[0] = 1;
        // 从索引1开始遍历(因为索引0已经初始化)
        for (int i = 1; i < length; i++) {
            // 核心逻辑:当前元素的左侧乘积 = 前一个元素 × 前一个元素的左侧乘积
            answer[i] = nums[i - 1] * answer[i - 1];
        }   

从1开始遍历(因为索引为0已经被初始化)把当前元素的左侧乘积计算出来放进结果数组。
当前元素的左侧乘积为 = 上一个元素的左侧乘积 * 上一个元素本身。

        int R = 1; // R表示当前元素右侧所有元素的乘积,初始时最右侧元素右边无元素,所以R=1
        for (int i = length - 1; i >= 0; i--) {
            // ① 当前元素的最终结果 = 左侧乘积(answer[i]) × 右侧乘积(R)
            answer[i] = answer[i] * R;
            // ② 更新R:把当前元素加入右侧乘积,供左边的元素使用
            R *= nums[i];
        }

从后往前进行遍历,先计算最终结果,即当前元素的左侧乘积乘上当前元素的右侧乘积
因为是从后往前,最后一个元素右侧乘积为1,最后一个元素的最终结果为它的左侧乘积直接乘上之前初始化的R(值为1)就行
再更新R,当前元素右侧乘积为之前的右侧乘积乘上当前遍历到的数组元素

4.示例

输入:nums = [2, 3, 4, 5]
目标:计算每个元素除自身外的乘积 → 结果应为 [3×4×5, 2×4×5, 2×3×5, 2×3×4] = [60, 40, 30, 24]

  • 第一步:左→右计算「左侧乘积」(存到 answer 数组)
    answer [0] = 1(索引 0 左侧无元素,乘积为 1)

i=1:answer [1] = nums [0] × answer [0] = 2 × 1 = 2(nums [1]=3 的左侧乘积:2)

i=2:answer [2] = nums [1] × answer [1] = 3 × 2 = 6(nums [2]=4 的左侧乘积:2×3)

i=3:answer [3] = nums [2] × answer [2] = 4 × 6 = 24(nums [3]=5 的左侧乘积:2×3×4)

此时 answer 数组:[1, 2, 6, 24]

  • 第二步:右→左计算「右侧乘积 * 左侧乘积
    初始化 R=1(最右侧元素右侧无元素,乘积为 1)

i=3(nums [3]=5):
answer [3] = 24 × 1 = 24(5 的结果:左侧乘积 24 × 右侧乘积 1)
R = 1 × 5 = 5(更新 R 为 nums [2] 右侧的乘积:5)

i=2(nums [2]=4):
answer [2] = 6 × 5 = 30(4 的结果:左侧乘积 6 × 右侧乘积 5)
R = 5 × 4 = 20(更新 R 为 nums [1] 右侧的乘积:4×5)

i=1(nums [1]=3):
answer [1] = 2 × 20 = 40(3 的结果:左侧乘积 2 × 右侧乘积 20)
R = 20 × 3 = 60(更新 R 为 nums [0] 右侧的乘积:3×4×5)

i=0(nums [0]=2):
answer [0] = 1 × 60 = 60(2 的结果:左侧乘积 1 × 右侧乘积 60)
R = 60 × 2 = 120(无实际作用,遍历结束)

最终 answer 数组:[60, 40, 30, 24]

二、缺失的第一个整数

1.题目

给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。

示例 1:
输入:nums = [1,2,0]
输出:3
解释:范围 [1,2] 中的数字都在数组中。
示例 2:
输入:nums = [3,4,-1,1]
输出:2
解释:1 在数组中,但 2 没有。
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
解释:最小的正数 1 没有出现

2.代码

步骤:

  1. 先定义数组长度
  2. 第一个for循环用来遍历数组的每一个数,里面的while循环用来进行置换到底,直到将数值k放到索引为k - 1 的位置(while循环条件是当前数满足「1~n」且「未归位」时,持续置换
  3. 第二个for循环用来遍历置换后的数组,找到第一个“索引+1≠数值”的位置此时当前位置的索引就是缺失的最小正整数
  4. 如果1~n都归位了,缺失的是n+1
class Solution {
    public int firstMissingPositive(int[] nums) {
        int n = nums.length; // 数组长度
        
        // 第一步:原地置换,让1~n的数归位(数值k → 索引k-1)
        for (int i = 0; i < n; ++i) {
            // while循环:当前数满足「1~n」且「未归位」时,持续置换
            while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                // 交换nums[i]和nums[nums[i]-1](把nums[i]放到正确位置)
                int temp = nums[nums[i] - 1]; // 先存目标位置的数
                nums[nums[i] - 1] = nums[i]; // 当前数归位
                nums[i] = temp; // 原目标位置的数放到当前位置,后续继续处理
            }
        }
        
        // 第二步:遍历数组,找第一个“索引+1≠数值”的位置
        for (int i = 0; i < n; ++i) {
            if (nums[i] != i + 1) {
                return i + 1; // 这个位置的“索引+1”就是缺失的最小正整数
            }
        }
        
        // 第三步:如果1~n都归位了,缺失的是n+1
        return n + 1;
    }
}

3.关键步骤

// 第一步:原地置换,让1~n的数归位(数值k → 索引k-1)
        for (int i = 0; i < n; ++i) {
            // while循环:当前数满足「1~n」且「未归位」时,持续置换
            while (nums[i] > 0 && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
                // 交换nums[i]和nums[nums[i]-1](把nums[i]放到正确位置)
                int temp = nums[nums[i] - 1]; // 先存目标位置的数
                nums[nums[i] - 1] = nums[i]; // 当前数归位
                nums[i] = temp; // 原目标位置的数放到当前位置,后续继续处理
            }
        }

关于nums[nums[i] - 1] != nums[i]

  • 左边 nums[nums[i]-1]:先取当前索引 i 对应的数值 nums[i],计算它的正确索引位置(nums[i]-1),再取这个正确索引位置上的数值。比如 nums[i]=3,则 nums[i]-1=2,nums[nums[i]-1] 就是索引 2 位置的数(3 的正确位置本就该是索引 2)。
  • 右边 nums[i]:当前索引 i 位置上的数值(也就是待归位的目标数)。

注意!!!
一定不能写成下面的这种!!!

int temp = nums[i];
nums[i] = nums[nums[i] - 1];
nums[nums[i] - 1] = temp;

应该是

int temp = nums[nums[i] - 1]; // 先存目标位置的数
nums[nums[i] - 1] = nums[i]; // 当前数归位
nums[i] = temp; // 原目标位置的数放到当前位置

原因:
第一个写法在赋值过程中修改了nums[i],导致后续计算目标索引时用了错误的值,交换逻辑彻底出错;第二个写法先保存目标位置的原始值再完成交换,全程用的都是原始值,逻辑正确。

举个例子:
假设当前 nums[i] = 4(n≥4),它的正确位置是 nums[3](即 nums[4-1]),且 nums[3] 不等于4,假设为1(需要交换)。

第一种写法(错误)

  1. temp = nums[i] → temp = 4(正确保存当前数);
  2. nums[i] = nums[nums[i] - 1]:
  • 先算右边:nums[i]-1 = 4-1 = 3 → nums[3] = 1;
  • 然后赋值:nums[i] = 1(此时nums[i]已经从 4 变成 1 了!);
  1. nums[nums[i] - 1] = temp:
  • 此时nums[i] = 1,所以nums[i]-1 = 0;
  • 执行后:nums[0] = 4(本应该是nums[3] = 4,一个把 4 放到索引 3,结果放到了索引 0)。

错误本质:第二步修改了nums[i],导致第三步计算目标索引时,用的是 “修改后的值”,而非最初的nums[i],交换位置完全偏离。

4.示例

输入 nums = [3,4,-1,1](n=4)
第一步:置换归位过程

  • i=0,nums[0]=3:
    ✅ 3>0、3≤4、nums[2]=-1≠3 → 交换nums[0]和nums[2] → 数组变为 [-1,4,3,1];
    ❌ 现在nums[0]=-1,不满足条件,退出while。
  • i=1,nums[1]=4:
    ✅ 4>0、4≤4、nums[3]=1≠4 → 交换nums[1]和nums[3] → 数组变为 [-1,1,3,4];
    ✅ 现在nums[1]=1,1>0、1≤4、nums[0]=-1≠1 → 交换nums[1]和nums[0] → 数组变为 [1,-1,3,4];
    ❌ 现在nums[1]=-1,退出while。
  • i=2,nums[2]=3:
    ❌ nums[2]=3,nums[3-1]=nums[2]=3 → 已归位,跳过。
  • i=3,nums[3]=4:
    ❌ nums[3]=4,nums[4-1]=nums[3]=4 → 已归位,跳过。
    置换后数组:[1,-1,3,4]

第二步:遍历找缺失值,判断 nums[i] != i + 1

  • i=0:nums[0]=1 = 0+1 → 继续;
  • i=1:nums[1]=-1 ≠ 1+1 → 返回 2(正确答案)。

如果本篇文章对您有帮助,可以点赞,收藏或评论哦!!!关注主包不迷路,让我们一起向前进步吧!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值