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 时多余操作。
  • 三次反转:
    1. 先把整个数组反转;
    2. 再把前 k 个元素反转;
    3. 最后把后 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。
LeetCode 41 缺失的第一个正数 图解

代码(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。
posted @ 2026-10-11 02:31  Phier  阅读(9)  评论(0)    收藏  举报