磕磕绊绊跟完了一刷,转眼两个月过去了。感谢有卡哥系统化的课程让学习难度降低不少,也算是领着我到算法世界门口多看了几眼,感谢一路上卡哥的付出 o7
一刷仅仅是开始的结束,要想熟练掌握算法的道还需久久为功,坚持总结归纳直到完全掌握。
一路博客记录下来,如今来看也没白费,哈哈。在这里感谢你的观看,祝你做任何事情都有坚持到底的决心,不要放弃!
- 刷题常回顾,不回顾只有短期记忆
- 刷题必须要手动推理,才能记忆更牢固,尽可能自己打印输出、手动推理解决 BUG
动态数组通过控制 idx 来实现算法解题,常用方法如下
确定好区间规范,使用左闭右开就一直使用,边界判断带入特殊值判断
// 核心判断在左在右
while (left < right) {
middle = (left + right) / 2;
// 根据比较情况更新左右边界
if(nums[middle] > target) right = middle;
else if(nums[middle] < target) left = middle + 1;
else return middle;
}
无序情况下、本地判断
一快一慢 ∣ − − A − − ↓ s − − ↓ f − − − ∣ |--A--\downarrow_{s}--\downarrow_{f}---| ∣−−A−−↓s−−↓f−−−∣ 对 A (快慢指针)操作
// 快指针right遍历,慢指针操作
for (int right = 0; right < N; ++right) {
if (nums[right] != val) nums[left++] = nums[right];
}
无序情况下、本地判断
一快一慢 ∣ − − ↓ s − − B − − ↓ f − − − ∣ |--\downarrow_{s}--B--\downarrow_{f}---| ∣−−↓s−−B−−↓f−−−∣ 对 B (滑动窗口)操作
for (int right = 0; right < N; ++right) {
// right - left 窗口操作
while (Condition) {
minLen = std::min(...)
}
}
有序情况下、对称情况下、缩小规模(夹逼)
指针碰头 if (left <= right)
与 if (left > right)
while(left < right) {
if (condition1) {
left++;
}
else {
right--;
}
}
常常结合排序,有序后便可以通过多个指针判定数字和、
while (right > left) {
sum = nums[i] + nums[left] + nums[right];
if (sum > 0) right--;
else if (sum < 0) left++;
else { condition... }
链表常用 struct 定义,可以定义构造函数便于初始化
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
// 操作 想着链表操作写代码
void addAtTail(int val) {
ListNode* node = new ListNode(val, dummyHead->pre, dummyHead);
dummyHead->pre->next = node;
dummyHead->pre = node;
size++;
}
建议链表处理都是用虚拟头节点(dummyhead)统一操作,避免处理头节点问题
ListNode* dummyHead = new ListNode(0);
dummyHead->next = head;
// 后续对节点的操作都一样
结论相遇点到入口 == 头到入口
快速判断一个元素是否出现集合里。 如果算法有这个要求就可以使用哈希表。
哈希表另一个常用的功能是保障一组元素没有重复
// 自定义哈希表 自定字符查询
for (int i = 0; i < t.length(); ++i) arr[t[i] - 'a']--;
// STL哈希表 查找元素值(可以是单个元素也可以是表达式)
if (hash.find(nums1[i]) != hash.end()) res.insert(nums1[i]);
类似于数组,但是存储内容为字符
往往采用双指针实现
while (right > left) {
tmp = s[left];
s[left] = s[right];
s[right] = tmp;
left++;
right--;
}
栈实现队列:两个栈实现队列,出栈容器为空需要一次性将所有入栈容器数据挪入
队列实现栈: 1.两个队列模拟 pop 时将 size-1 元素挪动到第二个队列,剩下的就是出栈元素
2.一个队列模拟:可以将元素取出后再次加入队列从而实现,每次挪动 size-1 元素
括号匹配:入栈另一半括号 )
,出栈直接比较与字符串是否相同
栈存储操作记录
string str = "";
for (char c : s) {
if (str.empty() || str.back() != c) str.push_back(c);
else str.pop_back();
}
逆波兰表达式求值:根据输入入栈求解表达式值
滑动窗口:用队列存储每次操作
两种遍历方式:递归与迭代
vector<int> inorderTraversal(TreeNode* root) {
std::vector<int> res;
std::stack<TreeNode*> stack;
if (root == nullptr) return res;
TreeNode* cur = root;
while (!stack.empty() || cur != nullptr) {
if (cur != nullptr) {
stack.push(cur);
cur = cur->left;
}
else {
cur = stack.top();
stack.pop();
res.push_back(cur->val);
cur = cur->right;
}
}
return res;
}
// 统一迭代法(null标记)(适用于前中后序遍历)
vector<int> inorderTraversal(TreeNode* root) {
std::vector<int> res;
std::stack<TreeNode*> stack;
if (root == nullptr) return res;
stack.push(root);
while (!stack.empty()) {
TreeNode* cur = stack.top();
if (cur != nullptr) {
stack.pop();
// 添加右节点
if (cur->right) stack.push(cur->right);
stack.push(cur);
stack.push(nullptr); // null作为标记说明还没有处理过
if (cur->left) stack.push(cur->left);
}
else {
// 遇到null标记了说明要处理之后的节点了
stack.pop();
cur = stack.top();
stack.pop();
res.push_back(cur->val);
}
}
return res;
}
求深度与求高度
求深度,深度是任意节点到根节点距离,高度是任意节点到叶子节点距离。
求高度后续遍历,求深度前序遍历。
回溯的核心是要试一下,试完了再还原回去
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
状态转移公式(递推公式)是很重要,但动规不仅仅只有递推公式。
对于动态规划问题,我将拆解为如下五步曲,这五步都搞清楚了,才能说把动态规划真的掌握了!
单调栈适合求当前元素左边或右边第一个比当前元素大或小的元素,找到元素进而确定数值下标等操作
单调栈指的是:栈内的元素是递增或递减的(从栈口到栈底),单调排列
栈内从栈口到栈底递增:求当前元素后面比他大的元素,遇到更大的元素就弹出之前的元素
只收留更小的元素,保证递增
栈内从栈口到栈底递减:求当前元素后比他小的元素
只收留更大的元素,保证递减
单调栈作用:存放遍历的元素
void DFS(param) {
if (终止条件) {
存放结果;
return;
}
for (选择本层节点的所有节点用于搜索) {
节点内容的处理;
DFS(图, 选择的节点);
回溯,撤销结果
}
}
广度优先搜索的代码类似于二叉树的层序遍历
// 代码模板
vector<vector<int>> levelOrder(TreeNode* root) {
std::queue<TreeNode*> que;
std::vector<std::vector<int>> res;
if (root != nullptr) que.push(root);
while (!que.empty()) {
// 记录每一层的size 控制弹出均为一层一层弹出
int layerSize = que.size();
TreeNode* cur;
std::vector<int> layerRes;
while (layerSize--) {
cur = que.front();
que.pop();
layerRes.push_back(cur->val);
if (cur->left != nullptr) que.push(cur->left);
if (cur->right != nullptr) que.push(cur->right);
}
res.push_back(layerRes);
}
return res;
}
// 递归法
void Order(TreeNode* cur, std::vector<std::vector<int>>& res, int depth) {
if (cur == nullptr) return;
if (depth == res.size()) res.push_back(vector<int>());
res[depth].push_back(cur->val);
Order(cur->left, res, depth + 1);
Order(cur->right, res, depth + 1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
std::vector<std::vector<int>> res;
Order(root, res, 0);
return res;
}