Contents

LeetCode 100题-5

二分查找

搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

输入: nums = [1,3,5,6], target = 5 输出: 2

nums 为 无重复元素 的 升序 排列数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from typing import List


class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        """
        使用二分查找找到目标值或插入位置

        思路:
        - 标准的二分查找模板
        - 当 left <= right 时继续循环
        - 如果 nums[mid] == target,返回 mid
        - 如果 nums[mid] < target,left = mid + 1
        - 如果 nums[mid] > target,right = mid - 1
        - 循环结束后,left 就是插入位置
        - 时间 O(log n),空间 O(1)
        """
        left, right = 0, len(nums) - 1

        while left <= right:
            mid = (left + right) // 2

            if nums[mid] == target:
                return mid
            elif nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1

        # left 就是插入位置
        return left


# 测试用例
assert Solution().searchInsert([1, 3, 5, 6], 5) == 2
assert Solution().searchInsert([1, 3, 5, 6], 2) == 1
assert Solution().searchInsert([1, 3, 5, 6], 7) == 4
assert Solution().searchInsert([1, 3, 5, 6], 0) == 0
assert Solution().searchInsert([1], 1) == 0
assert Solution().searchInsert([], 1) == 0

# 手动推导 nums=[1,3,5,6], target=2:
#   left=0, right=3, mid=1
#   nums[1]=3 > 2, right=0
#   left=0, right=0, mid=0
#   nums[0]=1 < 2, left=1
#   left=1 > right=0,退出
#   返回 left=1
# 结果:1

# 手动推导 nums=[1,3,5,6], target=7:
#   left=0, right=3, mid=1
#   nums[1]=3 < 7, left=2
#   left=2, right=3, mid=2
#   nums[2]=5 < 7, left=3
#   left=3, right=3, mid=3
#   nums[3]=6 < 7, left=4
#   left=4 > right=3,退出
#   返回 left=4
# 结果:4

搜索二维矩阵

给你一个满足下述两条属性的 m x n 整数矩阵:

每行中的整数从左到右按非严格递增顺序排列。 每行的第一个整数大于前一行的最后一个整数。 给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false 。

输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3 输出:true

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
from typing import List


class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        """
        将二维矩阵看作一维有序数组进行二分查找

        思路:
        - 由于每行的第一个整数大于前一行的最后一个整数
        - 可以将整个矩阵看作一个升序的一维数组
        - 索引 idx 在矩阵中的位置:
          - 行号 row = idx // n
          - 列号 col = idx % n
        - 标准的二分查找
        - 时间 O(log(m*n)) = O(log m + log n),空间 O(1)

        也可以用两次二分:先二分找到所在行,再二分在行内查找
        """
        if not matrix or not matrix[0]:
            return False

        m, n = len(matrix), len(matrix[0])
        left, right = 0, m * n - 1

        while left <= right:
            mid = (left + right) // 2
            row = mid // n
            col = mid % n
            mid_val = matrix[row][col]

            if mid_val == target:
                return True
            elif mid_val < target:
                left = mid + 1
            else:
                right = mid - 1

        return False


# 测试用例
assert Solution().searchMatrix([[1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 60]], 3) == True
assert Solution().searchMatrix([[1, 3, 5, 7], [10, 11, 16, 20], [23, 30, 34, 60]], 13) == False
assert Solution().searchMatrix([[1]], 1) == True
assert Solution().searchMatrix([[1]], 2) == False
assert Solution().searchMatrix([[1, 3]], 3) == True

# 手动推导 matrix=[[1,3,5,7],[10,11,16,20],[23,30,34,60]], target=3:
#   m=3, n=4, 总长度=12
#   left=0, right=11, mid=5
#   row=5//4=1, col=5%4=1, mid_val=matrix[1][1]=11
#   11 > 3, right=4
#   left=0, right=4, mid=2
#   row=2//4=0, col=2%4=2, mid_val=matrix[0][2]=5
#   5 > 3, right=1
#   left=0, right=1, mid=0
#   row=0, col=0, mid_val=matrix[0][0]=1
#   1 < 3, left=1
#   left=1, right=1, mid=1
#   row=1, col=1, mid_val=matrix[0][1]=3
#   3 == 3, 找到!返回 True
# 结果:True

在排序数组中查找元素的第一个和最后一个位置

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]。

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

输入:nums = [5,7,7,8,8,10], target = 8 输出:[3,4]

nums 是一个非递减数组

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
from typing import List


class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        """
        使用二分查找分别找左边界和右边界

        思路:
        - 核心:二分查找时,当找到 target 时不立即返回,而是继续缩小范围
        - 找左边界:当 nums[mid] == target 时,right = mid - 1
        - 找右边界:当 nums[mid] == target 时,left = mid + 1
        - 最后 left 指向右边界+1,right 指向左边界-1
        - 时间 O(log n),空间 O(1)
        """
        if not nums:
            return [-1, -1]

        # 找左边界
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] < target:
                left = mid + 1
            else:
                right = mid - 1
        left_bound = left

        # 找右边界
        left = 0
        right = len(nums) - 1
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] > target:
                right = mid - 1
            else:
                left = mid + 1
        right_bound = right

        # 检查是否找到
        if left_bound < len(nums) and nums[left_bound] == target:
            return [left_bound, right_bound]
        return [-1, -1]


# 测试用例
assert Solution().searchRange([5, 7, 7, 8, 8, 10], 8) == [3, 4]
assert Solution().searchRange([5, 7, 7, 8, 8, 10], 6) == [-1, -1]
assert Solution().searchRange([], 0) == [-1, -1]
assert Solution().searchRange([1], 1) == [0, 0]
assert Solution().searchRange([1], 0) == [-1, -1]
assert Solution().searchRange([2, 2], 2) == [0, 1]
assert Solution().searchRange([1, 2, 3, 4, 5], 3) == [2, 2]

# 手动推导 nums=[5,7,7,8,8,10], target=8:
# 找左边界:
#   left=0, right=5, mid=2, nums[2]=7 < 8, left=3
#   left=3, right=5, mid=4, nums[4]=8 >= 8, right=3
#   left=3, right=3, mid=3, nums[3]=8 >= 8, right=2
#   left=3 > right=2,退出,left_bound=3
# 找右边界:
#   left=0, right=5, mid=2, nums[2]=7 <= 8, left=3
#   left=3, right=5, mid=4, nums[4]=8 <= 8, left=5
#   left=5, right=5, mid=5, nums[5]=10 > 8, right=4
#   left=5 > right=4,退出,right_bound=4
# 检查:nums[3]=8 == target,返回 [3, 4]
# 结果:[3, 4]

搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同 。

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 向左旋转,使数组变为 [nums[k], nums[k+1], …, nums[n-1], nums[0], nums[1], …, nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2] 。

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

输入:nums = [4,5,6,7,0,1,2], target = 0 输出:4

nums 中的每个值都 独一无二 题目数据保证 nums 在预先未知的某个下标上进行了旋转

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
from typing import List


class Solution:
    def search(self, nums: List[int], target: int) -> int:
        """
        在旋转排序数组中使用二分查找

        思路:
        - 旋转排序数组可以看作两个有序数组的拼接
        - 二分时,判断 mid 落在哪个有序区间
        - 如果 nums[left] <= nums[mid],说明 [left, mid] 是有序的
        - 否则 [mid, right] 是有序的
        - 根据 target 是否在有序区间内决定搜索方向
        - 时间 O(log n),空间 O(1)
        """
        if not nums:
            return -1

        left, right = 0, len(nums) - 1

        while left <= right:
            mid = (left + right) // 2

            if nums[mid] == target:
                return mid

            # 判断哪半边是有序的
            if nums[left] <= nums[mid]:
                # 左半边 [left, mid] 是有序的
                if nums[left] <= target < nums[mid]:
                    # target 在左半边
                    right = mid - 1
                else:
                    # target 在右半边
                    left = mid + 1
            else:
                # 右半边 [mid, right] 是有序的
                if nums[mid] < target <= nums[right]:
                    # target 在右半边
                    left = mid + 1
                else:
                    # target 在左半边
                    right = mid - 1

        return -1


# 测试用例
assert Solution().search([4, 5, 6, 7, 0, 1, 2], 0) == 4
assert Solution().search([4, 5, 6, 7, 0, 1, 2], 3) == -1
assert Solution().search([1], 0) == -1
assert Solution().search([1], 1) == 0
assert Solution().search([4, 5, 6, 7, 0, 1, 2], 7) == 3
assert Solution().search([4, 5, 6, 7, 0, 1, 2], 4) == 0
assert Solution().search([4, 5, 6, 7, 0, 1, 2], 2) == 6

# 手动推导 nums=[4,5,6,7,0,1,2], target=0:
#   left=0, right=6, mid=3, nums[3]=7
#   nums[0]=4 <= nums[3]=7,左半边有序
#   target=0 不在 [4,7) 范围内,左=mid+1=4
#   left=4, right=6, mid=5, nums[5]=1
#   nums[4]=0 <= nums[5]=1,左半边有序
#   target=0 在 [0,1) 范围内,右=mid-1=4
#   left=4, right=4, mid=4, nums[4]=0
#   nums[4]==0,找到!返回 4
# 结果:4

寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1 到 n 次 旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到: 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2] 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7] 注意,数组 [a[0], a[1], a[2], …, a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], …, a[n-2]] 。

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素 。

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

输入:nums = [3,4,5,1,2] 输出:1 解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

nums 中的所有整数 互不相同 nums 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
from typing import List


class Solution:
    def findMin(self, nums: List[int]) -> int:
        """
        使用二分查找找到旋转排序数组中的最小值

        思路:
        - 旋转排序数组的最小值位于"断点"处
        - 二分时,如果 nums[mid] > nums[right],说明最小值在右半边
        - 如果 nums[mid] < nums[right],说明最小值在左半边(包括 mid)
        - 使用 nums[right] 作为比较基准,因为旋转后右半边一定比左半边小(或等于)
        - 时间 O(log n),空间 O(1)
        """
        left, right = 0, len(nums) - 1

        while left < right:
            mid = (left + right) // 2

            # 如果中点值大于右端点值,最小值一定在右半边
            if nums[mid] > nums[right]:
                left = mid + 1
            else:
                # nums[mid] <= nums[right],最小值在左半边或就是 mid
                right = mid

        # 循环结束时,left == right,指向最小值
        return nums[left]


# 测试用例
assert Solution().findMin([3, 4, 5, 1, 2]) == 1
assert Solution().findMin([4, 5, 6, 7, 0, 1, 2]) == 0
assert Solution().findMin([11, 13, 15, 17]) == 11
assert Solution().findMin([2, 1]) == 1
assert Solution().findMin([1]) == 1
assert Solution().findMin([2, 3, 4, 5, 1]) == 1

# 手动推导 nums=[3,4,5,1,2]:
#   left=0, right=4, mid=2, nums[2]=5
#   5 > 2, left=mid+1=3
#   left=3, right=4, mid=3, nums[3]=1
#   1 <= 2, right=mid=3
#   left=3, right=3,退出
#   返回 nums[3]=1
# 结果:1

寻找两个正序数组的中位数

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。

算法的时间复杂度应该为 O(log (m+n)) 。

输入:nums1 = [1,3], nums2 = [2] 输出:2.00000 解释:合并数组 = [1,2,3] ,中位数 2

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
from typing import List


class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        """
        使用二分查找在两个有序数组中找第 k 小的元素

        思路:
        - 核心思想:找第 k 小的元素(k = (m+n+1)//2 或 (m+n+2)//2)
        - 每次排除 k/2 个元素
        - 使用两个指针分别在两个数组中移动
        - 找中位数就是找第 (m+n+1)//2 和 (m+n+2)//2 小的元素
        - 时间 O(log(m+n)),空间 O(1)

        也可以用划分数组的方法:
        - 将两个数组分别划分,使左边元素个数等于右边
        - 保证 max(left) <= min(right)
        - 中位数由左右边界元素决定
        """
        def find_kth(k: int) -> float:
            """找到两个数组中第 k 小的元素(k 从 1 开始)"""
            idx1, idx2 = 0, 0

            while True:
                # 边界情况处理
                if idx1 == len(nums1):
                    # nums1 已遍历完,直接从 nums2 取
                    return nums2[idx2 + k - 1]
                if idx2 == len(nums2):
                    # nums2 已遍历完,直接从 nums1 取
                    return nums1[idx1 + k - 1]
                if k == 1:
                    # 找最小的,取两个数组当前元素的较小值
                    return min(nums1[idx1], nums2[idx2])

                # 正常情况:比较两个数组中第 k/2 个元素
                new_idx1 = min(idx1 + k // 2 - 1, len(nums1) - 1)
                new_idx2 = min(idx2 + k // 2 - 1, len(nums2) - 1)
                pivot1 = nums1[new_idx1]
                pivot2 = nums2[new_idx2]

                if pivot1 <= pivot2:
                    # 排除 nums1 中 idx1 到 new_idx1 的元素
                    k -= (new_idx1 - idx1 + 1)
                    idx1 = new_idx1 + 1
                else:
                    # 排除 nums2 中 idx2 到 new_idx2 的元素
                    k -= (new_idx2 - idx2 + 1)
                    idx2 = new_idx2 + 1

        m, n = len(nums1), len(nums2)
        left_k = (m + n + 1) // 2  # 第 k 小的元素
        right_k = (m + n + 2) // 2  # 第 k 大的元素

        # 中位数是两个中间元素的平均值
        return (find_kth(left_k) + find_kth(right_k)) / 2


# 测试用例
assert Solution().findMedianSortedArrays([1, 3], [2]) == 2.0
assert Solution().findMedianSortedArrays([1, 2], [3, 4]) == 2.5
assert Solution().findMedianSortedArrays([0, 0], [0, 0]) == 0.0
assert Solution().findMedianSortedArrays([], [1]) == 1.0
assert Solution().findMedianSortedArrays([2], []) == 2.0
assert Solution().findMedianSortedArrays([1, 3, 5, 7], [2, 4, 6, 8]) == 4.5

# 手动推导 nums1=[1,3], nums2=[2]:
#   m=2, n=1, 总长度=3
#   left_k = (2+1+1)//2 = 2, right_k = (2+1+2)//2 = 2
#   只需要找第 2 小的元素
#   find_kth(2):
#     idx1=0, idx2=0, k=2
#     k!=1, 比较 nums1[min(0+0,1)]=1 和 nums2[min(0+0,1)]=2
#     1 <= 2, k -= (0-0+1) = 1, idx1 = 1
#     k=1, 返回 min(nums1[1]=3, nums2[0]=2) = 2
#   返回 2/1 = 2.0
# 结果:2.0

有效的括号

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 每个右括号都有一个对应的相同类型的左括号。

输入:s = “()”

输出:true

s 仅由括号 ‘()[]{}’ 组成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class Solution:
    def isValid(self, s: str) -> bool:
        """
        使用栈判断括号是否匹配

        思路:
        - 遍历字符串中的每个字符
        - 如果是左括号,压入栈中
        - 如果是右括号,检查栈顶是否有匹配的左括号
        - 匹配则弹出栈顶,否则返回 False
        - 最后检查栈是否为空
        - 时间 O(n),空间 O(n)
        """
        stack = []
        # 左括号到右括号的映射
        mapping = {')': '(', '}': '{', ']': '['}

        for char in s:
            if char in mapping:
                # 是右括号,检查栈顶
                top = stack.pop() if stack else '#'
                if mapping[char] != top:
                    return False
            else:
                # 是左括号,压入栈中
                stack.append(char)

        # 栈为空说明所有括号都匹配
        return not stack


# 测试用例
assert Solution().isValid("()") == True
assert Solution().isValid("()[]{}") == True
assert Solution().isValid("(]") == False
assert Solution().isValid("([])") == True
assert Solution().isValid("([)]") == False
assert Solution().isValid("{[]}") == True
assert Solution().isValid("") == True
assert Solution().isValid("(") == False
assert Solution().isValid(")") == False

# 手动推导 "(])":
#   遍历 '(' -> 栈: ['(']
#   遍历 ')' -> 是右括号,栈顶'('匹配,弹出 -> 栈: []
#   遍历 ']' -> 是右括号,栈空,# != '[',返回 False
# 结果:False

# 手动推导 "([])":
#   遍历 '(' -> 栈: ['(']
#   遍历 '[' -> 栈: ['(', '[']
#   遍历 ']' -> 栈顶'['匹配,弹出 -> 栈: ['(']
#   遍历 ')' -> 栈顶'('匹配,弹出 -> 栈: []
#   栈为空,返回 True
# 结果:True

最小栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

MinStack() 初始化堆栈对象。 void push(int val) 将元素 val 推入堆栈。 void pop() 删除堆栈顶部的元素。 int top() 获取堆栈顶部的元素。 int getMin() 获取堆栈中的最小元素。

输入: [“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”] [[],[-2],[0],[-3],[],[],[],[]]

输出: [null,null,null,null,-3,null,0,-2]

解释: MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.getMin(); –> 返回 -3. minStack.pop(); minStack.top(); –> 返回 0. minStack.getMin(); –> 返回 -2.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class MinStack:
    """
    最小栈:在常数时间内获取最小元素的栈

    思路:
    - 使用两个栈:主栈和最小栈
    - 主栈:存储所有元素
    - 最小栈:存储当前最小元素
    - push 时:如果元素 <= 最小栈顶,也压入最小栈
    - pop 时:如果元素等于最小栈顶,最小栈也弹出
    - getMin:返回最小栈顶
    - 所有操作都是 O(1)
    """

    def __init__(self):
        self.stack = []      # 主栈
        self.min_stack = [] # 最小栈

    def push(self, val: int) -> None:
        """将元素推入栈"""
        self.stack.append(val)
        # 如果最小栈为空,或者当前值 <= 最小值,入栈
        if not self.min_stack or val <= self.min_stack[-1]:
            self.min_stack.append(val)

    def pop(self) -> None:
        """弹出栈顶元素"""
        if self.stack:
            val = self.stack.pop()
            # 如果弹出的值等于当前最小值,最小栈也要弹出
            if self.min_stack and val == self.min_stack[-1]:
                self.min_stack.pop()

    def top(self) -> int:
        """获取栈顶元素"""
        return self.stack[-1] if self.stack else None

    def getMin(self) -> int:
        """获取最小元素"""
        return self.min_stack[-1] if self.min_stack else None


# 测试用例
minStack = MinStack()
minStack.push(-2)     # stack: [-2], min_stack: [-2]
minStack.push(0)      # stack: [-2,0], min_stack: [-2]
minStack.push(-3)    # stack: [-2,0,-3], min_stack: [-2,-3]
assert minStack.getMin() == -3  # --> 返回 -3
minStack.pop()       # stack: [-2,0], min_stack: [-2]
assert minStack.top() == 0      # --> 返回 0
assert minStack.getMin() == -2  # --> 返回 -2

# 手动推导操作序列:
# push(-2): stack=[-2], min_stack=[-2]
# push(0):  stack=[-2,0], min_stack=[-2](0 > -2,不入最小栈)
# push(-3): stack=[-2,0,-3], min_stack=[-2,-3](-3 <= -2,需要入最小栈)
# getMin: 返回 min_stack[-1] = -3
# pop: val=-3, stack=[-2,0], val == min_stack[-1],min_stack弹出 -> min_stack=[-2]
# top: 返回 stack[-1] = 0
# getMin: 返回 min_stack[-1] = -2

字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

测试用例保证输出的长度不会超过 105。

输入:s = “3[a]2[bc]” 输出:“aaabcbc”

s 由小写英文字母、数字和方括号 ‘[]’ 组成 s 保证是一个 有效 的输入。 s 中所有整数的取值范围为 [1, 300]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class Solution:
    def decodeString(self, s: str) -> str:
        """
        使用两个栈解码字符串

        思路:
        - 使用两个栈:数字栈和字符串栈
        - 遇到数字:解析完整的数字
        - 遇到 '[':将当前的字符串和数字入栈,开始新的解码
        - 遇到字母:追加到当前字符串
        - 遇到 ']':弹出数字栈,重复当前字符串,追加到栈顶字符串
        - 时间 O(n),空间 O(n)
        """
        num_stack = []    # 存储重复次数
        str_stack = []    # 存储之前的字符串
        current_str = ""  # 当前正在构建的字符串
        num = 0           # 当前解析的数字

        for char in s:
            if char.isdigit():
                # 解析数字(可能是多位)
                num = num * 10 + int(char)
            elif char == '[':
                # 开始新的编码段
                # 将当前字符串和数字入栈
                str_stack.append(current_str)
                num_stack.append(num)
                # 重置
                current_str = ""
                num = 0
            elif char == ']':
                # 结束当前编码段
                # 弹出重复次数
                repeat_times = num_stack.pop()
                # 弹出之前的字符串
                prev_str = str_stack.pop()
                # 重复当前字符串,并追加到之前的字符串
                current_str = prev_str + current_str * repeat_times
            else:
                # 字母字符,追加到当前字符串
                current_str += char

        return current_str


# 测试用例
assert Solution().decodeString("3[a]2[bc]") == "aaabcbc"
assert Solution().decodeString("3[a2[c]]") == "accaccacc"
assert Solution().decodeString("2[abc]3[cd]ef") == "abcabccdcdcdef"
assert Solution().decodeString("abc3[cd]xyz") == "abccdcdcdxyz"
assert Solution().decodeString("a") == "a"
assert Solution().decodeString("10[a]") == "a" * 10

# 手动推导 "3[a2[c]]":
#   char='3': num=3
#   char='[': str_stack=[""], num_stack=[3], current_str="", num=0
#   char='a': current_str="a"
#   char='2': num=2
#   char='[': str_stack=["", "a"], num_stack=[3, 2], current_str="", num=0
#   char='c': current_str="c"
#   char=']': repeat_times=2, prev_str="a", current_str="a"+"c"*2="acc"
#   char=']': repeat_times=3, prev_str="", current_str=""+"acc"*3="accaccacc"
# 结果:"accaccacc"

每日温度

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

输入: temperatures = [73,74,75,71,69,72,76,73] 输出: [1,1,4,2,1,1,0,0]

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from typing import List


class Solution:
    def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
        """
        使用单调递减栈找到下一个更高温度的距离

        思路:
        - 维护一个存储索引的单调递减栈
        - 栈中存储的是尚未找到更高温度的日期索引
        - 当遇到比栈顶温度更高的日子,计算距离
        - 每个元素最多入栈出栈一次,时间 O(n),空间 O(n)
        """
        n = len(temperatures)
        result = [0] * n  # 初始化为0,表示没有更高温度
        stack = []        # 存储索引,栈底到栈顶对应温度递减

        for i, temp in enumerate(temperatures):
            # 如果当前温度比栈顶温度高,说明栈顶日期等到了更高温度
            while stack and temperatures[stack[-1]] < temp:
                prev_idx = stack.pop()
                result[prev_idx] = i - prev_idx

            # 将当前日期索引入栈
            stack.append(i)

        return result


# 测试用例
assert Solution().dailyTemperatures([73, 74, 75, 71, 69, 72, 76, 73]) == [1, 1, 4, 2, 1, 1, 0, 0]
assert Solution().dailyTemperatures([30, 40, 50, 60]) == [1, 1, 1, 0]
assert Solution().dailyTemperatures([30, 60, 90]) == [1, 1, 0]
assert Solution().dailyTemperatures([70, 60, 50, 60]) == [0, 1, 0, 0]
assert Solution().dailyTemperatures([100]) == [0]

# 手动推导 [73,74,75,71,69,72,76,73]:
#   i=0, temp=73: stack=[], 入栈 -> stack=[0]
#   i=1, temp=74: 74>73(栈顶), 弹出0, result[0]=1, stack=[], 入栈 -> stack=[1]
#   i=2, temp=75: 75>74(栈顶), 弹出1, result[1]=1, stack=[], 入栈 -> stack=[2]
#   i=3, temp=71: 71<75, 入栈 -> stack=[2,3]
#   i=4, temp=69: 69<71, 入栈 -> stack=[2,3,4]
#   i=5, temp=72: 72>69(栈顶), 弹出4, result[4]=1
#               72>71(栈顶), 弹出3, result[3]=2
#               72<75, 入栈 -> stack=[2,5]
#   i=6, temp=76: 76>72(栈顶), 弹出5, result[5]=1
#               76>75(栈顶), 弹出2, result[2]=4
#               76<... 无栈顶, 入栈 -> stack=[6]
#   i=7, temp=73: 73<76, 入栈 -> stack=[6,7]
# 结果:[1,1,4,2,1,1,0,0]

柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

输入:heights = [2,1,5,6,2,3] 输出:10 解释:最大的矩形为图中红色区域,面积为 10

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
from typing import List


class Solution:
    def largestRectangleArea(self, heights: List[int]) -> int:
        """
        使用单调递增栈找到最大矩形面积

        思路:
        - 维护一个存储索引的单调递增栈
        - 栈中存储的是高度递增的柱子索引
        - 当遇到比栈顶更低的柱子时,弹出栈顶,计算以其为高的矩形面积
        - 关键:栈中的每个柱子,左右第一个比它低的索引决定了最大宽度
        - 时间 O(n),空间 O(n)

        图解单调栈计算过程:
        对于高度数组 [2,1,5,6,2,3]
        我们需要找到每个位置作为最矮柱子时的最大矩形

        核心思想:
        - 对于每个高度 h,它能形成的最大矩形是向左右扩展直到遇到更低的柱子
        - 用单调递增栈记录索引
        - 当遇到更低的柱子时,弹出栈顶,计算以其为高的矩形
        """
        n = len(heights)
        stack = []        # 存储索引,栈中对应的高度单调递增
        max_area = 0

        for i in range(n):
            # 当栈不为空且当前高度比栈顶高度低时
            # 说明栈顶高度在位置 i 无法继续扩展
            while stack and heights[stack[-1]] > heights[i]:
                height = heights[stack.pop()]  # 栈顶柱子的高度

                # 计算宽度:
                # - 左边界:栈中新的栈顶索引 + 1(如果栈非空)
                # - 右边界:i - 1(当前位置 - 1)
                # - 宽度 = 右边界 - 左边界 + 1
                if stack:
                    width = i - stack[-1] - 1
                else:
                    width = i  # 栈为空时,左边界是 0

                max_area = max(max_area, height * width)

            # 将当前索引入栈
            stack.append(i)

        # 处理栈中剩余的柱子(它们的右边界是数组末尾)
        while stack:
            height = heights[stack.pop()]

            if stack:
                width = n - stack[-1] - 1
            else:
                width = n  # 栈为空时,左边界是 0

            max_area = max(max_area, height * width)

        return max_area


# 测试用例
assert Solution().largestRectangleArea([2, 1, 5, 6, 2, 3]) == 10
assert Solution().largestRectangleArea([2, 4]) == 4
assert Solution().largestRectangleArea([]) == 0
assert Solution().largestRectangleArea([1]) == 1
assert Solution().largestRectangleArea([2, 2]) == 4
assert Solution().largestRectangleArea([5, 4, 3, 2, 1]) == 9  # 最大矩形是整个宽度 * 最小高度1

# 手动推导 [2,1,5,6,2,3]:
#   i=0, h=2: stack=[], 入栈 -> stack=[0]
#   i=1, h=1: 1<2,弹出0,height=2,width=1(栈空),area=2 -> max=2
#             入栈 -> stack=[1]
#   i=2, h=5: 5>1,入栈 -> stack=[1,2]
#   i=3, h=6: 6>5,入栈 -> stack=[1,2,3]
#   i=4, h=2: 2<6,弹出3,height=6,width=1,area=6 -> max=6
#             2<5,弹出2,height=5,width=2,area=10 -> max=10
#             2>1,入栈 -> stack=[1,4]
#   i=5, h=3: 3>2,入栈 -> stack=[1,4,5]
#   处理剩余:弹出5,height=3,width=1,area=3
#             弹出4,height=2,width=4(n=6, stack=[1]),area=8
#             弹出1,height=1,width=6(栈空),area=6
# 结果:max=10

数组中的第 K 个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

输入: [3,2,1,5,6,4], k = 2 输出: 5

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import heapq
from typing import List


class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        """
        使用最小堆维护K个最大元素

        思路:
        - 维护一个大小为K的最小堆
        - 遍历数组,堆大小小于K时直接入堆
        - 当堆大小等于K时,如果当前元素大于堆顶(当前第K大的最小值),则替换
        - 堆顶始终是第K大的元素
        - 时间 O(n log k),空间 O(k)

        进阶:可以用快速选择算法,平均 O(n),最坏 O(n²)
        """
        # 创建最小堆
        heap = []

        for num in nums:
            if len(heap) < k:
                # 堆未满,直接入堆
                heapq.heappush(heap, num)
            elif num > heap[0]:
                # 当前元素比堆顶大,替换堆顶(保持堆中都是较大的元素)
                heapq.heapreplace(heap, num)

        # 堆顶就是第K大的元素
        return heap[0]


# 测试用例
assert Solution().findKthLargest([3, 2, 1, 5, 6, 4], 2) == 5
assert Solution().findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4) == 4
assert Solution().findKthLargest([1], 1) == 1
assert Solution().findKthLargest([5, 5, 5, 5, 5], 1) == 5
assert Solution().findKthLargest([7, 7, 7, 7, 7], 3) == 7

# 手动推导 nums=[3,2,1,5,6,4], k=2:
#   遍历3: heap=[], len<2, push(3) -> heap=[3]
#   遍历2: heap=[3], len<2, push(2) -> heap=[2,3]
#   遍历1: heap=[2,3], len=2, 1<=2(堆顶), 不变 -> heap=[2,3]
#   遍历5: heap=[2,3], len=2, 5>2(堆顶), replace -> heap=[3,5]
#   遍历6: heap=[3,5], len=2, 6>3(堆顶), replace -> heap=[5,6]
#   遍历4: heap=[5,6], len=2, 4<=5(堆顶), 不变 -> heap=[5,6]
#   返回 heap[0]=5
# 结果:5

前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

输入:nums = [1,1,1,2,2,3], k = 2

输出:[1,2]

k 的取值范围是 [1, 数组中不相同的元素的个数] 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import heapq
from typing import List
from collections import Counter


class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        """
        统计频率后使用最小堆找前K个高频元素

        思路:
        - 先用 Counter 统计每个元素出现的频率
        - 创建 (频率, 元素) 的元组列表
        - 使用最小堆维护频率最高的K个元素
        - 堆顶是K个中最小的频率
        - 时间 O(n log k),优于 O(n log n)
        - 空间 O(n) 用于存储频率
        """
        # 统计频率
        freq = Counter(nums)

        # 创建 (频率, 元素) 的元组列表
        # Python 的 heapq 是最小堆,所以频率取负数变为最大堆
        heap = [(-count, num) for num, count in freq.items()]

        # 将列表转换为堆
        heapq.heapify(heap)

        # 取出前K个高频元素
        result = []
        for _ in range(k):
            count, num = heapq.heappop(heap)
            result.append(num)

        return result


# 测试用例
assert Solution().topKFrequent([1, 1, 1, 2, 2, 3], 2) == [1, 2]
assert Solution().topKFrequent([1], 1) == [1]
assert set(Solution().topKFrequent([1, 1, 1, 2, 2, 3, 3, 3], 2)) == {1, 3}
assert Solution().topKFrequent([1, 2], 2) == [1, 2]
assert Solution().topKFrequent([4, 4, 4, 2, 2, 3, 1], 2) == [4, 2]

# 手动推导 nums=[1,1,1,2,2,3], k=2:
#   统计频率: {1:3, 2:2, 3:1}
#   创建堆: [(-3,1), (-2,2), (-1,3)] -> heapify
#   弹出第1个: (-3, 1), result=[1]
#   弹出第2个: (-2, 2), result=[1, 2]
# 结果:[1, 2]

数据流的中位数

中位数是有序整数列表中的中间值。如果列表的大小是偶数,则没有中间值,中位数是两个中间值的平均值。

例如 arr = [2,3,4] 的中位数是 3 。 例如 arr = [2,3] 的中位数是 (2 + 3) / 2 = 2.5 。 实现 MedianFinder 类:

MedianFinder() 初始化 MedianFinder 对象。

void addNum(int num) 将数据流中的整数 num 添加到数据结构中。

double findMedian() 返回到目前为止所有元素的中位数。与实际答案相差 10-5 以内的答案将被接受。

输入 [“MedianFinder”, “addNum”, “addNum”, “findMedian”, “addNum”, “findMedian”] [[], [1], [2], [], [3], []] 输出 [null, null, null, 1.5, null, 2.0]

解释 MedianFinder medianFinder = new MedianFinder(); medianFinder.addNum(1); // arr = [1] medianFinder.addNum(2); // arr = [1, 2] medianFinder.findMedian(); // 返回 1.5 ((1 + 2) / 2) medianFinder.addNum(3); // arr[1, 2, 3] medianFinder.findMedian(); // return 2.0

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
import heapq


class MedianFinder:
    """
    数据流中位数:使用两个堆维护中位数

    思路:
    - 使用两个堆:最大堆(存较小的一半)和最小堆(存较大的一半)
    - max_heap:存较小的一半,堆顶是最大值,用负数实现最大堆
    - min_heap:存较大的一半,堆顶是最小值
    - 保持 max_heap 元素个数 >= min_heap 元素个数(差不超过1)
    - 中位数:
      - 如果两个堆大小相同,取两个堆顶的平均值
      - 如果 max_heap 多一个,取 max_heap 堆顶
    - 所有操作 O(log n)
    """

    def __init__(self):
        self.max_heap = []  # 最大堆,存较小的一半(用负数实现)
        self.min_heap = []  # 最小堆,存较大的一半

    def addNum(self, num: int) -> None:
        """
        添加一个数字到数据结构中
        """
        # 先放入最大堆
        heapq.heappush(self.max_heap, -num)

        # 平衡:将最大堆的堆顶(最大值)移到最小堆
        heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))

        # 确保 max_heap 元素个数 >= min_heap 元素个数
        if len(self.min_heap) > len(self.max_heap):
            heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))

    def findMedian(self) -> float:
        """
        返回当前数据流的中位数
        """
        if len(self.max_heap) > len(self.min_heap):
            # max_heap 多一个,直接返回堆顶
            return -self.max_heap[0]
        else:
            # 两个堆大小相同,返回平均
            return (-self.max_heap[0] + self.min_heap[0]) / 2


# 测试用例
mf = MedianFinder()
mf.addNum(1)     # arr = [1]
mf.addNum(2)     # arr = [1, 2]
assert mf.findMedian() == 1.5  # (1 + 2) / 2
mf.addNum(3)     # arr = [1, 2, 3]
assert mf.findMedian() == 2.0

mf2 = MedianFinder()
mf2.addNum(6)
assert mf2.findMedian() == 6.0

# 手动推导 add(1), add(2), findMedian():
# add(1):
#   max=[-1], min=[]
#   移动: max=[], min=[1]
#   平衡: len(min)=1 > len(max)=0, 移动: max=[-1], min=[]
#   状态: max=[-1], min=[]  # max存1
#
# add(2):
#   max=[-1,-2], min=[]  # max存1,2
#   移动: max=[-2], min=[1]  # pop返回-1(对应1),push 1到min
#   平衡: len(min)=1 == len(max)=1, 不需要移动
#   状态: max=[-2], min=[1]  # max存2,min存1
#
# findMedian: len(max)=1 == len(min)=1
#   返回 (-(-2) + 1) / 2 = (2 + 1) / 2 = 1.5
# 结果:1.5

# 继续 add(3):
#   max=[-2,-3], min=[1]  # max存2,3,min存1
#   移动: max=[-3], min=[1,2]  # pop返回-2(对应2),push 2到min
#   平衡: len(min)=2 > len(max)=1, 移动: max=[-2,-3], min=[1]  # pop(min)返回1,push -1到max
#   状态: max=[-2,-3], min=[1]  # max存2,3,min存1
#
# findMedian: len(max)=2 > len(min)=1
#   返回 -(-2) = 2
# 结果:2.0