主要看的视频是b站的代码随想录:代码随想录
代码随想录
- 数组
操作:
—— 二维数组的创立的元素添加;
String s;//s也是用vector定义的,操作同vector
vector<vector<int>> res;
vector<int> temp;
res.push_back(temp);//尾插
res.pop_back();//尾删
int n = res.back()//取最后一个元素
reverse(res);//二维数组仍然可以反转元素,就算元素是数组
——O(n)级别常用方法是双指针,一般是从左右往中间、同时从左往右、滑动窗口(满足条件时让两端移动,题号209);
——单精度浮点数7、8位精读,双精读浮点数52位。
——当值输入为2^31时,建议定义为long int
- 链表(常用递归和迭代)
通用思想——重要的是获得pre,即要处理节点的前一个节点;迭代:把解决一个大问题变成解决一个小问题的重复;
虚拟头节点帮助删除、插入节点;
递归有些难以理解,而且慢;
类、结构体后有分号,方法后面没有;
dummyHead初始化时别忘了指向head;
常用双指针,左右、快慢指针;
- 哈希表(判断元素是否出现过、相交、再现)
- 定义
vector<int> ans(size,value);
unordered_set<key> set;//集合的数学定义
unordered_map<key,value> map;//key是去重的、唯一的,所有的操作对象都是key,比如查找;key是直接查询的元素
——set的使用
unordered_set<key> set;//定义
Set.insert(s);//插入
set.count(r);//出现的的次数
Set.find(r)!=set.end();//查找r
Set.erase(first,last);//删除,[f,l)左开右闭
Set.clear();//清空
Set.empty();//判空
Set.size();//大小
——map的使用
unordered_map<key,value> map;//定义
Map.insert(make_pair(key,val));//插入,key和val不都为int类型时;
map.count(key);//出现的的次数
map.find(key)!=set.end();//查找key
map.erase(first,last);//删除,[f,l)左开右闭
map.clear();//清空
map.empty();//判空
map.size();//大小
元素是否出现,map.cout(r),返回的是出现次数,或者map.find(r)!=map.end();表示找完也找不到
尾插,v.push_back(1),v.emplace_back(1),但后者效率高;
初始化 vector<int> ans(k);//k个元素,不是方括号;//vector<int> ans(k,0);//k个0元素
unordered_set<ListNode *> visited; 1.存储但数值键 2.无序;
认真读题;遇到任何题目,首先考虑数据结构;
哈希表:1.数组,值小时 2用.set 值大时,无规律 3.map 映射
for(auto& ch:s) 指遍历串中的每一个字符,每次赋值给ch;&表示不需要复制,直接取元素;
AddressSanitizer:DEADLYSIGNAL——可能是无限、传参错误
ans.back()取vector数组的最后一个元素,为空时会地址报错;
注意continue和break别用混了,一个是继续下一个循环,一个是跳出循环;
防止数据越界, long sum = (long)nums[k]+nums[i]+nums[l]+nums[r];后面long
数据运算剪枝要考虑整体的极限、极端情况;
判断循环可用快慢指针;
先考虑基本数据结构;
小写字母哈希存储,优先使用a[26]
Vector数组默认初始化赋值0;
- 字符串(算法思路与数组相通)
Swap(a,b)交换,o(1);
Reverse(s.begin()+i,s.end()+j);//反转字符串
将一个复杂的问题分步骤、分方法解决,一个步骤、方法解决一个小问题;
字符串操作工具
- 使用 split 将字符串按空格分割成字符串数组;
- 使用 reverse 将字符串数组进行反转;
- 使用 join 方法将字符串数组拼成一个字符串。
——String:append():在string后面添加文本
使用append()添加文本常用方法:
直接在后面添加另一个完整的字符串:
如str1.append(str2);
添加另一个字符串的某一段子串:
如str1.append(str2, 11, 7);
添加几个相同的字符:
如str1.append(5, ‘.’);
——String :substr():
截取函数substr()的意思是:可以将字符串的某一部分,通过该函数提取出来,形成一个新的字符串。
String s = a.substr(起始位置,长度(省略表示到字符串尾));
Str.erase();对字符串进行删除操作
Str.erase(str.begin()+9);//删除指定位置的元素,写数值3会删除位置3后的元素
Str.erase(9,10);//删除指定位置开始10长度的所有元素,参数必须是数值
Str.erase(str.begin()+9,str.end()-4);//删除范围内元素,左开右闭,参数必须是迭代器
KMP算法:求最大相等前后缀长度,为next数组;
Str.find()函数:
Str.find(s串,起始位置);
Str.find(s串,起始位置,出现次数);
Str.find(c,起始位置);
如果查找成功,find()函数返回子串或字符在字符串中第一次出现的位置;否则,返回一个特殊值string::npos,表示查找失败。
- 队列、栈
- 基本定义
存储元素对的栈
stack<pair<TreeNode*,int>> st;//栈的每个元素为<节点,数值>对
st.push(make_pair(root,0));//加入元素
auto [node,cnt] = st.top();//推测类型
——队列操作
加入队尾 q.push(x); 获得长度,int size =q.size();;
queue<int> q;//队列
queue<TreeNode*> q{rt};//队列元素为treenode类型,并初始加入rt
m=q.front()->val;//这都可以
返回队前元素 q.front();
删除队首元素, q.pop();
交换两个队列的元素 swap(q1,q2);
std::string 类本身就提供了类似「入栈」和「出栈」的接口
string st;
st.empty();//判空
st.back();//尾元素
st.pop_back();//尾删
st.push_back(s[i]);//尾插
——stoi()函数(string to int),把string 型转化为int型stoi(“123”) = 123;
}; int i = strll(‘1’)强制转换为long long 型;
atoi()//将char*型转化为int型;stoi//将string 型转化为int型;s.c_str(),将string 类型转化为char *类型
—— 双端队列deque,C++中deque是stack和queue默认的底层实现容器;
deque<int> q;//声明
q.push_front(1);//前插
q.push_back(2);//尾插
int n1 = q.front();//读取前元素
int n2 = q.back();//读取尾元素
q.pop_front();//前出队
q.pop_back();//尾出队
int len = q.size();//获取大小
q.empty()//判断空
—— 优先队列priority_queue,默认存储容器是vector数组,逻辑实现是大根堆。大根堆每棵树满足根大于孩子;(s.begin(),s.end())是左开右闭;
(1)小根堆原则是每棵树满足根小于孩子;
逻辑结构:完全二叉树;
物理结构:一维数组;
左孩子下标 = 父*2+1,右孩子下标=父*2+2;
父节点下标= (孩子节点下标-1)/2;
- 使用。Priority_queue<int> q;//基于大根堆的优先队列,左大
Priority_queue<int,vector<int>,less<int>> q//int为元素类型,less表示基于大根堆;
Priority_queue<double,vector<double>,greater<double>>q//doubl
e为元素类型,greater表示基于小根堆;
q.empty() //判空
auto x = q.top()//读取头元素
q.pop()//删除头元素
Int len = q.size();//读取长度
q.push(x);//加入x,并排序
K auto/decltype;类型推导
Int a=1;
auto b= a;//自行推到类型
decltyp(a) c = a;//获取a的类型
lq.top().first//优先队列的首元素对的第一个元素
- 注意
调用本类的方法是 this->pop();区分与q1.pop() 是调用q1的封装方法
思考边界情况;
理清思路,写代码之前,写好算法思想,分解为几个步骤,理清思路。
出栈时注意-和/的操作数顺序,先出栈为右操作数;
- 树(递归定义、操作
做题:一、确定遍历方式 二、递归、迭代
树的定义是递归实现的,所有操作都可以通过递归实现。
树的一切操作都基于遍历(遍历后才能处理):
- DFS,共6种=前中后*2(交换左右顺序),递归、迭代+队列都可以。
- BFS,迭代+队列,队列为主,少数情况下用栈。
建议画图变成,照着图调试;
树是递归定义的,理论上树的一切操作都可以递归实现,树是适合递归的最佳体质。
- 基础理论
A树种:
完全二叉树:每一层都满节点;
满二叉树:共k层,前k-1层满,第k层左孩子节点连续;
二叉搜索树(b树):左孩子节点<根节点<右孩子节点;
平衡二叉搜索树(b+树):在b树基础上,满足递归定义-左右子树高度差不超过1;b+树是map set的底层实现;unordered_map底层实现是哈希表;
B存储方式:链式(指针)、线性(数组)
链式 val,next;二叉树一般由链表实现,传输根节点;
线式 完全二叉树或者满二叉树,lch=p*2+1,rch=p*2+2;p=(ch-1)/2
C二叉树遍历
深度优先搜索:孩子优先;前中后序,递归法(方法自调用)、迭代法-用栈模拟递归(leetcode 144)。前中后是根的顺序,左孩子一定在右孩子的左边。
广度优先遍历:兄弟优先;迭代法,使用队列。
D定义
struct TNode
{
Int val;
TNode* lch,*rch; //指针指向左右孩子,左右孩子同样是节点,递归定义
TNode(int x):val(x),lch(NULL),rch(NULL){} //不用分号,是一个构造方法
};
TNode *p = new TNode(x);//创建对象,->是链表节点的专属;
m叉树:Rt是根节点,rt的children是一个节点数组,这是m叉树的定义结构
//m叉树 struct Node{ |
- 遍历
迭代法实现遍历二叉树,看leetcode94;
迭代法使用栈模拟递归;
DFS(深度优先搜索)前序遍历(中左右):遍历顺序等于处理顺序,迭代好写;
后序遍历(左右中):前序遍历中左右颠倒,然后反转res数组
中序遍历(左中右):遍历顺序不等于处理顺序。
BFS(广度优先遍历)层序遍历,树结构借助队列;
BFS 的使用场景总结:层序遍历、(无权)最短路径问题。102的讲解中详细介绍。
Dijkstra 算法适合带权最短路径问题。
遍历树,如果每层只有头,一般是传参修改未覆盖;
传指针参数并覆盖操作:void handle(Node* &last,Node* &p,Node* &nextstart);
递归三要素:(1)确认传参和返回值(2)确定终止条件 (3)确定单层递归的逻辑(每次递归重复处理的信息)。写递归不必追求代码简短,主要是易于理解为主。
迭代三要素类似递归:(1)确认参数和迭代值(2)确定中止、终止条件(3)确定单层递归的逻辑
不要定义与方法同名的变量,可以定义与类的成员相似的变量,尽量区分。
要有成对思想,有入口就要考虑出口,有加入,就要考虑到删除,有追求就要考虑到失败。
二叉树递归演示,最简单的 3个节点就够了;
完全二叉树可以利用其由满二叉树组成的性质计算节点数(222);
完全二叉树的子树是完全二叉树。
求高度建议后序遍历,求深度建议前序遍历。
abs取绝对值,absolute。
一般题目,后序遍历居多,也就是先计算,递归最末尾处返回。
中序+(前序/后序)可以确定一颗二叉树,前序+后序不能确定一颗二叉树。
——二叉搜索树
递归定义,左子树所有val<根节点val<右子树所有val。
递归和迭代都比较简单,因为二叉搜素树严格有序。
验证:中序遍历,单调递增。
回溯、向上返回,是递归的后序遍历。
delete node;//可以释放内存
- (递归)回溯(与递归相辅相成)
有递归就有回溯,通常出现在递归的下面。
纯暴力的搜索:组合(无序)、切割、子集、排列、棋盘问题。
如何理解:想象成图形结构,所有回溯法都可以抽象为一个树形结构-n叉树(回溯也是递归过程),宽度是集合的大小,深度是递归的深度。
void backtracking(参数) { void backtracking(参数) { if (终止条件) { 存放结果; return; } for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) { 处理节点; backtracking(路径,选择列表); // 递归前后操作对称-回溯 回溯,撤销处理结果 } } |
回溯三部曲:
1 确定递归的参数和返回值
2 确定终止条件
3 确定单层递归逻辑
剪枝:for循环内,处理n;
常用剪枝:i<=n-(k-path.size())+1
—— if(path.back()==target) //取元素之前要先保证非空,要不然会报空指针异常
——回溯用target-num[i]作为下一层递归值时,最终出口是target==0,不是target==path.back();
——横向树层去重,针对数值:数组中有重复元素,先排序,然后同一树层去重,if(i>index&&nums[i]==nums[i-1]) continue;
————注意递归参数 find(nums,i+1);//i+1不是index+1
————纵向全树树枝去重,针对下标。vector<bool> set(size+1,false);//使用vector或者set使得每一层用过的元素作为标记,如果当前值出现过,那就把他跳过;
————全排列去重:used<bool>数组;还有一种去重是用过的设置为超出额度的值;需要区分的是整棵树(即整个答案,如不重复数组元素的全排列)不能有重复的值,还是同层内不可有重复的值(如数组内元素可以相同);
————for(i=row-1,j=col+1;i>=0&&j<n;i--,j++)//&&如果换成逗号会报地址越界错误,操蛋
————通过写方法把一个复杂问题分解成一步步简单的操作和方法;
————难题就是有一个个简单题组成的,是简单思维的有机组合,多花些时间拆解和组装而已;
- 贪心算法
算法思想:每次找到局部最优,最后达到全局最优。如每次取面额最大的钱,最后使得面额最大。思考大概找到最优,结果推到全局最优,想不出反例即可。实用主义,差不多就行。
贪心无套路。
需要左右都满足情况时,可以考虑分别满足,“左一层,右一层”,使得同时满足两种情况,即分开讨论;
分类讨论。思路清晰再写代码好些;
不要两边一起贪;
题号406 static bool cmp(vector<int> &a,vector<int> &b)//取地址能减少复制的时间和空间 {//按身高排序,保证身高最高靠前 if(a[0]==b[0]) return a[1]<b[1];//如果第一属性相等,第二属性逆序 else return a[0]>b[0];//第一属性顺序 } vector<vector<int>> reconstructQueue(vector<vector<int>>& people) { sort(people.begin(),people.end(),cmp); list<vector<int>> que;//链表,元素是数组 for(int i=0;i<people.size();i++) { int pos=people[i][1]; auto it = que.begin();//list<vector<int>>::iterator it 迭代器 while(pos--) it++;//迭代器没法直接加数字 que.insert(it,people[i]);//it位置插入 } vector<vector<int>> res(que.begin(),que.end());//把链表的元素复制给二维数组 |
题目一定有答案,我们努力靠直觉、思考去靠近就行了;
- 动态规划
表示状态并进行转移,即规划动态的转移
状态转移
有:基础问题、背包问题、打家劫舍、股票问题、子序列问题
|
规范化、过程化。
越嫌麻烦越麻烦,越求快越慢。不要嫌麻烦,慢慢扎实推导。慢慢推到递推公式,一定能成功,不用着急,不会太难,也不会太简单。
————0-1背包问题()
建议听讲课程后写出代码。
0-1即两种选择,背包即容器。
根据dp意义去推导dp推导公式;
- 单调栈
找到第一个更大、更小的元素。
用去Mod模拟转圈、首尾相连。
for(int i=0;i<s.size()*2;i++) s[i%s.size()]; |
形成一个中间大、两边小或者相反的形状,保证了数据的有序性;
heights.insert(heights.begin(),0); heights.push_back(0); |
顺入逆出;
- 图论
DFS,深度优先搜索,回溯是一种dfs;dfs,每一条道走到头,递归+回溯,一条条射线,主要是搜索所有路径。
void dfs(参数) { if (终止条件) { 存放结果; return; } for (选择:本节点所连接的其他节点) { 处理节点; dfs(图,选择的节点); // 递归 回溯,撤销处理结果//回溯 }} |
BFS,广度优先搜索,一圈一圈向外搜索,寻找最短路径,光源向外同步发散。地图可以有障碍。用栈和队列存储都可以,不用递归,也不用回溯。
void bfs(参数) { queue<类型> q; q.push(); visited 数组: while(!q.empty()) { 获取首元素; for(所有延申方向) { 入队; if(不合法) continue; if(目标) { 处理。 } } }} |
代码就是益智小游戏。
队列元素成对:
queue<pair<int,int>> q; q.push({x,y}); pair<int,int> cur = q.front();q.pop(); int nextx = cur.first+dir[i][0]; int nexty = cur.second+dir[i][1]; |
visited数组访问了,立刻设为true,防止重复访问;
正难则反;