又是经典DP,又是没做对。挫败
上来看错题了,以为是求分割方法,呼呼呼几下算法切出来,结果aaaaaaa那个超长数据集超时,这才看题。兵家大忌!看清题目!!
求最小切的次数。
我觉的算法题,千万不要一上来就陷入一个局部问题去考虑某某地方怎么优化,而应该是从全局的角度考虑问题的思路,确定复杂度,确定了复杂度,你才能明白哪里是关键,再去有针对性的优化。否则,这边还是O(N^3)着呢,你去优化O(N)的地方到了O(lgN)?蛋疼。
而且上来不要考虑最优解,很容易让你陷入死胡同,简单来说,就是上来不要怕麻烦,先来个复杂解,再想办法用记忆,用DP,用dfs bfs来优化。这才是根本。
最后来看这个题
Palindrome Partitioning II
:
求最小分割次数,最笨的方法,先求出来可达矩阵,即任何两点之间是不是回文先判断出来。
然后就是走路了,最笨的方法就是从0开始走,用贪心尽量走得远,走到len就是一个解,最后比比最小的解就是最终结果。
毫无疑问,超时。
超时解:
char[] cc;
int len;
int[] count;
int[][] isp;
public boolean jisp(int start,int end){
int ost=start;
Boolean res=null;
while(start<=end){
if(cc[start]==cc[end]){
if(end-start<=1){
res=true;
break;
}
if(isp[start+1][end-1]==1){
res=true;
break;
}
}else
return false;
start++;
end--;
}
if(res)
while(ost<=start){
isp[start][end]=1;
start--;
end++;
}
return res;
}
public int dp(int start){
if(count[start]!=0)
return count[start];
if(start>=len)
return 0;
int currentC=Integer.MAX_VALUE;
for(int i=len-1;i>=start;i--)
if(isp[start][i]==1||jisp(start,i)){
int son=dp(i+1);
if(currentC>son+1)
currentC=son+1;
}
count[start]=currentC;
return count[start];
}
public int minCut(String s) {
cc=s.toCharArray();
len=cc.length;
count=new int[len+3];
isp=new int[len][len];
return dp(0)-1;
}
这里面还用了点技巧,求回文的时候用一个isp来存储尝试的结果,如果一个start 到end是回文,那么,从start++ 到end-- 循环里面的都是回文。
但这个可以继续改进,分析一下复杂度,其实dp里面已经是O(N)了,因为存在记忆,所以dp(0~len)算一遍拉到,这个题的关键在哪里也就呼之欲出了:求回文的方法。
回头看一下我这个求回文,优化还不够,面对那个大数据的aaaabbaaaa有个问题就是很多时候求回文求到最后发现原来不是回文,下次还要从新判,浪费了大量时间,所以fail fast一下,果然就通过了
char[] cc;
int len;
int[] count;
int[][] isp;
public boolean jisp(int start, int end) {
if (isp[start][end] == -1)
return false;
if (isp[start][end] == 1)
return true;
int ost = start;
int oed = end;
Boolean res = null;
while (start <= end) {
if (cc[start] == cc[end]) {
if (end - start <= 1) {
res = true;
break;
}
if (isp[start + 1][end - 1] == 1) {
res = true;
break;
}
} else {
res = false;
break;
}
start++;
end--;
}
if (res)
while (ost <= start) {
isp[start][end] = 1;
start--;
end++;
}
if (!res)
while (ost <= start) {
isp[start][end] = -1;
start--;
end++;
}
return res;
}
public int dp(int start) {
if (count[start] != 0)
return count[start];
if (start >= len)
return 0;
int currentC = Integer.MAX_VALUE;
for (int i = len - 1; i >= start; i--)
if (isp[start][i] == 1 || jisp(start, i)) {
int son = dp(i + 1);
if (currentC > son + 1)
currentC = son + 1;
}
count[start] = currentC;
return count[start];
}
public int minCut(String s) {
cc = s.toCharArray();
len = cc.length;
count = new int[len + 3];
isp = new int[len][len];
return dp(0) - 1;
}
基本没怎么改,就是把false的时候,外圈也全部false了,下次判的时候就不用再从新遍历了。
但是看看神的解,还是有差距啊
public class Solution {
public int minCut(String s) {
int[][] dp=new int[s.length()][s.length()];
int[] count=new int[s.length()+1];
for(int i=s.length()-1;i>=0;i--)
{
count[i]=Integer.MAX_VALUE;
for(int j=i;j<s.length();j++)
{
if(s.charAt(i)==s.charAt(j)&&(j-i<2||dp[i+1][j-1]==1))
{
dp[i][j]=1;
count[i]=Math.min(1+count[j+1],count[i]);
}
}
}
return count[0]-1;
}
}
估计不解释下下次自己在看都看不懂了,循环中的那两行。
第一行是在判可达矩阵,第二行是在求当前的最短切割。外圈i是start,内圈j是end
但是首先要判start-end是否是回文,回文判的时候用了记忆:
1. end-start<2 直接有结果
2. >2时,只看start-1 end+1 为什么可以?因为start-1外圈i已经转过,end+1那时候肯定也判定过,所以start-1
end+1不是就不是,是就一定是
一旦判定是回文,然后记录下来(为了求下一个外圈)。
同时算count,count记录的就是最优子结构:从count【i】开始切几下就好。容易得到状态专一方程
f(i)=Math.min(f[j+1]+1)&&dp[i][j]==1;
所以那一行程序很好理解
参考链接
http://blog.youkuaiyun.com/yutianzuijin/article/details/16850031
最后推荐一下 神降临 吉木
code rewrite
int len;
char[] cc;
public int minCut(String s) {
len = s.length();
cc = s.toCharArray();
byte[][] dp=new byte[len+5][len+5];
int[] minjump = new int[len + 5];
for (int i = 0; i < minjump.length; i++)
minjump[i] = Integer.MAX_VALUE;
minjump[len] = 0;
for (int i = len - 1; i >= 0; i--) {
int currentMinj = Integer.MAX_VALUE;
for(int j=len-1;j>=i;j--){
if((j-i<=1||dp[i+1][j-1]==1)&&cc[i]==cc[j]){
dp[i][j]=1;
currentMinj=Math.min(currentMinj,minjump[j+1]);
}
}
minjump[i]=currentMinj+1;
}
return minjump[0] - 1;
}