Leetcode 面试150题(补充)

题号直达: 80 删除有序数组中的重复项 II | 135 分发糖果 | 452 用最少数量的箭引爆气球 | 61 旋转链表 | 63 不同路径 II

LeetCode 80. 删除有序数组中的重复项 II ↑

题目

给定一个非递减有序整数数组 nums,原地删除重复元素,使每个不同数值最多出现两次。返回删除后新数组的长度 k;nums 的前 k 个元素即为结果(顺序不变)。

说明:数组以引用传入,函数内修改对调用方可见;评测根据你返回的长度 k 检查 nums[0 … k-1]。

要求:不要使用额外数组;在 O(1) 额外空间下完成。

测试样例

  • 示例 1: nums = [1, 1, 1, 2, 2, 3]
    输出:5
    解释:前 5 个元素为 [1, 1, 2, 2, 3]。
  • 示例 2: nums = [0, 0, 1, 1, 1, 1, 2, 3, 3]
    输出:7
    解释:前 7 个元素为 [0, 0, 1, 1, 2, 3, 3]。

提示

  • 1 <= nums.length <= 3 * 10^4
  • -10^4 <= nums[i] <= 10^4
  • nums 已按非递减序排列

代码(Python)

class Solution(object):
    def removeDuplicates(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        count = 1
        prev = nums[0]
        end = len(nums) - 1
        i = 1
        while i <= end:
            if nums[i] == prev:
                if count == 1:
                    count += 1
                    i = i + 1
                else:
                    nums.pop(i)
                    end -= 1
            else:
                prev = nums[i]
                count = 1
                i = i + 1
        return end + 1

思路解析

用 prev 记录当前段数值,count 表示该值已连续保留的个数(最多允许到 2)。指针 i 从左向右扫到 end(有效区间右边界,pop 后 end 左移)。

  • 与 prev 相同且 count == 1:第二次出现,保留并 count += 1,i 前进。
  • 与 prev 相同且已有两次:用 nums.pop(i) 删掉当前位,end -= 1,i 不动(后面元素前移)。
  • 遇到新值:更新 prev,count 重置为 1,i 前进。

扫描结束后,有效长度为 end + 1,作为返回值。

  • 时间复杂度:O(n²) 量级(pop 可能触发后续元素整体前移;若改用双指针写入可优化为 O(n))。
  • 额外空间:O(1)。

LeetCode 135. 分发糖果 ↑

题目

n 个孩子站成一排,整数数组 ratings 表示每个孩子的评分。按规则分糖果:

  1. 每个孩子至少 1 颗糖。
  2. 相邻两个孩子中,评分更高者必须拿到更多糖。

返回满足条件所需的最少糖果总数。

测试样例

  • 示例 1: ratings = [1, 0, 2]
    输出:5
    解释:可分配 [2, 1, 2],总和为 5。
  • 示例 2: ratings = [1, 2, 2]
    输出:4
    解释:可分配 [1, 2, 1];第三位评分未高于右邻,只需 1 颗。

提示

  • n == ratings.length
  • 1 <= n <= 2 * 10^4
  • 0 <= ratings[i] <= 2 * 10^4

代码(Python)

class Solution(object):
    def candy(self, ratings):
        """
        :type ratings: List[int]
        :rtype: int
        """
        left = [0] * len(ratings)
        left[0] = 1
        right = [0] * len(ratings)
        right[-1] = 1
        merge = []
        for i in range(1, len(ratings)):
            if ratings[i - 1] < ratings[i]:
                left[i] = left[i - 1] + 1
            else:
                left[i] = 1

        for i in range(1, len(ratings)):
            if ratings[len(ratings) - 1 - i] > ratings[len(ratings) - i]:
                right[len(ratings) - 1 - i] = right[len(ratings) - i] + 1
            else:
                right[len(ratings) - 1 - i] = 1

        for i in range(0, len(ratings)):
            merge.append(max(left[i], right[i]))

        return sum(merge)

思路解析

两次贪心扫描,分别满足「比左邻高则多拿」与「比右邻高则多拿」:

  • left[i]:只根据左侧关系,从左到右递推;若 ratings[i-1] < ratings[i] 则 left[i] = left[i-1] + 1,否则为 1。
  • right[i]:只根据右侧关系,从右到左递推(代码中用下标 len-1-i 实现);若当前位评分高于右邻则 right 在右邻基础上 +1,否则为 1。

每个孩子同时要满足左右两侧约束,故实际糖果数为 max(left[i], right[i]),再求和。

  • 时间复杂度:O(n)。
  • 空间复杂度:O(n)(left、right、merge 数组)。

LeetCode 452. 用最少数量的箭引爆气球 ↑

题目

在二维平面上,若干球形气球水平直径落在 x 轴上,用二维数组 points 表示,其中 points[i] = [x_start, x_end] 为第 i 个气球的直径范围(y 坐标任意)。

沿 x 轴某点竖直向上射箭,若 x_start <= x <= x_end,则该箭可引爆覆盖该 x 的气球;一支箭射出后沿同一 x 无限向上。

求引爆全部气球所需的最少箭数。

测试样例

  • 示例 1: points = [[10,16],[2,8],[1,6],[7,12]]
    输出:2
    解释:例如在 x = 6 射一箭可引爆 [2,8]、[1,6],在 x = 11 再射一箭可引爆 [10,16]、[7,12]。
  • 示例 2: points = [[1,2],[3,4],[5,6],[7,8]]
    输出:4
    解释:区间互不重叠,每个气球各需一箭。

提示

  • points.length == n
  • 1 <= n <= 10^5
  • points[i].length == 2
  • -2^31 <= x_start < x_end <= 2^31 - 1

代码(Python)

class Solution(object):
    def findMinArrowShots(self, points):
        """
        :type points: List[List[int]]
        :rtype: int
        """
        if not points:
            return 0

        points.sort(key=lambda balloon: balloon[1])
        pos = points[0][1]
        ans = 1
        for balloon in points:
            if balloon[0] > pos:
                pos = balloon[1]
                ans += 1

        return ans

思路解析

贪心:按区间右端点 x_end 升序排序。第一支箭尽量打在「当前组」最靠右的公共位置,取最早结束区间的 x_end 为 pos。

从左到右扫:若当前气球 x_start > pos,说明与上一支箭的覆盖点不重叠,需要新射一箭,令 pos = 当前 x_end,ans += 1;否则该气球已被 pos 处的箭覆盖。

  • 时间复杂度:O(n log n)(排序主导)。
  • 空间复杂度:O(1) 或 O(log n)(排序栈,视实现而定)。

LeetCode 61. 旋转链表 ↑

题目

给定链表头节点 head,将链表向右旋转 k 个位置后,返回新的头节点。

测试样例

  • 示例 1: head = [1, 2, 3, 4, 5],k = 2
    输出:[4, 5, 1, 2, 3]
  • 示例 2: head = [0, 1, 2],k = 4
    输出:[0, 1, 2]
    解释:k 大于长度时等价于 k % length 次旋转。

提示

  • 链表中节点数目范围 [0, 500]
  • -100 <= Node.val <= 100
  • 0 <= k <= 2 * 10^9

代码(Python)

class Solution(object):
    def rotateRight(self, head, k):
        """
        :type head: Optional[ListNode]
        :type k: int
        :rtype: Optional[ListNode]
        """
        if not head:
            return None
        length = 0
        p = head
        while p.next:
            p = p.next
            length = length + 1
        length += 1
        k = (k - 1) % length + 1
        if k == length:
            return head
        p = head
        for i in range(length - k - 1):
            p = p.next
        nex = p.next
        ans = nex
        p.next = None
        while nex.next is not None:
            nex = nex.next
        nex.next = head
        return ans

思路解析

  1. 求长度 length:走到尾节点计数(单节点时 while p.next 不进入,再 length += 1)。
  2. 规范化 k:用 k = (k - 1) % length + 1 将旋转步数映射到 1 … length;若 k == length 则相当于转一整圈,直接返回 head。
  3. 找断点:从头走 length - k - 1 步,得到旋转前「新尾」节点 p;p.next 即为旋转后的新头 ans。
  4. 拼接:断开 p.next,尾指针 nex 走到原链表末尾,令 nex.next = head,完成「尾接原头」的右旋。
  • 时间复杂度:O(n)。
  • 空间复杂度:O(1)。

LeetCode 63. 不同路径 II ↑

题目

给定 m × n 整数矩阵 obstacleGrid。0 表示空位,1 表示障碍。机器人从左上角出发,每次只能向下或向右,到达右下角;路径不能经过障碍格。

返回从起点到终点的不同路径条数。题目保证答案 ≤ 2 × 10^9。

测试样例

  • 示例 1:
obstacleGrid = [[0,0,0],
                [0,1,0],
                [0,0,0]]

输出:2

  • 示例 2:
obstacleGrid = [[0,1],
                [0,0]]

输出:1

提示

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j] 为 0 或 1

代码(Python)

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """
        if obstacleGrid[0][0] == 1 or obstacleGrid[-1][-1]:
            return 0
        grid = []
        for i in range(len(obstacleGrid)):
            grid.append([0] * len(obstacleGrid[0]))
        grid[-1][-1] = 1  # 终点肯定只有一种走法

        # 最后一列
        for i in range(len(obstacleGrid) - 2, -1, -1):
            if obstacleGrid[i][-1] == 1:
                grid[i][-1] = 0
            else:
                grid[i][-1] = grid[i + 1][-1]

        # 最后一行
        for i in range(len(obstacleGrid[0]) - 2, -1, -1):
            if obstacleGrid[-1][i] == 1:
                grid[-1][i] = 0
            else:
                grid[-1][i] = grid[-1][i + 1]

        for i in range(len(obstacleGrid) - 2, -1, -1):
            for j in range(len(obstacleGrid[0]) - 2, -1, -1):
                if obstacleGrid[i][j] == 1:
                    grid[i][j] = 0
                else:
                    grid[i][j] = grid[i + 1][j] + grid[i][j + 1]

        return grid[0][0]

思路解析

从终点反推:grid[i][j] 表示从 (i, j) 到右下角的路径数。起点或终点为障碍则直接 0;终点 grid[-1][-1] = 1。

先填最后一列(只能向下)与最后一行(只能向右):遇障碍 0,否则继承下一格/右一格的方案数。再自右下向左上填内部:grid[i][j] = grid[i+1][j] + grid[i][j+1](障碍处为 0)。答案为 grid[0][0]。

  • 时间复杂度:O(m × n)。
  • 空间复杂度:O(m × n)(grid 表;可原地复用 obstacleGrid 优化)。
posted @ 2026-10-12 00:38  Phier  阅读(3)  评论(0)    收藏  举报