1) 前缀和
1 应用场景: 需频繁的对某一区间进行求和
2 算法思想:用prevSum[i + 1] 记录nums[0...i]之和,要求nums[i..j] 之间的和只需 prevSum[j+1] - prevSum[i]就好。
3 表达式:prevSum[i+1] = nums[0] + nums[1] + ... + nums[i]; nums[i..j] = prevSum[j+1] - prevSum[i];
4 例子: 303. 区域和检索 - 数组不可变(中等) 304. ⼆维区域和检索 - 矩阵不可变(中等) 560. 和为K的⼦数组(中等)
题目 | |||
2)差分数组
1 应用场景: 频繁对某一区间加加减减
2 算法思想: 用diff[i]记录nums[i]与nums[i-1]之间的差值,如果要对区间[i...j]加N的话,只需diff[i] += N, diff[j+1] -= N;
3 表达式:diff[0] = 0; diff[i] = nums[i] - nums[i-1];//i = 1;
4 例子:370 区间加法,1109 航班预定统计, 1094 拼车
3)滑动窗口
1 应用场景:子串问题
2 算法思想:右指针找可行解,左指针找最优解。
3 表达式:
4 例子:76. 最⼩覆盖⼦串(困难) 567. 字符串的排列(中等) 438. 找到字符串中所有字⺟异位词(中等) 3. ⽆重复字符的最⻓⼦串(中等)
5 注意:
while(right < nums.size()) {
...
while(是否需要收缩左窗口) {
}
}
题目 | 解题思路 |
3 无重复字符的最长子串 | step 1: 定义一个hash map, 用已判断是否包含重复字符; step 2: 采用滑动窗口,res = max(res, right - left); 当判断包含重复字符时,令left = right; |
567 字符串的排列 | step 1: 分别定义cnt1, cnt2 数组,用来记录字符出现的次数。 step 2:维护一个大小为s1.size() 的窗口,然后不断滑动此窗口,当cnt1 == cnt2 时,即代表包含s1 的全排列。 |
4) 二分搜索
1 应用场景: 有序数组中查找某一个值
2 算法思想: 不断地找mid值,筛选一半的区间。
3 表达式:
4 注意点:闭区间,while 要带等号; left = mid - 1; right = mid -1;
5 例子:704. ⼆分查找(简单) 34. 在排序数组中查找元素的第⼀个和最后⼀个位置(中等)
5) 队列/栈
232 用队列实现栈:用一个队列即可实现栈,当入队的时候记录top 的元素,当pop()时候将已经入队的元素重新加入一次队列就是栈顺序了。
225 用栈实现队列: 用两个栈,先将数据入栈1,然后从栈1出栈到栈2
20 有效括号: 遇到左括号就入栈,遇到右括号就去栈中寻找最近的左括号看是否匹配。
6) 数据结构设计
146 LRU缓存机制 : 最近使用,在将来使用的概率也大,如果需要淘汰,就将那么最久未使用的置换掉。
数据结构类型:unordered_map<key, ListNode>;
460 LFU缓存机制: 最不常使用,淘汰那些使用次数最少的。
7) 二叉树
二叉树遍历框架:
void traverse(TreeNode* root) {
if (root == nullprt) return nullptr;
//前序遍历位置
traverse(root->left);
//中序遍历位置
traverse(root->right);
//后序遍历位置
}
8) 二叉搜索树
void BST(TreeNode root, int target) {
if (root == nullprt) return nullptr;
if (root->val == val) {
//找到目标了,做点什么
} else if (root->val > val) {
BST(root->left, target);
} else if (root->val < val) {
BST(root->right, target)
}
}
9)多叉树遍历
void traverse(TreeNode* root) {
if (root == nullptr) return nullptr;
for (TreeNode child : root->child) {
traverse(child);
}
}
10) 图
vector<bool> visited;
void traverse(Graph graph, int s) {
if (visited[s]) return;
visited[s] = true;
for (int neighbor : graph.neighbor(s)) {
traverse(graph, neighbor);
}
visited[s] = false;
}
1.图的搜索
广度优先搜索:1,定义一个先入先出的队列;2,把起始节点加入对列中,接下来每次从队列取出一个数,并把与之相邻且还没有到达过的节点加入该队列;3,重复这个过程。
深度优先搜索:1,沿着图中的边尽可能的深入搜索,先访问图中起始节点v1; 2,访问与v1相邻的任意节点v2;3,再沿着v2访问与之相邻的节点v3,以此类推。4,如果某个节点已经被访问过,回到与vi得前一个节点vi-1;5 并继续访问与vi-1相邻且还没访问过得节点。6重复这个过程。
使用场景:1 在无权图中找两个节点最短距离,使用广度优先搜索算法,2 求图中所有符合条件的算法,使用深度优先所有算法。
时间复杂度:加设图有v个节点,e条边。每个节点只会访问一次并且沿着边访问下一节点是否访问过了避免重复访问,因此时间复杂度为O(v+e);
11) DFS
vector<vector<int>> res;
void dfs(vector<int> nums, vector<int> path) {
if (满足条件) {
res.add(path);
return;
}
for (选择 : 选择列表) {
做选择
dfs(nums, path);
撤销选择
}
return;
}
题目 | 思路 |
129 求根节点到叶节点数字之和 | step 1:采用DFS算法,将当prevSum 带下去,sum = prevSum * 10 + root->val; step 2:遍历树 |
12) BFS
int BFS(Node* start, Node* end) {
Queue<Node> q;
q.push(start);
while (!q.empty()) {
int size = q.size();
for (int i = 0; i < size; i++) {
Node cur = q.front(), q.pop();
...
for (Node* node : cur.adj()) {
q.push(node);
}
}
}
}
}
13) 动态规划
动态规划
1 暴力递归:
2 带备忘录递归: 自顶向下
3 dp数组迭代法: 自底向上
14) 双指针
题目 | 思路 |
19 删除链表倒数第N个节点 | step 1: 定义slow,fast 指针,让fast 指针先走N步,然后slow,fast 一起前进; step 2:当fast 指针走到链表尾部时,slow 指针位于倒数第N 个节点上; step 3:slow->next = slow->next->next; 将倒数第N 个指针删除 |
21 合并两个有序链表 | step 1:定义一个dummy 节点,它指向新合成的链表L3;在定义P1,P2指针分别指向链表L1,L2; step 2: 不断比较P1,P2大小,将小的加入到L3中; step 3: 当P1,P2都比较遍历完,新的有序链表就是需要的结果; |
141 环形链表 | step 1:定义slow,fast 指针;slow 指针每次前进一步, fast 指针每次前进两步; step 2:如果slow, fast 相遇代表有换。 |
876 链表的中间节点 | step 1:定义slow,fast 指针,slow指针每次前进一步,fast 指针每次前进两步; step 2: 当fast指针走到链表尾部时,slow 指针指向的是中间节点。 |
160 相交链表 | step 1 :定义p1,p2 指针分别指向链表L1,L2; step 2:让p1 指针遍历完L1 之后再遍历L2, 让p2指针遍历完L2后再遍历L1,当他们相遇的节点即为相交节点。 |
15) 字符串
567 字符串的排列 | step 1: 定义vector<int> cnt1(26,0), cnt2(26,0); 记录字符出现的次数; step 2: 维护一个大小为str1 的窗口,判断cnt1与cnt2 是否相等; |
step 1:将str子集全部拆解出来; substr(pos, len); step 2: 判断是否为回文子串; | |