返回

用动态规划和回溯法解决 LeetCode 491: 递增子序列

后端

LeetCode 491: 递增子序列

这个时代,动态规划 DP 算法绝对是数据结构和算法面试中不得不提的重头戏。那么,什么是动态规划呢?简单来说,它是一种通过将一个复杂的问题分解成若干个小问题的递归方法,同时保存子问题的解决方案,避免重复计算。其中,最常见的动态规划问题类型之一就是递增子序列问题,比如今天 LeetCode 上的这道题目:491. 递增子序列。

在讲解具体解法之前,我们先来明确一下什么是递增子序列。递增子序列是指从给定序列中选择一个或多个元素形成的新序列,且新序列中的元素在原始序列中的顺序与原始序列中的顺序相同。例如,序列 [1, 3, 5, 6] 的递增子序列包括 [1], [1, 3], [1, 3, 5], [1, 3, 5, 6], [3], [3, 5], [3, 5, 6], [5], [5, 6], [6]。

言归正传,本题的目的是找出给定数组中所有不同的递增子序列。我们可以将问题分解为子问题:对于数组中的每个元素,我们有两个选择:将它加入到当前递增子序列中,或者不加入。如果我们选择将它加入,那么我们递归地找出以这个元素结尾的所有递增子序列;如果不加入,那么我们递归地找出不包含这个元素的所有递增子序列。

思路明确后,我们可以使用回溯法来求解。回溯法是一种深度优先搜索算法,它通过系统地探索所有可能的解,找到满足约束条件的解。回溯法的核心在于:在每一次递归调用中,我们尝试做出一个选择,然后递归地探索这个选择所导致的所有可能性。如果这个选择导致了一个死胡同,我们就回溯到上一次递归调用,尝试另一个选择。

具体到本题,我们可以使用以下步骤实现回溯法:

  1. 初始化一个空列表 res 来存储结果。
  2. 对于数组中的每个元素 nums[i]:
    • 如果 nums[i]res 中最后一个元素大,那么我们将它加入 res,并递归地找出以 nums[i] 结尾的所有递增子序列。
    • 如果 nums[i] 小于或等于 res 中最后一个元素,那么我们跳过它,继续递归地找出不包含 nums[i] 的所有递增子序列。
  3. 重复步骤 2,直到遍历完所有元素。

下面是 Python 代码实现:

def findSubsequences(nums):
    res = []
    backtrack(nums, [], res)
    return res

def backtrack(nums, path, res):
    if len(path) >= 2:
        res.append(path[:])

    for i in range(len(nums)):
        if not path or nums[i] >= path[-1]:
            path.append(nums[i])
            backtrack(nums[i+1:], path, res)
            path.pop()

时间复杂度:O(2^N),其中 N 为数组的长度。在最坏的情况下,对于数组中的每个元素,我们都需要递归地探索两种可能性:加入或不加入该元素。

空间复杂度:O(N),其中 N 为数组的长度。回溯过程中,我们使用栈来保存当前的递归路径,栈的最大深度为 N。

总结

递增子序列问题是动态规划和回溯法中常见的一种问题类型。通过将问题分解成子问题并使用回溯法系统地探索所有可能的解,我们可以有效地找出给定数组中所有不同的递增子序列。这种思想在解决其他类似问题时也具有广泛的适用性。

SEO 优化