Palindrome Partitioning
Given a string s, partition s such that every substring of the partition is a palindrome.
Return all possible palindrome partitioning of s.
For example, given s =
"aab",Return
[
["aa","b"],
["a","a","b"]
]
Naive Way:第一感觉可以用recursive的方法,对String第一个palindrome及它后面的部分
分别进行recursive call求palindrome partition。具体下来就是:
如果一个String只有一个字符,则返回单个字符的list。
否则,遍历String,一旦遇到palindrome就提取该子串作为头,对后部分分别进行recursive call,
合并两者,加入结果中。
有一个问题就是如何求包含第一个字母的所有palindrome。在longest palindrome substring中
在O(n)内可以求出以所有字符为中心最大范围的palindrome,当然用在这里肯定也可以求处包含第一个
字符的所有palindrome。当肯定不是最佳的,但是要想找出在小于O(n)的时间内求出包含第一个字符
的所有palindrome,看来是不可能的,所以,干脆就用这个方法了。
这里我用求longest palindrome substring中的方法求出所有包含第一个字符的palindrome的
最后位置,以此方便分割字符。
算法复杂度应该是f(n)+f(n-1)+f(n-2)+... 而f(n)在最坏的情况应该是O(2^n)的,所以最后的
算法复杂度是O(2^n)。不知道这样算对不对,总之是一个很差的算法复杂度。
public class Solution {
public List<List<String>> partition(String s) {
List<List<String>> gross = new ArrayList<List<String>>();
List<Integer> num = panlindromeIndex(s);
for(int i = 0;i < num.size();i++){
String left = s.substring(0,num.get(i));
List<List<String>> right = partition(s.substring(num.get(i),s.length()));
if(right.size()==0){
List<String> base = new ArrayList<String>();
base.add(left);
gross.add(base);
}
for(int j = 0;j < right.size();j++){
right.get(j).add(0,left);
gross.add(right.get(j));
}
}
return gross;
}
// return a list of position(i) where [0~i] is a panlindrome
private List<Integer> panlindromeIndex(String s){
List<Integer> list = new ArrayList<Integer>();
s = preProcess(s);
int p = 0;
int f[] = new int[s.length()];
for(int i = 1;i < s.length();i++){
f[i] = 1;
if(i < p + f[p]){
if(p+f[p] - i > f[2*p-i])
f[i] = f[2*p-i];
else
f[i] = p+f[p]-i;
}
while(i-f[i] >= 0 && i + f[i] < s.length()){
if(s.charAt(i-f[i])!=s.charAt(i+f[i]))
break;
f[i]++;
}
if(i+f[i] > p+f[p]){p=i;}
}
for(int i = 1;i < f.length;i+=2){
if(i-f[i] < 0)
list.add((i+f[i])/2);
if(i-1-f[i-1] < 0)
list.add((i-1+f[i-1])/2);
}
return list;
}
private String preProcess(String s){
StringBuilder rlst = new StringBuilder();
for(int i = 0;i < s.length();i++){
rlst.append('#');
rlst.append(s.charAt(i));
if(i==s.length()-1)
rlst.append('#');
}
return rlst.toString();
}
}
Improved Way:为了提高算法效率,我思考了一下以上算法的缺陷。recursive call里产生
的子字符串很多都是重复的,这里使算法效率降低了很多。有没有办法先把这些有效的子字符串
写好,然后需要的时候直接调用呢?这时我想到了求longest palindrome substring最
原始的方法——DP。DP可以用一个二维矩阵告诉我某一段子字符串是否回文,着就相当于把所有
可能的子字符串都先求出来了,那如何调用呢。DP得到的是一个二维矩阵,通过上一层的某段
是否回文,可以用DFS的方法,遍历下一层对应所有可能的回文子串,这里又是用带回溯的DFS,
就像N-Queen和Sudoku一样。
算法复杂度为O(n^2)。比之前提高了不少。
public class Solution {
public List<List<String>> partition(String s) {
// DP
List<List<String>> gross = new ArrayList<List<String>>();
Deque<Range> deque = new LinkedList<Range>();
Stack<Range> stack = new Stack<Range>();
boolean opt[][] = new boolean[s.length()][s.length()+1];
// base case
for(int i = 0;i < s.length();i++){
opt[i][i+1] = true;
opt[i][i] = true;
}
// iteration
for(int i = 2;i <= s.length();i++)
for(int j = 0;j+i <= s.length();j++)
opt[j][j+i] = s.charAt(j)==s.charAt(j+i-1) && opt[j+1][j+i-1];
// construct result using DFS
for(int i = 1;i <= s.length();i++){
if(opt[0][i]){
Range range = new Range(0,i);
stack.push(range);
}
}
while(!stack.isEmpty()){
Range range = stack.pop();
deque.offerLast(range);
boolean hasNext = false;
if(range.e==s.length()){
List<String> list = new ArrayList<String>();
for(int i = 0;i < deque.size();i++){
Range next = deque.pollFirst();
list.add(s.substring(next.b,next.e));
deque.offerLast(next);
}
gross.add(list);
}else{
for(int i = range.e+1;i <= s.length();i++){
if(opt[range.e][i]){
Range sub = new Range(range.e,i);
stack.push(sub);
hasNext = true;
}
}
}
// back trace
if(!hasNext || range.e==s.length()){
if(!stack.isEmpty()){
Range peek = stack.peek();
while(!deque.isEmpty()){
if(deque.pollLast().b == peek.b)
break;
}
}
}
}
return gross;
}
class Range{
int b;
int e;
Range(int begin, int end){
b = begin;
e = end;
}
}
}
看了看自己第一次的做法,发现比这种用DFS遍历的方法高明多了,是根据DP的二维矩阵从后往前
进行遍历的方法。运行时间也是最短的。
public class Solution {
public List<List<String>> partition(String s) {
List<List<String>> lst = new ArrayList<List<String>>();
List<String> temp = new ArrayList<String>();
int len = s.length();
boolean opt[][] = new boolean[len][len];
// initialize
for(int i = 0;i < len;i++){Arrays.fill(opt[i], false);}
for(int i = 0;i < len;i++){opt[i][i] = true;}
// recurrence
for(int u = 1;u < len;u++){
for(int i = 0;i+u < len;i++){
if(u >= 2){
opt[i][i+u] = (opt[i+1][i+u-1] && s.charAt(i)==s.charAt(i+u));
}else if(u == 1){
opt[i][i+u] = s.charAt(i) == s.charAt(i+u)? true:false;
}
}
}
// generate result according to opt
generateList(0,len,s,opt,temp,lst);
//show result
//System.out.print(lst);
return lst;
}
private boolean generateList(int i, int len, String s, boolean opt[][], List<String> temp,List<List<String>> lst){
for(int j = 0;j < len; j ++){
if(opt[i][j]){
List<String> newCombo = new ArrayList<String>(temp);
newCombo.add(s.substring(i, j+1));
if(j == len-1){
lst.add(newCombo);
}else{
generateList(j+1,len,s,opt,newCombo,lst);
}
}
}
return true;
}
}
突然发现我的算法课老师Michael讲的如何回溯DP得到的矩阵,得到正式的结果的那种从尾到头的方法,其实是一种DFS。
No comments:
Post a Comment