Leetcode 刷题笔记(三十) ——动态规划篇之子序列问题:回文

文章目录

  • 系列文章目录
  • 前言
  • 题录
    • 647. 回文子串
    • 5. 最长回文子串
    • 516. 最长回文子序列
    • 132. 分割回文串 II

系列文章目录

一、 数组类型解题方法一:二分法
二、数组类型解题方法二:双指针法
三、数组类型解题方法三:滑动窗口
四、数组类型解题方法四:模拟
五、链表篇之链表的基础操作和经典题目
六、哈希表篇之经典题目
七、字符串篇之经典题目
八、字符串篇之 KMP
九、解题方法:双指针
十、栈与队列篇之经典题目
十 一、栈与队列篇之 top-K 问题
十 二、二叉树篇之二叉树的前中后序遍历
十 三、二叉树篇之二叉树的层序遍历及相关题目
十 四、二叉树篇之二叉树的属性相关题目
十 五、 二叉树篇之二叉树的修改与构造
十 六、 二叉树篇之二叉搜索树的属性
十 七、二叉树篇之公共祖先问题
十 八、二叉树篇之二叉搜索树的修改与构造
十 九、回溯算法篇之组合问题
二 十、回溯算法篇之分割、子集、全排列问题
二十一、贪心算法篇之入门题目
二十二、贪心算法篇之进阶题目
二十三、动态规划篇之基础题目
二十四、动态规划篇之背包问题:01背包
二十五、动态规划篇之背包问题:完全背包
二十六、动态规划篇之经典问题:打家劫舍
二十七、动态规划篇之买股票问题(一)
二十八、动态规划篇之子序列问题:连续子序列和不连续子序列
二十九、动态规划篇之子序列问题:编辑距离
更新中 …


前言

Leetcode 刷题笔记(三十) ——动态规划篇之子序列问题:回文_第1张图片
刷题路线来自 :代码随想录

题录

647. 回文子串

Leetcode 链接
Leetcode 刷题笔记(三十) ——动态规划篇之子序列问题:回文_第2张图片
题解:
方式一:动态规划
dp[i][j]: i 表示子串起始下标,j 表示子串结尾下标,该子串是否为字符串。
递推公式:
dp[i][j] 分两种情况

  1. 子串长度(j - i)小于等于 2,如 a、aa, 只要起始字符等于结尾字符就是回文串
  2. 子串长度大于 2,如 a…a,在起始字符等于结尾字符的同时,还要保证 … 是回文串再行,而 … 是不是回文串由 dp[i + 1][j - 1] 状态决定。

注意遍历顺序结构: 第一层(起始位置)需要倒着遍历,因为首尾字符相同时,需要查看 i +1 开始 j - 1 结尾的状态值,也就是 dp[i + 1][j - 1] 状态,如果正着遍历,i + 1 下层的状态还没有更新;第二点:i,j 的含义决定第二次遍历顺序应从 j = i 开始,结尾下标 j 应大于等于起始下标 i。
返回值: 回文子串数目另外记录

class Solution {
    public int countSubstrings(String s) {
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        int res = 0;
        for (int i = len - 1; i >= 0; i--) {  // 注意从后向前遍历
            for (int j = i; j < len; j++) {
            	// 判断首尾字符是否相等
                if (s.charAt(i) == s.charAt(j)) {
                	// 情况一和情况二合起来写了,简洁一点
                    if (j - i <= 1 || dp[i + 1][j - 1] == true) {
                        dp[i][j] = true;
                        res++;
                    }
                }
                // 不相等不用处理,默认为 false
            }
        }
        return res;
    }
}

方式二:双指针法
先确定中间子串,如果中间子串为回文串,子串区间每次左右各扩大 1。
中间初始子串:由 1 个字符或两个字符组成。

class Solution {
    public int countSubstrings(String s) {
        int len = s.length();
        int res = 0;
        for (int i = 0; i < len; i++) {
        	// 1 个字符作为中心子串
            res += extend(s, i, i, len);
            // 两个字符作为中心子串
            res += extend(s, i, i + 1, len);
        }
        return res;
    }

    public int extend(String s, int i, int j, int len) {
        int result = 0;
        while (i >= 0 && j < len && s.charAt(i) == s.charAt(j)) {
        	// 不越界情况下,新增首尾两个字符相同,继续扩大区间
            i--;
            j++;
            result++;
        }
        return result;
    }
}

5. 最长回文子串

Leetcode 链接
Leetcode 刷题笔记(三十) ——动态规划篇之子序列问题:回文_第3张图片

题解:
和上题类似,这里是求最长回文串
方式一:动态规划
同上题,是回文串时记录最大长度,和最大长度对应的起始末尾下标。

class Solution {
    public String longestPalindrome(String s) {
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        int maxLen = 0;
        int start = 0;
        int end = 0;

        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
            	// 首尾字符相同
                if (s.charAt(i) == s.charAt(j)) {	
                	// 情况一和情况二
                    if (j - i <= 1 || dp[i + 1][j - 1] == true) {
                    	// 当前子串是回文串,更新当前状态
                        dp[i][j] = true;
                        // 回文子串最大长度判断
                        if (j - i + 1 > maxLen) {
                            maxLen = j - i + 1;
                            start = i;
                            end = j;
                        }
                    }
                }
            }
        }
        return s.substring(start, end + 1);
    }
}

方式二:双指针

class Solution {
    public String longestPalindrome(String s) {
        int size = s.length();
        // 记录最长回文串的起始下表和结尾下标
        int start = 0;
        int end = 0;
        for (int i = 0; i < size; i++) {
            int len1 = extend(s, i, i, size);
            int len2 = extend(s, i, i + 1, size);
            // 取最大长度情况
            int len = Math.max(len1, len2);
            // 判断 start 和 end 是否需要更新
            if (len > end - start) {
            	// 知道中心串下标 i 和长度求 start 和 end 
                start = i - (len - 1) / 2;
                end = start + len - 1;
            }
        }
        return s.substring(start, end + 1);
    }

    public int extend(String s, int i, int j, int size) {
        while (i >= 0 && j < size && s.charAt(i) == s.charAt(j)) {
            i--;
            j++;
        }
        // 注意,abc,i = 0, j = 2 跳出的循环,所以回文串长度应该为 j - i - 1,而不是 j - i + 1。
        return j - i - 1;
    }
}

516. 最长回文子序列

Leetcode 链接
Leetcode 刷题笔记(三十) ——动态规划篇之子序列问题:回文_第4张图片
题解:
注意这里是子序列,指的是不连续的回文串。
状态 dp[i][j]: 表示下标从 i 到 j 的该子串的最长回文子序列长度
递推公式: 首尾字符相等时 dp[i][j] = dp[i + 1][j - 1] + 2,不相等时dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1])
举个例子:
a…b 时:a 不等于 b,a…b 的最长回文子序列应该的 a… 和 …b 中的最大值。
a…a 时:a 等于 a,a…a 的最长回文子序列应该是 … 的最长回文子序列长度加上 2 (表示首尾新增的这两个 a)。

class Solution {
    public int longestPalindromeSubseq(String s) {
        int len = s.length();
        int[][] dp = new int[len + 1][len + 1];
        for (int i = 0; i < len; i++) dp[i][i] = 1;
        for (int i = len - 1; i >= 0; i--) { 
            for (int j = i + 1; j < len; j++) {
                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][len - 1];
    }
}

132. 分割回文串 II

Leetcode 链接
Leetcode 刷题笔记(三十) ——动态规划篇之子序列问题:回文_第5张图片
题解:
131. 分割回文串 是一道回溯题目,在回溯篇有题解
本题在分割数组时用到动态规划,在判断是否回文时也使用动态规划。后者前边题目已经做过多次, 重点在前者,难点在弄清分割和判断回文的下标,所以这里举例说明。

判断回文先使用双指针法。
状态 dp[i]:以 i -1 结尾的子串最小分割次数
例子:aabb
dp 数组:[-1,0,1,2,3]
i 表示 dp 数组下标,i - 1 为子串结尾下标,j 表示新增分割线下标
i = 4 时子串就为 aabb
j = 0: | aabb , aabb 不是回文串 dp[4] 值不变 dp[4] = 3
j = 1:a | abb , abb 不是回文串 dp[4] 值不变 dp[4] = 3
j = 2:aa | bb , bb 是回文串 dp[4] 值取 aa 状态 dp[2] + 1(1 表示分割一次) 和 原来dp[4] 最小值
j = 3:aab | b , b 是回文串 dp[4] 值取 aab 状态 dp[3] + 1 和 原来dp[4] 最小值

初始化每个状态为分割次数最大值:aabb 、dp 数组: [-1,0,1,2,3]

class Solution {
    public int minCut(String s) {
        int len = s.length(); 

        int[] dp = new int[len + 1];
        // 初始化:如: aabb  dp 数组:[-1,0,1,2,3]
        for (int i = 0; i <= len; i++) {
            dp[i] = i - 1;
        }
		// i = 1 时表示子串为 a ,不用分割,所以 i 从 2 开始
        for (int i = 2; i <= len; i++) {
            for (int j = 0; j < i; j ++) {
                if (isPal(s, j, i - 1)) {
                    dp[i] = Math.min(dp[j] + 1, dp[i]);
                }
            }
        }
        return dp[len];
    }
    // 判断回文
    public boolean isPal(String s, int start, int end) {
        while (start < end) {
            if (s.charAt(start) != s.charAt(end)) {
                return false;
            }
            start++;
            end--;
        }
        return true;
    }
}

双 dp:

class Solution {
    public int minCut(String s) {
        int len = s.length(); 
        int[] dp = new int[len + 1];
        boolean[][] mat = getMat(s, len);
        
        for (int i = 0; i <= len; i++) {
            dp[i] = i - 1;
        }
        for (int i = 2; i <= len; i++) {
            for (int j = 0; j < i; j++) {
                if (mat[j][i - 1]) {
                    dp[i] = Math.min(dp[j] + 1, dp[i]);
                }
            }
        }
        return dp[len];
    }
    // 回文串的判断
    public boolean[][] getMat(String s, int len) {
        boolean[][] dp = new boolean[len][len];
        for (int i = len - 1; i >= 0; i--) {
            for (int j = i; j < len; j++) {
                if (s.charAt(i) == s.charAt(j)) {
                    if (j - i <= 1 || dp[i + 1][j - 1] == true) {
                        dp[i][j] = true;
                    }
                }
            }
        }
        return dp;
    }
}

你可能感兴趣的:(算法,算法,动态规划,leetcode)