回文子序列问题解题模板

回文子序列问题解题模板及本质


回文子序列问题的本质:

  • 回文子序列是一个动态规划相关的经典问题,其目标是寻找一个序列中的最长回文子序列。
  • 回文子序列的本质特点:
    • 回文的定义:从左到右读与从右到左读一样。
    • 子序列的定义:不用连续,但保持相对位置。
  • 核心思想:
    • 通过状态转移和子问题划分解决问题。
    • 回文的性质通常反映在递归和动态规划的左右扩展关系中。

经典问题及解题模板

1. Leetcode 516. 最长回文子序列

问题描述:

给定一个字符串 s,寻找其中最长的回文子序列的长度。

解法特点:

  • 使用动态规划解决问题,通过判断两端字符是否相等来决定状态转移。
  • 经典的二维动态规划问题。

解题模板:动态规划
核心步骤:
  1. 定义状态:
    • 定义 dp[i][j] 表示字符串 s[i...j] 的最长回文子序列长度。
    • i 表示起始位置,j 表示结束位置。
  2. 状态转移方程:
    • 如果 s[i] == s[j]

      dp\[i\]\[j\] = dp\[i+1\]\[j-1\] + 2

      两端字符可以构成回文的一部分。
    • 如果 s[i] != s[j]

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

      两端字符无法同时参与回文,则选择舍弃一侧。
  3. 初始化:
    • 对于单字符回文:dp[i][i] = 1
    • 对于空区间:dp[i][j] = 0j < i)。
  4. 结果:
    • 答案是 dp[0][n-1],即从字符串的起点到终点的最长回文子序列长度。

代码模板:动态规划
java 复制代码
class Solution {
    public int longestPalindromeSubseq(String s) {
        int n = s.length();
        // 初始化 DP 数组
        int[][] dp = new int[n][n];
        
        // 初始化单字符的情况
        for (int i = 0; i < n; i++) {
            dp[i][i] = 1; // 每个单字符自身构成回文
        }
        
        // 从短区间向长区间递推
        for (int len = 2; len <= n; len++) { // 子区间长度
            for (int i = 0; i <= n - len; i++) {
                int j = i + len - 1; // 计算右边界
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2; // 两端相等
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); // 两端不相等
                }
            }
        }
        
        return dp[0][n - 1]; // 全区间最长回文长度
    }
}

复杂度分析

  1. 时间复杂度:

    • 外层子区间循环需要进行 (O(n^2)) 计算。
    • 每次计算时,只需根据公式访问相关状态。
    • 总时间复杂度为 (O(n^2))。
  2. 空间复杂度:

    • 二维 DP 数组占用 (O(n^2))。

本问题扩展:逻辑与技巧

2. Leetcode 1312. 让字符串成为回文的最少插入次数

问题描述:

给定一个字符串 s,返回将其转化为回文字符串的最少插入次数。

本质和解法:

  • 本质:求一个字符串的"最少插入次数",实际上可以转化为寻找"最长回文子序列"的补集长度。
  • 公式关系:
    最少插入次数 = 字符串长度 - 最长回文子序列的长度。

代码模板:动态规划
java 复制代码
class Solution {
    public int minInsertions(String s) {
        int n = s.length();
        // 这里复用最长回文子序列的 DP 定义
        int[][] dp = new int[n][n];
        
        // 初始化单字符情况
        for (int i = 0; i < n; i++) {
            dp[i][i] = 1;
        }
        
        // 从短区间向长区间递推
        for (int len = 2; len <= n; len++) {
            for (int i = 0; i <= n - len; i++) {
                int j = i + len - 1;
                if (s.charAt(i) == s.charAt(j)) {
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                } else {
                    dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]);
                }
            }
        }
        
        return n - dp[0][n - 1]; // 补集长度
    }
}

3. Leetcode 647. 回文子串

问题描述:

给定一个字符串 s,计算该字符串中有多少个回文子串


代码模板:动态规划解决回文子串问题
  • 核心思路:
    通过动态规划记录每个子区间是否是回文。
java 复制代码
class Solution {
    public int countSubstrings(String s) {
        int n = s.length();
        boolean[][] dp = new boolean[n][n]; // 定义 DP 表:dp[i][j] 表示 s[i...j] 是否是回文
        int count = 0; // 记录回文子串的数量
        
        // 遍历子区间长度
        for (int len = 1; len <= n; len++) {
            for (int i = 0; i <= n - len; i++) {
                int j = i + len - 1; // 计算右边界
                
                if (len == 1) { // 单字符回文
                    dp[i][j] = true;
                } else if (len == 2) { // 两个字符,仅限相等情况
                    dp[i][j] = s.charAt(i) == s.charAt(j);
                } else { // 多字符的情况
                    dp[i][j] = s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1];
                }
                
                if (dp[i][j]) {
                    count++; // 如果是回文子串,计数加一
                }
            }
        }
        
        return count; // 返回总数
    }
}

如何快速写出 AC 代码

  1. 明确子问题的递归关系:右边界和子问题依赖;
  2. 初始化边界条件,例如单字符、空区间;
  3. 状态转移的方向是从短区间到长区间,逐步递推;
  4. 边界处理清晰,提升鲁棒性。

典型例题总结

  1. 最长回文子序列问题: Leetcode 516
  2. 最少插入次数: Leetcode 1312
  3. 回文子串数量: Leetcode 647
  4. 问题转化扩展: 通过 DP 模板处理多种回文字符串问题,灵活应用递归关系。

通过动态规划模板可以快速实现并解决回文相关问题,适配多种场景,非常适合面试。

相关推荐
这儿有一堆花3 分钟前
比特币:固若金汤的数字堡垒与它的四道防线
算法·区块链·哈希算法
客卿1238 分钟前
力扣100-移动0
算法·leetcode·职场和发展
CM莫问3 小时前
<论文>(微软)WINA:用于加速大语言模型推理的权重感知神经元激活
人工智能·算法·语言模型·自然语言处理·大模型·推理加速
计信金边罗5 小时前
是否存在路径(FIFOBB算法)
算法·蓝桥杯·图论
MZWeiei5 小时前
KMP 算法中 next 数组的构建函数 get_next
算法·kmp
Fanxt_Ja6 小时前
【JVM】三色标记法原理
java·开发语言·jvm·算法
luofeiju7 小时前
行列式的性质
线性代数·算法·矩阵
緈福的街口7 小时前
【leetcode】347. 前k个高频元素
算法·leetcode·职场和发展
半桔7 小时前
【Linux手册】冯诺依曼体系结构
linux·缓存·职场和发展·系统架构
pen-ai7 小时前
【统计方法】基础分类器: logistic, knn, svm, lda
算法·机器学习·支持向量机