题目描述
给你一个字符串
s
,找到s
中最长的 回文 子串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
力扣代码:
class Solution {
public:// expandAroundCenter 函数通过给定的中心 (left, right) 扩展回文串// 当左指针和右指针的字符相等时,继续扩展,直到找到不相等或越界为止pair<int, int> expandAroundCenter(const string& s, int left, int right) {// 通过 while 循环,扩展回文while (left >= 0 && right < s.size() && s[left] == s[right]) {--left; // 向左扩展++right; // 向右扩展}// 返回最终的左右指针位置,这两个位置表示的是最大回文子串的左右边界// 注意,退出 while 循环时,left 和 right 已经越界或不相等,因此要返回的是 left + 1 和 right - 1return {left + 1, right - 1};}// longestPalindrome 函数遍历字符串中的每个字符,并尝试以每个字符作为回文中心string longestPalindrome(string s) {int start = 0, end = 0; // 用来记录最长回文子串的起始和结束位置// 遍历字符串中的每个字符for (int i = 0; i < s.size(); ++i) {// expandAroundCenter 函数可以找到以字符 i 为中心的回文子串auto [left1, right1] = expandAroundCenter(s, i, i); // 奇数长度的回文子串auto [left2, right2] = expandAroundCenter(s, i, i + 1); // 偶数长度的回文子串// 更新最大回文子串的起始和结束位置// 如果奇数长度回文子串更长,则更新 start 和 endif (right1 - left1 > end - start) {start = left1;end = right1;}// 如果偶数长度回文子串更长,则更新 start 和 endif (right2 - left2 > end - start) {start = left2;end = right2;}}// 返回最长回文子串return s.substr(start, end - start + 1); // substr(start, length) 返回子串}
};
以上是力扣的官方题解,但是我第一次读起来感觉非常晦涩难懂,就加了一些注释,可能也有新手跟我一开始有一样的困惑,下边是一些当时做这道题有些不明白的点:
函数解释:
expandAroundCenter
:
- 这是一个辅助函数,给定一个左右中心
left
和right
,扩展回文串直到遇到不相等的字符或超出字符串边界。- 它返回回文串的左右边界,注意由于扩展时,
left
和right
最后会越过实际的回文子串,因此返回的边界是left + 1
和right - 1
。
longestPalindrome
:
- 该函数遍历每个字符,将其作为中心尝试寻找最长的回文子串。
- 对于每个字符,尝试找到两种情况的回文子串:
- 奇数长度回文:以该字符为中心,调用
expandAroundCenter(s, i, i)
。- 偶数长度回文:以该字符和下一个字符之间的空隙为中心,调用
expandAroundCenter(s, i, i + 1)
。- 每次调用后,比较回文子串的长度,如果比当前记录的最长回文子串长,就更新起始和结束位置。
- 最后,使用
substr
返回最长回文子串。
pair操作解释:
pair<int, int>
是 C++ 标准库中 std::pair
的一种使用方式。std::pair
是一个模板类,用于存储两个数据元素,这两个元素可以是任意类型。pair<int, int>
就是一个特定类型的 pair
,表示存储两个 int
类型的元素。
1. pair<int, int>
的结构
pair<int, int>
实际上是一个包含两个整数的对象,它的结构如下:
pair<int, int> p;
其中 p.first
是第一个整数,p.second
是第二个整数。你可以通过这两个成员来访问这对值。
2. 使用示例
#include <iostream>
using namespace std;int main() {// 创建一个 pair,存储两个整数pair<int, int> p = {3, 7}; // 初始化,first = 3, second = 7// 访问 pair 的成员cout << "First: " << p.first << endl; // 输出 3cout << "Second: " << p.second << endl; // 输出 7// 修改 pair 的成员p.first = 5;p.second = 10;cout << "Updated First: " << p.first << endl; // 输出 5cout << "Updated Second: " << p.second << endl; // 输出 10return 0;
}
3. 结构化绑定(C++17及以后)
在 C++17 中,可以使用结构化绑定来简化对 pair
类型的访问。这让我们可以像拆解元组那样访问 pair
的元素。
例如,以下是 pair<int, int>
结合结构化绑定的写法:
#include <iostream>
using namespace std;int main() {pair<int, int> p = {3, 7}; // 初始化 pair// 使用结构化绑定auto [a, b] = p;// 访问拆解后的变量cout << "a: " << a << endl; // 输出 3cout << "b: " << b << endl; // 输出 7return 0;
}
这里,auto [a, b] = p;
是结构化绑定的语法,p
被拆解成两个变量 a
和 b
,它们分别对应 p.first
和 p.second
。
4. pair<int, int>
的常见应用
- 返回多个值:函数可以返回一个
pair
来同时返回两个相关的值。 - 映射(Map)操作:
std::map
和std::unordered_map
中的元素是pair<const Key, Value>
,其中first
是键,second
是值。 - 记录坐标、区间、最值等:
pair
可以用来存储一对相关的数据,例如一个区间的开始和结束位置。
const操作解释:
1. const
const
是常量的意思,表示这个变量的值在函数内部不能被修改。对于 const string& s
来说,意味着:
- 你不能修改
s
所引用的字符串的内容。 - 你不能让
s
引用另一个字符串。
2. string&
string&
表示 s
是一个引用,即它是对某个 string
类型对象的引用,而不是该对象的副本。
引用是 C++ 中的一种类型,它就像是对象的别名。通过引用,你可以直接访问原始对象,而不需要复制该对象。
3. 常量引用 const string&
const
与&
结合在一起,就意味着“对一个字符串的引用,但不能修改它”。你不能改变s
引用的字符串内容,但你可以读取它。- 由于是引用,这意味着你不需要复制整个字符串,只是借用原始字符串的内存,因此它比传值方式(
string s
)更加高效,尤其是当字符串较长时。
为什么要使用 const string&
?
- 性能优化:传递大的对象(如字符串)时,如果传递的是引用,而不是副本,会更高效,避免了内存的复制。
- 保证不修改数据:
const
保证函数内部不会修改传入的字符串,符合常量性设计,避免无意间改变数据。 - 保持不变性:保证字符串在函数内部不能被修改,使得代码更安全、更容易理解。
示例:
#include <iostream>
#include <string>
using namespace std;void printString(const string& s) {// 你可以读取字符串,但不能修改它cout << s << endl;// s[0] = 'A'; // 错误:不能修改常量引用
}int main() {string str = "Hello, world!";printString(str); // 传递字符串的常量引用return 0;
}
auto操作解释:
auto
是 C++11 引入的关键字,用来让编译器根据变量初始化的类型来自动推导出变量的类型。它使代码更加简洁,并且减少了手动指定类型时的出错机会,尤其是在函数返回类型较为复杂时。
在力扣代码中:
auto [left1, right1] = expandAroundCenter(s, i, i);
auto [left2, right2] = expandAroundCenter(s, i, i + 1);
这里的 expandAroundCenter
函数返回了一个 pair<int, int>
类型的值。通过使用 auto
,你不需要显式写出返回类型 pair<int, int>
,编译器会根据 expandAroundCenter
函数的返回类型自动推导出 left1, right1
和 left2, right2
的类型。
为什么选择 auto
?
-
简化代码:
auto
让你不需要手动声明变量类型(pair<int, int>
),这样就能避免类型重复声明,并且保持代码的简洁和可读性。 -
避免重复类型声明:
expandAroundCenter
返回的是pair<int, int>
类型,手动声明时,你可能需要写出pair<int, int> left1, right1;
,这样显得冗长,使用auto
可以省略这种重复。 -
提高可维护性:如果你修改了
expandAroundCenter
的返回类型(例如改变了返回类型结构),使用auto
后,你不需要去修改每个相关变量的类型声明,这样使得代码更易于维护。
示例:
如果以上代码你显式声明类型,而不用 auto
,代码会是这样:
pair<int, int> left1, right1;
pair<int, int> left2, right2;
left1 = expandAroundCenter(s, i, i);
right1 = expandAroundCenter(s, i, i + 1);