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.
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.
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); }
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); }