leetcode动态规划1 5.最长回文子串 (longest-palindromic-substring) 简单解法

最长回文子串 (longest-palindromic-substring)

在刷leetcode的动态规划部分的时候,遇到的第一个题目是5.最长回文子串 (longest-palindromic-substring) ,这道题不用动态规划去做会更简单一些,这里提供这样一种解题方法和代码(vs版和leetcode版,实测可运行)

解题思路

我们这里使用一个字符串"abbc"作为输入的样例,这样看起来会更加易懂

1. 扩展字符串:

将原字符串的每一个字符用一个原字符串中没有出现过的字符包围(这里使用’#'字符)
实例:“a b b c" → \rightarrow "# a # b # b # c #” (这里的空格仅为了展示清晰,实际运行过程中不存在)

2. 计算最长回文串:

对每一个字符,求以该字符为单核的最长子串长度。
实例:以加粗字符为核

字符串最长回文子串最长回文子串长度
# a # b # b # c ##1
# a # b # b # c ##a#3
# a # b # b # c ##1
# a # b # b # c ##b#3
# a # b # b # c ##b#b#5
# a # b # b # c ##b#3
# a # b # b # c ##1
# a # b # b # c ##c#3
# a # b # b # c ##1

3. 长度换算:

我们可以看到,对于第二步中的每一个最长回文子串,去掉字符‘#’ 之后的字符串就是一个回文串,而最长的那一个字符串删去字符‘#’ 之后就是原字符串的最长回文子串。我们可以看作每一个原字符串字符与一个井字符 一起出现,同时最后多一个井字符结尾,所以

最长回文子串长度 maxLength = (maxLength’(带有‘#’的最长回文子串长度) - 1) / 2

实际运行时,因为每一个带’#'的回文子串的长度很显然都是奇数,所以只需要简单的除以2就可以。

4. 回文子串核的索引:

我们现在有扩展后的字符串的回文子串的核的位置,我们称为coreIdx’, 然后我们要求原字符串中回文子串的核的位置,我们称为coreIdx, 这里我们假设双核的子串(如"abba")的核的索引为双核中左侧的索引,也就是第一个字符b的索引。

首先,我们发现

  1. 所有‘#’的索引都是偶数(这是很显然的)
  2. 所有‘#’为核的回文子串去掉‘#’后都是双核(因为两侧的原字符串字符成对出现)
  3. 所有以原字符串为核的回文子串去掉’#'后都是单核(原因同2)

那么,我们对于所有以‘#’为核的字符串来说,它在原字符串中核的位置应该等于该‘#’左侧字符在原字符串中的索引。

分情况讨论:

  1. 以原字符串字符为核, coreIdx = (coreIdx’ - 1) / 2
  2. 以’#‘为核, coreIdx = ((coreIdx’ - 1) - 1) / 2 = coreIdx’ / 2 - 1, 从上面的结论1中,coreIdx是偶数,所以实际上这个表达式和 (coreIdx’ - 1) / 2是等价的

也就是说,我们可以用coreIdx = (coreIdx’ - 1) / 2去求所有的回文子串的核的位置,有了核的位置以及子串长度(子串长度可以表示单核还是双核),求出来实际的子串就非常简单了。

时间复杂度

一个独立的循环,运行2n+1次,每个循环最多运行n次,最坏情况下每次循环约执行n/2次,时间复杂度
O( n 2 n^2 n2)

代码

visual studio 2019 版

#include <iostream>
#include <string>
#include <algorithm>
#include <vector>
#include <vector>

using namespace std;


void leetcode_dp_5_s1() {
	string s;
	cin >> s;				// 输入字符串
	int l = s.size();		// l: 原字符串长度
	string b = s;			// b: backup,原字符串备份
	for (int i = 0; i <= l; i++) {
		s.insert(2 * i, 1, '#');	// 字符串扩展
	}
	l = s.size();			// l: 扩展后字符串长度
	vector<int> strLen;		// 记录扩展后每一个字符的回文子串长度(这里实际记录的是核的单侧有多少字符)
	for (int i = 0; i <= l; i++) strLen.push_back(0);
	int j = 0;
	while (j < l) {
		int curStep = 1;
		// 对于每一个字符来说,如果从核的索引 +- curStep 没有越界 且 两侧字符相等,那么回文子串长度+1 
		while (!(j - curStep < 0 || j + curStep >= l) && s[j - curStep] == s[j + curStep]) {
			strLen[j]++;
			curStep++;
		}
		j++;
	}
	int max = *max_element(strLen.begin(), strLen.end());	// 求出最大值
	int idx = distance(strLen.begin(), max_element(strLen.begin(), strLen.end()));	// 求出最大值索引
	int gap = (max - 1) / 2;	// 计算原字符串中核的左侧有多少元素
	int left = (idx - 1) / 2 - gap; // 计算核的位置
	string res = b.substr(left, max);	// 获取子串
	cout << res;
}

leetcode 版

class Solution {
public:
    string longestPalindrome(string s) {
        int l = s.size();
        string b = s;
        for (int i = 0; i <= l; i++) {
            s.insert(2 * i, 1, '#');
        }
        l = s.size();
        vector<int> strLen;
        for (int i = 0; i <= l; i++) strLen.push_back(0);
        int j = 0;
        while (j < l) {
            int curStep = 1;
            while (!(j - curStep < 0 || j + curStep >= l) && s[j - curStep] == s[j + curStep]) {
                strLen[j]++;
                curStep++;
            }
            j++;
        }
        int max = *max_element(strLen.begin(), strLen.end());
        int idx = distance(strLen.begin(), max_element(strLen.begin(), strLen.end()));
        int gap = (max - 1) / 2;
        int left = (idx - 1) / 2 - gap;
        string res = b.substr(left, max);
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值