回文三板斧(第一招:暴力)

写周赛题解有一段时间了,感觉周赛题目的类型比较分散,不利于系统的学习,所以萌生了写专题的想法。
接下来的一段时间,我会写一些常用的算法或者数据结构,希望能帮到大家。如果大家有想了解的算法,也可以在文末留言。

最近在各大网站刷了三十多道回文串,感觉这是一个很容易和其他算法相结合的类型。

  • 和暴力枚举结合:从一个字符串中最多删除一个字符,是否能使其变为回文串。题目链接
  • 计数问题:一个字符串中包含多少个回文子串。题目链接
  • 和动态规划结合:对字符串进行分割,使分割后的子串都是非空的回文串,问最少的分割次数。题目链接

接下来,让我们循序渐进从O(n)的算法讲起,一起迭代优化,为解决上述问题做好基建(最终算法基于二分的暴力,已经会的同学可以关掉了 )。

回文串是个什么铁憨憨

正读和反读都相同的字符序列为“回文”,如“aba”、“abba”是“回文”,“abcde”和“bba”则不是“回文”。

再比如古人秀出天际的回文诗:
莺啼岸柳弄春晴, 柳弄春晴夜月明;
明月夜晴春弄柳, 晴春弄柳岸啼莺。

O(n)的解法

从定义可知,一个长为 n 的字符串 S 是回文串的充要条件是,对于 i ∈ [0, n-1],都有S[i]与S[n-1-i] 相等。也就是S的前一半和后一半是"镜像"的。基于此,老铁们应该都有了O(n)的枚举解法:

bool is_palindrome(const std::string &str) {
  // 只枚举前一半就 OK 了
  for(int i = 0, n = str.size(); i < n/2; i++) {
    if(str[i] != str[n-i-1]) {
      return false; // 只要有一个位置不相等,那就肯定不是回文咯
    }
  }
  return true; // 所有位置都符合要求,那当然是回文咯
}

求个最长回文子串试试?

现在老铁们已经会O(n)的暴力解法了,那接下来尝试寻找一下最长回文子串
对于一个长度为 n 的字符串 S,我们可以先枚举从 n 到 1 枚举长度,再从 0 到 n-1 枚举子串的起始位置,然后再检查该子串是否是回文的。是不是很简单?可这是O(n^3) 的解法,CPU累到吐血。
机智的老铁应该瞬间就想到了基于二分的 O(n^2*lgn)的解法:
在 [0,n/2] 内二分长度 len,然后从 0 到 n-1 枚举子串的起始位置 pos,然后检查以 pos 为起点的,长度为 len*2 或者 len*2+1的子串是否为回文。是不是还是很简单?而且复杂度看上去也很高级!

老铁注意了,这里有个坑:我们二分的其实是子串长度的二分之一,举个例子,abba 有长度为 1,2,4 的回文子串,却没有长度为 3 的!abcba 有长度为 1,3,5 的回文子串,却没有长度为 2, 4 的!这不具备二分所需要的单调性,这是子串长度的奇偶性造成的。

    bool is_palindrome(const char *str, int n) {
        // 只枚举前一半就 OK 了
        for(int i = 0; i < n/2; i++) {
            if(str[i] != str[n-i-1]) {
                return false; // 只要有一个位置不相等,那就肯定不是回文咯
            }
        }
        return true; // 所有位置都符合要求,那当然是回文咯
    }   
    std::string fastFind(const string &s) {
        int l = 0, r = s.size()/2 + 1; // 设置二分的边界
        std::string anw;
        while(l <= r) {
            int mid = (l+r)/2;
            bool find = false;
            // 寻找长度为 mid*2 或者 mid*2+1的回文子串
            for(int i = 0; i + mid*2 <= s.size() && anw.size() < mid*2+1; i++) {
                if(mid*2 > anw.size() && is_palindrome(s.c_str() + i, mid*2)) {
                    anw = s.substr(i, mid*2);
                    find = true;
                }
                if(i+mid*2+1 <= s.size()) {
                    if(mid*2+1 > anw.size() && is_palindrome(s.c_str() + i, mid*2+1)) {
                        anw = s.substr(i, mid*2+1);
                        find = true;
                    }
                }
            }
            // 调整边界
            if(find) {
                l = mid+1;
            } else {
                r = mid-1;
            }
        }
        return anw;
    }

画个大饼

本文讲述了回文串以及回文子串的暴力解法,后续会继续更新更科学的解法~
回文三板斧(第一招:暴力)_第1张图片

你可能感兴趣的:(开卷有益,---,回文,---)