最长回文子串

*************

C++

题目:5. 最长回文子串 - 力扣(LeetCode)

*************

看一眼题目:

这个题目跟之前有点不太一样,这个我不会用矩阵了。难度有所上升了,变得有些棘手了。

突然想到 char 和 int 为什么需要相互转换。本质上来说,char 和 int 都是需要开辟一点空间用来存储,命名不一样而已。char 只有1字节,而int可以是2个字节。翻译成机器语言就是:

char: 00 00 00 00;

int:    00 00 00 00 00 00 00 00;

本质上来说,都是让计算机开辟出一个空间用于存储数据,这个就相当于快捷指令,char:给我一间房,安排上8个座位; int:我这个包间需要的大一点,给我一间房,安排上16个座位。

扯远了,现在回到题目上。这个起手就是 get 一下 string 的长度 n,然后构建一个简单的矩阵,起手的矩阵是 dp[0][j],这是一个只有一行的矩阵,为了保持统一,我喜欢使用m & i 用于描述行, n & j 用于描述列,个人习惯,S & M 用于描述行也行,人要是行,干一行,行一行。一行行,行行行;要是不行,干一行,不行一行。一行不行,行行不行‌13。

如果字符串是空的,返回空字符串。如果字符串只有一个元素,那么就是他本身。这两个情况的代码很好写。优化一下这两个情况就是,如果s只有0个或者1个元素,子串就是s本身。

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.empty()) return "";

想到一个解决方案就是,遍历整个s,从第 i 个字母开始判断,如果第 i - 1 = i + 1个字母相等,那么就是回文,指针滑到i - 2 =? i + 2, 如果是,那么判断i - 3 =? i + 3,以此类推。这是一个很天才的想法,后来看到别人的解答才知道这个思路是中心扩展法。这里有一个语法center(s, m, m),注意,这里的m是同一个元素,意思是回文子串是s,回文子串的起点是m,终点也是m,这里对应的是奇数回文子串,1个s只有1个m。对应偶数的话就是center(s, m, m + 1),这里的1个s有两个m,字符串的起点是m,重点是m + 1。

举例,

sabcdcbas
i01234567

s =  a b c d c b a s ,这是一个even number,为什么even是偶数的意思呢,可能是因为even还有其他的意思,为平等的,不倾斜的。假设center(s, d, d),那么就是s的起点是d,终点也是d。然后比较2号位和4号位,以此类推。

接下来的我看的不是很懂,就直抄过来吧,以下是完整的代码。

class Solution {
public:
    string longestPalindrome(string s) {
        if (s.empty()) return "";
        
        int start = 0, end = 0; // to store the sting's start and end

        for (int i = 0; i < s.size(); i++){
            int len1 = expandAroundCenter(s, i, i); // for odd numbers
            int len2 = expandAroundCenter(s, i, i + 1); // for even numbers
            int length = max(len1, len2);

            if (length > end - start){
                start = i - (length - 1) / 2; //fresh the start
                end = i + length / 2; // note that / means round down
            }
        }
        return s.substr(start, end - start + 1);

    private:
        int expandAroundCenter(const string& s, int left, int right) {
        while (left >= 0 && right < s.size() && s[left] == s[right]) {
            --left;
            ++right;
        }
        return right - left - 1; 
    }
};

但是,话说回来,我还是喜欢矩阵,不使用矩阵的话总感觉缺点什么。

矩阵 s = b a b a d; 

s[0] = b, s[1] = a 

矩阵 dp[i][i] 代表什么?就是坐标,dp[1][3]表示字符串第一位和第三位。

请上我的老朋友,矩阵,为了方便我理解。

dp01234
sbabad
0bT
1aT
2bT
3aT
4bT

单个字母总是回文,因为就是它本身,所以初始化都是T。

  • 检查 s[0] 和 s[1] 是否相等,即 b == a,不相等,所以 dp[0][1] 为 false
  • 检查 s[1] 和 s[2] 是否相等,即 a == b,不相等,所以 dp[1][2] 为 false
  • 检查 s[2] 和 s[3] 是否相等,即 b == a,不相等,所以 dp[2][3] 为 false
  • 检查 s[3] 和 s[4] 是否相等,即 a == d,不相等,所以 dp[3][4] 为 false

然后依次填充:

dp01234
sbabad
0bTFTFF
1aFTFTF
2bTFTFF
3aFTFTF
4bTFTFT

矩阵的起手式已经很熟练了,直接可以写得出来:

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

       int n = s.size(); // get the length of the string
       vector<vector<bool>> dp(n, vector<bool>(n, false)); // initialise the array
       string longest = "";  // initialise the longest string
    }
};

考虑到如果矩阵只有1个元素或者是空的话,就直接返回他本身就可以了,到这一步也是比较的简单,可以直接写出来。

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

       int n = s.size(); // get the length of the string
       vector<vector<bool>> dp(n, vector<bool>(n, false)); // initialise the array
       string longest = "";  // initialise the longest string

       //int n = s.size();
       if (n < 2) return s;
    }
};

很好理解了,单个字母的回文就是他本身。

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

       int n = s.size(); // get the length of the string
       vector<vector<bool>> dp(n, vector<bool>(n, false)); // initialise the array
       string longest = "";  // initialise the longest string

       //int n = s.size();
       if (n < 2) return s;

       // all the single is good
       for (int i = 0; i < n; i++){
        dp[i][i] = true;
        longest = s[i];
       }

检查两个相邻的字母是否能够组成回文呢?

  • 检查 s[0] 和 s[1] 是否相等,即 b == a,不相等,所以 dp[0][1] 为 false
  • 检查 s[1] 和 s[2] 是否相等,即 a == b,不相等,所以 dp[1][2] 为 false
  • 检查 s[2] 和 s[3] 是否相等,即 b == a,不相等,所以 dp[2][3] 为 false
  • 检查 s[3] 和 s[4] 是否相等,即 a == d,不相等,所以 dp[3][4] 为 false

回顾上面的array,就更加的好理解了,加上一点语法

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

       int n = s.size(); // get the length of the string
       vector<vector<bool>> dp(n, vector<bool>(n, false)); // initialise the array
       string longest = "";  // initialise the longest string

       //int n = s.size();
       if (n < 2) return s;

       // all the single is good
       for (int i = 0; i < n; i++){
        dp[i][i] = true;
        longest = s[i];
       }

       // check that if two in a row are same
       for (int i = 0; i < n - 1; i++){
           if (dp[i][i + 1] = true){
            longest = s.substr(i, 2);
           }
       }
    }
};

难度继续提升,如果回文长度>2,至少3个字母呢?首先得是首尾元素相同。

  • 从长度为3的子串开始检查。对于子串 s[0]s[1]s[2]("bab"),首尾字符相同(b == b),且 dp[1][1] 为 true(因为单个字符是回文),所以 dp[0][2] 为 true
  • 对于子串 s[1]s[2]s[3]("aba"),首尾字符相同(a == a),且 dp[2][2] 为 true,所以 dp[1][3] 为 true
  • 对于子串 s[2]s[3]s[4]("bad"),首尾字符不同(b != d),所以 dp[2][4] 为 false

4个字母就是:

  • 对于子串 s[0]s[1]s[2]s[3]("baba"),首尾字符相同(b == a),且 dp[1][2] 为 false,所以 dp[0][3] 为 false
  • 对于子串 s[1]s[2]s[3]s[4]("abad"),首尾字符相同(a == d),且 dp[2][3] 为 false,所以 dp[1][4] 为 false

所以递归一下,就是,先要首尾元素相同,如果是,那就检查去掉首尾字母之后的串的首尾字母是否相同,如果是,继续套娃。

就是先假设回文有3个字母的:

knock knock. who's that?             s[0]s[1]s[2]这三个是不是回文啊?

knock knock. who's that?             s[1]s[2]s[3]这三个是不是回文啊?

knock knock. who's that?             s[n  - 2]s[n - 1]s[n]这三个是不是回文啊?

3个字母的问完了就去问四个字母的:

knock knock. who's that?             s[0]s[1]s[2]s[3]这三个是不是回文啊?

以此类推

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

       int n = s.size(); // get the length of the string
       vector<vector<bool>> dp(n, vector<bool>(n, false)); // initialise the array
       string longest = "";  // initialise the longest string

       //int n = s.size();
       if (n < 2) return s;

       // all the single is good
       for (int i = 0; i < n; i++){
        dp[i][i] = true;
        longest = s[i];
       }

       // check that if two in a row are same
       for (int i = 0; i < n - 1; i++){
           if (s[i] == s[i + 1]){
            dp[i][i + 1] = true;
            longest = s.substr(i, 2);
           }
       }

        for (int len = 3; len <= n; len++) {
            for (int i = 0; i < n - len + 1; i++) {
                int j = i + len - 1;
                if (s[i] == s[j] && dp[i + 1][j - 1]) {
                    dp[i][j] = true;
                    longest = s.substr(i, len);
                }
            }
        }
        
        return longest;
    }
};

今天还顺便多学了一个语法,就是在 C++ 中,s.substr(i, len) 是一个字符串成员函数,它用于从字符串 s 中提取一个子串。这个子串从索引 i 开始,长度为 len

函数 substr 会返回一个新的字符串,这个字符串包含了从索引 i 开始的 len 个字符。

示例

假设我们有一个字符串 s = "Hello, World!"

string s = "Hello, World!";
string sub = s.substr(7, 5); // 从索引 7 开始,长度为 5

在这个例子中,sub 的值将会是 "World",因为从索引 7(包括索引 7)开始的 5 个字符是 "World"

今天的学习到此为止,可能明天我还依然喜欢矩阵吧,这玩意真的好使。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值