刷题笔记
回溯
https://leetcode-cn.com/problems/subsets/solution/c-zong-jie-liao-hui-su-wen-ti-lei-xing-dai-ni-gao-/
按照这个思路来看就好
个人理解:全部的组合情况,全部的排列情况,全部的子集情况等,遇到这类题想到回溯,其实回溯是dfs的一种,需要在dfs子层后复原状态。
ps:像后面的dfs模版中,若visit数组是全局变量/传引用的话,也需要这样进行回溯,不过会很麻烦,所以就直接传值,避免去考虑这个事情。
classic: leetcode 17
随机
//全部随机
vector<int> vec(n);
iota(vec.begin(), vec.end(), 0); //从0开始递增1
random_suffle(vec.begin(),vec.begin());
//单个值
rand()
二分
必看:自己对于两种模版的整理+总结,。 链接
以下为选看:
关于如何确定l<r 以及+1 -1 链接
关于为什么要用下面这两个模版 以及最终ans的值的选择 链接
模版1
int n = nums.size();
int l = 0, r = n - 1, mid = 0;
while(l < r){
mid = l + (r-l)/2;
if(check){
l = mid + 1;
}else{
r = mid;
}
}
模版2 better
while(l <= r){
mid = l + (r-l)/2;
if(正确情况) return mid;(大概率使用)
if(check){
l = mid + 1;
}else{
r = mid - 1;
}
}
//有时候需要判断return的l/r谁满足条件
if(xxx) return l;
else return r;
upper_bound & lower_bound & equal_range可
vector和list 可以用 STL的upper_bound
static bool cmp(const T & t1, const T & t2){
return t1.a < t2.a;
}
upper_bound(vec.begin(),vec.end(),target, (cmp));
upper_bound(l.begin(),l.end(),target, (cmp));
map和set 可以用 容器自带的upper_bound
map<int,int> mp;
set<int> st;
mp.upper_bound(target);
st.upper_bound(target);
暂时不确定cmp怎么写,应该就是set/map cmp自定义的方法
lower_bound:返回第一个>=p的role的下标
upper_bound:返回第一个>p的role的下标
使用时一般减去begin()
lower_bound(role.begin(),role.end(),p)-role.begin();
若结果为role.end(),即不存在这样的下标。
equal_range相当于lower和upper的集合体
.first返回lower_bound
.second返回upper_bound
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin>>T;
while(T--)
{
int n,m;
cin>>n>>m;
vector<int> peo(n);
vector<int> role(m);
for(int i=0;i<n;i++) cin>>peo[i];
for(int i=0;i<m;i++) cin>>role[i];
sort(role.begin(),role.end());
for(auto p:peo)
{
int res=0;
//自带二分
// lower_bound:返回第一个>=p的role的下标
// upper_bound:返回第一个>p的role的下标
//要减去role.begin()才返回下标
res =m - (lower_bound(role.begin(),role.end(),p)-role.begin());
// for(int j=m-1;j>=0;j--)
// {
// if(role[j]>=p) res++;
// else break;
// }
cout<<res<<" ";
}
cout<<endl;
}
return 0;
}
-------------
//手写
#include <bits/stdc++.h>
//先排序
using namespace std;
// [l,r)
int bs(int a[], int n, int x) { //lower 返回第一个>=x的下标
int l = 0, r = n, mid;
while (l < r) {
mid = (l + r) / 2;
if (a[mid] < x) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
int us(int a[], int n, int x) { //upper 返回第一个>x的下标
int l = 0, r = n;
while (l < r) {
int mid = l + r / 2;
if (a[mid] <= x) {
l = mid + 1;
} else {
r = mid;
}
}
return l;
}
int b[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
int main() {
cout << bs(b, 9, 5) << endl;
return 0;
}
单调栈
模版
stack<int> st;
for(int i = 0; i < nums.size(); i++)
{
while(!st.empty() && st.top() > nums[i])
{
st.pop();
}
st.push(nums[i]);
}
关于单调栈的使用情况
每个元素找到它右边第一个比它大的元素的位置,求它们的距离
从左到右遍历,维护一个从栈底到栈顶递减的栈,因为遇到新元素大于栈顶元素时,栈顶元素就遇到了对应的比它大的最近元素,要弹栈。这也是栈从底到顶单调递减的原因
每个元素找到它右边第一个比它小的元素的位置,求它们的距离
从左到右,维护栈底到栈顶递增的栈,遇到递减元素,弹栈
每个元素找到它左边第一个比它大的元素的位置,求它们的距离
相当于把1的数组头尾调过来,只需要从右向左遍历维护栈底到栈顶递减栈即可
每个元素找到它左边第一个比它小的元素的位置,求它们的距离
相当于把2的数组头尾调过来,只需要从右向左遍历维护栈底到栈顶递增栈即可
question:
经典739
稍复杂(base left right 计算面积的) 42接雨水(凹进去的 递减栈)84(突出来的递增栈)
自动机
https://leetcode-cn.com/problems/string-to-integer-atoi/solution/zi-fu-chuan-zhuan-huan-zheng-shu-atoi-by-leetcode-/
设计类题目
设计类题目一般指通过自己选取合适的数据结构,去实现题目规定的操作。
一般需要设计出索引结构(index table)以及数据库结构(database)。
大致可以分为两类题目。
-
哈希 配合 链表(双向)
经典题:LRU - 剔除最近没有被访问过的节点
大致思路:
使用哈系实现索引,索引的key为题干中要去改变/访问的key,value为链表的迭代器,用这样的方式最快速找到元素在链表中的位置。使用链表作为数据库,链表的node可以存储某个结构/pair/tuple。
使用原因:
因为链表没有排序能力,所以这种题适合不需要排序的情况,例如LRU只需要移除某个节点+插入/去除首尾。 -
哈希 配合 set
经典题:LFU - 剔除被访问频率最低的节点
大致思路:
使用hash实现索引,索引的key为题干中要去改变/访问的key,value为一个结构A(可以是pair/tuple或者struct)。使用set< A >作为数据库,根据题意重载<,实现set中的排序。
使用原因:
这类题在数据库具备自定义排序能力,所以采用set。先用hash索引到对应结构A,再通过se去erase和emplace。上述这种思路是logn的思路,还有一种O(1)的思路,面试有考到过。
这里用到了一个性质,list的iterator可以当作地址,找到不同的链表的某一位(即,不拘泥于同一个链表)
使用两个hash
hash1:key为freq,value是一个list< Node(key,value,freq)>, 这个list中放所有freq为这个freq的Node,同时每次都是从前面插入,后面pop,所以越靠后的时间越久。
hash2: key为key, value为list< Node >::iterator, 即这个key对应的Node的迭代器,可以直接找到hash1的对应的list中对应的那个位置(很强)
有了这两个结构,get可以直接找到位置,并把freq+1,去掉原先的Node,加到freq+1的list中,并更新hash2
set如果满了的话直接去掉hash1中的minFreq的第一个Node,同时过程中维护一个minFreq, 每次erase freq的node时候判断下,若当前freq没了需要erase掉整个freq且这个freq就是minFreq,将minFreq+1. -
其他演变的类型
在LRU基础上,演变出了432这种,可以通过list进行排序,不过这种排序要求每次变化可以通过前后模拟得到,较为特殊。
在LFU的基础上,演变出了6126这种题,本质上是通过set来做数据库,但由于题目要求的获取情况要分类,所以将数据库set改为unordered<分类指标,set< A >>,实现功能。
一道涵盖颇多技巧的题 432
### 解题思路
### 代码
```cpp
/*
精髓:
怎么解决排序问题?使链表的node结构 表示某个数量及这个数量的所有key(用一个set存);而不是只想着一个key的事儿
https://leetcode-cn.com/problems/all-oone-data-structure/solution/quan-o1-de-shu-ju-jie-gou-by-leetcode-so-7gdv/ 官方题解 使用stl
理解上:看宫水三叶的题解
//emplace的好处:1 少了一个构造函数 2 参数列表语法糖 详见
https://www.jianshu.com/p/3906f24d2697
//list stl
//emplace的返回值是新增node的迭代器
//prev() next()用法
//http://c.biancheng.net/view/7384.html
//struct构造函数
//https://www.cnblogs.com/wlw-x/p/11566191.html
*/
class AllOne {
public:
struct Node
{
set<string> st;
int nums = 0; //node对应的数
//构造函数:方便下文使用
Node(set<string> a, int b):st(a),nums(b){}
//https://www.cnblogs.com/wlw-x/p/11566191.html
};
unordered_map<string,list<Node>::iterator> hash;
list<Node> delist;
AllOne() {
}
void inc(string key) {
//find
if(!hash.count(key))//not exist
{
if(delist.size()==0||delist.front().nums>1)//直接放在首位
{
set<string> st({key});
delist.emplace_front(st,1);
}
else //跟首位合并 set加上该key
{
//.front()是返回一个copy,不影响该node本身,
//.begin()是返回对应的迭代器,可以直接对该node进行操作
delist.begin()->st.emplace(key);
}
hash[key] = delist.begin();
}
else //exist
{
auto cur = hash[key];
auto nxt = next(cur);
if(nxt == delist.end() || nxt->nums > cur->nums +1 ) //需要新增node的情况
{
set<string> st({key});
hash[key] = delist.emplace(nxt,st,cur->nums+1); //语法糖 也可以像下面这样
//hash[key] = delist.emplace(nxt,Node(st,cur->nums+1));
//插入后返回值为插入node对应的迭代器
}
else
{
nxt->st.emplace(key);
hash[key] = nxt;
}
//对当前cur减去该key 并判断是否需要erase掉node
//最后再erase,先erase会造成cur迭代器失效,无法进行其他操作
cur->st.erase(key);
if(cur->st.empty())
{
delist.erase(cur);
}
}
}
void dec(string key) {
auto cur = hash[key];
auto pre = prev(cur);
if(cur->nums==1)//key不存在的情况
{
hash.erase(key);
}
else //key还存在的情况
{
if(cur==delist.begin()||pre->nums < cur->nums-1)//需要新增node的情况
{
set<string> st({key});
hash[key] = delist.emplace(cur,st,cur->nums-1);
//插入后返回值为插入node对应的迭代器
}
else
{
pre->st.emplace(key);
hash[key]=pre;
}
}
cur->st.erase(key);
if(cur->st.empty())
{
delist.erase(cur);
}
}
string getMaxKey() {
return (delist.empty())?"":*(delist.back().st.begin());
}
string getMinKey() {
return (delist.empty())?"":*(delist.front().st.begin());
}
};
/**
* Your AllOne object will be instantiated and called as such:
* AllOne* obj = new AllOne();
* obj->inc(key);
* obj->dec(key);
* string param_3 = obj->getMaxKey();
* string param_4 = obj->getMinKey();
*/
经典LRU 146
### 解题思路
1 思路:LRU,所以要维护一个链表/队列,去排任务的顺序,又因为要求get和put的复杂度为O(1),因此可以构造(key,list::interator)的hash,最快速度索引到链表的位置。
2 pair的原因是:get的时候要得到value
3 emplace进pair时可以省略掉make_pair
4 更新的过程中要考虑hash是否更新
5 list.back()是新建的一个值,不影响远list
### 代码
```cpp
class LRUCache {
public:
unordered_map<int,list<pair<int,int>>::iterator> hash;
list<pair<int,int>> list;
int capacity = 0;
LRUCache(int capacity) {
this -> capacity = capacity;
}
int get(int key) {
if(!hash.count(key)) return -1;
auto e = hash[key];
int val = e->second;
list.erase(e);
list.emplace_front(key,val);
hash[key] = list.begin();
return val;
}
void put(int key, int value) {
int val = 0;
if(hash.count(key)){
auto e = hash[key];
list.erase(e);
list.emplace_front(key,value);
hash[key] = list.begin();
}else{
list.emplace_front(key,value);
hash[key] = list.begin();
}
if(list.size()>capacity){
hash.erase(list.back().first);
list.pop_back();
}
return ;
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
460 LFU
同样涵盖技巧+细节
/*
看官方题解:https://leetcode-cn.com/problems/lfu-cache/solution/lfuhuan-cun-by-leetcode-solution/
两种方法这里用第一种实现
1 利用set的自带排序 olgn
2 一个hash<int,list<Node>::iterator> 一个hash<int,list<Node>> o1
第一个hash中的value可以是任意一条list(第二个hash的value)的某一个地址
//Node类的比较函数 定义在结构体中
//https://zhuanlan.zhihu.com/p/368510835
//结构体注意事项
//https://www.jianshu.com/p/18307f614c5b
*/
class LFUCache {
public:
struct Node{
int key;
int value;
int freq;
int time;
//注意:如果写了带参数的构造函数,一定要把不带参数的也写上,因为重写后,默认构造函数会失效。
Node():key(),value(),freq(),time(){}
Node(int a,int b,int c,int d):key(a),value(b),freq(c),time(d){}
//Node类的比较函数 定义在结构体中
//这两处一定要写const 不然编译不过
bool operator < (const Node &a) const{
return freq == a.freq ? time < a.time : freq < a.freq;
}
};
int capacity;
unordered_map<int,Node> hash;
set<Node> st;
int time = 0;
LFUCache(int capacity) {
this->capacity = capacity;
}
int get(int key) {
if(capacity==0) {
return -1;
}
if(!hash.count(key)) {
return -1;
}
time++;
Node node = hash[key];
st.erase(node);
node.freq++;
node.time = time;
st.emplace(node);
hash[key] = node;
return node.value;
}
void put(int key, int value) {
if(capacity==0) {
return;
}
time++;
if(!hash.count(key)) {
if(st.size()==capacity) {
hash.erase(st.begin()->key);
st.erase(*st.begin());
}
st.emplace(key,value,1,time);
hash[key] = Node(key,value,1,time);
}else{
Node node = hash[key];
st.erase(node);
node.freq++;
node.time = time;
node.value = value;
st.emplace(node);
hash[key]=node;
}
return;
}
};
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache* obj = new LFUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
6126
链接
/*
类似 LFU
这种类型 无法像LRU那样通过链表来存储,因为需要存储结构带有排序,因此用set来存
需要注意的是:在重载<时,set和bq是相反的
*/
class FoodRatings {
public:
struct Food{
string food;
string cuisine;
int rating;
Food():food(),cuisine(),rating(){}
Food(string food, string cuisine, int rating):food(food),cuisine(cuisine),rating(rating){}
bool operator<(const Food &t)const {
if(rating != t.rating){
return rating > t.rating;
}else{
return food < t.food;
}
}
};
//index table
//food's name - Food
unordered_map<string,Food> hash;
//database table
//cuisines - Foods(rating & name & with sort): so use set
unordered_map<string, set<Food>> db;
FoodRatings(vector<string>& foods, vector<string>& cuisines, vector<int>& ratings) {
int n = foods.size();
for(int i = 0; i < n; i++){
Food f = Food(foods[i],cuisines[i],ratings[i]);
hash[foods[i]] = f;
db[cuisines[i]].emplace(f);
}
}
void changeRating(string food, int newRating) {
auto f = hash[food];
auto newf = Food(f.food,f.cuisine,newRating);
//auto st = db[f.cuisine];
db[f.cuisine].erase(f);
db[f.cuisine].emplace(newf);
hash[food].rating = newRating;
}
string highestRated(string cuisine) {
return db[cuisine].begin()->food;
}
};
/**
* Your FoodRatings object will be instantiated and called as such:
* FoodRatings* obj = new FoodRatings(foods, cuisines, ratings);
* obj->changeRating(food,newRating);
* string param_2 = obj->highestRated(cuisine);
*/
355 设计推特
最优做法应该是合并k个有序链表
个人做法
背包汇总
dp[i][j]
i:考虑前i个物品
j:背包体积
值:价值
外层循环:考虑前i个物品
内层循环:遍历背包容量(压缩成一维后:01->从右往左;完全->从左到右)
01与完全的区别
01背包:该格子的点的 上面格子 和 左上格子;
dp[i][j] = max (dp[i-1][j],dp[i-1][j-weight[i]]+val[i]);
压缩成一维后,为了左上格子的变化不影响该点,从右往左遍历背包容量(内层循环从右往左)
完全背包: 该格子点的 上面格子 和 左边格子;
dp[i][j] = max (dp[i-1][j],dp[i][j-weight[i]]+val[i]);
可以这么理解,即使到外层遍历考虑前i个物品的这一层了,这一层依然可以选若干回,所以是同层的左边
压缩成一维后,为了左边格子先更新(内层循环从左往右)
状态转移的定义除了max/min,还可以“或”(针对不同题目)
01背包
每个物品只能一次
vector<int> weight(n); //物品质量;n为物品数量
vector<int> val(n); //物品价值
vector<int> dp(m+1); //m为背包称重上限
/*
1 这里m+1是增加一个为0的哨兵排,使得初始化更方便
2 很多情况val就是weight,或者val恒为1
3 初始化dp的时候
一般 转移方程为max-> 初始化为0
转移方程为min-> 初始化为INT_MAX,并将dp[0]=0
状态定义除了max(),还可以或(针对不同题目)
*/
for(int i=0;i<n;i++){ //考虑前i个物品
for(int j=m; j>=weight[i]; j--){ //为了能装进去 遍历到weight[i]
dp[j]=max(dp[j],dp[j-weight[i]]+val[i]);
}
}
完全背包
每个物品若干次
vector<int> weight(n); //物品质量;n为物品数量
vector<int> val(n); //物品价值
vector<int> dp(m+1); //m为背包称重上限
/*
1 这里m+1是增加一个为0的哨兵排,使得初始化更方便
2 很多情况val就是weight,或者val恒为1
3 初始化dp的时候
一般 转移方程为max-> 初始化为0
转移方程为min-> 初始化为INT_MAX,并将dp[0]=0
*/
for(int i=0;i<n;i++){ //考虑前i个物品
for(int j=weight[i]; j<=m; j++){ //为了能装进去 从weight[i]开始
dp[j]=max(dp[j],dp[j-weight[i]]+val[i]);
}
}
例子
322 硬币兑换
分组背包
每组物品中只能抽一个(一次)
类似01背包,外层加一个组的循环,有空详细看看
例子
区间变化\染色问题
静态询问:所有改变结束后在查询
动态查询:过程中的查询 – 线段树法
静态询问
差分数组法(区间修改,静态询问)
https://leetcode.cn/problems/range-addition/solution/370-qu-jian-jia-fa-by-flytowns-vnjh/
非线段树解法
https://leetcode-cn.com/problems/QO5KpG/solution/lcp-52-by-flytowns-2ivu/
动态询问
以后补
dfs
void dfs
一般适用于地图和回溯
整理了一个自己习惯用的模版
class Solution {
public:
//把一些dfs过程中用的变量定义为全局变量
//可以节省dfs的参数列表的数量
int res = INT_MAX;
vector<int> st; //start
vector<int> ed; //end
vector<string> mx;
vector<vector<bool>> visit;
//方向数组
int dir[4][2] = {{0,1},{1,0},{0,-1},{-1,0}};
void dfs(int x, int y, int xxxx){
if(x==end[0]&&y==emd[1]){
res = min(res,time);
return;
}
for(int i=0;i<4;i++){
int newx = x + dir[i][0];
int newy = y + dir[i][1];
if(newx<0||newy<0||newx>=mx.size()||newy>=mx[0].size()||visit[newx][newy]) continue;
//这里使用了回溯思想,当然也可以直接把visit作为函数的参数,就不需要visit->false这步复原了,但这样会占据更多空间
visit[x][y] = true;
dfs(x+dir[i][0],y+dir[i][1],xxx);
visit[x][y] = false;
}
}
int conveyorBelt(vector<string>& matrix, vector<int>& start, vector<int>& end) {
//首先 把全局变量赋值 + 初始化visit数组(vector)
st = start;
ed = end;
mx = matrix;
int m = mx.size();
int n = mx[0].size();
visit.resize(m,vector<bool>(n,false));
//开始dfs
dfs(start[0],start[1],0);
return res;
}
};
有返回值的dfs
例子
树的dfs有的情况不好采用返回值为空的dfs。即使传参变为引用有时逻辑也不好想。
dfs时不用想内部怎么实现的,只需要想三件事。
1 边界return
2 获取子层dfs的值(按照期望的dfs的定义)
3 用子层dfs的值更新该层/res/return
ps:
后来思考了一下,其实用void dfs也能做。只不过需要再dfs前把传入的引用变量赋值,并且dfs函数的最后,要更新引用值。
这道题做的时候自己的问题在于,想的有点多,因为从理解上外层dfs只拿到了根root的sum和num,但是在遍历过程中其实把每一个node都算了。
所以编码时,可以先不思考复杂的递归逻辑,定义好每一个递归干了啥,ex。该dfs只获取以该root为跟的sum和num,只判断该树是否满足即可。
!!!!这类题有时候有种特点->最终结果是在过程中更新的,不作为返回值,返回值用作其他更直观的属性。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int ans = 0;
vector<int> dfs(TreeNode* root){
if(root==nullptr) return {0,0};
auto l = dfs(root->left);
auto r = dfs(root->right);
int num = l[0]+r[0]+root->val, sum = l[1]+r[1]+1;
if(root->val == num/sum) ans++;
return {num,sum};
}
int averageOfSubtree(TreeNode* root) {
dfs(root);
return ans;
}
};
树的dfs&路径类问题
可以通过prefix优化
还有一些树的dfs见leetcode之路
树形dp 配合 dfs
用一次dfs解决,dfs返回一个值,同时在过程中维护ans,将路径拆成两部分的和
543
1245树的直径
2538
位操作 + dfs
__builtin_popcount可以计算一个数中二进制为1的数量
1239
记忆化 + dfs
个人理解:
首先肯定这类题可以通过dfs本身解决,使用记忆化可以降低复杂度。
这类dfs有个特性,dfs过程中各个部分可以相对独立开,这样就可以把dfs过程中的量保存下来,之后再dfs到了同样情况的时候,直接使用。
一般是返回值作为要保存的量,用一个hashmap去保存(dfs的位置,返回值)
word break 2
longest increasing path in a matrix
241
bfs
bfs跟dfs的一个区别在于:
bfs的边界判断在入队前就进行了。
dfs的边界判断可以在dfs前,也可以在下一轮dfs开始的时候。
//200 岛屿数量
/*
bfs
遍历矩阵,值为1,岛屿数++,从这个点开始进行bfs遍历
bfs遍历到值为1的点后,把该点值变为0,相当于标记
*/
class Solution {
public:
int dir[4][2]={{-1,0},{0,1},{1,0},{0,-1}};//二维数组的初始化
int numIslands(vector<vector<char>>& grid)
{
int res=0;
int n=grid.size();
if(n==0) return 0;
int m=grid[0].size();
queue<pair<int,int>> que;
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
if(grid[i][j]=='1')
{
res++;
grid[i][j]='0';
que.push(make_pair(i,j));
while(!que.empty())
{
auto cur = que.front();
que.pop();
for(int a=0;a<4;a++)
{
int newi=cur.first+dir[a][0];
int newj=cur.second+dir[a][1];
if(newi<0||newj<0||newi>=n||newj>=m||grid[newi][newj]=='0')continue;
grid[newi][newj]='0';
que.push(make_pair(newi,newj));
}
}
}
}
}
return res;
}
};
toposort with bfs
拓扑排序一般针对于如下问题:
有一堆有序的item,让你给出整体的顺序。
经典: 207
复杂一些的如下
class Solution{
public:
vector<pair<char,int>> equationSolve(vector<string> v){
// we return by empty vector if system is not solvable
// otherwise we return by a single solution (note that there exists an integer solution)
// assume that each variable is a char
/*
A = B + C
B = C + D + 5
C = 4
D = 6
*/
unordered_map<char,pair<bool,int>> vars; // key: char(ex. A); pair <true, val of it>
vector<pair<vector<char>,int>>equations; // vec[0]等式左边 vec[1-n]等式右边的变量 int等式最后的variable
vector<pair<char,int>>ans;
// extract variable and integer form the equations
for(string s:v){
pair<vector<char>,int>eq;
eq.second=0;
for(int i=0;i<s.length();i++){
char d=s[i];
if(d>='A'&&d<='Z'){
eq.first.push_back(d);
vars[d]={false,0};
}
else if(d>='0'&&d<='9'){
for(;i<s.length();i++) //这里假定只有加法 且等式右边只有一个且最后一个是
eq.second=10*eq.second+(s[i]-'0');
}
}
equations.push_back(eq);
}
int N=0;// number of unknown variables
unordered_map<char,int>id; //每个var给一个id
for(auto it=vars.begin();it!=vars.end();++it)id[it->first]=N++;
int num_eq=equations.size();
vector<int>numedges(num_eq,0); //记录每个equation的右侧含有几个变量
vector<vector<int>>graph(num_eq); // i: 每个等式右侧的变量 j:等式id
for(int k=0;k<num_eq;k++){
int size=equations[k].first.size();
//int x=id[equations[k].first[0]];
for(int i=1;i<size;i++){
int y=id[equations[k].first[i]];
numedges[k]++;
graph[y].push_back(k);
}
}
queue<int>q;
for(int i=0;i<num_eq;i++)if(numedges[i]==0)q.push(i); //把等式右侧没有变量的作为初始 即 x = 5之类
while(!q.empty()){
for(int i=q.size()-1;i>=0;i--){
int k=q.front();q.pop();
char d=equations[k].first[0];
int x=id[d]; //获取想求的var的id
int val=equations[k].second;
if(vars[d].first&&vars[d].second!=val)return ans; //这步是验证是否正确,我觉得醉开始可以不写
vars[d]={true,val}; //求出
for(int j=0;j<graph[x].size();j++){ //因为已经求出了x,现在用x去更新当x作为等式右边变量时候的式子
int l=graph[x][j];
numedges[l]--;
equations[l].second+=val;
if(numedges[l]==0)q.push(l);
}
}
}
for(auto a:equations){ //可以先不写
char d=a.first[0];
if(vars[d].second != a.second)return ans;
}
for(auto it=vars.begin();it!=vars.end();++it)
ans.push_back({it->first,it->second.second});
return ans;
}
};
int main(void){
Solution* s=new Solution;
auto a=s->equationSolve({"A=B+C","B=C+D+5","C=4","D=6"});
for(auto p:a)cout<<p.first<<" "<<p.second<<endl;cout<<endl;
a=s->equationSolve({"A=B+C","B=C+A","C=A+B"});
for(auto p:a)cout<<p.first<<" "<<p.second<<endl;cout<<endl;
a=s->equationSolve({"A=B+24","B=A+3"});
for(auto p:a)cout<<p.first<<" "<<p.second<<endl;
return 0;
}
// output (note that the 3rd test has no solution
D 6
C 4
A 19
B 15
C 0
A 0
B 0
优先队列+bfs
有些时候,常规dfs和bfs会超时,这时候可以用bfs+优先队列解决。
因为bfs一旦到达终点,便结束,无需像dfs那样全部遍历完。
bfs配合上优先队列的排序,即,将bfs的队列换为优先队列,并重载小于号。
这样可以达到剪枝的作用。
ex. 走迷宫问题,当每个砖消耗时间不同的时候。
值得注意的是:
bfs的visit数组的更新位置需要根据题意进行选择。
1 入队时,更新visit
2 pop前,更新visit
取决于,已经入队但尚未出队的点,是否允许再次访问。
下面代码中进行了标注
若想要输出路径,可以在每一个点上记录来源方向,最后结果反推
class Solution {
public:
int res = INT_MAX;
struct st {
int x, y, time;
st(int x, int y, int time): x(x), y(y), time(time) {}
bool operator<(const st& s) const { return s.time < time; } //这样声明!要写const
};
int dir[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; //> v < ^
int check(char c){
if(c=='>') return 0;
else if(c=='v') return 1;
else if(c=='<') return 2;
else return 3;
}
int conveyorBelt(vector<string>& matrix, vector<int>& start, vector<int>& end) {
priority_queue<st> pq; //这样声明!
pq.emplace(start[0],start[1],0);
vector<vector<bool>> visit(matrix.size(),vector<bool>(matrix[0].size(),false));
while(!pq.empty()){
st s = pq.top();
//cout<<s.x<<" "<<s.y<<" "<<s.time<<endl;
//2 pop update visit
visit[s.x][s.y] = true; //此题在这里操作
pq.pop();
if(s.x==end[0]&&s.y==end[1]){
res = s.time;
break;
}
int di = check(matrix[s.x][s.y]);
for(int i=0;i<4;i++){
int newx = s.x+dir[i][0], newy = s.y+dir[i][1];
if(newx<0||newx>=matrix.size()||newy<0||newy>=matrix[0].size()||visit[newx][newy]) continue;
//1 emplace update visit
//visit[][]=true;
if(i==di) pq.emplace(s.x+dir[i][0],s.y+dir[i][1],s.time);
else pq.emplace(s.x+dir[i][0],s.y+dir[i][1],s.time+1);
}
}
return res;
}
};
DP
1.字符串DP
a.思路一
提示 1-1
将所有子串按照其末尾字符的下标分组。
提示 1-2
考虑两组相邻的子串:以 s[i-1] 结尾的子串、以 s[i] 结尾的子串。
提示 1-3
以s[i]结尾的子串,可以看成是以s[i-1]结尾的子串,在末尾添加上s[i]组成。
上面这一串提示是思考子串统计类问题的通用技巧之一。
例子1
例子2 300最长递增子序列 见个人blog—leetcode之路
b.思路二
遍历长度,两边扩展
例子 5最长回文子串 见个人blog—leetcode之路
2. 常规dp
a.单层
word break
跳跃问题 45 403
b.双层
可以第二维度用一个[k]
ex. 股票系列问题 见leetcode之路2
3.特殊
二分+dp 1235规划兼职工作
对空间复杂度有限制的题目
ex. 要求在原数组上处理
1.自己定义新的状态
ex. 原先数组只有0,1;自己新增2,3表明变化
例子
2. 分块进行
经典“最”问题
1. LIS 最长递增子序列
最优方法:二分+贪心
300
朴素方法:dp
2.LCS 最长公共子序列
二维dp
1143题(见leetoce之路)
3. 最长公共子串
在LCS基础上稍微改动
见leetcode之路
spilt
使用getline和istringstream实现
void split(const string& s, vector<string>& sv,const char flag = ' ') {
sv.clear();
istringstream iss(s);
string temp;
while (getline(iss, temp, flag)) {
sv.push_back(temp);
}
}
使用find实现
str.find(c,pos); //从pos之后开始找
用这个比较好,可以自定义多种分隔的情况
void split( const string &str, vector<string> &vec, char c){
vec.clear();
string sub;
for (size_t start=0, end=0; end!=string::npos; start=end+1){
end = str.find( c, start );
sub = str.substr( start, end-start );
if (!sub.empty()){
vec.push_back(sub);
}
}
}
void split(string & str, vector<string> & vec, vector<char> c){ //分隔符有三种的情况
vec.clear();
string sub;
for(size_t start = 0, end = 0; end != string::npos; start = end+1){
end = min(str.find(c[0],start),min(str.find(c[1],start),str.find(c[2],start)));
sub = str.substr(start,end-start);
if(!sub.empty()){
vec.emplace_back(sub);
}
}
}
链表
1. 快慢指针
模版(奇数位于中间 偶数位于靠后的)
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next){
slow = slow -> next;
fast = fast -> next -> next;
}
return slow;
}
进阶版,找到相遇点
287 142
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next){
slow = slow -> next;
fast = fast -> next -> next;
if(slow==fast) break;
}
while(l!=slow){
l=l->next;
slow=slow->next;
}
return l;
}
2. 反转链表
三种题
1 全部反转
2 反转部分(提供left, right)
3 间隔k个反转
总结:
反转操作需要 pre cur next三个节点
对于后两种,需要在head前新建一个节点。反转时要先将pre = end->next。循环条件(cur!=end)
对于第一种,反转时pre = nullptr.循环条件(nxt)
优先队列
1. 题型
a. topK
ex. 347
通过优先队列末位淘汰
维护过程注意事项:先emplace 再判断>k进行pop 最稳妥
for(auto e : mp){
pq.emplace(make_tuple(e.second, e.first));
if(pq.size() > k) pq.pop();
}
b. 合并有序链表/数组
ex. 23, 378
通过优先队列选择想要的点
c.与排序配合的
253 会议室2
2. 关于优先队列的排序方法
a. 用自带第三个参数的greater和less,封装到pair/tuple中(目测最简单的)
greater 升序
less 降序
例子
b. 重载()
//cmp重载方法 1.放到结构体里 2.排序与sort正好相反(升序 return a>b;降序 return a<b)
struct cmp1{
bool operator ()(int a,int b){
return a>b;//最小值优先
}
};
struct cmp2{
bool operator ()(int a,int b){
return a<b;//最大值优先
}
};
struct cmp3{
bool operator (){pair<int,int> a, pair<int,int> b}{
return a.second<b.second; //第二个元素最大值优先
}
}
c. 结构体内重载<
一定重载小于号
https://www.acwing.com/blog/content/5069/
//val小顶堆(val从小到大)
struct Node{
int x, y, val;
Node(int x, int y, int val):x(x),y(y),val(val){}
bool operator<(const Node &t)const {
return val > t.val;
}
};
//val大顶堆(val从大到小)
struct Node{
int x, y, val;
Node(int x, int y, int val):x(x),y(y),val(val){}
bool operator<(const Node &t)const {
return val < t.val;
/*
可以这样记,首先优先队列的重载都是反的。
参数中:this < t
return this->val < t.val
正常顺序就是 this < t了,应该是升序
然后反过来,变成降序,即大顶堆。
*/
}
};
//使用的时候
priority_queue<Node, vector<Node>> pq;
//不加第三个参数!
d. lambda
优先队列的重载方法
auto cmp = [](const pair<int,int> & a, const pair<int,int> & b){return a.first > b.first;}; //优先队列反过来 依据first升序
priority_queue<pair<int,int>, vector<pair<int,int>>, decltype(cmp)> q(cmp);
//按照first的最小堆
//ex. https://leetcode.cn/problems/network-delay-time/solution/743-wang-luo-yan-chi-by-flytowns-t2r9/
set的重载方法 (类似优先队列)
auto cmp = [](int & a, int & b){return a < b;}; //min heap set是正常的顺序,不用反过来
set<int,decltype(cmp)> st(cmp);
注意!
auto cmp = [](const std::vector<int>& a, const std::vector<int>& b) {
if (a[3] != b[3]) {
return a[3] < b[3];
}
// 如果第四个元素相同,比较整个向量
return a < b;
};
std::set<std::vector<int>, decltype(cmp)> q(cmp);
q.insert({2, 3, 4, 3});
q.insert({2, 3, 3, 5});
这种如果cmp中只有 return a[3] < b[3]; 会有问题,需要这么写一下
map的重载方法
auto cmp = [](const int &a, const int &b) { return a > b; };
std::map<int, int, decltype(cmp)> myMap(cmp);
sort的重载方法(可以直接写在里面)
vector<int> vec;
sort(vec.begin(), vec.end(), [](int & a, int & b){
return a < b;
});
upper_bound/lower_bound重载方法
auto iter = lower_bound(vec.begin(), vec.end(), cur, [](const int & a, const int & b){return a < b;});
//注意这里有时编译不过要加const 且后面 a < b 不要 =, upper_bound也是一样
3 补充 关于set(heap)的排序方法(注意:与优先队列相反)
当使用set时,要自定义set的排序函数时,从小到大就是从小打大,不用反过来,与优先队列相反。
eg.
//set(val从大到小)
struct Node{
int x, y, val;
Node():x(),y(),z(){} // 若编译过不去,需要加一下
Node(int x, int y, int val):x(x),y(y),val(val){}
bool operator<(const Node &t)const {
return val < t.val; //这里也可以写为 this->val < t.val;
/*
参数中:this < t
return this->val < t.val
正常顺序就是 this < t了,是升序,又因为不像pq那样需要反过来,所以就是升序。
*/
}
};
//使用的时候
set<Node> pq;
双指针
背向双指针
回文串 按长度从小到大dp
ex 5,125
相向双指针
几数之和- 先排序 再相向双指针(>2个数 先从左到右确定一个 另外一边进行双指针)
ex 1,15,18
同向双指针(滑动窗口)
模版
/*
同向双指针 --- 滑动窗口
以leetcode_3 无重复字符的最长字串 为例:给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度。
模版如下
*/
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int slow = 0, fast = 0, res = 0;
int n = s.size();
unordered_map<char,int> mp;
/*
定义变量:
快慢指针int slow = 0, int fast = 0; 输入的string s;
hashmap用于记录string s当中slow到fast(包含)之间所有的字母出现的频率;
int res记录符合题目要求的最长substring长度等
*/
while(fast < n){
//更新fast指针位置的mp
mp[s[fast]]++;
//满足某种条件->需要滑动slow指针 ex. 该题:若当前点mp>1 说明前面有重复的 进行循环滑动
while(mp[s[fast]]>1){
mp[s[slow]]--;
slow++;
}
//更新res
res = max(res, fast - slow + 1);
//递增fast
fast++;
}
return res;
}
};
1 基础题:
3 链接
2 中等类型:
3 特殊类型:
4 复杂类型:
自己在最外层加循环条件的 一般遇不到(或有其他方法)ex. 395
排序
基数排序
字典树 Trie Tree
实现Trie Tree
trie tree + 记录过程
dfs + 字典树 word search 2
位操作
见c++个人总结
OOD
class A{
};
class Farm{
public:
int kind;
Farm(int kind);
virtual int getKind();
int getKind(int isOutput); //重载
void setKind(int kind);
~Farm();
A * a; //调用A类的对象
};
Farm::Farm(int kind){
this->kind = kind;
}
Farm::~Farm() {
}
int Farm::getKind() {
cout << "Farm getKind" << endl;
return kind;
}
void Farm::setKind(int kind) {
this->kind = kind;
}
class SmallFarm : public Farm {
public:
using Farm::Farm;
int showKind();
int getKind();
void interface();
};
int SmallFarm::showKind() {
return kind;
}
int SmallFarm::getKind() { //多态
cout << "smallFarm getKind" << endl;
return kind;
}
void SmallFarm::interface() {
cout << "interface" << endl;
}
int main() {
Farm f(10);
A *p = new A();
f.a = p;
SmallFarm smallFarm(1);
smallFarm.getKind();
cout << "----" <<endl;
Farm * fp = new SmallFarm(1);
fp->getKind();//基类没加virtual时,这里输出的还是基类的
return 0;
}
Union Find
261
//323 (模版)
// https://leetcode.cn/problems/number-of-connected-components-in-an-undirected-graph/submissions/479111070/
class Solution {
public:
vector<int> father;
void init(int n) {
father.resize(n);
for(int i = 0; i < n; i++) father[i] = i;
}
int find(int x) {
if(x == father[x]) return x;
else return father[x] = find(father[x]);
}
void merge(int x, int y) {
father[find(x)] = find(y);
}
int countComponents(int n, vector<vector<int>>& edges) {
init(n);
for(auto e:edges) {
merge(e[0],e[1]);
}
int res = 0;
for(int i = 0; i < n; i++) {
if(father[i] == i) res++;
}
return res;
}
};
Greedy
快速幂
50
/*
x^77
77: 1 0 0 1 1 0 1
x64 x8 x4 x1
64+8+4+1 = 77
*/
//time O(logn)
//space O(1)
class Solution {
public:
double myPow(double x, int n) {
long N = n;
if (N < 0) N = -N;
double ans = 1.0;
while (N > 0) {
if ((N & 1) == 1) {
ans *= x; //把二进制为1的取出来
}
N >>= 1;
x *= x;//相当于求出x1,x2,x4,x8,x16
}
return n >= 0 ? ans : 1.0 / ans;
}
};
时间复杂度/空间复杂度
全指一般情况,特殊情况特殊分析
一些公式
O(n) + O(n/2) + O(n/4) + … = O(2n) = O(n)
O(log1) + o(log2) + … + o(logn) = o(nlogn)
树的DFS
时间:O(N) N为节点数量
空间: O(d) d为树的深度,一般为logN
树的BFS
时间:O(N) N为节点数量
空间:O(d) d是树的宽度,即为节点最多的一层的节点个数
图 DFS/BFS
时间:O(N) 空间:看存储的大小
(DFS加上递归本身的深度,BFS加上queue,但一般这两部分都小于存储本身)
图 toposort
时间:O(N + E) N为节点数量, E为边的数量 (邻接表存储)邻接矩阵是O(N^2)
空间: O(N + E) (邻接表) 临界矩阵是 O(N^2)因为矩阵数组是二维的
图 topsort
时间:O(N + E) N为节点数量, E为边的数量 (邻接表存储)
空间: O(N + E) (邻接表)
普通DFS
时间: O(N) 节点数
空间:存储+递归层数 一般O(N)
普通BFS
时间:O(N)
空间:存储+queue 一般O(N)
注意
有一些求全部排列的,不符合以上,可以考虑为排列组合的感觉,直接相乘
ex. leetcode 17
leetcode 797 分析