• 5.最长回文子串 INnoVation


    题目地址: https://leetcode.cn/problems/longest-palindromic-substring/

    解法1 - 中心枚举法

    思路

    有两种回文串

    • 长度为偶数:若中心点坐标为i,左右两坐标起点是i - 1, j + 1

    • 长度为奇数:若中心点坐标为i,左右两坐标起点是i, j + 1

    将字符串中的每个字符视为中心,尝试向两边延伸,最后枚举完所有字符后,结果就是最长字符

    枚举到左右两边字符不相等,此时s[l + 1] ~ s[r - 1]就是最长回文串

    时间复杂度

    假定每个字母都向左右试探n步

    0号字母走0步,
    1号走1步,
    2号走两步
    ...
    n/2走n/2步
    ...
    n-2走2步
    n-1走1步
    n走0步
    

    \(0+1+2+...+\frac{n}{2} + ... + 2 + 1 + 0\)

    根据等差数列公式

    $ = \frac{n^2}{4}$

    所以是\(N^2\)级别的

    代码

    //substr的两个参数分别是(起始位置,字符串长度)
    class Solution {
    public:
        string longestPalindrome(string s) {
            string ans;
            for(int i = 0; i < s.size(); i ++) {
              	// 枚举奇数长度回文串
                int l = i - 1, r = i + 1;
                while(l >= 0 && r < s.size() && s[l] == s[r]) --l, ++r;
                if(ans.size() < r - l - 1) ans = s.substr(l + 1, r - l - 1);
                
              	// 枚举偶数长度回文串
                l = i, r = i + 1;
                while(l >= 0 && r < s.size() && s[l] == s[r]) --l, ++r;
                if(ans.size() < r - l - 1) ans = s.substr(l + 1, r - l - 1);
            }
            return ans;
        }
    };
    

    思路2 二分 + Hash

    思路

    对字符串正序和逆序分别求Hash值,

    • 枚举每个字母作为中点,

    • 二分测出半径

      如果两侧字符串Hash值相等,那就可以继续延长,如果不等,那就缩短

    其他需要注意的点

    1. 当回文串长度为奇数和偶数时,枚举坐标不同,为了方便处理,在每两个字符串之间添加一个#,这样偶数长度回文串也会变成奇数,比如 abba --> a#b#b#a,假设原长度是x,那么添加的#的长度就是x + 1,两者之和等于\(2x + 1\),必然是奇数

    2. 后面求出回文串长度后,如何确定原字符串长度?

      可查看某一端点的字符是#还是字符,如果是#,那就是#比字符多一个,假设半径是mid,总长度就是mid

      比如#a#b#a#, mid = 3,字符总数就是3

      如果端点值是字符,那就是字符比#多一个,假设半径是mid,总长度就是mid + 1

      比如a#b#a, mid = 2,字符总数就是2 + 1 = 3

    3. 字符串相反, 位置对应问题

      0 1 2 3 4 5 6 7
      0 7 6 5 4 3 2 1
      n = 7, 假设此时i = 4, mid = 2
      则应该对比[2,3] 和 [5,6]
      [2,3]的坐标就是[i-mid, i-1]
      [5,6]的坐标在反方向是[2,3],即[n-i-(mid-1), n-i]
      

    时间复杂度

    枚举n个字母 n,假定每个字母左右都有n个字母,每次耗时\(O(\log^N)\),总的就是\(O(N\log^N)\)级别的

    代码

    typedef unsigned long long ULL;
    class Solution {
    public:
        static const int N = 2010;
        char str[N];
        int base = 131;
        ULL p[N], hl[N], hr[N];
        ULL get(ULL h[], int l, int r)
        {
            return h[r] - h[l - 1] * p[r - l + 1];
        }
    
        string longestPalindrome(string s) {
            strcpy(str + 1, s.c_str());
            int n = s.size() * 2;
            for(int i = n; i >= 1; i-=2)
            {
                str[i] = str[i/2];
                str[i - 1] = 'z' + 1;
            }
    
            p[0] = 1;
            for(int i = 1, j = n; i <= n; i++, j--)
            {
                p[i] = p[i - 1] * base;
                hl[i] = hl[i - 1] * base + str[i];
                hr[i] = hr[i - 1] * base + str[j];
            }
            
            int st = 0, radius = 0;
            for(int i = 1; i <= n; i++)
            {
                int l = 0, r = min(i - 1, n - i);
                while(l < r)
                {
                    int mid = (l + r + 1) >> 1;
                    if(get(hl, i - mid, i - 1) == get(hr, n - i - mid + 1, n - i)) l = mid;
                    else r = mid - 1;
                }
                if(l >= radius)
                {
                    st = i;
                    radius = l;
                }
            }
            string ans;
            for(int i = st - radius; i <= st + radius; i++)
                if(str[i] <= 'z') ans += str[i];
            return ans;
        }
    };
    

    Q&A

    1. 二分必须使用

    int mid = (l + r + 1) >> 1;
    if(get(hl, i - mid, i - 1) == get(hr, n - i - mid + 1, n - i)) 
      l = mid;
    else 
      r = mid - 1;
    

    不能使用

    int mid = (l + r) >> 1;
    if(get(hl, i - mid, i - 1) != get(hr, n - i - mid + 1, n - i)) 
      r = mid;
    else 
      l = mid + 1;
    

    以cbabb为例,在加上#字符后变为#c#b#a#b#b,当i = 5,即字符a时,二分的结果错误

    原因是第二种二分,在等号成立时,只能保证这个长度的字符串相等, 而l = mid+1,扩大了相等的范围,把不相等的字符包括进来了

  • 相关阅读:
    CompletionService--实现并行获取future.get()结果
    ConcurrentHashMap+FutureTask实现高效缓存耗时较长的业务结果获取
    Exchanger--线程执行过程中交换数据
    线程池的处理流程
    CyclicBarrier--栅栏,挡住指定数量线程后一次放行
    Semaphore--信号量,控制并发线程数,如流量控制
    CountDownLatch---多线程等待
    线程安全相关概念
    SimpleDateFormat非线程安全
    记java.lang.NoSuchMethodError错误解决过程
  • 原文地址:https://www.cnblogs.com/INnoVationv2/p/16869230.html
Copyright © 2020-2023  润新知