leetcode 刷题笔记本
基础数据结构
1.字符串
substr()用法: s.substr(i,j)表示从下标为i的位置开始截取j位
形式 : s.substr(pos, len)
返回值: string,包含s中从pos开始的len个字符的拷贝(pos的默认值是0,len的默认值是s.size() - pos,即不加参数会默认拷贝整个s)
异常 :若pos的值超过了string的大小,则substr函数会抛出一个out_of_range异常;若pos+n的值超过了string的大小,则substr会调整n的值,只拷贝到string的末尾
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s="123abc";
string a=s.substr(2,2);//从下标为2的位置开始,拷贝两个字符返回。
cout<<a;
return 0;
}
程序运行结果: 3a
①不加参数会拷贝整个s。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s="123abc";
string a=s.substr();//不加参数会拷贝整个s
cout<<a;
return 0;
}
程序运行结果: 123abc
②只加参数pos,会从pos位置开始拷贝剩余全部字符。
#include<iostream>
#include<string>
using namespace std;
int main()
{
string s="123abc";
string a=s.substr(3);//从下标为3的位置开始,拷贝剩余全部字符返回。
cout<<a;
return 0;
}
程序运行结果: abc
find_first_of()函数和find_last_of()函数
1、find_first_of()函数
正向查找在原字符串中第一个与指定字符串(或字符)中的某个字符匹配的字符,返回它的位置。若查找失败,则返回npos。(npos定义为保证大于任何有效下标的值。)
2、find_last_of()函数
逆向查找在原字符串中最后一个与指定字符串(或字符)中的某个字符匹配的字符,返回它的位置。若查找失败,则返回npos。(npos定义为保证大于任何有效下标的值。)
查找字符串a是否包含子串b,不是用strA.find(strB) > 0 而是 strA.find(strB) != string:npos
其中string:npos是个特殊值,说明查找没有匹配
// leetcode 1704. 判断字符串的两半是否相似
class Solution {
public:
bool halvesAreAlike(string s) {
string a = s.substr(0, s.size() / 2);
string b = s.substr(s.size() / 2);
string h = "aeiouAEIOU";
int sum1 = 0, sum2 = 0;
for (int i = 0; i < a.size(); i++) {
if (h.find_first_of(a[i]) != string::npos) {
sum1++;
}
}
for (int i = 0; i < b.size(); i++) {
if (h.find_first_of(b[i]) != string::npos) {
sum2++;
}
}
return sum1 == sum2;
}
};
作者:力扣官方题解
链接:https://leetcode.cn/problems/determine-if-string-halves-are-alike/solutions/1960619/pan-duan-zi-fu-chuan-de-liang-ban-shi-fo-d21g/
来源:力扣(LeetCode)
char 转 int
- 直接转换:
char a = '0'
int ia = a - '0';
** 判断字符是否为数字字符**
string s = "aa123";
// 方法1 isdigital()函数
isdigital(s[i]);
// 方法2
s[i] >= '0' && s[i] <= '9'
2.函数stoi(),atoi(),to_string();
- atoi():把字符串转换成整型数。使用该函数时要注意atoi返回的是int类型,注意输入str的范围不要超出int类型的范围
判断字符是否为字母
字母(不区分大小写):isalpha()
大写字母:isupper()
小写字母:islower()
2. 哈希表unordered_map()
常见的创建 unordered_map 容器的方法:
1.通过调用 unordered_map 模板类的默认构造函数,可以创建空的 unordered_map 容器。如
std::unordered_map<std::string, std::string> umap;
2.在创建 unordered_map 容器的同时,可以完成初始化操作。如
std::unordered_map<std::string, std::string> umap{
{"Python教程","http://c.biancheng.net/python/"},
{"Java教程","http://c.biancheng.net/java/"},
{"Linux教程","http://c.biancheng.net/linux/"} };
3.调用 unordered_map 模板中提供的复制(拷贝)构造函数,将现有 unordered_map 容器中存储的键值对,复制给新建 unordered_map 容器。
std::unordered_map<std::string, std::string> umap2(umap);
常用函数:
- at(key) 返回容器中存储的键 key 对应的值,如果 key 不存在,则会抛出 out_of_range 异常。
- emplace() 向容器中添加新键值对,效率比 insert() 方法高。
- insert() 向容器中添加新键值对。
- count(key) 在容器中查找以 key 键的键值对的个数。
- find(key) 查找以 key 为键的键值对,如果找到,则返回一个指向该键值对的正向迭代器;反之,则返回一个指向容器中最后一个键值对之后位置的迭代器(如果 end() 方法返回的迭代器)。
哈希表遍历:
unordered_map<int,int> map;
//方法一
for(auto [num,occ] : map){
int key = num;
int val = occ;
cout<<"key: "<<key<<" val: "<<val<<endl;
}
//方法二
for(auto &it:map){
int key = it.first;
int val = it.second;
cout<<"key: "<<key<<" val: "<<val<<endl;
}
3.二叉树
前序遍历:根-左-右
中序遍历:左-根-右
后序遍历:左-右-根
前序/中序/后序可以看根节点所在位置。
递归遍历方法:
// 后序遍历
class Solution {
public:
void travel(TreeNode* root, vector<int> &v){
if(root->left) travel(root->left, v);
if(root->right) travel(root->right,v);
v.push_back(root->val);//前序、中序和后序遍历不同在于这一句的位置
}
vector<int> postorderTraversal(TreeNode* root) {
if(!root){
return {};
}
vector<int> vec;
travel(root,vec);
return vec;
}
};
迭代遍历方法
迭代遍历方法用栈实现,迭代方法前序、中序、后序会不太一样,具体见《代码随想录:二叉树的迭代遍历》
二叉搜索树(Binary Search Tree):
定义:
- 当前节点的左子树中的数均小于当前节点的数;
- 当前节点的右子树中的数均大于当前节点的数;
- 所有左子树和右子树自身也是二叉搜索树。
形象地,可看作左小右大的二叉树,,可以用中序遍历(左根右)的方式
4.unordered_set
5.动态数组类型Vector
常见用法在此省略,写一些不常见的
- reference back(): 返回vector最后一个元素的引用。在某些题会有不错的效果,eg.
56.合并区间
。
如果是一个m*2的矩阵grid,对其最后元素的访问可以为grid.back()[0],grid.back()[1]
STL常用函数
-
sort()
sort() 函数位于头文件中,因此在使用该函数前,程序中应包含如下语句:#include <algorithm>
sort()函数可以对给定区间所有元素进行排序。它有三个参数sort(begin, end, cmp),其中begin为指向待sort()的数组的第一个元素的指针,end为指向待sort()的数组的最后一个元素的下一个位置的指针,cmp参数为排序准则,cmp参数可以不写,如果不写的话,默认从小到大进行排序。
常用sort() + lambda 表达式实现自定义排序[9]. -
accumulate():accumulate定义在
#include<numeric>
中,作用有两个,一个是累加求和,另一个是自定义类型数据的处理。
1.累记求和
int sum = accumulate(vec.begin() , vec.end() , 42);
accumulate带有三个形参:头两个形参指定要累加的元素范围,第三个形参则是累加的初值。
可以使用accumulate把string型的vector容器中的元素连接起来:
string sum = accumulate(v.begin() , v.end() , string(" "));
这个函数调用的效果是:从空字符串开始,把vec里的每个元素连接成一个字符串。
2.自定义数据类型的处理
lambda表达式
一个lambda 表达式具有以下形式:
[capture list](parameter list) -> return type { function body }
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type、parameter list和function body 和任何普通函数一样,分别表示返回类型、参数列表和函数体。
一个完整的lambda 表达式如下:
auto f = [](int a) -> int { return a + 1; };
std::cout << f(1) << std::endl; // 输出: 2
可以省略参数列表和返回类型,但必须包含捕获列表和函数体:
auto f = [ ] { return 42;}
使用捕获列表:
lambda 表达式还可以通过捕获列表捕获一定范围内的变量:
- [] 不捕获任何变量。
- [&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
- [=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
- [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
- [bar] 按值捕获 bar 变量,同时不捕获其他变量。
- [this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量
算法
动态规划
动态规划的核心思想是数学归纳法。动态规划只能应用于有最优子结构的问题,最优子结构的意思指局部最优解能决定全局最优解。
动态规划和其他遍历算法(如深/广度优先搜索)都是将原问题拆成多个子问题然后求解然后求解,他们之间的最本质的区别在于,动态规划保存子问题的解,避免重复计算。
可以通过对动态规划进行空间压缩,起到节省空间消耗的效果。
在一些情况下,动态规划可以看成带有状态记录(memoization)的优先搜索。状态记录的意思为,如果一个子问题在优先搜索时已经计算过一次,可以把它的结果存储下来,在遍历到该子问题时可以直接使用该结果。
动态规划是自下向上的,即先解决子问题,再解决父问题;而用带有状态记录的优先搜索是自上而下的,即从父问题搜索到子问题,若重复搜索到同一个子问题则进行状态记录,防止重复计算。
动态规划解题:找到"状态"和"选择" -> 明确dp数组/函数的定义 ->寻找"状态"之间的关系
进一步细化为:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
动态规划的难点和关键在于找到正确的状态转移方程,然后通过计算和存储子问题的解来求解最终问题。
ACM 模式训练
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
cin.get() == '\n'
其他
C/C++中的取整函数
#include <math.h>
double floor (double x) // 对x进行向下取整
double ceil (double y) // 对y进行向上取整
前缀和
计算一个数组范围内的相关个数可以考虑前缀和(可减少时间复杂度)。
Leetcode 6347. 统计范围内的元音字符串数
位操作
& 按位与运算符:两位同时为1,结果才为1,否则为0。
| 按位或运算符:两位中有一个为1,结果就为1。
^ 异或运算符:两位值不同,结果为1,否则为0。
~ 取反运算符:将0变1,1变0,就是反着来。
<< 左移运算符:各二进制位全部左移若干位,左边丢弃,右边补0。
>> 右移运算符:各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。
两个不同长度的数据进行位运算时,系统会将二者按右端对齐,然后进行位运算。短的那个数据如果是负数,左边补1,否则补0。
#include <iostream>
using namespace std;
int main() {
int n;
while (cin >> n) {
if (!(n & (n - 1)))
cout << "ACM" << endl;
else cout << "MCA" << endl;
}
return 0;
}
常见问题
内存溢出:
Line 8: Char 17: runtime error: signed integer overflow: 2147000000 + 1000000 cannot be represented in type 'int' (solution.cpp)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior prog_joined.cpp:17:17
解决方法
将变量定义为 long long int, int、long int、long long int的范围为:
int -2147483648~2147483647
long int -2147483648~2147483647
long long int -9223372036854775808~9223372036854775807
相应的无符号类型的各自表示范围为:
unsigned int 0~4294967295
unsigned long int 0~4294967295
unsigned long long int 0~18446744073709551615
参考链接:
[1] 字符串操作——substr用法
[2] 什么是核心代码模式,什么又是ACM模式?
[3] [ACM模式]牛客网ACM机试模式Python&Java&C++主流语言OJ输入输出案例代码总结
[4] C++ STL unordered_map容器用法详解
[5] C++中accumulate的用法
[6] C++string中find_first_of()函数和find_last_of()函数
[7] C++11 lambda表达式精讲
[8] C++ 中的 Lambda 表达式
[9] C++ sort()排序详解
[10] C++ lambda表达式与函数对象
[11] 代码随想录
[12] labuladong的算法小抄
[13] LeetCode 101 :A Leetcode Gringding Guide(C++)
[14] int,long,long long类型的数值范围
[15] int、long int 和 long long int 的取值范围