1. 动态规划进阶:子序列与回文串问题解析
动态规划作为算法设计中的重要方法,在处理子序列和回文串问题时展现出独特的优势。本文将深入探讨这两类问题的解法思路和实现细节,帮助读者掌握动态规划在这些场景下的应用技巧。
1.1 子序列问题概述
子序列问题要求我们在不改变元素相对顺序的前提下,从原始序列中选取元素组成新序列。与子数组不同,子序列的元素不需要连续。这类问题通常需要找到满足特定条件的最长子序列,如递增、等差或斐波那契性质。
动态规划解决子序列问题的关键在于:
- 状态定义:通常以dp[i]表示以第i个元素结尾的子序列的最优解
- 状态转移:根据问题特性设计转移方程,考虑前驱状态的影响
- 初始化:处理边界情况,如单个元素的子序列
- 填表顺序:根据状态依赖关系确定计算顺序
1.2 回文串问题特点
回文串问题关注字符串及其子串的对称性质。动态规划在此类问题中的应用主要体现在:
- 预处理阶段:构建二维DP表记录所有子串的回文信息
- 状态转移:利用回文串的对称性减少重复计算
- 空间优化:根据问题特点优化存储结构
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 子序列问题详解
2.1 最长递增子序列(LIS)
最长递增子序列问题是动态规划的经典案例。给定一个无序的整数数组,找到其中最长的严格递增子序列的长度。
2.1.1 基本解法
标准解法使用O(n²)时间复杂度的动态规划:
- 定义dp数组,dp[i]表示以nums[i]结尾的最长递增子序列长度
- 初始化所有dp[i]为1,因为每个元素自身构成长度为1的子序列
- 对于每个i,遍历所有j < i,如果nums[i] > nums[j],则更新dp[i]
java复制public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
int max = 1;
for (int i = 1; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
max = Math.max(max, dp[i]);
}
return max;
}
2.1.2 优化解法
可以使用二分查找将时间复杂度优化到O(nlogn):
- 维护一个tails数组,其中tails[i]表示长度为i+1的所有递增子序列的最小末尾元素
- 对于每个元素,使用二分查找确定其在tails中的位置并更新
java复制public int lengthOfLIS(int[] nums) {
int[] tails = new int[nums.length];
int size = 0;
for (int num : nums) {
int i = 0, j = size;
while (i != j) {
int m = (i + j) / 2;
if (tails[m] <
