算法思想复习
在考虑更优解时,先从数据状况入手,一般C++1s可以运行 1 0 7 − 1 0 8 10^7 - 10^8 107−108次,看有何特点可以利用,如有序,回文,重复,推公式等
1.1双指针、快慢指针
- 双指针求和,先使数组有序,然后根据大小判断哪个指针移动。
- 叠加时需要考虑是否爆int甚至是大数运算。
- 633需要考虑从0开始,两个数相等也可,爆int。
- 字母注意大小写的考虑。
- 指针在数组或容器中移动,需要考虑,所指是否溢出。
- 680
- 88 需要在原数组操作,为避免覆盖可以从后往前。
- 141 链表迭代首要判断所指是否为空。
1.2排序
- partition用于求解第k元素问题,215使用partition。
- 堆用于求解k个最小的问题也可以用于求解第k个元素问题。
- 桶排序347
- 75 不能用h - -,会使l提前跟h撞
1.3贪心
- 435 区间贪心
- 406 先插入最大的身高,使其有序,之后插入的位置对其就没有影响
- 对于边界不好处理的问题可以自己加个边界 605
- 53 连续是关键
- 763
1.4二分查找
适用于有序查找。求重复元素最左或最右时推出判断为 l < h。
- 744
- upper_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
lower_bound( begin,end,num):从数组的begin位置到end-1位置二分查找第一个大于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。
lower_bound( begin,end,num,greater< type> () ):从数组的begin位置到end-1位置二分查找第一个小于或等于num的数字,找到返回该数字的地址,不存在则返回end。通过返回的地址减去起始地址begin,得到找到数字在数组中的下标。 - 540 153
- 对于右边界赋值不变的情况,不能取 <= 会死循环,如 h = m。
- 34 二分变种,第二个h为size,因为可能指向最后一个。
int lower_bound(int A[], int l, int r, int x) {
while (l < r) {
int m = l + (r - l) / 2;
if (A[m] >= x)
r = m;
else
l = m + 1;
}
return l;
}
int upper_bound(int A[], int l, int r, int x) {
while (l < r) {
int m = l + (r - l) / 2;
if (A[m] > x)
r = m;
else
l = m + 1;
}
return l;
}
模板:
while (l < r) {
int m = l + (r - l) / 2;
if (true)
r = m;
else
l = m + 1;
}
return l;
也可用于左闭右开情况
1.5递归
- 241 要处理最后的单数字
- 95
1.6搜索
1.6.1BFS
- 279 127
1.6.2DFS
- 695 dfs后记得改变状态,bfs也可。
- 注意547无向图遍历的走向,也可用并查集
- 染色、填充 130 417
dfs + 回溯、剪枝: - 93 前导0, 小于 等于255, 组数不够情况
- 79 把board也当做标记,可以更快
- 257 temp不加 & 可以不回溯,回溯适用于所标记变量是全局使用的
- 含有重复元素的搜索 ,47, 先排序,然后搜索时判断是否相等,并且之前的元素已被使用
- 77 剪枝所剩循环不足以使k == 0, 40 判断重复时i > index.216 剪枝
- 131每次循环是以取string字符个数,更优解是从后往前分解。
- 37 dfs要有返回 51,行迭代,所以只需判断不在同列
1.7动态规划
1.7.1 斐波那契数列
1.7.2 矩阵的最小路径和
- 经典题 64, 空间优化 O(1)操作
- 62组合求解方式,总共需要走 m + n - 2步,这其中有 m - 1步是往下走
class Solution {
public:
int uniquePaths(int m, int n) {
int m1 = m + n - 2;
int n1 = m - 1;
long long re = 1;
for (int i = 1; i <= n1; i++)
re = re * (m1 - n1 + i) / i;
return (int)re;
}
};
1.7.3数组区间
- 413
1.7.4分割整数
- 343 对于第i个数最大可能为第j个数最大× (i - j)或者 j × (i - j)有动态方程 : d p [ i ] = m a x ( d p [ i ] , m a x ( j ∗ d p [ i − j ] , j ∗ ( i − j ) ) ) dp[i] = max(dp[i], max(j * dp[i - j], j * (i - j))) dp[i]=max(dp[i],max(j∗dp[i−j],j∗(i−j)))
- 91
1.7.5最长递增子序列
- 300注意初始化应为1, nlog(n)解法
- 646用贪心要排序第二个元素,因为第二个元素决定了,还剩多少空间可以贪心。始终记住贪心策略是使目前最优而不影响后续最优情况。
int findLongestChain(vector<vector<int>>& pairs) {
int ssize = pairs.size();
if (ssize < 1)
return 0;
sort(pairs.begin(), pairs.end(), [] (const vector<int> &a, const vector<int> &b) {
return a[1] < b[1];
});
int re = 1;
int end = pairs[0][1];
for (int i = 1; i < ssize; i++) {
if (end < pairs[i][0]) {
re++;
end = pairs[i][1];
}
}
return re;
}
1.7.6 0-1背包
参考背包九讲
- 416注意sum不能被整除的特判
- 494公式推导
- 474分组背包
- 322完全背包,内侧循环为正序
- 518完全背包求方案数,注意初始化,状态变量定义为bool
- 377顺序完全背包
1.7.7 股票交易
- 309状态转移
- 714注意初始状态的赋值
- 123设为INT_MIN迭代从 0开始
- 188对于k与size大小比较的讨论,我好菜。
1.7.8 字符串编辑
- 583拿总长度减去最长公共序列
- 72
- dp[i - 1][j - 1]代表删除, dp[i - 1][j - 1] + 1代表该变值, dp[i - 1][j]代表插入,初始状态。
- 650越小越好
1.8数学相关
- 素数
bool isPrime(int n) {
if (n <= 1) return false;
for (int i = 2; i * i <= n; i++) {
if (n % i == 0)
return false;
}
return true;
}
- 最大公约数(gcd)
int gcd (int a,int b) {
return b == 0 ? a : gcb(b , a % b);
}
位操作实现:
int gcd(int a, int b) {
if (a < b)
return gcd(b, a)l
if (b == 0)
return a;
if ((a & 1) == 0 && (b & 1) == 0)) {
retrun 2 * gcd(a >> 1, b >> 1);
} else if ((a & 1) == 1 && (b & 1) == 0) {
return gcd(a >> 1, b);
} else if((a & 1) == 0 && (b & 1) == 1) {
return gcd(a, b >> 1);
} else {
return gcd(b, a - b);
}
]
- 最小公倍数(lcm),即除去最大公约数
- 进制转换,504用递归写正好不需要reverse。405
- 阶乘
- 大数加减
1.8.2 位运算
- 260 n&(-n) 得到 n 的位级表示中最低的那一位。-n 得到 n 的反码加 1,对于二进制表示 10110100,-n 得到 01001100,相与得到 00000100
交换两个整数
a = a ^ b;
b = a ^ b;
a = a ^ b;
- n & (n - 1)操作,可以去除最低位的1
- 342 476 371 318
2数据结构
2.1链表
- 一定要注意判空
- 注意节点的更新,比如cnt,pre等
- 19 移动到nullptr情况
- 24
- 一般需要加个头,方便处理
2.2树
2.2.1递归
-
110从下往上的方法
-
617 437 111 404 687
-
337使用dfs时需要对比哪个方式最大并不是路线已固定
-
非递归中序遍历,先用cur存当前节点,一直遍历left到底,push,弹出最左,打印,然后push该节点right,循环。
2.2.2BST
2.2.3 Trie
- 677
2.3栈和队列
- 栈的经典作用,括号匹配,匹配更新
- 栈实现队,队实现栈,数组实现栈队
栈实现队列:队列要求先进先出,在第一个栈反转后,再用第二栈反转即可
class MyQueue {
public:
stack<int> s1,s2;
MyQueue() {
}
void push(int x) {
s1.push(x);
}
int pop() {
if (s2.empty()){
while (!s1.empty()) {
int temp = s1.top();
s1.pop();
s2.push(temp);
}
}
int re = s2.top();
s2.pop();
return re;
}
int peek() {
if (s2.empty()){
while (!s1.empty()) {
int temp = s1.top();
s1.pop();
s2.push(temp);
}
}
int re = s2.top();
return re;
}
bool empty() {
if (s1.empty() && s2.empty())
return true;
return false;
}
};
用队实现栈:栈要求后进先出,所以使用两个队列,当有push时,把q1所有元素放到q2,
再push元素,最后在q2元素放入q1即可实现栈序
class MyStack {
public:
/** Initialize your data structure here. */
queue<int> q1,q2;
MyStack() {
}
void push(int x) {
if(q1.empty()) {
q1.push(x);
} else {
while (!q1.empty()) {
int temp = q1.front();
q1.pop();
q2.push(temp);
}
q1.push(x);
while (!q2.empty()) {
int temp = q2.front();
q2.pop();
q1.push(temp);
}
}
}
int pop() {
int temp = q1.front();
q1.pop();
return temp;
}
int top() {
return q1.front();
}
bool empty() {
return q1.empty();
}
};
数组实现队:用head和tail来记录相应位置
int q[100], head = 0, tail = 0;
void push(int x) {
q[++tail] = x;
}
int pop() {
return q[++head];
}
bool empty() {
return head >= tail;
}
数组实现栈:
int s[100], head = 0;
void push(int x) {
s[++head] = x;
}
int top() {
return s[head - 1];
}
void pop() {
head--;
}
bool empty() {
return head == 0;
}
- 单调队列、单调栈 lc239
//单调队列:
// 假设解决滑动窗口问题
int a[200000];
struct node
{
int x,p;
node(){}
node(int xx,int pp){x=xx;p=pp;}
}list[200000];
int main()
{
int n,m;
scanf("%d%d",&n,&m);
for(int i = 1;i <= n; i++) scanf("%d", &a[i]);
int head = 1, tail = 1;
list[1] = node(a[1], 1);
for(int i = 2;i <= n; i++)
{
while(head <= tail && list[tail].x <= a[i]) tail--;//删尾,目前数比对队尾大
list[++tail] = node(a[i], i);//得到最优解并插入
while(i - list[head].p >= m) head++;//去头
if(i >= m) printf("%d\n", list[head]);
}
return 0;
- 739其实是一个从后往前的单调栈,在前面并且大的值我们就没必要把,日期靠后并且比他小的值存储。 503
2.4Hash
特点:空间O(N),查找O(1).
- 128
class Solution {
public:
int longestConsecutive(vector<int>& num) {
if(num.size()==0)return 0;
unordered_set<int> record(num.begin(),num.end());
int res = 1;
for(int n : num){
if(record.find(n)==record.end()) continue;
record.erase(n);
int prev = n-1,next = n+1;
while(record.find(prev)!=record.end()) record.erase(prev--);
while(record.find(next)!=record.end()) record.erase(next++);
res = max(res,next-prev-1);
}
return res;
}
};
2.5字符串
- 205 647 696
- 双指针做substr巨好用。
2.6数组
- 283 378二分是第一个大于等于
- 交换元素 ,确定这个数所指的位置是否是该数。
while (nums[nums[i] - 1] != nums[i])
swap(nums[i], nums[nums[i] - 1]);
- 287双指针解法
- 667
- 对于标记操作,可以在数组自身进行
- 769
2.7 图
- 785二分图 定理:无向图G为二分图的一个充要条件是 1、G中至少包含两个顶点 2、G中所有的回路长度都必须是偶数.记得加一层循环处理非连通。
- 拓扑排序
- 并查集