Longest Palindromic Subsequence

Longest Palindromic Subsequence (not substring, for the substring solution, please find answers in my GitHub) is to find (one of) the longest palindromic subsequence in a string. It is short for LPS in this article (here we only need to find one of LPS). See an example from another article.

  s = 1 5 2 4 3 3 2 4 5 1
LPS = 1 5   4 3 3   4 5 1

Ok, I think now you are clear about the definition of the LPS (again not substring). Let's start our related algorithms and let them evolve.


I. Naive Recursive Version, time complexity O(2^n) (worst case, O(n)=2O(n-1)+1), space complexity O(n) = O(h) for stack operation.

string longestPalindrome(const string &s, int begin, int end) {
	if (begin > end) return "";
	if (begin == end) return s.substr(begin, 1);
	if (s[begin] == s[end]) return s[begin] + longestPalindrome(s, begin+1, end-1) + s[end];
	string str1 = longestPalindrome(s, begin, end-1);
	string str2 = longestPalindrome(s, begin+1, end);
	return str1.size() > str2.size()? str1: str2;
}

string longestPalindrome(string s) {
	return longestPalindrome(s, 0, s.size()-1);
}

 

Here we return immediately once we find the s[begin] == s[end] holds, ignoring s[begin+1, end] and s[begin, end-1]

Why? Does this mean s[begin] and s[end] are parts of the LPS we are seeking for if s[begin] == s[end] holds?  


1. We can guarantee that s[begin, end] has the LPS. 

  • But it is possible we get the same LPS from s[begin+1, end] or s[begin, end-1], such as "asaa" (LPS: asa)
  • However, LPS' from s[begin+1, end] or s[begin, end-1] is <= LPS from s[begin, end]. It is simply because s[begin, end] is longer than s[begin+1, end] or s[begin, end-1].

2. s[begin] and s[end] are parts of s[begin, end]'s LPS, head and tail respectively. If this does not hold, we have two situations.

  • s[begin, end]'s LPS does not have s[begin] nor s[end]. We know it is not true as s[begin] + LPS + s[end]  > LPS. So the real LPS should be s[begin] + LPS + s[end]not LPS.
  • s[begin, end]'s LPS contains only one of s[begin] and s[end]. It is true in certain cases, such as "asaa" (LPS: asa). But In these cases, LPS.head == LPS.tail holdssince palindrome property. If LPS contains s[begin] (vice versa for s[end]), LPS.tail == LPS.head ==  s[begin] == s[end]. So rule 2 still holds.

II. Top-down Dynamic Programming Version,  time complexity O(n^2)  (worst case, O(n)=O(n-1)+n) ,  space complexity O(n^2) .
string longestPalindrome(const string &s, int begin, int end, vector<vector<string>> &mem) {
	if (begin > end) return "";
	if (mem[begin][end] != "#") return mem[begin][end];
	if (begin == end) return mem[begin][end].assign(s, begin, 1);
	if (s[begin] == s[end]) return mem[begin][end] = s[begin] + longestPalindrome(s, begin+1, end-1, mem) + s[end];
	string str1 = longestPalindrome(s, begin, end-1, mem);
	string str2 = longestPalindrome(s, begin+1, end, mem);
	return mem[begin][end] = str1.size() > str2.size()? str1: str2;
}

string longestPalindrome(string s) {
	int N = s.size();
	vector<vector<string>> mem(N, vector<string>(N, "#"));
	return longestPalindrome(s, 0, N-1, mem);
}

III. Bottom-up Dynamic Programming Version,  time complexity O(n^2) ,  space complexity O(n^2) .

string longestPalindrome(string s) {
	if (s.empty()) return s;
	int N = s.size();
	vector<vector<string>> mem(N, vector<string>(N));
	for (int i=N-1; i>=0; --i) {
		for (int j=i; j<N; ++j) {
			if (s[i] == s[j]) {
				if (j-i <= 1) mem[i][j].assign(s, i, j-i+1);
				else mem[i][j] = s[i] + mem[i+1][j-1] + s[j];
			} else {
				mem[i][j] = mem[i][j-1].size()>=mem[i+1][j].size()? mem[i][j-1]: mem[i+1][j];
			}
		}
	}
	return mem[0][N-1];
}


IV. Space Optimized Bottom-up Dynamic Programming Version, time complexity O(n^2), space complexity O(n).

string longestPalindrome(string s) {
	if (s.empty()) return s;
	int N = s.size();
	vector<vector<string>> mem(2, vector<string>(N));
	for (int i=N-1; i>=0; --i) {
		for (int j=i; j<N; ++j) {
			if (s[i] == s[j]) {
				if (j-i <= 1) mem[i%2][j].assign(s, i, j-i+1);
				else mem[i%2][j] = s[i] + mem[(i+1)%2][j-1] + s[j];
			} else {
				mem[i%2][j] = mem[i%2][j-1].size()>=mem[(i+1)%2][j].size()? mem[i%2][j-1]: mem[(i+1)%2][j];
			}
		}
	}
	return mem[0][N-1];
}


V. Tracing Bottom-up Dynamic Programming Version, time complexity O(n^2), space complexity O(n^2).

/***************** This version is used when string is very large ***************/

// time complexity O(n), space complexity O(n)
string getLPS(vector<vector<char>> &trace, int i, int j, const string &s) {
	if (i > j) return "";
	if (i == j) return s.substr(i, 1);
	if (trace[i][j] == '#') return s[i] + getLPS(trace, i+1, j-1, s) + s[j];
	if (trace[i][j] == '-') return getLPS(trace, i, j-1, s);
	if (trace[i][j] == '|') return getLPS(trace, i+1, j, s);
}

// time complexity O(n^2), space complexity O(n^2)
int longestPalindrome(string s, string &res) {
	if (s.empty()) return 0;
	int N = s.size();
	vector<vector<int>> mem(2, vector<int>(N));
	vector<vector<char>> trace(N, vector<char>(N, '#'));
	for (int i=N-1; i>=0; --i) {
		for (int j=i; j<N; ++j) {
			if (s[i] == s[j]) {
				if (j-i <= 1) mem[i%2][j] = j - i + 1;
				else mem[i%2][j] = mem[(i+1)%2][j-1] + 2;
			} else {
				mem[i%2][j] = max(mem[i%2][j-1], mem[(i+1)%2][j]);
				trace[i][j] = mem[i%2][j-1]>=mem[(i+1)%2][j]? '-': '|';
			}
		}
	}
	res = getLPS(trace, 0, N-1, s);
	return mem[0][N-1];
}


VI. Tracing Bottom-up Dynamic Programming Version, time complexity O(n^2), space complexity O(n^2).Only one mem!

/***************** This version is used when string is very large ***************/

// time complexity O(n), space complexity O(n)
string getLPS(const vector<vector<int>> &mem, int i, int j, const string &s) {
	if (i > j) return "";
	if (i == j) return s.substr(i, 1);
	if (s[i] == s[j]) return s[i] + getLPS(mem, i+1, j-1, s) + s[j];
	if (mem[i][j-1] >= mem[i+1][j]) return getLPS(mem, i, j-1, s);
	if (mem[i][j-1] < mem[i+1][j]) return getLPS(mem, i+1, j, s);
}

// time complexity O(n^2), space complexity O(n^2)
int longestPalindrome(string s, string &res) {
	if (s.empty()) return 0;
	int N = s.size();
	vector<vector<int>> mem(N, vector<int>(N));
	for (int i=N-1; i>=0; --i) {
		for (int j=i; j<N; ++j) {
			if (s[i] == s[j]) {
				if (j-i <= 1) mem[i][j] = j - i + 1;
				else mem[i][j] = mem[i+1][j-1] + 2;
			} else {
				mem[i][j] = max(mem[i][j-1], mem[i+1][j]);
			}
		}
	}
	res = getLPS(mem, 0, N-1, s);
	return mem[0][N-1];
}


VII. Tracing Top-down Dynamic Programming Version, time complexity O(n^2), space complexity O(n^2).Only one mem!

/***************** This version is used when string is very large ***************/  

// time complexity O(n), space complexity O(n)  
string getLPS(const vector<vector<int>> &mem, int i, int j, const string &s) {  
	if (i > j) return "";  
	if (i == j) return s.substr(i, 1);  
	if (s[i] == s[j]) return s[i] + getLPS(mem, i+1, j-1, s) + s[j];  
	if (mem[i][j-1] >= mem[i+1][j]) return getLPS(mem, i, j-1, s);  
	if (mem[i][j-1] < mem[i+1][j]) return getLPS(mem, i+1, j, s);  
}

// time complexity O(n^2), space complexity O(n^2)  
int longestPalindrome(const string &s, int begin, int end, vector<vector<int>> &mem) {
	if (begin > end) return 0;
	if (mem[begin][end] != 0) return mem[begin][end];
	if (begin == end) return mem[begin][end] = 1;
	if (s[begin] == s[end]) return mem[begin][end] = 2 + longestPalindrome(s, begin+1, end-1, mem);
	return mem[begin][end] = max(longestPalindrome(s, begin, end-1, mem), longestPalindrome(s, begin+1, end, mem));
}

string longestPalindrome(string s) {
	int N = s.size();
	vector<vector<int>> mem(N, vector<int>(N, 0));
	longestPalindrome(s, 0, N-1, mem);
	return getLPS(mem, 0, N-1, s);
}


你可能感兴趣的:(Algorithm)