近日参加了一家国内某知名互联网公司的在线笔试,有一道算法题比较有意思,拿出来分享一下。
题目:
给定一个字符串,可以随意删除字符串里的任何字符(也可以不删除),目标是删除一些字符后让剩下的字符能够组成回文,并返回该输入字符串所能构成的最长回文长度。
例如,输入:"eabbfa"。
删除e和f后,构成"abba",因此返回4。
提示:
先看一个递归的方法:
int recursive(string& s, int start, int end) { if (start == end) { return 1; } if (start > end) { return 0; } if (s[start] == s[end]) { return recursive(s, start + 1, end - 1) + 2; } else { return max(recursive(s, start, end - 1), recursive(s, start + 1, end)); } } int longestPalindrom(string s) { return recursive(s, 0, s.length() - 1); }
但是递归方法会计算许多重复子问题,导致效率下降。
这题可以用动态规划来做。申请一个二维数组,dp[i][j],代表了字符串从 i 到 j 位置的子串中能够包含的最长回文长度。
显然,这里的起始条件是dp[i][i] = 1,i取值为[0, s.length() - 1],因为只有一个字符的字符串一定是回文。
之后可以用 k 代表 i 和 j 的距离,从1开始递增。
当s[i] == s[j]时,进行状态转移,返回dp[i][j] = max(dp[i + 1][j - 1] + 2, max(dp[i + 1][j] + 1, dp[i][j - 1] + 1));
大致思路就是这样。
代码:
int longestPalindrom(string s) { int len = s.length(); if (len <= 1) { return len; } vector<vector<int>> dp(len, vector<int>(len, 0)); int max_len = 1; // single character is palindrom for (int i = 0; i < len; ++i) { dp[i][i] = 1; } // k is distance between i and j for (int k = 1; k < len; ++k) { for (int i = 0; i < len - k; ++i) { int j = i + k; if (s[i] == s[j]) { dp[i][j] = max(dp[i + 1][j - 1] + 2, max(dp[i + 1][j] + 1, dp[i][j - 1] + 1)); } else { dp[i][j] = max(dp[i + 1][j - 1], max(dp[i + 1][j], dp[i][j - 1])); } } } return dp[0][len-1]; } int main() { string s = "rdgsaraytbbdrcdfcbsbaaoio"; int res = longestPalindrom(s); return 0; }
如果有更好的思路或者发现代码有纰漏,欢迎留言指正。