Below you will find pages that utilize the taxonomy term “Algorithm”
「算法模版」动态规划
「状态」-> 「选择」 -> 定义 dp
数组/函数的含义 ;自底向上进行递推求解。
# 自顶向下递归的动态规划
def dp(状态1, 状态2, ...):
for 选择 in 所有可能的选择:
# 此时的状态已经因为做了选择而改变
result = 求最值(result, dp(状态1, 状态2, ...))
return result
# 自底向上迭代的动态规划
# 初始化 base case
dp[0][0][...] = base case
# 进行状态转移
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 求最值(选择1,选择2...)
核心框架
备忘录 - 斐波那契数
int fib(int n) { if (n == 0 || n == 1) return n; // 分别代表 dp[i - 1] 和 dp[i - 2] int dp_i_1 = 1, dp_i_2 = 0; for (int i = 2; i <= n; i++) { int dp_i = dp_i_1 + dp_i_2; dp_i_2 = dp_i_1; dp_i_1 = dp_i; } return dp_i; }
最优子结构 - 零点兑换
c++语法
万能开头:
#include<bits/stdc++.h>
using namespace std;
在 C++ 中,using namespace std;
指令允许用户使用 std 命名空间中的所有标识符,而无需在它们前面加上 std::
。
标准输入输出
标准输入是 cin
, cin
用 >>
运算符把输入传给变量。
标准输出是 cout
,用 <<
运算符把需要打印的内容传递给 cout
,endl
是换行符。
#include <bits/stdc++.h>
int a;
cin >> a; // 从输入读取一个整数
// 输出a
std::cout << a << std::endl;
// 可以串联输出
// 输出:Hello, World!
std::cout << "Hello" << ", " << "World!" << std::endl;
string s = "abc";
a = 10;
// 输出:abc 10
std::cout << s << " " << a << std::endl;
算法 - C++STL常用容器
插入函数总结
方法 | 适用容器 | 作用 | 性能特性 |
---|---|---|---|
push | queue 、stack 、priority_queue | 添加元素到容器末尾或顶部 | 适用于特定容器,性能与容器实现相关 |
push_back | vector 、deque 、list | 添加元素到容器末尾 | 需要拷贝或移动元素 |
emplace | set 、map 、unordered_set 等 | 在容器中直接构造元素 | 避免不必要的拷贝或移动 |
emplace_back | vector 、deque 、list | 在容器末尾直接构造元素 | 避免不必要的拷贝或移动 |
insert | 大多数容器 | 将元素插入到容器的指定位置 | 需要拷贝或移动元素 |
「算法模版」树
层级遍历
while 循环控制⼀层⼀层往下⾛,for 循环利⽤ sz 变量控制从左到右遍历每⼀层⼆叉树节点。
// 输⼊⼀棵⼆叉树的根节点,层序遍历这棵⼆叉树
void levelTraverse(TreeNode root) {
if (root == null) return 0;
Queue<TreeNode> q = new LinkedList<>();
q.push(root);
int depth = 1;
// 从上到下遍历⼆叉树的每⼀层
while (!q.isEmpty()) {
int sz = q.size();
// 从左到右遍历每⼀层的每个节点
for (int i = 0; i < sz; i++) {
TreeNode cur = q.poll();
printf("节点 %s 在第 %s 层", cur, depth);
// 将下⼀层节点放⼊队列
if (cur.left != null) {
q.push(cur.left);
}
if (cur.right != null) {
q.offer(cur.right);
}
}
depth++;
}
}
「算法模版」图
存储图
邻接矩阵
邻接表
// 对于每个点k,开一个单链表,存储k所有可以走到的点 // h[k]存储这个单链表的头结点 int h[N], e[N], ne[N], idx; // 添加一条边a->b void add(int a, int b) { e[idx] = b, ne[idx] = h[a], h[a] = idx ++ ; } // 初始化 idx = 0; memset(h, -1, sizeof h);
回溯算法
回溯法:一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即回溯并且再次尝试。
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择 : 本层集合中的元素) {
处理节点;
backtracking(路径, 选择列表); // 递归
撤销处理; // 回溯
}
}
unordered_map
1. 创建 unordered_map
对象
#include <unordered_map>
#include <string>
int main() {
// 默认构造函数
std::unordered_map<std::string, int> map1;
// 初始化列表构造函数
std::unordered_map<std::string, int> map2 = {{"apple", 1}, {"banana", 2}};
// 拷贝构造函数
std::unordered_map<std::string, int> map3(map2);
return 0;
}
2. 插入
insert
插入一个键值对
map1.insert({"orange", 3}); map1.insert(std::make_pair("grape", 4));
operator[]
通过键插入或访问值。如果键不存在,会插入一个默认值。
map1["apple"] = 10; // 插入或修改 int value = map1["apple"]; // 访问
3. 访问元素
at
访问指定键的值,如果键不存在会抛出
std::out_of_range
异常。int value = map1.at("apple");
operator[]
访问或插入指定键的值。
int value = map1["apple"];
「持续更新」算法题笔记
万能开头
#include<bits/stdc++.h>
using namespace std;
在 C++ 中,using namespace std;
指令允许用户使用 std 命名空间中的所有标识符,而无需在它们前面加上 std::
。
leetcode hot 100
hash
两数之和
题目描述:给定一个整数数组
nums
和一个整数目标值target
,请你在该数组中找出 和为目标值target
的那 两个 整数,并返回它们的数组下标。方法:找数
x
,寻找数组中是否存在target - x
。使用哈希表,可以将寻找
target - x
的时间复杂度降低到从 O(N) 降低到 O(1) —— 创建一个哈希表,对于每一个x
,我们首先查询哈希表中是否存在target - x
,然后将x
插入到哈希表中,即可保证不会让x
和自己匹配。
二叉树
二叉树
二叉树的实现方式
最常见的二叉树就是类似链表那样的链式存储结构,每个二叉树节点有指向左右子节点的指针
class TreeNode { public: int val; TreeNode* left; TreeNode* right; // 构造函数,参数是int x, :后面的部分是初始化列表,{} 是构造函数的函数体为空,即不需要额外的操作。 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} }; // 你可以这样构建一棵二叉树: TreeNode* root = new TreeNode(1); root->left = new TreeNode(2); root->right = new TreeNode(3); root->left->left = new TreeNode(4); root->right->left = new TreeNode(5); root->right->right = new TreeNode(6); // 构建出来的二叉树是这样的: // 1 // / \ // 2 3 // / / \ // 4 5 6
public
用法总结:public
关键字用于指定类成员的访问权限,允许外部代码直接访问这些成员。- 如果不加
public
,这些成员默认是private
的,外部代码无法直接访问它们。
在
TreeNode
的构造函数中,初始化列表的作用是:
数组链表
1. 动态数组
动态数组底层还是静态数组,只是自动帮我们进行数组空间的扩缩容,并把增删查改操作进行了封装。
// 创建动态数组
// 不用显式指定数组大小,它会根据实际存储的元素数量自动扩缩容
ArrayList<Integer> arr = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 在末尾追加元素,时间复杂度 O(1)
arr.add(i);
}
// 在中间插入元素,时间复杂度 O(N)
// 在索引 2 的位置插入元素 666
arr.add(2, 666);
// 在头部插入元素,时间复杂度 O(N)
arr.add(0, -1);
// 删除末尾元素,时间复杂度 O(1)
arr.remove(arr.size() - 1);
// 删除中间元素,时间复杂度 O(N)
// 删除索引 2 的元素
arr.remove(2);
// 根据索引查询元素,时间复杂度 O(1)
int a = arr.get(0);
// 根据索引修改元素,时间复杂度 O(1)
arr.set(0, 100);
// 根据元素值查找索引,时间复杂度 O(N)
int index = arr.indexOf(666);