天天看點

【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,你可以通過在字元串前面添加字元将其轉換為回文串。找到并傳回可以用這種方式轉換的最短回文串。