【Leetcode】回文串

经典题目

    • 5. 最长回文子串(多种解法)
    • 131. 分割回文串(dfs)
    • 132. 分割回文串 II(动态规划)
    • 214. 最短回文串(KMP)

5. 最长回文子串(多种解法)

leetcode 5
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

思路:

  • 反转字符串得到s’,求s和s’的最长公共子串,并判断该子串是否对应s和s’的同一位置。时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 动态规划。维护一个二维数组 dp[i][j] 表示 s[i] 到 s[j] 是否为回文子串,易知状态转移方程为 dp[i][j] = dp[i+1][j-1]&&s[i]==s[j]。时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( n 2 ) O(n^2) O(n2)
  • 中心扩展法。根据回文串长度的奇偶性分两种情况判断,isPalindrome(s, i, i)和isPalindrome(s, i, i+1),分别用来搜索以 s[i] 为中心和以 s[i] s[i+1] 为中心的最长回文子串。时间复杂度 O ( n 2 ) O(n^2) O(n2),空间复杂度 O ( 1 ) O(1) O(1)
  • Manacher算法

反转字符串

class Solution {
public:
    string longestPalindrome(string s) {
    	int n = s.size();
        if(n < 2) return s;
        // 反转s
		string rev_s = "";
		for(int i = n - 1; i >= 0; i--) rev_s += s[i];
        // 声明dp数组,n+1维是因为状态转移方程里有[i-1][j-1]
		int dp[n+1][n+1];
        memset(dp, 0, sizeof(dp));
        // 填dp数组
		int maxx = 0, end = 0; // maxx是最长回文子串长度,end是对应的s中的index
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= n; j++) {
				if(s[i - 1] == rev_s[j - 1]) dp[i][j] = dp[i-1][j-1] + 1;
				if(dp[i][j] > maxx && n - j + dp[i][j] == i) { // n - j + dp[i][j]是将s'中第j位的字符返回到s中对应的index
                    maxx = dp[i][j];
                    end = i - 1;
				}
			}
		}
		return s.substr(end - maxx + 1, maxx);
    }
};

动态规划

class Solution {
public:
    string longestPalindrome(string s) {
    	int n = s.size();
        if(n < 2) return s;
        // 声明dp数组,做了空间复杂度优化,原始dp[i][j]表示从s[i]到s[j]是否为回文子串
        int dp[n];
        memset(dp, 0, sizeof(dp));
        int maxx = 1, end = 0;
		for(int i = n - 1; i >= 0; i--) {     // 从后往前遍历(因为需要dp[i+1][j-1])
			for(int j = n - 1; j >= i; j--) { // 从后往前遍历(因为需要dp[i+1][j-1])
				if(i == j) dp[j] = 1;
				else if(i + 1 == j) {
					dp[j] = (s[i] == s[j]);
					if(dp[j] == 1 && maxx < 2) {
						maxx = 2;
						end = j;
					}
				}
				else {
					dp[j] = dp[j-1] && (s[i] == s[j]);
					if(dp[j] == 1 && j - i + 1 > maxx) {
						maxx = j - i + 1;
						end = j;
					}
				}
			}
		}
		return s.substr(end - maxx + 1, maxx);
    }
};

中心扩展法

class Solution {
public:
	int maxx = 0;
	int l_max = 0;
	
	void findLongest(string&s, int l, int r) {
		int n = s.size();
		while(l >= 0 && r < n && s[l] == s[r]) {
			l--;
			r++;
		}
		if(r - l - 1 > maxx) {
			maxx = r - l - 1;
			l_max = l + 1;
		}
	}
	
    string longestPalindrome(string s) {
    	int n = s.size();
    	if(n < 2) return s;
    	for(int i = 0; i < n; i++) {
    		findLongest(s, i, i);
    		findLongest(s, i, i + 1);
    	}
    	return s.substr(s, l_max, maxx);
	}
};

Manacher算法

class Solution {
public:
    string longestPalindrome(string s) {

	}
};

131. 分割回文串(dfs)

leetcode 131
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。

思路: 题目要求所有可能的分割方案,因此想到使用dfs暴力搜索。
注意: 搜索的范围(for循环)是当前的左边界可能对应的回文右边界。

class Solution {
public:
    vector<vector<string>> ans;
    // 判断是否是回文串
    bool isValid(string& s, int l, int i) {
        while(l < i) {
            if(s[l++] != s[i--]) return false;
        }
        return true;
    }
    // dfs搜索
    void dfs(string& s, vector<string>& vec, int l) {
        int n = s.size();
        if(l > n - 1) { // 如果左区间溢出右边界,返回
            ans.push_back(vec);
            return;
        }
        for(int i = l; i < n; i++) { // 从当前左边界l搜索可能的回文右边界i
            if(!isValid(s, l, i)) continue;
            vec.push_back(s.substr(l, i - l + 1));
            dfs(s, vec, i + 1);
            vec.pop_back(); // 回退
        }
    }

    vector<vector<string>> partition(string s) {
        int n = s.size();
        if(n == 0) return ans;
        vector<string> vec;
        dfs(s, vec, 0);
        return ans;
    }
};

132. 分割回文串 II(动态规划)

leetcode 132
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回符合要求的最少分割次数。

思路: 因为求的是最值(最小分割次数),并且可以将问题划分为最优子问题,所以考虑使用动态规划来做。
注意: 因为之后要用min比较得到最小分割次数,所以初始化的时候将dp数组初始为当前可能的最大分割次数。

class Solution {
public:
    // 判断是否是回文
    bool isValid(string&s, int l, int r) {
        while(l < r) {
            if(s[l++] != s[r--]) return false;
        }
        return true;
    }

    int minCut(string s) {
        int n = s.size();
        if(n < 2) return 0;
        int dp[n];
        for(int i = 0; i < n; i++) dp[i] = i; // 初始化为最大分割次数
        for(int i = 0; i < n; i++) {
            if(isValid(s, 0, i)) dp[i] = 0;   // 如果整个都是个回文串,dp[i]当然等于0
            else {
                for(int j = 1; j <= i; j++) { // 遍历以当前位置结尾的回文串
                    if(isValid(s, j, i)) {
                        dp[i] = min(dp[i], dp[j-1] + 1); // 寻找最小分割次数的组合
                    }
                }
            }
        }
        return dp[n-1];
    }
};

214. 最短回文串(KMP)

leetcode 214
给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。

你可能感兴趣的:(DSA)