132. 分割回文串 II
Q:
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。
示例:
输入: “aab”
输出: 1
解释: 进行一次分割就可将 s 分割成 [“aa”,“b”] 这样两个回文子串。
A:
1.我最开始想到了要两次DP,先算一个是否是回文数的dp数组,再算所求的DP。
但第二个DP数组我用的二维数组,然后就变成了O(N^3)时间。因为对于其中每个元素都要从左边界遍历到右边界进行分割,没有想到可以利用第一个DP中的数据。
代码是对的,自己机器跑了,但AC不了,第26个用例超时:
超时的代码!:
1 class Solution: 2 def minCut(self, s: str) -> int: 3 #先算一个dp数组记录是否是回文数 4 #dp[i][j]记录s[i,j]闭区间是否是回文数 5 l=len(s) 6 if not s: 7 return 0 8 dp=[[0 for i in range(l)] for j in range(l)] 9 for i in range(l): 10 dp[i][i]=1 #一个元素的一定是回文数,递推起始条件 11 for i in range(l-1): 12 if s[i]==s[i+1]: 13 dp[i][i+1]=1 #两个相邻元素的若值相同,也为回文数 14 for step in range(2,l): 15 i=0 16 while i+step<l: 17 j=i+step #j为右边界,考察s[i,j]是否是回文数 18 #矩阵的右上半部,满足i<=j才有意义 19 dp[i][j]=int(dp[i+1][j-1] and s[i]==s[j]) 20 #当前字符串为回文数的条件:两边界截掉是回文数 21 i+=1 22 #回文数dp数组建立好,考虑对于s[i,j]字符串的最少分割次数f(i,j), 23 #若当前字符串为回文数即dp[i][j]==1则分割次数f(i,j)为0,不用割, 24 #否则可能的切割位置为i+1到j-1,假设切割位置为k(i<k<j), 25 #还需要知道f(i,k),f(k+1,j),故还需要一个求最少分割次数的dp数组 26 dp2=[[float('inf') for i in range(l)] for j in range(l)] 27 for i in range(l): 28 dp2[i][i]=0 #单个字符串不用割,其他元素初始化为无穷大 29 for step in range(1,l): #对[i,i+step]闭区间考察,i取0时,0+step应该小于l 30 i=0 31 while i+step<l: 32 if dp[i][i+step]==1: #若s[i,i+step]已是回文 33 dp2[i][i+step]=0 34 else: 35 #若s[i,i+step]不是回文,则一定要切,下面开始切 36 cur_min=float('inf') 37 for cut in range(i,i+step): 38 #找割数最小的割法 39 cur_min=min(cur_min,dp2[i][cut]+dp2[cut+1][i+step]+1) 40 dp2[i][i+step]=cur_min 41 i+=1 42 # print(dp) 43 # print(dp2) 44 return dp2[0][l-1]
2.正确代码:
第一个dp数组一样,必须是二维的。第二个dp数组一维的,dp2[i]表示从i开始到末尾的字符串的最小分割数,对于每个dp[i],在i+1到n-1找分割点k就完事了,i到k为回文串通过第一个DP数组查询,O(1) ,剩下的k到n-1是之前算好的,所以第二个DP数组要从右往左算,因为右边界不动。总体O(N^2)复杂度。
c++:
1 class Solution { 2 public: 3 #define inf INT_MAX 4 int minCut(string s) { 5 int n=s.size(); 6 vector<vector<bool>> is_huiwen(n,vector<bool>(n,false)); 7 for(int i=0;i<n;++i){is_huiwen[i][i]=true;} 8 for(int i=0;i<n-1;++i){is_huiwen[i][i+1]=(s[i]==s[i+1]?true:false);} 9 //下往上,左往右算dp 10 for(int i=n-3;i>=0;--i){ 11 for(int j=i+2;j<n;++j){ 12 is_huiwen[i][j]=(s[i]==s[j] and is_huiwen[i+1][j-1])?true:false; 13 } 14 } 15 vector<int> dp(n,inf);//dp[i]表示s截止到i最小分割次数(inf表示没法分割) 16 for(int i=0;i<n;++i){ 17 if(is_huiwen[0][i]){ 18 dp[i]=0; 19 continue; 20 } 21 for(int cut=1;cut<=i;++cut){ 22 if(is_huiwen[cut][i] and dp[cut-1]!=inf){ 23 dp[i]=min(dp[i],dp[cut-1]+1); 24 } 25 } 26 } 27 return dp[n-1]; 28 } 29 };
python:
1 class Solution: 2 def minCut(self, s: str) -> int: 3 l=len(s) #先算一个dp数组记录是否是回文,dp[i][j]记录s[i,j]闭区间是否是回文数 4 if not l: 5 return 0 6 dp=[[0 for i in range(l)] for j in range(l)] 7 for i in range(l-1,-1,-1): #从下往上,从右往左算 8 for j in range(l-1,i-1,-1): 9 if s[i]==s[j]: 10 dp[i][j]=1 if j-i<2 else dp[i+1][j-1] 11 dp2=[float('inf') for i in range(l)] #dp2[i]表示从i开始到末尾字符串的最小分割数 12 for i in range(l-1,-1,-1): 13 if dp[i][l-1]: #当前字符串是回文数 14 dp2[i]=0 15 else: 16 _min=float('inf') 17 for cut in range(i,l-1): #不同割点位置 18 if dp[i][cut]==1: 19 _min=min(dp2[cut+1]+1,_min) 20 dp2[i]=_min 21 print(dp2) 22 return dp2[0]
进击的小🐴农