另参见回文子序列

哪些单词对能拼成回文串

vector<vector<int>> palindromePairs(vector<string>& words) {
    // 将w1分成w1[0..cut)、w1[cut..)两段,0<=cut<=len,两种情况:
    // 1. w1[0..cut)是回文,w1[cut..)是w2反转,w2+w1是回文
    // 2. w1[cut..)是回文,w1[0..cut)是w2反转,w1+w2是回文
    // 若w1、w2互为反转,比如abc、cba,
    //  abc|null + cba,处理单词abc、cut=len,返回{0,1}
    //  abc + null|cba,处理单词cba、cut=0,  返回{0,1}
    // 这就重复了,这时用cut>0或cut<len去掉一种重复。

    const int N = words.size();
    unordered_map<string, int> mp; // reverse=>idx
    for (int i = 0; i < N; i++) {
        string reverse(words[i].rbegin(), words[i].rend());
        mp[reverse] = i;
    }
    
    vector<vector<int>> ans;
    for (int i = 0; i < N; i++) {
        const string &word = words[i];
        const int len = word.size();
        for (int cut = 0; cut <= len; cut++) {
            // case 1
            if (isPalindrome(word, 0, cut - 1)) {
                string s2 = word.substr(cut);
                if (mp.count(s2) && mp[s2] != i) {
                    ans.push_back({mp[s2], i});                     
                }
            }
            // case 2
            if (isPalindrome(word, cut, len - 1)) {
                string s1 = word.substr(0, cut);
                if (mp.count(s1) && mp[s1] != i && cut < len) { // 用cut<len去重
                    ans.push_back({i, mp[s1]});                    
                }
            }
        }
    }
    return ans;
}

bool isPalindrome(const string &s, int lo, int hi) {
    while (lo < hi) {
        if (s[lo++] != s[hi--]) return false;
    }
    return true;
}

回文数

bool isPalindrome(int x) {
    // 以0结尾的正数没有回文数
    if (x < 0 || (x > 0 && x % 10 == 0)) return false;

    int rev = 0;
    while (x > rev) {
        rev = rev * 10 + x % 10;
        x /= 10;
    }
    return rev == x || rev / 10 == x;
}

>=N的最小回文素数

int primePalindrome(int N) {
    // 先生成回文,再判断是素数。因为1<=N<=10^8,只需[1..10^5)的数生成回文:
    //   正反拼接(偶数位)或 正反拼接且最后一位重合(奇数位)
    // 特别地,所有偶位数回文都是11的倍数
    //   因为1001%11=(1111-11*10)%11=0、100001%11=(111111-1111*10)%11=0、...
    //   所以abccba%11=(a*100001+b*1001*10+11*100)%11=0
    // 所以偶数位回文只有11是素数回文,而11只有当8<=N<=11时才返回。
    // 接下来只需判断奇数位回文
    if (8 <= N && N <= 11) return 11;
    for (int i = 1; i < 1e5; i++) {
        string s = to_string(i);
        s += string(s.rbegin() + 1, s.rend());
        int x = stoi(s);
        if (x >= N && isPrime(x)) return x;
    }
    return -1;
}

bool isPrime(int num) {
    if (num <= 1) return false;
    for (int i = 2; i * i <= num; i++) {
        if (num % i == 0) return false;
    }
    return true;
}

所有回文分割

“分割"类的标准回溯写法

vector<vector<string>> partition(string s) {
    vector<vector<string>> ans;
    vector<string> parts;
    search(s, 0, parts, ans);
    return ans;
}

void search(const string &s, int idx, vector<string> &parts, vector<vector<string>> &ans) {
    const int N = s.size();
    if (idx == N) {
        ans.push_back(parts);
        return;
    }
    // 尝试割下s[idx..i]
    for (int i = idx; i < N; i++) {
        if (!isPalindrome(s, idx, i)) continue;
        parts.push_back(s.substr(idx, i - idx + 1));
        search(s, i + 1, parts, ans);
        parts.pop_back();
    }
}

bool isPalindrome(const string &s, int l, int r) {
    while (l < r) {
        if (s[l++] != s[r--]) return false;
    }
    return true;
}

最少回文分割数

int minCut(string s) {
    // 设dp[i][j]表示s[i..j]是回文串,0<=i<=j<N
    // dp[i][j] = s[i]==s[j] && dp[i+1][j-1]
    const int N = s.size();
    vector<vector<bool>> dp(N, vector<bool>(N, false));
    for (int i = N - 1; i >= 0; i--) {
        for (int j = i; j < N; j++) {
            dp[i][j] = s[i] == s[j];
            if (i + 1 <= j - 1) dp[i][j] = dp[i][j] && dp[i+1][j-1];
        }
    }

    // 设cut[i]表示子串s[0..i]的minCut,0<=i<N
    // dp[0][i]==true时,cut[i]=0;否则,
    // 对于0<=k<i的k若满足dp[k+1][i]==true,cut[i]=min{ cut[k]+1 }
    vector<int> cut(N, INT_MAX);
    for (int i = 0; i < N; i++) {
        if (dp[0][i]) {
            cut[i] = 0;
        } else {
            for (int k = 0; k < i; k++) {
                if (dp[k+1][i]) {
                    cut[i] = min(cut[i], cut[k] + 1);
                }
            }
        }
    }
    return cut[N-1];
}

回文排列

Given a string s, return all the palindromic permutations (without duplicates) of it. Return an empty list if no palindromic permutation could be form.

For example:

Given s = "aabb", return ["abba", "baab"].

Given s = "abc", return [].

Hint:

  1. If a palindromic permutation exists, we just need to generate the first half of the string.
  2. To generate all distinct permutations of a (half of) string, use a similar approach from: Permutations II or Next Permutation.
vector<string> generatePalindromes(string s) {
    unordered_map<char, int> mp;
    int oddCnt = 0;
    for (char c : s) {
        mp[c]++;
        if (mp[c] % 2 == 1) oddCnt++;
        else oddCnt--;
    }
    if (oddCnt > 1) return {};

    string odd, half;
    for (auto &e : mp) {
        if (e.second % 2 == 1) odd = string(1, e.first);
        half += string(e.second / 2, e.first);
    }

    vector<string> ans;
    permute(half, 0, odd, ans);
    return ans;
}

void permute(string &half, int idx, const string &odd, vector<string> &ans) {
    if (idx == half.size()) {
        ans.push_back(half + odd + string(half.rbegin(), half.rend()));
        return;
    }

    unordered_set<char> seen; // 防止相同元素再次交换
    for (int i = idx; i < half.size(); i++) {
        if (seen.count(half[i])) continue;
        seen.insert(half[i]);

        swap(half[i], half[idx]);
        permute(half, idx + 1, odd, ans);
        swap(half[i], half[idx]);
    }
}

对称数

旋转180度看上去一样的数叫对称数。求在[low, high]范围内对称数的个数

class Solution {
    unordered_map<string, string> mp = { {"0","0"}, {"1","1"}, {"6","9"}, {"8","8"}, {"9","6"} };
public:
    int strobogrammaticInRange(string low, string high) {
        int count = 0;
        expand("", low, high, count); // n%2==0
        for (string s : {"0", "1", "8"}) { // n%2==1
            expand(s, low, high, count);
        }
        return count;
    }

    // 扩展num两端,添加数对
    void expand(string num, const string &low, const string &high, int &count) {
        const int len = num.size(), lowLen = low.size(), highLen = high.size();
        if (len > highLen) return; // 排除过长的
        if (len >= lowLen && !(len > 1 && num[0] == '0')) { // 长度合适,排除以‘0’开头的
            if (lowLen == highLen) {
                if (low <= num && num <= high) count++;
            } else {
                if ((len == lowLen && num >= low) || (len == highLen && num <= high) 
                    || (lowLen < len && len < highLen)) count++;
            }
        }

        for (auto &e : mp) {
            expand(e.first + num + e.second, low, high, count);
        }
    }
};