分类: 未分类

  • Kth Largest Element in an Array,LeetCode,215

    题目描述:

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

    示例 1:

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

    示例 2:

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

    解题思路:

    这道题有多种解法,常见的有以下几种:

    1. 排序法

    最直接的方法是将数组排序,然后取第 k 个最大的元素。

    代码实现(Python):

    def findKthLargest(nums, k): nums.sort(reverse=True) return nums[k-1]

    时间复杂度: O(n log n),其中 n 是数组的长度。

    空间复杂度: O(1),如果忽略排序算法的额外空间。

    2. 堆排序法

    使用最小堆来维护前 k 个最大的元素。

    代码实现(Python):

    import heapq

    def findKthLargest(nums, k): return heapq.nlargest(k, nums)[-1]

    或者使用最小堆手动维护:

    import heapq

    def findKthLargest(nums, k): min_heap = [] for num in nums: if len(min_heap) < k: heapq.heappush(min_heap, num) else: heapq.heappushpop(min_heap, num) return min_heap[0]

    时间复杂度: O(n log k),其中 n 是数组的长度。

    空间复杂度: O(k),用于存储堆。

    3. 快速选择法

    基于快速排序的分区思想,通过选择一个 pivot 将数组分为两部分,然后判断第 k 个最大的元素在哪个部分。

    代码实现(Python):

    def findKthLargest(nums, k): def partition(left, right, pivot_index): pivot_value = nums[pivot_index] nums[pivot_index], nums[right] = nums[right], nums[pivot_index] store_index = left for i in range(left, right): if nums[i] > pivot_value: nums[store_index], nums[i] = nums[i], nums[store_index] store_index += 1 nums[right], nums[store_index] = nums[store_index], nums[right] return store_index

    def select(left, right, k_smallest):
        if left == right:
            return nums[left]
        pivot_index = left
        pivot_index = partition(left, right, pivot_index)
        if k_smallest == pivot_index:
            return nums[k_smallest]
        elif k_smallest < pivot_index:
            return select(left, pivot_index - 1, k_smallest)
        else:
            return select(pivot_index + 1, right, k_smallest)
    
    return select(0, len(nums) - 1, k - 1)

    时间复杂度: O(n),平均情况下,最坏情况下为 O(n^2)。

    空间复杂度: O(1),递归栈空间。

    4. 计数排序法(适用于特定情况)

    如果数组中的元素范围有限,可以使用计数排序。

    代码实现(Python):

    def findKthLargest(nums, k): min_val, max_val = min(nums), max(nums) count = [0] * (max_val - min_val + 1) for num in nums: count[num - min_val] += 1 for i in range(len(count) - 1, -1, -1): k -= count[i] if k <= 0: return i + min_val

    时间复杂度: O(n + m),其中 n 是数组的长度,m 是元素的范围。

    空间复杂度: O(m),用于存储计数数组。

    总结:

    • 排序法简单直接,但时间复杂度较高。
    • 堆排序法适用于大数据集,时间复杂度较好。
    • 快速选择法在平均情况下效率最高,但最坏情况下时间复杂度较差。
    • 计数排序法适用于元素范围有限的情况。

    根据具体需求和数据特点选择合适的解法。

  • Course Schedule,LeetCode,207

    题目描述:

    你是一位系统管理员,手里有两门课程的选课清单,清单里是一个由若干对 [课程A, 课程B] 组成的数组,含义是选修课程A之前,必须先选修课程B。请你判断是否可能完成所有课程的选修。

    示例 1:

    输入: 2, [[1,0]] 输出: true 解释: 总共有两门课程,课程编号分别为0和1。课程0是课程1的前置课程,因此可以先选修课程0,再选修课程1。

    示例 2:

    输入: 2, [[1,0],[0,1]] 输出: false 解释: 总共有两门课程,课程编号分别为0和1。课程0是课程1的前置课程,同时课程1也是课程0的前置课程,这会导致循环依赖,因此无法完成所有课程的选修。

    思路分析:

    这道题实际上是一个经典的拓扑排序问题,常用于判断一个有向图中是否存在环。我们可以通过以下步骤来解决这个问题:

    1. 构建邻接表和入度表:
      • 邻接表:用于存储每个课程的后续课程。
      • 入度表:用于存储每个课程的前置课程数量。
    2. 初始化队列:
      • 将所有入度为0的课程(即没有前置课程的课程)加入队列。
    3. 进行拓扑排序:
      • 从队列中逐个取出课程,并将其从邻接表中删除,同时将其后续课程的入度减1。
      • 如果某个后续课程的入度减为0,则将其加入队列。
      • 重复上述过程,直到队列为空。
    4. 判断是否完成所有课程的选修:
      • 如果最终所有课程的入度都为0,则说明可以完成所有课程的选修,返回true。
      • 否则,说明存在循环依赖,返回false。

    代码实现:

    from collections import deque, defaultdict

    def canFinish(numCourses, prerequisites):

    构建邻接表和入度表

    adj_list = defaultdict(list)
    in_degree = [0] * numCourses
    
    for dest, src in prerequisites:
        adj_list[src].append(dest)
        in_degree[dest] += 1
    
    # 初始化队列
    queue = deque([i for i in range(numCourses) if in_degree[i] == 0])
    
    # 进行拓扑排序
    visited_courses = 0
    while queue:
        course = queue.popleft()
        visited_courses += 1
        for neighbor in adj_list[course]:
            in_degree[neighbor] -= 1
            if in_degree[neighbor] == 0:
                queue.append(neighbor)
    
    # 判断是否完成所有课程的选修
    return visited_courses == numCourses

    示例

    print(canFinish(2, [[1,0]])) # 输出: true print(canFinish(2, [[1,0],[0,1]])) # 输出: false

    复杂度分析:

    • 时间复杂度: O(V + E),其中V是课程数量,E是课程依赖关系的数量。我们需要遍历所有课程和依赖关系来构建邻接表和入度表,并进行拓扑排序。
    • 空间复杂度: O(V + E),邻接表和入度表的总空间复杂度为O(V + E)。

    通过上述分析和代码实现,我们可以有效地判断是否可能完成所有课程的选修。希望这个解答对你有所帮助!如果有任何进一步的问题,欢迎继续提问。

  • Number of Islands,LeetCode,200

    题目描述:

    给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算岛屿的数量。一个岛屿被水包围,并且通过水平方向或垂直方向上相邻的陆地连接形成。你可以假设网格的四个边都被水包围。

    示例:

    输入: 11110 11010 11000 00000

    输出: 1

    输入: 11000 11000 00100 00011

    输出: 3

    解题思路:

    这个问题可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来解决。基本思路是遍历整个网格,每当遇到一个 '1' 时,就从这个位置开始进行深度优先搜索或广度优先搜索,将所有与其相连的 '1' 都标记为 '0',这样可以避免重复计数。每进行一次这样的搜索,岛屿数量就增加 1。

    深度优先搜索(DFS)解法:

    1. 遍历网格的每一个单元格。
    2. 当遇到一个 '1' 时,从这个位置开始进行深度优先搜索,将所有与其相连的 '1' 都标记为 '0'
    3. 每次启动新的深度优先搜索时,岛屿数量增加 1。

    代码实现(Python):

    class Solution: def numIslands(self, grid: List[List[str]]) -> int: if not grid: return 0

        def dfs(i, j):
            if i < 0 or i >= len(grid) or j < 0 or j >= len(grid[0]) or grid[i][j] == '0':
                return
            grid[i][j] = '0'  # 标记为已访问
            dfs(i + 1, j)
            dfs(i - 1, j)
            dfs(i, j + 1)
            dfs(i, j - 1)
    
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    dfs(i, j)
                    count += 1
    
        return count

    广度优先搜索(BFS)解法:

    1. 遍历网格的每一个单元格。
    2. 当遇到一个 '1' 时,从这个位置开始进行广度优先搜索,将所有与其相连的 '1' 都标记为 '0'
    3. 每次启动新的广度优先搜索时,岛屿数量增加 1。

    代码实现(Python):

    from collections import deque

    class Solution: def numIslands(self, grid: List[List[str]]) -> int: if not grid: return 0

        def bfs(i, j):
            queue = deque([(i, j)])
            while queue:
                x, y = queue.popleft()
                if 0 <= x < len(grid) and 0 <= y < len(grid[0]) and grid[x][y] == '1':
                    grid[x][y] = '0'  # 标记为已访问
                    queue.append((x + 1, y))
                    queue.append((x - 1, y))
                    queue.append((x, y + 1))
                    queue.append((x, y - 1))
    
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    bfs(i, j)
                    count += 1
    
        return count

    复杂度分析:

    • 时间复杂度: O(M * N),其中 M 和 N 分别是网格的行数和列数。每个单元格最多被访问一次。
    • 空间复杂度: O(M N),在最坏情况下,如果整个网格都是陆地,递归调用栈的深度或队列的大小可以达到 M N。

    这两种方法都可以有效地解决这道题目,选择其中一种即可。希望这些详细的解释和代码示例对你有所帮助!

  • House Robber,LeetCode,198

    House Robber(打家劫舍)是 LeetCode 上的第 198 题,是一道经典的动态规划问题。

    题目描述

    你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

    给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

    示例 1:

    输入:[1,2,3,1] 输出:4 解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。 偷窃到的最高金额 = 1 + 3 = 4 。

    示例 2:

    输入:[2,7,9,3,1] 输出:12 解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。 偷窃到的最高金额 = 2 + 9 + 1 = 12 。

    解题思路

    这个问题可以使用动态规划来解决。我们定义一个数组 dp,其中 dp[i] 表示偷到第 i 个房子时能够获得的最大金额。

    状态转移方程:

    • 如果偷第 i 个房子,那么不能偷第 i-1 个房子,最大金额为 dp[i-2] + nums[i]
    • 如果不偷第 i 个房子,那么最大金额为 dp[i-1]

    所以,状态转移方程为:

    [ dp[i] = \max(dp[i-1], dp[i-2] + nums[i]) ]

    初始条件:

    • dp[0] = nums[0],因为如果只有一个房子,最大金额就是那个房子的金额。
    • dp[1] = max(nums[0], nums[1]),因为如果有两个房子,最大金额是这两个房子中金额较大的那个。

    代码实现

    以下是使用 Python 实现的动态规划解法:

    def rob(nums): if not nums: return 0 n = len(nums) if n == 1: return nums[0]

    dp = [0] * n
    dp[0] = nums[0]
    dp[1] = max(nums[0], nums[1])
    
    for i in range(2, n):
        dp[i] = max(dp[i-1], dp[i-2] + nums[i])
    
    return dp[-1]

    示例

    print(rob([1,2,3,1])) # 输出:4 print(rob([2,7,9,3,1])) # 输出:12

    优化空间复杂度

    我们可以进一步优化空间复杂度,因为每次状态转移只依赖于前两个状态,所以可以用两个变量来代替数组。

    def rob(nums): if not nums: return 0 n = len(nums) if n == 1: return nums[0]

    prev2 = nums[0]
    prev1 = max(nums[0], nums[1])
    
    for i in range(2, n):
        current = max(prev1, prev2 + nums[i])
        prev2 = prev1
        prev1 = current
    
    return prev1

    示例

    print(rob([1,2,3,1])) # 输出:4 print(rob([2,7,9,3,1])) # 输出:12

    总结

    这道题通过动态规划的思想,将问题分解为子问题,逐步求解。通过状态转移方程和初始条件的设定,能够有效地求解出最大偷窃金额。优化后的解法在空间复杂度上更加高效,适用于大规模输入的情况。

    希望这个详细的解答对你有所帮助!如果有更多问题,欢迎继续提问。

  • Dungeon Game,LeetCode,174

    《Dungeon Game》(地牢游戏)是 LeetCode 上的第 174 题,它是一道经典的动态规划问题。题目描述如下:

    题目描述:

    表述一个地下城游戏,包含 m x n 个房间,这些房间由二维网格 dungeon 表示。网格中的负整数表示这一位置对骑士的生命值造成的伤害;正整数表示这一位置对骑士的生命值进行的恢复。 骑士初始化时位于左上角的房间,且初始生命值为一个正整数。你必须带领骑士到达右下角的房间(即 dungeon[m-1][n-1]),此时骑士的生命值至少为 1。 编写一个函数来计算确保骑士能够安全到达右下角房间所需的最小初始生命值。

    示例:

    输入: dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]] 输出: 7 解释: 初始时骑士的生命值为 7,在路径上骑士的生命值会如下变化: 路径: [1,3,2,1,5,4,7]

    解题思路:

    1. 逆向思考:从右下角(终点)开始向左上角(起点)进行动态规划。因为我们需要确保骑士到达每个房间时生命值至少为 1。
    2. 状态定义:设 dp[i][j] 表示从房间 (i, j) 到终点所需的最小初始生命值。
    3. 状态转移方程
      • 如果 i == m-1j == n-1(即终点),则 dp[i][j] = max(1, 1 - dungeon[i][j])
      • 如果 i == m-1(最后一行),则 dp[i][j] = max(1, dp[i][j+1] - dungeon[i][j])
      • 如果 j == n-1(最后一列),则 dp[i][j] = max(1, dp[i+1][j] - dungeon[i][j])
      • 其他情况,则 dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j])
    4. 初始化:从右下角开始初始化 dp 数组。
    5. 结果:最终 dp[0][0] 即为所需的最小初始生命值。

    代码实现(Python):

    def calculateMinimumHP(dungeon): m, n = len(dungeon), len(dungeon[0]) dp = [[0] * n for _ in range(m)]

    # 从右下角开始初始化dp数组
    for i in range(m-1, -1, -1):
        for j in range(n-1, -1, -1):
            if i == m-1 and j == n-1:
                dp[i][j] = max(1, 1 - dungeon[i][j])
            elif i == m-1:
                dp[i][j] = max(1, dp[i][j+1] - dungeon[i][j])
            elif j == n-1:
                dp[i][j] = max(1, dp[i+1][j] - dungeon[i][j])
            else:
                dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j])
    
    return dp[0][0]

    示例

    dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]] print(calculateMinimumHP(dungeon)) # 输出: 7

    解释:

    1. 初始化:创建一个 m x ndp 数组,用于存储每个位置到终点所需的最小初始生命值。
    2. 逆向遍历:从右下角开始遍历网格,根据状态转移方程计算每个位置的 dp 值。
    3. 结果:最终 dp[0][0] 即为从起点到终点所需的最小初始生命值。

    通过这种逆向动态规划的方法,可以有效地解决这个地牢游戏问题,确保骑士能够安全到达终点。

  • Maximum Gap,LeetCode,164

    题目描述

    给定一个无序的数组,找出数组在排序后相邻元素之间最大的差值。

    如果数组元素个数小于 2,则返回 0。

    示例

    输入: [3,6,9,1] 输出: 3 解释: 排序后的数组是 [1,3,6,9],其中相邻元素的最大差值是 3。

    解题思路

    这个问题可以通过多种方法来解决,其中一种高效的方法是使用“桶排序”的思想。以下是详细的解题步骤:

    方法一:基于桶排序的思路

    1. 找出最大值和最小值
      • 首先遍历数组,找出最大值 max_val 和最小值 min_val
    2. 计算桶的数量和每个桶的宽度
      • 设数组长度为 n,则桶的数量 bucket_num 可以设为 n + 1
      • 每个桶的宽度 bucket_size(max_val - min_val) / (n - 1)
    3. 初始化桶
      • 创建一个长度为 bucket_num 的数组 buckets,每个桶存储两个值:桶内的最小值和最大值。
    4. 将元素分配到桶中
      • 遍历数组中的每个元素 num,计算它应该放入哪个桶:
        • 桶的索引 bucket_index = (num - min_val) / bucket_size
        • 更新该桶的最小值和最大值。
    5. 计算最大差值
      • 遍历 buckets 数组,计算相邻非空桶之间的最大差值。

    代码实现:

    def maximumGap(nums): if len(nums) < 2: return 0

    max_val = max(nums)
    min_val = min(nums)
    n = len(nums)
    
    # 计算桶的数量和每个桶的宽度
    bucket_num = n + 1
    bucket_size = (max_val - min_val) // (n - 1) + 1
    
    # 初始化桶
    buckets = [[float('inf'), float('-inf')] for _ in range(bucket_num)]
    
    # 将元素分配到桶中
    for num in nums:
        bucket_index = (num - min_val) // bucket_size
        buckets[bucket_index][0] = min(buckets[bucket_index][0], num)
        buckets[bucket_index][1] = max(buckets[bucket_index][1], num)
    
    # 计算最大差值
    max_gap = 0
    prev_max = min_val
    
    for bucket in buckets:
        if bucket[0] == float('inf'):
            continue
        max_gap = max(max_gap, bucket[0] - prev_max)
        prev_max = bucket[1]
    
    return max_gap

    示例

    print(maximumGap([3, 6, 9, 1])) # 输出: 3

    复杂度分析:

    • 时间复杂度:O(n),其中 n 是数组的长度。每个元素只被处理一次,桶的数量也是 O(n)。
    • 空间复杂度:O(n),用于存储桶的数组。

    其他方法:

    1. 直接排序法
      • 先对数组进行排序,然后遍历排序后的数组,计算相邻元素之间的差值,找出最大差值。
      • 时间复杂度:O(n log n),空间复杂度:O(1)(取决于排序算法)。
    2. 基数排序法
      • 使用基数排序对数组进行排序,然后计算最大差值。
      • 时间复杂度:O(nk),其中 k 是数字的最大位数。
      • 空间复杂度:O(n)。

    基于桶排序的方法在时间复杂度上更优,特别是对于大数组,效果更明显。

    希望这个详细的解答对你有帮助!如果有其他问题,欢迎继续提问。

  • Find Minimum in Rotated Sorted Array,LeetCode,153

    题目描述:

    假设按照升序排序的数组在预先未知的某个点上进行了旋转。

    例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2]

    请找出其中最小的元素。

    你可以假设数组中不存在重复元素。

    示例 1:

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

    示例 2:

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

    解题思路:

    这个问题可以通过二分查找来解决。由于数组是旋转过的有序数组,我们可以利用二分查找的特性来找到最小值。

    1. 初始化:设置两个指针 leftright,分别指向数组的起始和末尾位置。
    2. 二分查找
      • 计算中间位置 mid
      • 比较 mid 位置的值与 right 位置的值:
        • 如果 nums[mid] > nums[right],说明最小值在 mid 的右侧,调整 leftmid + 1
        • 否则,说明最小值在 mid 的左侧或者就是 mid,调整 rightmid
    3. 终止条件:当 leftright 相等时,left(或 right)指向的就是最小值。

    代码实现:

    def findMin(nums): left, right = 0, len(nums) - 1

    while left < right:
        mid = (left + right) // 2
    
        if nums[mid] > nums[right]:
            left = mid + 1
        else:
            right = mid
    
    return nums[left]

    示例

    print(findMin([3,4,5,1,2])) # 输出: 1 print(findMin([4,5,6,7,0,1,2])) # 输出: 0

    详细解释:

    1. 初始化
      • left = 0right = len(nums) - 1
    2. 二分查找过程
      • 计算中间位置 mid
      • 比较 nums[mid]nums[right]
        • 如果 nums[mid] > nums[right],说明最小值在 mid 的右侧,调整 leftmid + 1
        • 否则,说明最小值在 mid 的左侧或者就是 mid,调整 rightmid
    3. 终止条件
      • leftright 相等时,循环结束,此时 left(或 right)指向的就是最小值。

    复杂度分析:

    • 时间复杂度:O(log n),二分查找的时间复杂度为对数级别。
    • 空间复杂度:O(1),只使用了常数级别的额外空间。

    通过这种方法,我们可以在对数时间内高效地找到旋转有序数组中的最小值。

  • Reverse Words in a String,LeetCode,151

    好的,我们来详细讲解一下 LeetCode 上的第 151 题:Reverse Words in a String。

    题目描述

    给定一个字符串,逐个翻转字符串中的每个单词。

    示例 1:

    输入: "the sky is blue" 输出: "blue is sky the"

    示例 2:

    输入: " hello world! " 输出: "world! hello" 解释: 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。

    示例 3:

    输入: "a good example" 输出: "example good a" 解释: 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

    说明:

    • 无空格字符构成一个单词。
    • 输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
    • 如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个。

    解题思路

    1. 去除首尾空格:首先去除字符串首尾的空格,以便后续处理。
    2. 分割字符串:使用空格将字符串分割成单词列表。
    3. 反转列表:将单词列表反转。
    4. 拼接字符串:将反转后的单词列表拼接成一个字符串,单词间用一个空格隔开。

    代码实现

    以下是使用 Python 实现的代码:

    class Solution: def reverseWords(self, s: str) -> str:

    Step 1: 去除首尾空格

        s = s.strip()
    
        # Step 2: 分割字符串
        words = s.split()
    
        # Step 3: 反转列表
        words.reverse()
    
        # Step 4: 拼接字符串
        return ' '.join(words)

    详细解释

    1. 去除首尾空格s = s.strip() 这一步使用 strip() 方法去除字符串首尾的空格。
    2. 分割字符串words = s.split() 使用 split() 方法按空格分割字符串,默认情况下 split() 会自动处理多个连续空格,只返回有效的单词。
    3. 反转列表words.reverse() 使用 reverse() 方法将单词列表反转。
    4. 拼接字符串return ' '.join(words) 使用 ' '.join(words) 将反转后的单词列表拼接成一个字符串,单词间用一个空格隔开。

    复杂度分析

    • 时间复杂度:O(n),其中 n 是字符串的长度。去除首尾空格、分割字符串和拼接字符串的操作都是线性的。
    • 空间复杂度:O(n),用于存储分割后的单词列表。

    其他语言实现

    如果你需要其他语言的实现,比如 Java 或 C++,可以参考以下代码:

    Java 实现

    public class Solution { public String reverseWords(String s) { // Step 1: 去除首尾空格 s = s.trim();

        // Step 2: 分割字符串
        String[] words = s.split("\\s+");
    
        // Step 3: 反转列表
        StringBuilder result = new StringBuilder();
        for (int i = words.length - 1; i >= 0; i--) {
            result.append(words[i]);
            if (i > 0) {
                result.append(" ");
            }
        }
    
        // Step 4: 返回结果
        return result.toString();
    }

    }

    C++ 实现

    #include #include #include #include using namespace std;

    class Solution { public: string reverseWords(string s) { // Step 1: 去除首尾空格 trim(s);

        // Step 2: 分割字符串
        istringstream iss(s);
        vector words;
        string word;
        while (iss >> word) {
            words.push_back(word);
        }
    
        // Step 3: 反转列表
        reverse(words.begin(), words.end());
    
        // Step 4: 拼接字符串
        string result;
        for (int i = 0; i < words.size(); i++) {
            result += words[i];
            if (i < words.size() - 1) {
                result += " ";
            }
        }
    
        return result;
    }

    private: void trim(string& s) { int start = 0, end = s.size() - 1; while (start <= end && s[start] == ' ') start++; while (end >= start && s[end] == ' ') end--; s = s.substr(start, end - start + 1); } };

    希望这个详细的解答能帮助你理解和解决这道题目。如果有任何进一步的问题,欢迎继续提问!

  • Maximum Product Subarray,LeetCode,152

    题目描述

    给定一个整数数组 nums,找出一个连续子数组(至少包含一个数字),使得该子数组的乘积最大。

    示例

    输入: [2,3,-2,4] 输出: 6 解释: 子数组 [2,3] 的乘积最大为 6。

    解题思路

    这道题可以使用动态规划来解决。由于数组中可能包含负数,我们需要维护两个变量:max_productmin_product,分别表示当前的最大乘积和最小乘积。因为负数乘以负数会变成正数,所以当前的最小乘积可能会成为未来的最大乘积。

    具体步骤

    1. 初始化 max_productmin_product 为数组的第一个元素。
    2. 遍历数组,对于每个元素 num
      • 如果 num 是负数,交换 max_productmin_product
      • 更新 max_productmax(num, max_product * num)
      • 更新 min_productmin(num, min_product * num)
      • 更新全局最大乘积 resultmax(result, max_product)
    3. 返回 result

    代码实现

    def maxProduct(nums): if not nums: return 0

    max_product = min_product = result = nums[0]
    
    for num in nums[1:]:
        if num < 0:
            max_product, min_product = min_product, max_product
    
        max_product = max(num, max_product * num)
        min_product = min(num, min_product * num)
    
        result = max(result, max_product)
    
    return result

    示例

    nums = [2, 3, -2, 4] print(maxProduct(nums)) # 输出: 6

    解释

    1. 初始化时,max_productmin_productresult 都设置为数组的第一个元素 2
    2. 遍历数组:
      • 对于 3
        • max_product 更新为 max(3, 2 * 3) = 6
        • min_product 更新为 min(3, 2 * 3) = 3
        • result 更新为 max(2, 6) = 6
      • 对于 -2
        • 由于 -2 是负数,交换 max_productmin_product,得到 max_product = 3, min_product = 6
        • max_product 更新为 max(-2, 3 * -2) = -2
        • min_product 更新为 min(-2, 6 * -2) = -12
        • result 保持为 6(因为 -2 小于 6
      • 对于 4
        • max_product 更新为 max(4, -2 * 4) = 4
        • min_product 更新为 min(4, -12 * 4) = -48
        • result 保持为 6(因为 4 小于 6

    最终返回的 result6,即子数组 [2, 3] 的乘积。

    复杂度分析

    • 时间复杂度:O(n),其中 n 是数组的长度,因为我们只需要遍历一次数组。
    • 空间复杂度:O(1),因为我们只需要常数级别的额外空间来存储变量。

    这种方法有效地处理了数组中包含负数的情况,确保能够找到最大的子数组乘积。

  • LRU Cache,LeetCode,146

    LRU Cache(最近最少使用缓存)是计算机科学中的一种缓存淘汰策略,用于在内存有限的情况下,自动淘汰最久未使用的数据,以便为新的数据腾出空间。LeetCode上的第146题就是关于实现一个LRU缓存机制的问题。

    题目描述

    LeetCode 146题的描述如下:

    运用你所掌握的数据结构,设计和实现一个  LRU (最近最少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put
    • 获取数据 get(key) – 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
    • 写入数据 put(key, value) – 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。

    要求

    • 你是否可以在 O(1) 时间复杂度内完成这两种操作?

    示例

    LRUCache cache = new LRUCache( 2 / 缓存容量 / );

    cache.put(1, 1); cache.put(2, 2); cache.get(1); // 返回 1 cache.put(3, 3); // 该操作会使得关键字 2 作废 cache.get(2); // 返回 -1 (未找到) cache.put(4, 4); // 该操作会使得关键字 1 作废 cache.get(1); // 返回 -1 (未找到) cache.get(3); // 返回 3 cache.get(4); // 返回 4

    解题思路

    为了实现 O(1) 时间复杂度的 getput 操作,我们需要使用一种高效的数据结构。哈希表可以提供 O(1) 的查找时间,但无法维护元素的顺序;双向链表可以维护元素的顺序,但查找时间为 O(n)。因此,我们可以结合这两种数据结构,使用 哈希表 + 双向链表 的方式来实现 LRU 缓存。

    具体实现如下:

    1. 哈希表:用于存储键到双向链表节点的映射,以便快速查找节点。
    2. 双向链表:用于维护节点的使用顺序,最近使用的节点放在链表头部,最久未使用的节点放在链表尾部。

    代码实现(Python)

    class ListNode: def init(self, key=0, value=0): self.key = key self.value = value self.prev = None self.next = None

    class LRUCache: def init(self, capacity: int): self.capacity = capacity self.cache = {} self.head = ListNode() self.tail = ListNode() self.head.next = self.tail self.tail.prev = self.head

    def get(self, key: int) -> int:
        if key in self.cache:
            node = self.cache[key]
            self._move_to_head(node)
            return node.value
        return -1
    
    def put(self, key: int, value: int) -> None:
        if key in self.cache:
            node = self.cache[key]
            node.value = value
            self._move_to_head(node)
        else:
            if len(self.cache) >= self.capacity:
                self._remove_tail()
            new_node = ListNode(key, value)
            self.cache[key] = new_node
            self._add_to_head(new_node)
    
    def _add_to_head(self, node):
        node.prev = self.head
        node.next = self.head.next
        self.head.next.prev = node
        self.head.next = node
    
    def _remove_node(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def _move_to_head(self, node):
        self._remove_node(node)
        self._add_to_head(node)
    
    def _remove_tail(self):
        node = self.tail.prev
        self._remove_node(node)
        del self.cache[node.key]

    示例使用

    cache = LRUCache(2) cache.put(1, 1) cache.put(2, 2) print(cache.get(1)) # 返回 1 cache.put(3, 3) # 该操作会使得关键字 2 作废 print(cache.get(2)) # 返回 -1 (未找到) cache.put(4, 4) # 该操作会使得关键字 1 作废 print(cache.get(1)) # 返回 -1 (未找到) print(cache.get(3)) # 返回 3 print(cache.get(4)) # 返回 4

    解释

    1. ListNode 类:定义双向链表的节点,包含键、值以及前驱和后继指针。
    2. LRUCache 类
      • __init__:初始化缓存容量、哈希表、双向链表的头尾节点。
      • get:查找键对应的节点,如果存在则移动到链表头部,返回值;否则返回 -1。
      • put:如果键存在则更新值并移动到链表头部;如果键不存在则添加新节点到链表头部,并检查是否需要淘汰最久未使用的节点。
      • _add_to_head_remove_node_move_to_head_remove_tail:辅助函数,用于操作双向链表。

    通过这种方式,我们确保了 getput 操作的时间复杂度均为 O(1)。