前言
本文记录力扣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.代码
步骤:
- 初始化数组长度和结果数组;
- 计算每个元素的左侧乘积,首先定义索引为0的左侧乘积为1,再进行for循环遍历(从前往后),计算当前元素的左侧乘积,其实也就是上一个元素的左侧乘积乘上上一个元素本身;
- 计算右侧乘积并和左侧乘积相乘,先定义右侧乘积为1,再进行for循环(从后往前),先计算最终结果,即之前计算出的左侧乘积乘上右侧乘积(因为是从后往前,最后一个元素右侧乘积为1,直接乘上就行),再更新右侧乘积,右侧乘积就是当前遍历到的元素乘上之前的右侧乘积。
- 最后返回结果数组。
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.代码
步骤:
- 先定义数组长度
- 第一个for循环用来遍历数组的每一个数,里面的while循环用来进行置换到底,直到将数值k放到索引为k - 1 的位置(while循环条件是当前数满足「1~n」且「未归位」时,持续置换)
- 第二个for循环用来遍历置换后的数组,找到第一个“索引+1≠数值”的位置,此时当前位置的索引就是缺失的最小正整数
- 如果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(需要交换)。
第一种写法(错误)
- temp = nums[i] → temp = 4(正确保存当前数);
- nums[i] = nums[nums[i] - 1]:
- 先算右边:nums[i]-1 = 4-1 = 3 → nums[3] = 1;
- 然后赋值:nums[i] = 1(此时nums[i]已经从 4 变成 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(正确答案)。
如果本篇文章对您有帮助,可以点赞,收藏或评论哦!!!关注主包不迷路,让我们一起向前进步吧!!!
——普通数组总结(下)(除自身以外数组的乘积,缺失的第一个整数)&spm=1001.2101.3001.5002&articleId=155755362&d=1&t=3&u=2e65ad8935c6467b86c0bdb9ffbf8371)
1506

被折叠的 条评论
为什么被折叠?



