在算法的世界里,动态规划:子序列问题 是一类非常经典且常见的题型。 其中,最长递增子序列 (Longest Increasing Subsequence, LIS) 和摆动序列 (Wiggle Subsequence) 是两个非常典型的例子。理解并掌握这两种问题的解法,对于提升动态规划的解题能力至关重要。本文将深入探讨这两种问题的底层原理,并提供可直接使用的代码示例。
最长递增子序列(LIS)
问题场景重现
给定一个无序的整数数组,找到其中最长递增子序列的长度。
例如,对于数组 [10, 9, 2, 5, 3, 7, 101, 18],最长递增子序列是 [2, 3, 7, 101],长度为 4。
底层原理深度剖析
解决 LIS 问题,动态规划是一种高效的方法。我们定义 dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度。
状态转移方程如下:
dp[i] = max(dp[j] + 1) for all j < i and nums[j] < nums[i]
如果不存在任何 j 满足 nums[j] < nums[i],则 dp[i] = 1,表示以 nums[i] 结尾的最长递增子序列只有它自身。
最终的结果是 max(dp[i]) for all i。
代码解决方案
def longest_increasing_subsequence(nums):
n = len(nums)
if n == 0:
return 0
dp = [1] * n # 初始化 dp 数组,每个元素初始值为 1
for i in range(1, n):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp) # 返回 dp 数组中的最大值
# 示例
nums = [10, 9, 2, 5, 3, 7, 101, 18]
print(longest_increasing_subsequence(nums)) # Output: 4
实战避坑经验总结
- 初始化:
dp数组的初始化非常重要,确保每个位置的初始值正确,这里初始值为 1,表示至少包含自身。 - 边界条件:需要考虑数组为空的情况,避免空指针异常。
- 理解状态转移方程:深刻理解状态转移方程是解决动态规划问题的关键,确保能够正确地从子问题推导出当前问题的解。
摆动序列
问题场景重现
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列肯定是一个摆动序列。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。
例如,对于数组 [1, 7, 4, 9, 2, 5],一个摆动序列是 [1, 7, 4, 9, 2],长度为 5。
底层原理深度剖析
解决摆动序列问题,我们需要维护两个 dp 数组:up[i] 和 down[i]。
up[i] 表示以 nums[i] 结尾的,且最后一个差值为正的摆动序列的长度。
down[i] 表示以 nums[i] 结尾的,且最后一个差值为负的摆动序列的长度。
状态转移方程如下:
- 如果
nums[i] > nums[i-1],则up[i] = down[i-1] + 1,down[i] = down[i-1] - 如果
nums[i] < nums[i-1],则down[i] = up[i-1] + 1,up[i] = up[i-1] - 如果
nums[i] == nums[i-1],则up[i] = up[i-1],down[i] = down[i-1]
最终的结果是 max(up[n-1], down[n-1])。
代码解决方案
def wiggle_max_length(nums):
n = len(nums)
if n < 2:
return n
up = [1] * n
down = [1] * n
for i in range(1, n):
if nums[i] > nums[i - 1]:
up[i] = down[i - 1] + 1
down[i] = down[i - 1]
elif nums[i] < nums[i - 1]:
down[i] = up[i - 1] + 1
up[i] = up[i - 1]
else:
up[i] = up[i - 1]
down[i] = down[i - 1]
return max(up[n - 1], down[n - 1])
# 示例
nums = [1, 7, 4, 9, 2, 5]
print(wiggle_max_length(nums)) # Output: 5
实战避坑经验总结
- 理解 up 和 down 的含义:确保理解
up和down数组分别表示的含义,以及它们之间的关系。 - 处理相等的情况:当
nums[i] == nums[i-1]时,up[i]和down[i]保持不变。 - 边界条件:对于长度小于 2 的数组,直接返回长度即可。
掌握了最长递增子序列和摆动序列的动态规划解法,可以让你在面对复杂的子序列问题时更加得心应手。希望本文能够帮助你更好地理解动态规划的思想,并在实际项目中灵活运用。在实际开发中,服务器性能监控可以使用 Prometheus + Grafana,通过反向代理 Nginx 将请求转发到不同的服务实例上实现负载均衡。同时,也可以使用宝塔面板来简化服务器管理和部署过程。优化服务器配置,例如调整 Nginx 的 worker 进程数和并发连接数,可以显著提升系统的吞吐量和响应速度。
冠军资讯
代码一只喵