Leetcode 普通数组 2
题号直达: 189 轮转数组 | 238 除自身以外数组的乘积 | 41 缺失的第一个正数
LeetCode 189. 轮转数组 ↑
题目
给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。
进阶: 尽可能想出更多的解决方案;能否使用空间复杂度为 O(1) 的原地算法?
测试样例
示例 1:
输入:nums = [1,2,3,4,5,6,7], k = 3
输出:[5,6,7,1,2,3,4]
解释:向右轮转 1 步 [7,1,2,3,4,5,6],轮转 2 步 [6,7,1,2,3,4,5],轮转 3 步 [5,6,7,1,2,3,4]。
示例 2:
输入:nums = [-1,-100,3,99], k = 2
输出:[3,99,-1,-100]
解释:向右轮转 1 步 [99,-1,-100,3],轮转 2 步 [3,99,-1,-100]。
提示: 1 ≤ nums.length ≤ 10^5,0 ≤ k ≤ 10^5。
代码(Python)解法一:辅助数组
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: None Do not return anything, modify nums in-place instead.
"""
k1 = k % len(nums)
p = [0] * len(nums)
for i in range(0, len(nums)):
if i - k1 < 0:
ind = i - k1 + len(nums)
else:
ind = i - k1
p[i] = nums[ind]
nums[:] = p
思路解析(解法一)
- 映射关系:向右轮转
k位后,新下标i位置上的元素,来自原数组下标(i - k) % n(即「谁会被移到位置i」)。代码里用k1 = k % len(nums)先对k取模,再根据i - k1是否小于 0 决定用i - k1 + len(nums)还是i - k1,等价于原下标(i - k1) % n。 - 流程:开一个与
nums等长的辅助数组p,对每个i令p[i] = nums[原下标],最后用nums[:] = p把结果写回nums,实现原地覆盖。 - 为什么必须写
nums[:] = p而不是nums = p? 题目要求原地修改,即改的是调用者传入的那一个列表对象。nums = p只会让局部变量nums指向新列表p,不会改动传入的那个列表,函数返回后调用者看到的仍是原数组;nums[:] = p则是把原列表里的元素整体替换成p的内容,改的是同一块内存,调用者才能看到轮转后的结果。 - 复杂度:时间复杂度 O(n),空间复杂度 O(n)(需要额外数组)。思路直观;若要求 O(1) 空间,则用解法二。
代码(Python)解法二:三次反转
class Solution(object):
def rotate(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: None Do not return anything, modify nums in-place instead.
"""
def reverse(i, j):
while i < j:
nums[i], nums[j] = nums[j], nums[i]
i += 1
j -= 1
n = len(nums)
k %= n # 轮转 k 次等于轮转 k % n 次
reverse(0, n - 1)
reverse(0, k - 1)
reverse(k, n - 1)
思路解析(解法二)
- 取模:轮转
k次与轮转k % n次效果相同(轮转n次等于没动),故先做k %= n,避免k > n时多余操作。 - 三次反转:
- 先把整个数组反转;
- 再把前
k个元素反转; - 最后把后
n - k个元素反转。
这样得到的序列等价于整体向右轮转k位。
- 直观理解:例如
[1,2,3,4,5,6,7]向右 3 位得到[5,6,7,1,2,3,4]。先整体反转变为[7,6,5,4,3,2,1],再反转前 3 个得[5,6,7,4,3,2,1],再反转后 4 个得[5,6,7,1,2,3,4],即目标结果。 - 复杂度:时间复杂度 O(n),空间复杂度 O(1),满足原地、O(1) 空间的进阶要求。
2. LeetCode 238. 除自身以外数组的乘积 ↑
题目
给定整数数组 nums,返回数组 answer,使得 answer[i] 等于 nums 中除 nums[i] 之外其余元素的乘积。
要求: 请不要使用除法,且在 O(n) 时间复杂度内完成此题。
进阶: 能否在 O(1) 的额外空间复杂度内完成?(输出数组不计入额外空间。)
测试样例
示例 1:
输入:nums = [1,2,3,4]
输出:[24,12,8,6]
示例 2:
输入:nums = [-1,1,0,-3,3]
输出:[0,0,9,0,0]
提示: 2 ≤ nums.length ≤ 10^5,-30 ≤ nums[i] ≤ 30。
代码(Python)解法一:利用整体乘积再除(使用除法,与题目要求不符)
class Solution(object):
def productExceptSelf(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
count = 0
ind = 0
ans = [0] * len(nums)
mul = 1
for i in range(0, len(nums)):
if nums[i] == 0:
count += 1
if count == 1:
ind = i
else:
mul *= nums[i]
if count >= 2:
return [0] * len(nums)
elif count == 1:
ans[ind] = mul
return ans
else:
for i in range(0, len(nums)):
ans[i] = mul / nums[i]
return ans
思路解析(解法一)
- 思路:先求全体元素的乘积
mul(遍历时跳过 0,只乘非零元)。若 0 的个数 ≥ 2,则任意位置都至少包含一个 0,结果全为 0;若恰好 1 个 0,则只有该位置结果为其余非零元之积mul,其余位置为 0;若无 0,则answer[i] = mul / nums[i],即「全体乘积除以当前元素」。 - ⚠️ 与题目要求不符:本题明确要求不要使用除法,且需在 O(n) 时间内完成。本解法在无 0 的情况下使用了
mul / nums[i],违反了「不使用除法」的要求,仅作理解「乘积与 0」的参考;正式作答建议采用解法二的「左右前缀积」写法。
代码(Python)解法二:左右前缀积(符合题意,不使用除法)
class Solution(object):
def productExceptSelf(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
L = [0] * len(nums)
R = [0] * len(nums)
ans = [0] * len(nums)
L[0] = 1
R[-1] = 1
for i in range(1, len(nums)):
L[i] = L[i - 1] * nums[i - 1]
R[len(nums) - 1 - i] = R[len(nums) - i] * nums[len(nums) - i]
for i in range(0, len(nums)):
ans[i] = L[i] * R[i]
return ans
思路解析(解法二)
- 思路:对每个位置
i,answer[i]= 其左侧所有数之积 × 其右侧所有数之积。用L[i]表示nums[0..i-1]的乘积(左侧前缀积),R[i]表示nums[i+1..n-1]的乘积(右侧后缀积),则ans[i] = L[i] * R[i]。边界上L[0] = 1、R[-1] = 1,然后各用一次正序、一次倒序循环即可填满L和R。 - 复杂度:时间复杂度 O(n),空间 O(n)(两个数组
L、R)。若想达到进阶的 O(1) 额外空间,可以只开一个ans,先在其中存左侧前缀积,再在一次倒序中乘上右侧积并覆盖ans。
3. LeetCode 41. 缺失的第一个正数 ↑
题目
给你一个未排序的整数数组 nums,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O(n) 且只使用常数级别额外空间的算法。
代码(Python)解法一:原地标记
class Solution(object):
def firstMissingPositive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
# 第一步:将所有非正数改为 n+1(因为这些数不影响答案)
for i in range(n):
if nums[i] <= 0:
nums[i] = n + 1
# 第二步:将出现的正数对应的索引位置标记为负数
for i in range(n):
num = abs(nums[i])
if num <= n:
# 将索引 num-1 位置的数标记为负数
nums[num - 1] = -abs(nums[num - 1])
# 第三步:找到第一个正数的位置
for i in range(n):
if nums[i] > 0:
return i + 1
# 如果所有位置都被标记,返回 n+1
return n + 1
思路解析(解法一)
- 范围缩小:答案一定在 [1, n+1] 之间,其中 n = len(nums)。把所有 ≤ 0 或 > n 的数统一改成 n+1,它们对答案没有影响。
- 原地哈希:再扫一遍数组,对每个合法正数
num(1 ≤num≤n),把下标num - 1位置的元素标记为负数,表示「数num出现过」。 - 找第一个缺失的正数:最后第三次遍历,找到第一个仍为正数的位置
i,说明i + 1没被标记出现过,即是最小缺失正数;若所有位置都被标记为负数,说明 1..n 都出现了,答案为n + 1。
代码(Python)解法二:原地交换(把数放回「应在的位置」)
class Solution(object):
def firstMissingPositive(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
n = len(nums)
# 第一步:将每个正数放到正确的位置
for i in range(n):
# 持续交换直到当前位置的值正确或无法交换
while 1 <= nums[i] <= n and nums[nums[i] - 1] != nums[i]:
# 把 nums[i] 放到它应该在的位置 (索引 nums[i]-1)
correct_pos = nums[i] - 1
nums[i], nums[correct_pos] = nums[correct_pos], nums[i]
# 第二步:找第一个不在正确位置的值
for i in range(n):
if nums[i] != i + 1:
return i + 1
return n + 1
思路解析(解法二)
- 核心想法:如果数组里有数
x,并且 1 ≤ x ≤ n,那它理想的位置就是下标x - 1。我们通过原地交换,把尽可能多的数放回到自己「该呆的格子」里。 - 循环交换:外层是下标
i的循环,内层while不断把nums[i]里的值换到nums[nums[i] - 1]处,直到当前位置要么放着正确的数,要么是无效数(≤0 或 >n)或重复数。 - 一次扫描找答案:重排完成后,如果某个位置
i上的值不是i + 1,说明i + 1这个正数缺失;若 1..n 都在正确位置上,则最小缺失正数是n + 1。

浙公网安备 33010602011771号