您的位置:首页 > 其它

Leetcode: Palindrome Partitioning II

2013-08-31 20:15 363 查看
参考:http://www.cppblog.com/wicbnu/archive/2013/03/18/198565.html

我太喜欢用dfs和回溯法了,但是这些暴力的方法加上剪枝之后复杂度依然是很高,显然不能达到题目的要求。

这个时候应该考虑动态规划,并且要复杂度尽量接近O(n^2)的算法。

下面这个方法更加简洁:自长到短找到回文串后,往后dfs,并记录递归深度表示并更新最小划分数。http://fisherlei.blogspot.com/2013/03/leetcode-palindrome-partitioning-ii.html

Given a string s, partition s such that every substring of the partition is a palindrome.

Return the minimum cuts needed for a palindrome partitioning of s.

For example, given s =
"aab"
,
Return
1
since the palindrome partitioning
["aa","b"]
could be produced using 1 cut.

题解:
类似矩阵连乘的动归思路。
dp[i][j]=min(dp[i][k]+dp[k+1][j]+1), i<=k<j.
但是用这个方程的时间是O(n^3),简化dp[i][j]为dp[i],表示从0到i的minCut.
dp[i]=min(dp[k]+1, dp[k]+i-k), 0<=k<i.

(s[k+1, i]是回文串) (s[k+1, i]不是回文串)

具体代码参见上述链接。

值得注意的是,计算是否为回文数的过程中也要用记忆化搜索才能减少重复比较的次数,it's smart~

MY CODE:

//
//  ParlindromePartitioningII.cpp
//  SJMcode
//
//  Created by Jiamei Shuai on 13-8-31.
//  Copyright (c) 2013年 Jiamei Shuai. All rights reserved.
//

#include <vector>
#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;

// 两处优化:
// 1.已经计算过的区间的最短划分次数用map纪录
// 2.回文串的判断结果也要用map记录

class Solution{
public:
int *minCutMat;
vector<vector<int> > map;

int IsPalindrome(string &s, int i, int j)
{
if(i>j) return false;
if(map[i][j]!= -1)
return map[i][j];
if(i==j)
return map[i][j]=1;

if(s[i]!=s[j])
return map[i][j]=0;
else{
if(j-i==1)
return map[i][j]=1;
else
return map[i][j]=IsPalindrome(s,i+1,j-1);
}
}

int minCut(string s) // 动态规划  d[i] = min{d[k]+1, d[k]+i-k}, 0<=k<i
{
int n = (int)s.length();
if(n==0||n==1)
return 0;

vector<int> min, vtmp;
min.clear();vtmp.clear();map.clear();
for(int i=0; i<s.size(); i++)
{
min.push_back(0);
vtmp.push_back(-1);
}
for(int i=0; i<s.size(); i++)
map.push_back(vtmp);

int tmp, ans;
for(int inter = 1; inter<n; inter++)
{
if(IsPalindrome(s, 0, inter))
min[inter]=0;
else{
ans = n+1;
for(int k = 0; k < inter; k++)
{
if(IsPalindrome(s, k+1, inter))
tmp = min[k]+1;
else
tmp = min[k] + inter - k;
if(tmp < ans)
ans = tmp;
}
min[inter] = ans;
}
}
return min[n-1];
}

// 较复杂的算法用dfs或者回溯法都太慢了,加上了所有的剪枝策略还是会超时
// 这种情况大多数都应该使用动态规划,要多总结,少犯错误。

int minCut2(string s) // 总是超时,复杂度太高
//这个方法相当于类似矩阵链乘的算法,dp[i][j] = min(dp[i][k]+dp[k+1][j]), i<=k<j,复杂度是O(n^3)
//可以简化dp[i][j]为dp[i],表示从0到i的minCut
{
int minCutNum = (int)s.size();
int len = (int)s.size();

minCutMat = new int[len*len]; // 注意new int[]而不是()
memset(minCutMat, -1, len*len*sizeof(int));

vector<int> vtmp;
vtmp.clear();map.clear();
for(int i=0; i<s.size(); i++)
vtmp.push_back(-1);
for(int i=0; i<s.size(); i++)
map.push_back(vtmp);

// Notice: if the string need no split and itself a palindrome, how to handle it? 注意细节
if(IsPalindrome(s, 0, len-1)) return 0;

split(s, 0, len-1, minCutNum);

delete []minCutMat;

return minCutNum;
}

int split(string &s, int begin, int end, int &minCutNum)
{
if(begin == end) return 0;

if(IsPalindrome(s, begin, end)) return 0;

int minCurrentSplit = (int)s.size();
int left,right;

for(int i = begin; i < end; i++)
{
assert(begin*s.size()+i < s.size()*s.size());
assert(begin*s.size()+i < s.size()*s.size());
if(minCutMat[begin*s.size()+i] >= 0)
left = minCutMat[begin*s.size()+i];
else
{
left = split(s, begin, i, minCutNum);
minCutMat[begin*s.size()+i] = left;
}
if(left >= minCutNum) { return 1<<20;}

if(minCutMat[(i+1)*s.size()+end] >= 0)
right = minCutMat[(i+1)*s.size()+end];
else
{
right = split(s, i+1, end, minCutNum);
minCutMat[(i+1)*s.size()+end] = right;
}
if(right >= minCutNum) return 1<<20;

int tmp = left + 1 + right;

minCurrentSplit = min(tmp, minCurrentSplit);

if(begin == 0 && end == s.size()-1) // outer loop
minCutNum = min(tmp, minCutNum);
}
return minCurrentSplit;
}

};

int main()
{
Solution sln;
cout << sln.minCut("apjesgpsxoeiokmqmfgvjslcjukbqxpsobyhjpbgdfruqdkeiszrlmtwgfxyfostpqczidfljwfbbrflkgdvtytbgqalguewnhvvmcgxboycffopmtmhtfizxkmeftcucxpobxmelmjtuzigsxnncxpaibgpuijwhankxbplpyejxmrrjgeoevqozwdtgospohznkoyzocjlracchjqnggbfeebmuvbicbvmpuleywrpzwsihivnrwtxcukwplgtobhgxukwrdlszfaiqxwjvrgxnsveedxseeyeykarqnjrtlaliyudpacctzizcftjlunlgnfwcqqxcqikocqffsjyurzwysfjmswvhbrmshjuzsgpwyubtfbnwajuvrfhlccvfwhxfqthkcwhatktymgxostjlztwdxritygbrbibdgkezvzajizxasjnrcjwzdfvdnwwqeyumkamhzoqhnqjfzwzbixclcxqrtniznemxeahfozp");

return 0;
}


附上更简洁的算法:

1:        int minCut(string s) {
2:            int len = s.size();
3:            int D[len+1];
4:            bool P[len][len];
5:            //the worst case is cutting by each char
6:            for(int i = 0; i <= len; i++)
7:            D[i] = len-i;
8:            for(int i = 0; i < len; i++)
9:            for(int j = 0; j < len; j++)
10:            P[i][j] = false;
11:            for(int i = len-1; i >= 0; i--){
12:                 for(int j = i; j < len; j++){
13:                      if(s[i] == s[j] && (j-i<2 || P[i+1][j-1])){
14:                           P[i][j] = true;
15:                           D[i] = min(D[i],D[j+1]+1);
16:                      }
17:                 }
18:            }
19:            return D[0]-1;
20:       }


以及使用回溯+剪枝的方法:

1:    int minCut(string s) {
2:      int min = INT_MAX;
3:      DFS(s, 0, 0, min);
4:      return min;
5:    }
6:    void DFS(string &s, int start, int depth, int& min)
7:    {
8:      if(start == s.size())
9:      {
10:        if(min> depth-1)
11:          min = depth-1;
12:        return;
13:      }
14:      for(int i = s.size()-1; i>=start; i--)  //find the biggest palindrome first
15:      {
16:        if(isPalindrome(s, start, i))
17:        {
18:          DFS(s, i+1, depth+1, min);
19:        }
20:
21:
22:      }
23:    }
24:    bool isPalindrome(string &s, int start, int end)
25:    {
26:      while(start< end)
27:      {
28:        if(s[start] != s[end])
29:          return false;
30:        start++; end--;
31:      }
32:      return true;
33:    }


总结下来,要学会分析问题,不能一成不变的只用一个算法,可能会非常低效。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: