温习数据结构与算法,准备一些比赛,为了学业和就业,以及提升自己的编程能力,将系统的刷刷算法,入手c++跟着carl,leetcode刷题笔记将持续更新…
string就是只能用来存放char的vector,但它和vector相比,额外提供了很多操作字符串的接口函数。
与Java的String对比:在Java文档中将String类对象称为不可变字符串,Java字符串中的字符是不能修改的。这一点和C++不同,C++中字符串时可以修改的。
在C语言中,把一个字符串存入一个数组时,也把结束符 '\0’存入数组,并以此作为该字符串是否结束的标志。
例如这段代码:
char a[5] = "asd";
for (int i = 0; a[i] != '\0'; i++) {
}
C++中,提供一个string类,string类会提供 size接口,可以用来判断string类字符串是否结束,就不用’\0’来判断是否结束。
遍历
string a = "asd";
for (int i = 0; i < a.size(); i++) {
}
for (auto iter = v1.begin(); iter != s1.end();++iter) {
cout << *iter ; // 输出当前字符
}
for (auto it : s1) {
cout << it ; // 输出当前字符
}
// 如果想用范围for语句改变容器中的元素值,请加上引用
for (auto &it : s1) {
cout << it ; // 输出当前字符
}
库函数要会使用,也要知道其原理
库函数
构造函数
string` `str; ``//生成一个空字符串
string` `str (``"ABC"``) ``//等价于 str="ABC"<br>
string` `str (``"ABC"``, strlen) // 将"ABC"存到str里,最多存储前strlen个字节
string` `s(``"ABC"``,stridx,strlen) //将"ABC"的stridx位置,做为字符串开头,存到str里.且最多存储strlen个字节.
string` `s(strlen, ``'A'``) ``//存储strlen个'A'到str里
成员函数
str1.length(); //获取字符串长度
str1.size(); //获取字符串数量,等价于length()
str1.capacity(); //获取容量,容量包含了当前string里不必增加内存就能使用的字符数
str1.resize(10); //表示设置当前string里的串大小,若设置大小大于当前串长度,则用字符\0来填充多余的.
str1.resize(10,char c); //设置串大小,若设置大小大于当前串长度,则用字符c来填充多余
str1.empty(); //判断为空, 为空返回true
str.reverse(str.begin(),str.end()); //反转
substr()子串
str.substr(3); //返回 [3] 及以后的子串
str.substr(2, 4); //返回 str[2]~str[2+(4-1)] 子串(即从[2]开始4个字符组成的字符串)
//拷贝
str2=str1.substr(2); //提取子串,提取出str1的下标为2到末尾,给str2
str2=str1.substr(2,3); //提取子串,从 str1的下标为2开始,提取3个字节给str2
const char *s1= str.data(); //将string类转为字符串数组,返回给s1
char *s=new char[10];
str.copy(s,count,pos); //将str里的pos位置开始,拷贝count个字符,存到s里.
replace()替换
str.replace(2, 4, "sz");//返回把 [2]~[2+(4-1)] 的内容替换为 "sz" 后的新字符串
str.replace(2, 4, "abcd", 3);//返回把 [2]~[2+(4-1)] 的内容替换为 "abcd" 的前3个字符后的新字符串
insert()插入
str.insert(2, "sz");//从 [2] 位置开始添加字符串 "sz",并返回形成的新字符串
str.insert(2, "abcd", 3);//从 [2] 位置开始添加字符串 "abcd" 的前 3 个字符,并返回形成的新字符串
str.insert(2, "abcd", 1, 3);//从 [2] 位置开始添加字符串 "abcd" 的前 [2]~[2+(3-1)] 个字符,并返回形成的新字符串
追加赋值
除了用重载的 +
操作符,还可以使用函数来完成。
str.push_back('a');//在 str 末尾添加字符'a'
str.append("abc");//在 str 末尾添加字符串"abc"
/*assign() :赋值函数 ,里面会重新释放分配字符串内存 */
str1.assign("HELLO"); //str1="HELLO"
str1.assign("HELLO", 4); //str1="HELL" ,只保留4个字符
str1.assign("HELLO", 2, 3); //str1="LLO" ,从位置2开始,只保留3个字符
str1.assign(5, 'c'); //str1="CCCCC" //按字符赋值
erase()删除
str.erase(3);//删除 [3] 及以后的字符,并返回新字符串
str.erase(3, 5);//删除从 [3] 开始的 5 个字符,并返回新字符串
str1.clear(); //删除所有
find()
/*查找成功返回位置,查找失败,则n等于-1*/
str.find("ab"); //返回字符串 ab 在 str 的位置
str.find("ab", 2); //在 str[2]~str[n-1] 范围内查找并返回字符串 ab 在 str 的位置
str.find("CDEfg",1,3); //从位置1处,查找"CDEfg"的前3个字符
str.rfind("ab", 2); //在 str[0]~str[2] 范围内查找并返回字符串 ab 在 str 的位置
str.find_first_of("apple", 2);//返回 apple 中任何一个字符首次在 str[2]~str[n-1] 范围中出现的位置
str.find_first_not_of("apple", 2);//返回除 apple 以外的任何一个字符在 str[2]~str[n-1] 范围中首次出现的位置
str.find_last_of("apple", 2);//返回 apple 中任何一个字符最后一次在 str[0]~str[2] 范围中出现的位置
sort()
-
要求输入一个范围[first, last)
-
随机迭代器,能用此算法的容器是支持随机访问的容器:vector, deque, string。
sort(v.begin(),v.end()); // 对 vector 排序
反向迭代器 sort(v.rbegin(),v.rend())
- 升序:sort(begin,end,less());
- 降序:sort(begin,end,greater()).
sort(vs,vs + 2); // 对数组排序
自己编写一个比较函数来实现,接着调用三个参数的sort:sort(begin,end,compare)就成了。
对于list容器,把compare作为sort的参数就可以了,即:sort(compare)
.
1)自己编写compare函数:
bool compare(int a,int b){
return a<b; //升序排列,如果改为return a>b,则为降序
}
快排qsort():
qsort ( 数组名 ,元素个数,元素占用的空间(sizeof),比较函数)
比较函数是一个自己写的函数 遵循 int com(const void *a,const void *b) 的格式。
#include "stdafx.h"
#include <algorithm>
#include "iostream"
#include "string"
using namespace std;
int compare(const void *a,const void *b)
{
return *(int*)b-*(int*)a;
}
int main(int argc, char* argv[])
{
int a[11]={2,4,5,6,1,2,334,67,8,9,0},i;
for(i=0;i<11;i++)
cout<<a[i]<<',';
qsort((void *)a,11,sizeof(int),compare);
cout<<'\n';
for(i=0;i<11;i++)
cout<<a[i]<<',';
return 0;
}
344.反转字符串
reverse库函数
reverse(s.begin(),s.end()) ;
对于字符串,我们定义两个指针(也可以说是索引下标),一个从字符串前面,一个从字符串后面,两个指针同时向中间移动,并交换元素。
class Solution {
public:
void reverseString(vector<char>& s) {
for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
swap(s[i],s[j]);
}
}
};
swap库函数可以有两种实现。
一种就是常见的交换数值:
int tmp = s[i];
s[i] = s[j];
s[j] = tmp;
一种就是通过位运算:
s[i] ^= s[j];
s[j] ^= s[i];
s[i] ^= s[j];
541. 反转字符串II
读题翻译写代码😶
class Solution {
public:
string reverseStr(string s, int k) {
for (int i = 0; i < s.size(); i += (2 * k)) {
// 1. 每隔 2k 个字符的前 k 个字符进行反转
// 2. 剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符
if (i + k <= s.size()) {
reverse(s.begin() + i, s.begin() + i + k );
continue;
}
// 3. 剩余字符少于 k 个,则将剩余字符全部反转。
reverse(s.begin() + i, s.begin() + s.size());
}
return s;
}
};
剑指Offer 05.替换空格
【法一】遍历是否是空格,新字符串拼接
class Solution {
public:
string replaceSpace(string s) {
string res;
for(int i = 0; i < s.size(); i++){
if(s[i] == ' ') res += "%20";
else res += s[i];
}
return res;
}
};
class Solution {
public:
string replaceSpace(string s) {
string res;
for(auto& c : s) {
if(c == ' ') res += "%20";
else res.push_back(c);
}
return res;
}
};
【法二】原地,数组扩容,统计空格个数,从后向前进行操作。
class Solution {
public:
string replaceSpace(string s) {
int count = 0; // 统计空格的个数
int sOldSize = s.size();
for (int i = 0; i < s.size(); i++) {
if (s[i] == ' ') {
count++;
}
}
// 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
s.resize(s.size() + count * 2);
int sNewSize = s.size();
// 从后先前将空格替换为"%20"
for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
if (s[j] != ' ') {
s[i] = s[j];
} else {
s[i] = '0';
s[i - 1] = '2';
s[i - 2] = '%';
i -= 2;
}
}
return s;
}
};
151.翻转字符串里的单词
c++没有split…,换思路:对于字符串不可变的语言(Java),首先得把字符串转化成其他可变的数据结构,同时还需要在转化的过程中去除空格, 对于字符串可变的语言(c++),就不需要再额外开辟空间了,直接在字符串上原地实现。在这种情况下,反转字符和去除空格可以一起完成
- 移除多余空格
- 将整个字符串反转
- 将每个单词反转
class Solution {
public:
string reverseWords(string s) {
// 反转整个字符串
reverse(s.begin(), s.end());
int n = s.size();
int idx = 0;
for (int start = 0; start < n; ++start) {
if (s[start] != ' ') {
// 填一个空白字符然后将idx移动到下一个单词的开头位置
if (idx != 0) s[idx++] = ' ';
// 循环遍历至单词的末尾
int end = start;
while (end < n && s[end] != ' ') s[idx++] = s[end++];
// 反转整个单词
reverse(s.begin() + idx - (end - start), s.begin() + idx);
// 更新start,去找下一个单词
start = end;
}
}
s.erase(s.begin() + idx, s.end());
return s;
}
};
剑指Offer58-II.左旋转字符串
【法一】原地,局部反转+整体反转 达到左旋转
- 反转区间为前n的子串
- 反转区间为n到末尾的子串
- 反转整个字符串
class Solution {
public:
string reverseLeftWords(string s, int n) {
reverse(s.begin(), s.begin() + n);
reverse(s.begin() + n, s.end());
reverse(s.begin(), s.end());
return s;
}
};
【法二】substr()子串拼接,但开辟新空间
class Solution {
public:
string reverseLeftWords(string s, int n) {
return s.substr(n)+s.substr(0,n);
}
};
28. 实现 strStr()
class Solution {
public:
int strStr(string haystack, string needle) {
return haystack.find(needle);
}
};
不用库函数,暴力匹配
class Solution {
public:
int strStr(string s, string p) {
int n = s.size(), m = p.size();
for(int i = 0; i <= n - m; i++){
int j = i, k = 0;
while(k < m and s[j] == p[k]){
j++;
k++;
}
if(k == m) return i;
}
return -1;
}
};
优化⬇️
KMP字符串匹配
当出现字符串不匹配时,**因为找到了最长相等的前缀和后缀,匹配失败的位置是后缀子串的后面,那么我们找到与其相同的前缀的后面从新匹配,**避免从头再去做匹配了。
需要构建前缀表,用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。
从匹配串某个位置跳转下一个匹配位置这一过程是与原串无关的,我们将这一过程称为找 next 点。
显然我们可以预处理出 next 数组,数组中每个位置的值就是该下标应该跳转的目标位置( next 点)
next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。
class Solution {
public:
int strStr(string s, string p) {
int n = s.size(), m = p.size();
if(m == 0) return 0;
//设置哨兵
s.insert(s.begin(),' ');
p.insert(p.begin(),' ');
vector<int> next(m + 1);
//预处理next数组
for(int i = 2, j = 0; i <= m; i++){
while(j and p[i] != p[j + 1]) j = next[j];
if(p[i] == p[j + 1]) j++;
next[i] = j;
}
//匹配过程
for(int i = 1, j = 0; i <= n; i++){
while(j and s[i] != p[j + 1]) j = next[j];
if(s[i] == p[j + 1]) j++;
if(j == m) return i - m;
}
return -1;
}
};