树
1、先序中序重建二叉树——构建中序哈希加快搜索,先序找根,中序划分左右子树
2、树的子结构——遍历树,看当前节点是否为子树结构,子树结构要求根左右均相等直至匹配完毕
3、生成二叉树的镜像——交换节点的左右子树,递归处理左右子树
4、对称的二叉树——递归判断左右子树交叉是否相等,注意递归结构true and false 的条件
5、二叉搜索树与双向链表——中序遍历逻辑,cur->right=root,cur->right->left=cur,cur=cur->right
6、序列化二叉树——序列化:先序遍历逻辑转string,反序列化:string转queue,先序遍历逻辑构建二叉树
8、II. 从上到下打印二叉树 II——ans.emplace_back({}),ans.back().emplace_back()
10、二叉搜索树的后序遍历序列——指针p小于根j滑过,指针p大于根j滑过,看指针是否到达p=j,根据m划分左右子树递归判断
11、二叉树中和为某一值的路径——常规dfs,递归终止条件,更新路径,递归左右子树,路径还原
12、I. 二叉树的深度——递归结束条件root==nullptr return 0,返回max(dfs(left),dfs(right))+1
13、二叉搜索树的第k大节点——右根左,使用&k不断k–,当k==0时返回节点
14、II. 平衡二叉树——递归判断树节点是否左右子树深度绝对值之差不超过1
15、 I. 二叉搜索树的最近公共祖先——二叉搜索树直接root与p q对比判断是左子树,右子树,还是当前节点
链表
2、合并两个排序的链表——不创建头结点完成,cur=head,cur->next=l1/l2…
4、反转链表——原地反转,pre=nullptr,cur->next=pre,pre=cur,cur=next
5、删除链表的节点——注意删除头节点的处理,使用一个cur完成,cur->next->val==val?
6、复杂链表的复制——哈希表,创建节点,建立next和random指针
7、二叉搜索树与双向链表——head和cur,结束head->left=cur,cur->right=head
8、两个链表的第一个公共节点——两个指针交叉走两个链表,while(p1!=p2)
栈
1、用两个栈实现队列——输入栈与输出栈,当输出栈为空才去从输入栈取数
3、包含min函数的栈——遇到更小的min压入栈,即min.top()>=x,min.push(x)
5、栈的压入、弹出序列——模拟栈,每压入一个元素便循环判断是否栈顶等于弹出序列,是则一直弹出
6、二叉搜索树的后序遍历序列——倒序遍历数组,保持递增则压入,出现递减则弹出,栈底为新的root
class Solution {
public:
bool verifyPostorder(vector<int>& postorder) {
stack<int> s;
int root=INT_MAX;
for(int i=postorder.size()-1;i>=0;i--){
if(postorder[i]>root) return false;
while(!s.empty()&&s.top()>postorder[i]){
root=s.top();
s.pop();
}
s.push(postorder[i]);
}
return true;
}
};
队列
1、用两个栈实现队列——输入栈与输出栈,当输出栈为空才去从输入栈取数
2、I. 滑动窗口的最大值——双指针+双端队列维护固定窗口大小的非单调递减序列
堆
1、最小的k个数——维护size为k的大根堆,如果小于堆顶进行交换,最后堆剩下最小的k个数
2、数据流的中位数——大根堆,小根堆,维持平衡,取大根堆与小根堆顶点作平均
3、丑数——取最小因子,判断是否重复,不重复放入堆标记访问过,重复则不作处理
4、I. 滑动窗口的最大值——将val与对应的下班压入大根堆,弹出时看下标是否在窗口范围,不是则连续弹出
二分查找
2、旋转数组的最小数字——判断mid与right的关系,注意mid==right时,right–
3、数字序列中某一位的数字——确定在几位数,确定在哪个数,确定在数的哪一位
4、数组中的逆序对——归并排序,当左数组大于右数组时,res+=m-i+1
5、和为s的两个数字——有序数组,双指针,小于target移动左指针,大于target移动右指针
7、II. 0~n-1中缺失的数字——标准二分,left=mid+1,right=mid-1(因为right出现了,未出现应在right左边)
位运算
class Solution {
public:
int hammingWeight(uint32_t n) {
int count = 0;
while (n) {
n &= n - 1;
count++;
}
return count;
}
};
2、I. 数组中数字出现的次数——求所有异或即ab异或,获取ab第一个不同的位数,根据位数进行分组,两个结果即ab
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
//计算所有数字的异或结果 得到a与b的差异
int ret = 0;
for (int n : nums)
ret ^= n;
//a与b的差异 为1代表某位上两数不同
int div = 1;
while ((div & ret) == 0)
div <<= 1;
//根据最低位的1 进行异或分组 a与b分到两组 其他数字相同的被分为一组
int a = 0, b = 0;
for (int n : nums)
if (div & n)
a ^= n;
else
b ^= n;
//输出结果形式
return vector<int>{a, b};
}
};
3、II. 数组中数字出现的次数 II——遍历32位作计算,将所有项指定位作加法sum+=(item>>i)&1,%3==1证明是出现一次数字的数位,进行还原ans|=i<<1
class Solution{
public:
int singleNumber(vector<int>&nums){
int ans=0;
//遍历每位数
for(int i=0;i<32;++i){
int sum=0;
//每位数相加
for(int item:nums)
sum+=(item>>i)&1;
//余数是否为1 是则还原1
if(sum%3==1)
ans|=1<<i;
}
return ans;
}
};
4、II. 0~n-1中缺失的数字——1~n异或数组的值,得到的结果即缺失的数字
class Solution {
public:
int missingNumber(vector<int>& nums) {
int sum1=0;
int sum2=0;
for(int i=0;i<nums.size();i++){
sum1^=nums[i];
sum2^=i+1;
}
return sum1^sum2;
}
};
5、不用加减乘除做加法——a^b
作不进位加法,a&b<<1
作进位加法
class Solution {
public:
int add(int a, int b) {
return b?add(a^b,(unsigned int)(a&b)<<1):a;
}
};
6、求1+2+…+n——利用&&实现递归代替for while等
class Solution {
int ans=0;
public:
int sumNums(int n) {
bool x=n>1&&sumNums(n-1)>0;
ans+=n;
return ans;
}
};
动态规划
1、I. 斐波那契数列——dp[i]=dp[i-1]+dp[i-2]
2、II. 青蛙跳台阶问题——dp[i]=dp[i-1]+dp[i-2]
3、机器人的运动范围——dp[i][j]=dp[i-1][j] | dp[i][j-1]
4、I. 剪绳子——尽量分成3*3,当余数为1 *4,当余数为2 *2,当余数为0 …
class Solution {
public:
int cuttingRope(int n) {
if(n<=3) return n-1;
int yushu=n%3,zheng=n/3;
if(yushu==1) return pow(3,zheng-1)*4;
if(yushu==2) return pow(3,zheng)*2;
return pow(3,zheng);
}
};
5、II. 剪绳子 ——可以造成越界,所以一边减一边乘一边取余,终止条件为余数是4 3 2 1…
class Solution {
public:
int cuttingRope(int n) {
if(n<=3) return n-1;
long res=1;
while(n>4){
n-=3;
res*=3;
res%=1000000007;
}
return int(n*res%1000000007);
}
};
6、正则表达式匹配——字母相等或遇到‘.’,dp[i][j]=dp[i-1][j-1];遇到‘’不匹配,dp[i][j]=dp[i][j-2];遇到’'匹配,dp[i][j]=dp[i-1][j]
class Solution {
public:
//动态规划 分三种情况递推公式 1.单个字符匹配 2.匹配0个 3.匹配1~n个
bool isMatch(string s, string p) {
//1、申请dp数组 dp[i][j] i为匹配串 j为模式串
int m = s.size()+1, n = p.size()+1;
vector<vector<bool>> dp(m, vector<bool>(n, 0));
//2、dp数组初始化 从字符串的第二个位置找* 每次跳2 只有 a*b*c*r*...类型符合
dp[0][0] = 1;
for (int i = 2; i <= n; i+=2)
if (p[i-1] == '*')
dp[0][i] = dp[0][i-2];
//3、根据递推公式计算dp数组
for (int i = 1; i < m; ++i) {
for (int j = 1; j < n; ++j) {
//1.单个字符匹配
if (s[i - 1] == p[j - 1] || p[j - 1] == '.')
dp[i][j] = dp[i - 1][j - 1];
//遇到* ————匹配0个 或 匹配1~n个
else if (p[j - 1] == '*') {
//2.匹配0个 —— 若不匹配为true 则不匹配
if(j > 1&& dp[i][j - 2]==1)
dp[i][j] = dp[i][j - 2];
//3.匹配1~n个
else if(s[i - 1] == p[j - 2] || p[j - 2] == '.')
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[m-1][n-1];
}
};
7、连续子数组的最大和——dp[i]=max(dp[i-1]+nums[i],nums[i])
8、1~n 整数中 1 出现的次数——想成密码锁,确定cur=0,cur=1,cur>1的递推公式
class Solution {
public:
//想成密码锁固定一个位置 三种情况的递推公式 cur=0 cur=1 cur=2
int countDigitOne(int n) {
int ans = 0;
//1、初始化四位参数low cur high digit
int low = 0, cur = n % 10, high = n / 10, digit = 1;
//2、确定循环的终止条件———— high和cur同时为0才终止
while (high!=0 ||cur != 0) {
//3、根据三种情况的递推方程进行计算
if (cur == 0) ans += high * digit;
else if (cur == 1) ans += high * digit + low + 1;
else ans += (high + 1) * digit;
//4、更新四位参数 low cur high digit
low += cur * digit;
cur = high % 10;
high /= 10;
//5、小心出界 作一个判断
if(digit<INT_MAX/10)digit *= 10;
}
return ans;
}
};
9、把数字翻译成字符串——dp[i]=dp[i-1]+<dp[i-2]凑成10~25>
10、礼物的最大价值——dp[i][j]=max(dp[i-1][j],dp[i][j-1])+grid[i][j]
11、丑数——dp[i]=min(dp1,min(dp2,dp3))
12、n个骰子的点数——dp[i-1][j]->dp[i][j+1],dp[i][j+2],dp[i][j+3]…
class Solution {
public:
vector<double> dicesProbability(int n) {
//初始化
vector<double> dp(6,1.0/6.0);
//开始递推
for(int i=2;i<=n;++i){
vector<double>ans(5*i+1,0);
for(int j=0;j<dp.size();++j)
for(int k=0;k<6;++k)
ans[j+k]+=dp[j]*1/6;
dp=ans;
}
return dp;
}
};
13、股票的最大利润——max(dp[i]=price[i]-minprice)
其他
2、替换空格——遇到空格,new_string.append("%20")
3、调整数组顺序使奇数位于偶数前面——双指针,一个指针维护处理好的,一个指针维护待处理的
4、顺时针打印矩阵——left,right,top,bottom,注意不要重复输出,对于单行单列不要左转和上转
5、打印从1到最大的n位数——s.resize(n,‘0’),dfs单层逻辑0123456789,最后去除前面的0以及000…
6、II. 和为s的连续正数序列——滑动窗口,当小于target移动右指针,当大于target移动左指针,当等于target保存结果并移动左指针
7、I. 翻转单词顺序——结尾加上空格,遇到空格则分割,最后将向量反转,拼接成字符串
9、扑克牌中的顺子——排序,计算0的个数,判断扑克牌是否连续,若相等则返回false,否则尝试使用0进行填补
10、圆圈中最后剩下的数字(约瑟夫环)—— dp[i]=(dp[i-1]+m)%i
11、矩阵中的路径——dfs,单层搜索逻辑 bool ans=dfs(i+1,j)||dfs(i,j+1)||dfs(i-1,j)||dfs(i,j-1) return ans;
12、表示数值的字符串——按照规则进行指针移动,特定出现需要使用else return false;
13、数值的整数次方——x=xx,n=n>>1,if(n&1==1) ans=x
class Solution {
public:
//快速幂
double myPow(double x, int n) {
double ans=1.0;
long t=n;
//负数问题转正数问题
if(n<0){
t=-t;
x=1/x;
}
//一边移位,一边累乘
while(t!=0){
if((t&1)==1)
ans=ans*x;
x=x*x;//x x2 x4...
t=t>>1;//1 2 4...
}
return ans;
}
};