字符串
- 熟悉字符串中的 基本操作,尤其是在数组中没有的独特操作;
- 理解不同 比较 函数之间的区别;
- 理解字符串 是否可变以及导致连接过程中出现的问题;
- 能够解决与字符串相关的基本问题,如排序、子串、字符串匹配等。
KMP算法
核心思想 减少模式串与主串的匹配次数(主指针不回溯)。 实现关键 模式串 构造next数组。 复杂度 O(m+n)
关键 : 构造 next 数组
// s[]是长文本,p[]是模式串,n是s的长度,m是p的长度
#include <string>
#include <iostream>
using namespace std;
cnst int N = 1000010;
string p, s;
int ne[N];
int main()
{
cin >> s >> p;
ne[0] = 0;
ne[1] = 0;
int i = 2, j = 0;
//求模式串的next数组-----------------------------------------------
for (i = 2; i < p.size() + 1; i++)
{
while (j && p[i - 1] != p[j]) j = ne[j];
if (p[i - 1] == p[j]) j++;
ne[i] = j;
}
//匹配-------------------------------------------------------------
for (i = 0, j = 0; i < s.size(); i++)
{
while (j && s[i] != p[j]) j = ne[j];
if (s[i] == p[j]) j++;
if (j == p.size())
{
cout << i - j + 1 << " ";
return 0;
}
}
}
最后总结下这个算法:
- 匹配失败时,总是能够让模式串回退到某个位置,使文本不用回退。
- 在字符串比较时,模式串提供的信息越多,计算复杂度越低。(有兴趣的可以了解一下 Trie 树,这是文本提供的信息越多,计算复杂度越低的一个例子。)
PMT数组 中的值是字符串的前缀集合与后缀集合的交集中最长元素的长度。例如,对于”aba”,它的前缀集合为{”a”, ”ab”},后缀 集合为{”ba”, ”a”}。两个集合的交集为{”a”},那么长度最长的元素就是字符串”a”了,长 度为1,所以对于”aba”而言,它在PMT表中对应的值就是1。再比如,对于字符串”ababa”,它的前缀集合为{”a”, ”ab”, ”aba”, ”abab”},它的后缀集合为{”baba”, ”aba”, ”ba”, ”a”}, 两个集合的交集为{”a”, ”aba”},其中最长的元素为”aba”,长度为3。
则图中最长公共前缀后缀长度为0;
下面再以”ABCDABD”为例,进行介绍:
- ”A”的前缀和后缀都为空集,共有元素的长度为0;
- ”AB”的前缀为[A],后缀为[B],共有元素的长度为0;
- ”ABC”的前缀为[A, AB],后缀为[BC, C],共有元素的长度0;
- ”ABCD”的前缀为[A, AB, ABC],后缀为[BCD, CD, D],共有元素的长度为0;
- ”ABCDA”的前缀为[A, AB, ABC, ABCD],后缀为[BCDA, CDA, DA, A],共有元素为”A”,长度为1;
- ”ABCDAB”的前缀为[A, AB, ABC, ABCD, ABCDA],后缀为[BCDAB, CDAB, DAB, AB, B],共有元素为”AB”,长度为2;
- ”ABCDABD”的前缀为[A, AB, ABC, ABCD, ABCDA, ABCDAB],后缀为[BCDABD, CDABD, DABD, ABD, BD, D],共有元素的长度为0。
字符串匹配朴素做法
//cpp
int ViolentMatch(string& s, string& p)//s为长字符串,p为短字符串。
{
for (int i = 0; i < m;)
{
int start = i, j = 0;//star 记录此次s中的开始位置
while (i < m && j < n && s[i++] == p[j++]);//一次比较,直到不相等,注意句尾带分号
if (j == n)//如果p串比较完了,返回开始位置
{
cout << start;
return 0;
}
i = start + 1;//i更新到本次起始位置的下一个位置
}
cout << -1;
return -1;
}
模拟
8. 字符串转换整数 (atoi)
请你来实现一个 atoi 函数,使其能将字符串转换成整数。
class Solution {
public:
int myAtoi(string str) {
int i = 0, flag = 1;
long res = 0; //默认flag = 1,正数
while (str[i] == ' ') i ++;
if (str[i] == '-') flag = -1;
if (str[i] == '-' || str[i] == '+') i ++;
for (; i < str.size() && isdigit(str[i]); i ++) {
res = res * 10 + (str[i] - '0');
if (res >= INT_MAX && flag == 1) return INT_MAX;
if (res > INT_MAX && flag == -1) return INT_MIN;
}
return flag * res;
}
};
415. 字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。
class Solution {
public:
string addStrings(string num1, string num2) {
int len1 = num1.length();
int len2 = num2.length();
for (int i = 0; i < len1 / 2; i ++){
swap(num1[i], num1[len1 - i - 1]);
}
for (int j = 0; j < len2 / 2; j ++){
swap(num2[j], num2[len2 - j - 1]);
}
string res = ""; int sign = 0;
int t = 0;
int p = 0, q = 0;
while (p < len1 && q < len2){
t = (num1[p] - '0') + (num2[q] - '0') + sign;
if (t >= 10){
t = t - 10;
sign = 1;
}else{
sign = 0;
}
res += (char)(t + '0');
p ++; q ++;
}
while (p < len1 ){
t = (num1[p] - '0') + sign;
if (t >= 10){
t = t - 10;
sign = 1;
}else{
sign = 0;
}
res += (char)(t + '0');
p ++;
}
while (q < len2){
t = (num2[q] - '0')+ sign;
if (t >= 10){
t = t - 10;
sign = 1;
}else{
sign = 0;
}
res += (char)(t + '0');
q ++;
}
if (sign == 1) res +='1';
int len = res.length();
for (int i = 0; i < len/2; i ++){
swap(res[i], res[len - i - 1]);
}
return res;
}
};
12. 整数转罗马数字
class Solution {
public:
string intToRoman(int num) {
string result = "";
int a[] = {1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1};
string b[] = {"M","CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX","V", "IV", "I"};
int index = 0, cnt;
while (num != 0){
cnt = num / a[index];
while (cnt --){
result += b[index];
}
num = num % a[index];
index ++;
}
return result;
}
};
滑动窗口
常见模型:找出滑动窗口中的最大值/最小值
int hh = 0, tt = -1;
for (int i = 0; i < n; i ++ )
{
while (hh <= tt && check_out(q[hh])) hh ++ ; // 判断队头是否滑出窗口
while (hh <= tt && check(q[tt], i)) tt -- ;
q[ ++ tt] = i;
}
3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
vector<int> m(128, 0); //ASCII码范围:0-127
int ans = 0;
int i = 0;
for (int j = 0; j < s.size(); j++) {
if(m[s[j]]!=0) i = max(i, m[s[j]]);
m[s[j]] = j + 1;
ans = max(ans, j - i + 1);
}
return ans;
}
};
//s[j] 是新遇到的字母, m 是记录字母对应的新位置,所以 m[s[j]] 就是新字母对应的新位置
思路
- j 表示子串的最后一个字母,计算子串长度为 j - i + 1
- 滑动窗口,保证每个窗口里字母都是唯一的
- 使用 vector m 来记录一个字母如果后面出现重复时,i 应该调整到的新位置
所以每次更新的时候都会保存 j + 1 ,即字母后面的位置
1446. 连续字符
给你一个字符串 s ,字符串的「能量」定义为:只包含一种字符的最长非空子字符串的长度。
class Solution {
public:
int maxPower(string s) {
int ans = 0;
int i = 0;
for (int j = 0; j < s.size(); j++) {
i = j;
while (s[i] == s[j]) j++;
j -- ;
ans = max(ans, j - i + 1 );
}
return ans;
}
};
请你返回字符串的能量。
其他类型
9. 回文数
判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
//判断回文数(串)
class Solution {
public:
bool isPalindrome(int x) {
string tmp=to_string(x);
string tmp2=tmp;
reverse(tmp2.begin(),tmp2.end());
if(tmp==tmp2) return true;
return false;
}
};
-------------------------
class Solution {
public:
bool isPalindrome(int x) {
if(x < 0)
return false;
int cur = 0;
int num = x;
while(num != 0) {
cur = cur * 10 + num % 10;
num /= 10;
}
return cur == x;
}
}
151. 翻转字符串里的单词
给定一个字符串,逐个翻转字符串中的每个单词。
说明:
无空格字符构成一个 单词 。
输入字符串可以在前面或者后面包含多余的空格,但是反转后的字符不能包括。
如果两个单词间有多余的空格,将反转后单词间的空格减少到只含一个
class Solution {
public:
string reverseWords(string s)
{
stack<string> stk;
string res, temp;
istringstream ss(s); // ss与数输入的字符串s绑定
while (ss >> temp) // 以空格为分隔符将其分割
{
stk.push(temp);
stk.push(" ");
}
if(!stk.empty())
stk.pop(); // 弹出最后压入的那个多余空格
while (! stk.empty())// 单词翻转后的新串
{
res += stk.top();
stk.pop();
}
return res;
}
};