When a man's stories are remembered, then he is immortal.

一个人的故事被记住了,他就千古不朽。

问题描述

来源:LeetCode第132题

难度:困难


给你一个字符串s,请你将s分割成一些子串,使每个子串都是回文。

返回符合要求的最少分割次数 。


示例 1:


输入:s = "aab"

输出:1

解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。


示例 2:


输入:s = "a"

输出:0


示例 3:


输入:s = "ab"

输出:1



提示:

  • 1 <= s.length <= 2000
  • s 仅由小写英文字母组成


动态规划解决

前面刚讲过《551,回溯算法解分割回文串》,第551要求返回所有可能分隔的结果,而这题要求返回最小的分隔次数。如果数据量不大的话我们是可以使用第551题的答案的,找出第551题所有可能分隔的方案中最小的分隔次数就是我们这题的答案。


但这题提示中明显数量比较大,所以使用回溯算法是行不通的,我们可以改成动态规划来解决。


定义dp[i]表示字符串前i个字符构成的子串能分隔的最少分隔次数,如果要求s的最少分隔次数,只需要返回dp[length-1]即可(length是字符串s的长度)。


1,如果子串从[0…i]是回文是,就不需要分隔,即

dp[i]=0;


2,如果子串从[0…i]不是回文的,我们就需要从子串[0…i]后面截取一个回文的子串[j…i],所以子串[0…i]可以分隔为[0…j-1]和[j…i],而[0…j-1]的最小分隔次数就是dp[j-1],[j…i]是一个回文串,不需要分隔,所以dp[i]=dp[j-1]+1,因为要求的是截取的最小次数,所以我们只需要保留最小的即可,即

dp[i]=min(dp[i],dp[j-1]+1);

如下图所示

553,动态规划解分割回文串 II_编程开发

因为这里求最小值,默认值我们给他每个都初始化最大的,来看下代码

1public int minCut(String s) { 2    int length = s.length(); 3    int[] dp = new int[length]; 4    //字符串s的回文子串最大也只能是字符串的长度length, 5    //所以这里都默认初始化为最大值 6    Arrays.fill(dp, length); 7    for (int i = 0; i < length; ++i) { 8        //如果字符串从0到i本身就是一个回文的,就不需要分隔, 9        //直接返回010        if (palindrome(s, 0, i)) {11            dp[i] = 0;12        } else {13            //否则就要分隔,找出最小的分隔方案14            for (int j = 1; j <= i; ++j) {15                if (palindrome(s, j, i)) {16                    dp[i] = Math.min(dp[i], dp[j - 1] + 1);17                }18            }19        }20    }21    return dp[length - 1];22}2324//判断从left到right之间的子串是否是回文的(使用双指针判断)25private boolean palindrome(String s, int left, int right) {26    while (left < right) {27        if (s.charAt(left++) != s.charAt(right--))28            return false;29    }30    return true;31}


动态规划代码优化

上面代码虽然也能给解决,但每次截取的时候都要判断一遍是否是回文的,明显效率不是很高,我们还可以先计算他的每个子串是否是回文的,在截取的时候直接用即可,回文串的判断前面刚介绍过,《540,动态规划和中心扩散法解回文子串》,这里就不在重复介绍。我们可以先把代码复制过来

1    int length = s.length(); 2    //判断子串[i…j]是否是回文串 3    boolean[][] dp = new boolean[length][length]; 4    for (int j = 0; j < length; j++) { 5        for (int i = 0; i <= j; i++) { 6            //如果i和j指向的字符不一样,那么dp[i][j]就 7            //不能构成回文字符串 8            if (s.charAt(i) != s.charAt(j)) 9                continue;10            dp[i][j] = j - i <= 2 || dp[i + 1][j - 1];11        }12    }13

再来看下最终代码

1public int minCut(String s) { 2    int length = s.length(); 3    int[] dp = new int[length]; 4 5    //判断子串[i…j]是否是回文串 6    boolean[][] palindrome = new boolean[length][length]; 7    for (int j = 0; j < length; j++) { 8        for (int i = 0; i <= j; i++) { 9            //如果i和j指向的字符不一样,那么dp[i][j]就10            //不能构成回文字符串11            if (s.charAt(i) != s.charAt(j))12                continue;13            palindrome[i][j] = j - i <= 2 || palindrome[i + 1][j - 1];14        }15    }1617    //字符串s的回文子串最大也只能是字符串的长度length,18    //所以这里都默认初始化为最大值19    Arrays.fill(dp, length);20    for (int i = 0; i < length; ++i) {21        //如果字符串从0到i本身就是一个回文的,就不需要分隔,22        //直接返回023        if (palindrome[0][i]) {24            dp[i] = 0;25        } else {26            //否则就要分隔,找出最小的分隔方案27            for (int j = 1; j <= i; ++j) {28                if (palindrome[j][i]) {29                    dp[i] = Math.min(dp[i], dp[j - 1] + 1);30                }31            }32        }33    }34    return dp[length - 1];35}


553,动态规划解分割回文串 II_动态规划_02

●540,动态规划和中心扩散法解回文子串

●529,动态规划解最长回文子序列

●517,最长回文子串的3种解决方式

●497,双指针验证回文串



截止到目前我已经写了500多道算法题了,为了方便大家阅读,我把部分算法题整理成了pdf文档,目前有1000多页,大家可以在公众号中回复关键字“pdf”即可获取下载链接。


553,动态规划解分割回文串 II_编程开发_03你点的每个赞,我都认真当成了喜欢