用动态规划和回溯法解决 LeetCode 491: 递增子序列
2023-09-22 09:42:33
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]。
言归正传,本题的目的是找出给定数组中所有不同的递增子序列。我们可以将问题分解为子问题:对于数组中的每个元素,我们有两个选择:将它加入到当前递增子序列中,或者不加入。如果我们选择将它加入,那么我们递归地找出以这个元素结尾的所有递增子序列;如果不加入,那么我们递归地找出不包含这个元素的所有递增子序列。
思路明确后,我们可以使用回溯法来求解。回溯法是一种深度优先搜索算法,它通过系统地探索所有可能的解,找到满足约束条件的解。回溯法的核心在于:在每一次递归调用中,我们尝试做出一个选择,然后递归地探索这个选择所导致的所有可能性。如果这个选择导致了一个死胡同,我们就回溯到上一次递归调用,尝试另一个选择。
具体到本题,我们可以使用以下步骤实现回溯法:
- 初始化一个空列表
res
来存储结果。 - 对于数组中的每个元素
nums[i]
:- 如果
nums[i]
比res
中最后一个元素大,那么我们将它加入res
,并递归地找出以nums[i]
结尾的所有递增子序列。 - 如果
nums[i]
小于或等于res
中最后一个元素,那么我们跳过它,继续递归地找出不包含nums[i]
的所有递增子序列。
- 如果
- 重复步骤 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。
总结
递增子序列问题是动态规划和回溯法中常见的一种问题类型。通过将问题分解成子问题并使用回溯法系统地探索所有可能的解,我们可以有效地找出给定数组中所有不同的递增子序列。这种思想在解决其他类似问题时也具有广泛的适用性。