📚 Part 1: C++ STL 使用手册


0. String - 字符串

#include <string>
using namespace std;

// 初始化
string s = "hello";
string s2(5, 'a');          // "aaaaa"
string s3 = to_string(123); // 数字转字符串

// 基本属性
int len = s.size();         // 长度(同 s.length())
bool empty = s.empty();     // 是否为空
char ch = s[0];             // 获取字符(不检查越界)
char ch2 = s.at(0);         // 获取字符(检查越界)
char front = s.front();     // 第一个字符
char back = s.back();       // 最后一个字符

// 查找
int idx = s.find("ll");         // 查找子串,找不到返回 string::npos
int last = s.rfind("l");        // 从后往前查找
bool has = s.find("el") != string::npos; // 是否包含

// 截取
string sub = s.substr(1, 3);    // 从位置1开始,长度3
string sub2 = s.substr(2);      // 从位置2到结尾

// 替换(C++ 无内置全局替换,手动实现)
s.replace(1, 3, "XYZ");         // 将 [1, 1+3) 替换为 "XYZ"

// 拼接
s += " world";                  // 直接拼接
s.append("!!!");                // append 拼接
string concat = s + "abc";      // + 运算符

// 插入 / 删除
s.insert(0, "start ");          // 在位置0插入
s.erase(0, 6);                  // 删除 [0, 0+6)
s.pop_back();                   // 删除最后一个字符
s.push_back('!');               // 追加一个字符

// 比较
bool eq = (s == "hello");
bool lt = (s < "world");        // 字典序比较
int cmp = s.compare("hello");   // 0:相等 <0:小于 >0:大于

// 判断前后缀(C++20)
// bool sw = s.starts_with("he");
// bool ew = s.ends_with("lo");

// 转换
// 字符串转数字
int num = stoi("123");
long l = stol("123");
double d = stod("3.14");

// 大小写转换(需要 <algorithm> 和 <cctype>)
transform(s.begin(), s.end(), s.begin(), ::toupper);
transform(s.begin(), s.end(), s.begin(), ::tolower);

// 字符数组互转
const char* cstr = s.c_str();   // string -> C风格字符串

stringstream - 字符串流

使用场景:字符串分割、类型转换、格式化拼接

#include <sstream>

// 字符串分割(按空格)
string line = "a b c d";
stringstream ss(line);
string token;
while (ss >> token) {
    cout << token << endl; // 依次输出 a b c d
}

// 按指定分隔符分割
string csv = "a,b,c";
stringstream ss2(csv);
while (getline(ss2, token, ',')) {
    cout << token << endl; // 依次输出 a b c
}

// 数字转字符串(C++11 前常用)
stringstream ss3;
ss3 << 42;
string result = ss3.str(); // "42"

// 拼接多种类型
stringstream builder;
builder << "id=" << 1 << ", name=" << "Alice";
cout << builder.str(); // "id=1, name=Alice"

1. vector - 动态数组

#include <vector>

// 初始化
vector<int> v;
vector<int> v2(5, 0);                    // [0, 0, 0, 0, 0]
vector<int> v3 = {1, 2, 3, 4, 5};
vector<int> v4(v3.begin(), v3.end());    // 拷贝构造
vector<vector<int>> mat(3, vector<int>(4, 0)); // 3x4 二维数组

// 增
v.push_back(1);                          // 尾部追加
v.emplace_back(2);                       // 尾部构造(效率更高)
v.insert(v.begin(), 0);                  // 头部插入(O(n),慎用)
v.insert(v.begin() + 2, 99);            // 指定位置插入

// 删
v.pop_back();                            // 删除尾部
v.erase(v.begin());                      // 删除头部
v.erase(v.begin() + 1, v.begin() + 3);  // 删除 [1, 3)
v.clear();                               // 清空

// 改
v[0] = 100;                              // 直接赋值(不检查越界)
v.at(0) = 100;                           // 安全赋值(检查越界)

// 查
int val = v[0];
int front = v.front();
int back = v.back();
int sz = v.size();
bool empty = v.empty();

// 遍历
for (int x : v) cout << x;
for (int i = 0; i < v.size(); i++) cout << v[i];

// 排序(需要 <algorithm>)
sort(v.begin(), v.end());                // 升序
sort(v.begin(), v.end(), greater<int>());// 降序
sort(v.begin(), v.end(), [](int a, int b){ return a > b; }); // 自定义

// 反转
reverse(v.begin(), v.end());

// 去重(先排序再去重)
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());

// 二分查找(需排序,需要 <algorithm>)
bool found = binary_search(v.begin(), v.end(), 3);
auto it = lower_bound(v.begin(), v.end(), 3); // 第一个 >= 3 的位置
auto it2 = upper_bound(v.begin(), v.end(), 3);// 第一个 >  3 的位置
int pos = it - v.begin();                // 转为下标

2. set / multiset - 集合

set - 有序不重复(红黑树)

#include <set>

// 初始化
set<int> s;
set<int> s2 = {3, 1, 2};               // 自动排序:{1, 2, 3}

// 增
s.insert(1);
s.insert({2, 3, 4});                    // 批量插入

// 删
s.erase(1);                             // 按值删除
s.erase(s.begin());                     // 按迭代器删除
s.clear();

// 查
bool has = s.count(1);                  // 0 或 1
auto it = s.find(1);                    // 返回迭代器,找不到返回 s.end()
int sz = s.size();
bool empty = s.empty();

// 有序特有操作
auto lo = s.lower_bound(3);             // 第一个 >= 3 的迭代器
auto hi = s.upper_bound(3);             // 第一个 >  3 的迭代器
int first = *s.begin();                 // 最小元素
int last = *s.rbegin();                 // 最大元素

// 遍历(有序)
for (int x : s) cout << x;

unordered_set - 无序不重复(哈希表)

#include <unordered_set>

unordered_set<int> us;
us.insert(1);
us.erase(1);
bool has = us.count(1);                 // O(1) 平均
// 无 lower_bound / upper_bound

multiset - 有序可重复

#include <set>

multiset<int> ms = {3, 1, 2, 1};       // {1, 1, 2, 3}
ms.insert(1);
ms.erase(ms.find(1));                   // 只删一个 1
ms.erase(1);                            // 删除所有 1
int cnt = ms.count(1);                  // 统计某值出现次数

3. map / unordered_map - 映射

map - 有序键值对(红黑树)

#include <map>

// 初始化
map<string, int> mp;
map<string, int> mp2 = {{"apple", 1}, {"banana", 2}};

// 增 / 改
mp["apple"] = 1;                        // 不存在则插入,存在则修改
mp.insert({"cherry", 3});               // 键已存在则不覆盖
mp.emplace("date", 4);                  // 原地构造

// 删
mp.erase("apple");                      // 按键删除
mp.erase(mp.begin());                   // 按迭代器删除
mp.clear();

// 查
int val = mp["apple"];                  // 不存在会自动插入0!
int val2 = mp.at("apple");             // 不存在抛异常(更安全)
bool has = mp.count("apple");           // 0 或 1
auto it = mp.find("apple");            // 找不到返回 mp.end()

// 安全取值
int v = mp.count("apple") ? mp["apple"] : 0;

// 遍历
for (auto& [key, val] : mp) {           // C++17 结构化绑定
    cout << key << ": " << val << endl;
}
for (auto it = mp.begin(); it != mp.end(); it++) {
    cout << it->first << ": " << it->second << endl;
}

// 有序特有
int firstKey = mp.begin()->first;
int lastKey = mp.rbegin()->first;
auto lo = mp.lower_bound("b");          // 第一个键 >= "b"
auto hi = mp.upper_bound("b");          // 第一个键 >  "b"

// 统计词频(常用技巧)
map<char, int> freq;
for (char c : "hello") freq[c]++;

unordered_map - 无序键值对(哈希表)

unordered_map<string, int> ump;

// 插入 / 更新
ump["a"] = 1;

// 删除
ump.erase("a");

// 查询是否存在
bool has = ump.count("a");       // 首选,简洁

auto it = ump.find("a");         // 需要取值时
if (it != ump.end()) {
    auto val = it->second;
}

if (ump.contains("a")) { ... }   // C++20,语义最清晰

// ⚠️ 勿用 ump["key"] 判断存在性——不存在时会自动插入默认值

// 限制:无 lower_bound / upper_bound
//       需有序遍历 → 改用 map

4. queue / deque / priority_queue - 队列

queue - 普通队列(FIFO)

#include <queue>

queue<int> q;
q.push(1);                              // 入队
q.push(2);
int front = q.front();                  // 查看队首
int back = q.back();                    // 查看队尾
q.pop();                                // 出队(无返回值!)
int sz = q.size();
bool empty = q.empty();

deque - 双端队列

#include <deque>

deque<int> dq;

// 头部操作
dq.push_front(1);
dq.pop_front();
int front = dq.front();

// 尾部操作
dq.push_back(2);
dq.pop_back();
int back = dq.back();

// 支持随机访问
int val = dq[0];
int sz = dq.size();

priority_queue - 优先队列(堆)

#include <queue>

// 大根堆(默认)
priority_queue<int> maxHeap;
maxHeap.push(3);
maxHeap.push(1);
maxHeap.push(2);
int top = maxHeap.top();               // 3
maxHeap.pop();

// 小根堆
priority_queue<int, vector<int>, greater<int>> minHeap;
minHeap.push(3);
minHeap.push(1);
int topMin = minHeap.top();            // 1

// 自定义比较(对 pair 按第一个元素升序)
priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq;
pq.push({2, 100});
pq.push({1, 200});
auto [dist, node] = pq.top();          // {1, 200}

// 自定义 lambda 比较
auto cmp = [](vector<int>& a, vector<int>& b) {
    return a[0] > b[0];                // 按第一个元素升序(小根堆)
};
priority_queue<vector<int>, vector<vector<int>>, decltype(cmp)> pq2(cmp);

5. stack - 栈

#include <stack>

stack<int> st;
st.push(1);                             // 入栈
st.push(2);
int top = st.top();                     // 查看栈顶(不弹出)
st.pop();                               // 弹出栈顶(无返回值!)
int sz = st.size();
bool empty = st.empty();

6. 常用算法 <algorithm>

#include <algorithm>

vector<int> v = {3, 1, 4, 1, 5, 9};

// 最大最小
int mx = *max_element(v.begin(), v.end());
int mn = *min_element(v.begin(), v.end());
auto [lo, hi] = minmax_element(v.begin(), v.end()); // C++17

// 求和(需要 <numeric>)
#include <numeric>
int sum = accumulate(v.begin(), v.end(), 0);

// 排列
sort(v.begin(), v.end());
next_permutation(v.begin(), v.end());   // 下一个排列
prev_permutation(v.begin(), v.end());   // 上一个排列

// 填充
fill(v.begin(), v.end(), -1);
fill_n(v.begin(), 3, 0);               // 前3个填0

// 复制
vector<int> dst(v.size());
copy(v.begin(), v.end(), dst.begin());

// 计数
int cnt = count(v.begin(), v.end(), 1);

// 查找
auto it = find(v.begin(), v.end(), 4);

// 翻转
reverse(v.begin(), v.end());

// 去重(先排序)
sort(v.begin(), v.end());
v.erase(unique(v.begin(), v.end()), v.end());

7. 常用数学 <cmath>

#include <cmath>
#include <climits>

// 数学运算
int mx = max(a, b);
int mn = min(a, b);
int ab = abs(-10);
double pw = pow(2, 3);                  // 2^3 = 8.0
double sq = sqrt(16.0);                 // 4.0
double ce = ceil(3.2);                  // 4.0
double fl = floor(3.8);                 // 3.0
long long ro = llround(3.6);            // 4

// 整数极值
int INF = INT_MAX;                      // 2147483647
int NEG_INF = INT_MIN;                  // -2147483648
long long LINF = LLONG_MAX;

// 常用技巧
int log2n = __lg(n);                    // floor(log2(n)),GCC 扩展
int len = __gcd(len1,len2)              //c++ 自带的求最大公约数的函数
bool isPow2 = n > 0 && (n & (n - 1)) == 0;

8. pair / tuple

#include <utility>
#include <tuple>

// pair
pair<int, string> p = {1, "hello"};
p.first;                                // 1
p.second;                               // "hello"
auto p2 = make_pair(2, "world");

// pair 排序(默认先按 first,再按 second)
vector<pair<int,int>> vp = {{2,1},{1,3},{1,2}};
sort(vp.begin(), vp.end());             // {1,2},{1,3},{2,1}

// tuple
tuple<int, string, double> t = {1, "a", 3.14};
get<0>(t);                              // 1
get<1>(t);                              // "a"
auto [x, y, z] = t;                    // C++17 结构化绑定

auto t2 = make_tuple(42, "hi", 1.5);

🧮 Part 2: 算法模板

1. 树的遍历

二叉树定义

struct TreeNode {
    int val;
    TreeNode* left;
    TreeNode* right;
    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
};

DFS - 深度优先遍历

前序遍历(根-左-右)

// 递归
void preorder(TreeNode* root, vector<int>& res) {
    if (!root) return;
    res.push_back(root->val);           // 1. 访问根
    preorder(root->left, res);          // 2. 遍历左
    preorder(root->right, res);         // 3. 遍历右
}

// 迭代
vector<int> preorderTraversal(TreeNode* root) {
    vector<int> res;
    if (!root) return res;
    stack<TreeNode*> st;
    st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top(); st.pop();
        res.push_back(node->val);
        if (node->right) st.push(node->right); // 先压右
        if (node->left)  st.push(node->left);  // 再压左
    }
    return res;
}

中序遍历(左-根-右)

// 递归
void inorder(TreeNode* root, vector<int>& res) {
    if (!root) return;
    inorder(root->left, res);
    res.push_back(root->val);
    inorder(root->right, res);
}

// 迭代
vector<int> inorderTraversal(TreeNode* root) {
    vector<int> res;
    stack<TreeNode*> st;
    TreeNode* curr = root;
    while (curr || !st.empty()) {
        while (curr) {
            st.push(curr);
            curr = curr->left;          // 一直往左走
        }
        curr = st.top(); st.pop();
        res.push_back(curr->val);       // 访问
        curr = curr->right;             // 转向右子树
    }
    return res;
}

后序遍历(左-右-根)

// 递归
void postorder(TreeNode* root, vector<int>& res) {
    if (!root) return;
    postorder(root->left, res);
    postorder(root->right, res);
    res.push_back(root->val);
}

// 迭代(前序变形 + 反转)
vector<int> postorderTraversal(TreeNode* root) {
    vector<int> res;
    if (!root) return res;
    stack<TreeNode*> st;
    st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top(); st.pop();
        res.push_back(node->val);
        if (node->left)  st.push(node->left);
        if (node->right) st.push(node->right);
    }
    reverse(res.begin(), res.end());    // 反转得到左右根
    return res;
}

BFS - 层序遍历

vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> res;
    if (!root) return res;
    queue<TreeNode*> q;
    q.push(root);
    while (!q.empty()) {
        int sz = q.size();              // 当前层节点数
        vector<int> level;
        for (int i = 0; i < sz; i++) {
            TreeNode* node = q.front(); q.pop();
            level.push_back(node->val);
            if (node->left)  q.push(node->left);
            if (node->right) q.push(node->right);
        }
        res.push_back(level);
    }
    return res;
}

线段树(Segment Tree)

/**
 * 线段树:区间查询 + 单点修改
 * 时间复杂度:构建 O(n),查询/更新 O(log n)
 */
class SegmentTree {
    vector<int> tree;
    int n;
public:
    SegmentTree(vector<int>& nums) {
        n = nums.size();
        tree.resize(4 * n);
        build(nums, 0, 0, n - 1);
    }

    void build(vector<int>& nums, int node, int start, int end) {
        if (start == end) {
            tree[node] = nums[start];
            return;
        }
        int mid = start + (end - start) / 2;
        build(nums, 2*node+1, start, mid);
        build(nums, 2*node+2, mid+1, end);
        tree[node] = tree[2*node+1] + tree[2*node+2];
    }

    int query(int l, int r) { return query(0, 0, n-1, l, r); }
    int query(int node, int start, int end, int l, int r) {
        if (l > end || r < start) return 0;
        if (l <= start && end <= r) return tree[node];
        int mid = start + (end - start) / 2;
        return query(2*node+1, start, mid, l, r)
             + query(2*node+2, mid+1, end, l, r);
    }

    void update(int index, int val) { update(0, 0, n-1, index, val); }
    void update(int node, int start, int end, int index, int val) {
        if (start == end) { tree[node] = val; return; }
        int mid = start + (end - start) / 2;
        if (index <= mid) update(2*node+1, start, mid, index, val);
        else              update(2*node+2, mid+1, end, index, val);
        tree[node] = tree[2*node+1] + tree[2*node+2];
    }
};

2. 图算法

图的表示

// 邻接表(稀疏图)
vector<vector<int>> graph(n);
graph[u].push_back(v);                  // 有向边 u -> v

// 带权邻接表
vector<vector<pair<int,int>>> graph(n);
graph[u].push_back({v, w});             // 有向边 u -> v,权重 w

// 邻接矩阵(密集图)
vector<vector<int>> mat(n, vector<int>(n, INT_MAX));
mat[u][v] = w;

DFS / BFS

// DFS(递归)
void dfs(int node, vector<bool>& visited, vector<vector<int>>& graph) {
    visited[node] = true;
    for (int nb : graph[node]) {
        if (!visited[nb]) dfs(nb, visited, graph);
    }
}

// BFS
void bfs(int start, vector<vector<int>>& graph) {
    int n = graph.size();
    vector<bool> visited(n, false);
    queue<int> q;
    q.push(start);
    visited[start] = true;
    while (!q.empty()) {
        int node = q.front(); q.pop();
        for (int nb : graph[node]) {
            if (!visited[nb]) {
                visited[nb] = true;
                q.push(nb);
            }
        }
    }
}

拓扑排序(Kahn 算法)

vector<int> topoSort(int n, vector<vector<int>>& graph) {
    vector<int> inDeg(n, 0);
    for (int u = 0; u < n; u++)
        for (int v : graph[u]) inDeg[v]++;

    queue<int> q;
    for (int i = 0; i < n; i++)
        if (inDeg[i] == 0) q.push(i);

    vector<int> res;
    while (!q.empty()) {
        int node = q.front(); q.pop();
        res.push_back(node);
        for (int nb : graph[node])
            if (--inDeg[nb] == 0) q.push(nb);
    }
    return res.size() == n ? res : vector<int>{}; // 有环则返回空
}

Dijkstra 最短路径

// 时间复杂度:O(E log V)
vector<int> dijkstra(int n, vector<vector<pair<int,int>>>& graph, int src) {
    vector<int> dist(n, INT_MAX);
    dist[src] = 0;
    // {距离, 节点},小根堆
    priority_queue<pair<int,int>, vector<pair<int,int>>, greater<>> pq;
    pq.push({0, src});

    while (!pq.empty()) {
        auto [d, u] = pq.top(); pq.pop();
        if (d > dist[u]) continue;      // 过期节点跳过
        for (auto [v, w] : graph[u]) {
            if (dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
                pq.push({dist[v], v});
            }
        }
    }
    return dist;
}

Floyd-Warshall 全源最短路

// 时间复杂度:O(V³)
void floydWarshall(vector<vector<int>>& dist, int n) {
    // 初始化:dist[i][i]=0,无边处为 INF
    for (int k = 0; k < n; k++)
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                if (dist[i][k] != INT_MAX && dist[k][j] != INT_MAX)
                    dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j]);
}

并查集(Union-Find)

class UnionFind {
    vector<int> parent, rank;
public:
    UnionFind(int n) : parent(n), rank(n, 0) {
        iota(parent.begin(), parent.end(), 0); // parent[i] = i
    }

    int find(int x) {
        if (parent[x] != x)
            parent[x] = find(parent[x]);       // 路径压缩
        return parent[x];
    }

    bool unite(int x, int y) {
        int rx = find(x), ry = find(y);
        if (rx == ry) return false;
        if (rank[rx] < rank[ry]) swap(rx, ry); // 按秩合并
        parent[ry] = rx;
        if (rank[rx] == rank[ry]) rank[rx]++;
        return true;
    }

    bool connected(int x, int y) { return find(x) == find(y); }
};

Kruskal 最小生成树

// 时间复杂度:O(E log E)
int kruskal(int n, vector<array<int,3>>& edges) {
    // edges[i] = {w, u, v}
    sort(edges.begin(), edges.end());
    UnionFind uf(n);
    int total = 0, cnt = 0;
    for (auto [w, u, v] : edges) {
        if (uf.unite(u, v)) {
            total += w;
            if (++cnt == n - 1) break;
        }
    }
    return cnt == n - 1 ? total : -1;
}

3. 回溯算法

组合问题

void backtrack(vector<vector<int>>& res, vector<int>& path, int start, int n, int k) {
    if ((int)path.size() == k) {
        res.push_back(path);
        return;
    }
    for (int i = start; i <= n; i++) {
        path.push_back(i);
        backtrack(res, path, i + 1, n, k);
        path.pop_back();                // 回溯
    }
}

vector<vector<int>> combine(int n, int k) {
    vector<vector<int>> res;
    vector<int> path;
    backtrack(res, path, 1, n, k);
    return res;
}

全排列

void backtrack(vector<vector<int>>& res, vector<int>& path,
               vector<int>& nums, vector<bool>& used) {
    if (path.size() == nums.size()) {
        res.push_back(path);
        return;
    }
    for (int i = 0; i < (int)nums.size(); i++) {
        if (used[i]) continue;
        used[i] = true;
        path.push_back(nums[i]);
        backtrack(res, path, nums, used);
        path.pop_back();
        used[i] = false;
    }
}

vector<vector<int>> permute(vector<int>& nums) {
    vector<vector<int>> res;
    vector<int> path;
    vector<bool> used(nums.size(), false);
    backtrack(res, path, nums, used);
    return res;
}

子集问题

void backtrack(vector<vector<int>>& res, vector<int>& path,
               vector<int>& nums, int start) {
    res.push_back(path);                // 每个状态都是一个子集
    for (int i = start; i < (int)nums.size(); i++) {
        path.push_back(nums[i]);
        backtrack(res, path, nums, i + 1);
        path.pop_back();
    }
}

vector<vector<int>> subsets(vector<int>& nums) {
    vector<vector<int>> res;
    vector<int> path;
    backtrack(res, path, nums, 0);
    return res;
}

4. 动态规划

0-1 背包

int knapsack(vector<int>& weights, vector<int>& values, int capacity) {
    int n = weights.size();
    vector<int> dp(capacity + 1, 0);
    for (int i = 0; i < n; i++)
        for (int w = capacity; w >= weights[i]; w--) // 从后往前
            dp[w] = max(dp[w], dp[w - weights[i]] + values[i]);
    return dp[capacity];
}

完全背包

int completeKnapsack(vector<int>& weights, vector<int>& values, int capacity) {
    vector<int> dp(capacity + 1, 0);
    for (int i = 0; i < (int)weights.size(); i++)
        for (int w = weights[i]; w <= capacity; w++) // 从前往后
            dp[w] = max(dp[w], dp[w - weights[i]] + values[i]);
    return dp[capacity];
}

最长公共子序列(LCS)

int longestCommonSubsequence(string& s1, string& s2) {
    int m = s1.size(), n = s2.size();
    vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            if (s1[i-1] == s2[j-1])
                dp[i][j] = dp[i-1][j-1] + 1;
            else
                dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
    return dp[m][n];
}

最长递增子序列(LIS)

// O(n log n)
int lengthOfLIS(vector<int>& nums) {
    vector<int> tails;
    for (int x : nums) {
        auto it = lower_bound(tails.begin(), tails.end(), x);
        if (it == tails.end()) tails.push_back(x);
        else *it = x;
    }
    return tails.size();
}

编辑距离

int minDistance(string& w1, string& w2) {
    int m = w1.size(), n = w2.size();
    vector<vector<int>> dp(m+1, vector<int>(n+1));
    for (int i = 0; i <= m; i++) dp[i][0] = i;
    for (int j = 0; j <= n; j++) dp[0][j] = j;
    for (int i = 1; i <= m; i++)
        for (int j = 1; j <= n; j++)
            if (w1[i-1] == w2[j-1])
                dp[i][j] = dp[i-1][j-1];
            else
                dp[i][j] = min({dp[i-1][j], dp[i][j-1], dp[i-1][j-1]}) + 1;
    return dp[m][n];
}

零钱兑换

// 最少硬币数
int coinChange(vector<int>& coins, int amount) {
    vector<int> dp(amount+1, amount+1);
    dp[0] = 0;
    for (int i = 1; i <= amount; i++)
        for (int c : coins)
            if (i >= c)
                dp[i] = min(dp[i], dp[i-c] + 1);
    return dp[amount] > amount ? -1 : dp[amount];
}

// 方案数
int change(int amount, vector<int>& coins) {
    vector<int> dp(amount+1, 0);
    dp[0] = 1;
    for (int c : coins)
        for (int i = c; i <= amount; i++)
            dp[i] += dp[i-c];
    return dp[amount];
}

5. 双指针

对撞指针

// 两数之和(有序数组)
vector<int> twoSum(vector<int>& nums, int target) {
    int l = 0, r = nums.size() - 1;
    while (l < r) {
        int sum = nums[l] + nums[r];
        if (sum == target) return {l, r};
        else if (sum < target) l++;
        else r--;
    }
    return {-1, -1};
}

// 三数之和
vector<vector<int>> threeSum(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    vector<vector<int>> res;
    int n = nums.size();
    for (int i = 0; i < n - 2; i++) {
        if (i > 0 && nums[i] == nums[i-1]) continue;
        int l = i+1, r = n-1;
        while (l < r) {
            int sum = nums[i] + nums[l] + nums[r];
            if (sum == 0) {
                res.push_back({nums[i], nums[l], nums[r]});
                while (l < r && nums[l] == nums[l+1]) l++;
                while (l < r && nums[r] == nums[r-1]) r--;
                l++; r--;
            } else if (sum < 0) l++;
            else r--;
        }
    }
    return res;
}

快慢指针(链表)

struct ListNode { int val; ListNode* next; };

// 链表中点
ListNode* findMiddle(ListNode* head) {
    ListNode* slow = head, *fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
    }
    return slow;
}

// 检测环
bool hasCycle(ListNode* head) {
    ListNode* slow = head, *fast = head;
    while (fast && fast->next) {
        slow = slow->next;
        fast = fast->next->next;
        if (slow == fast) return true;
    }
    return false;
}

6. 单调栈

// 下一个更大元素
vector<int> nextGreaterElement(vector<int>& nums) {
    int n = nums.size();
    vector<int> res(n, -1);
    stack<int> st;                       // 存下标
    for (int i = 0; i < n; i++) {
        while (!st.empty() && nums[st.top()] < nums[i]) {
            res[st.top()] = nums[i];
            st.pop();
        }
        st.push(i);
    }
    return res;
}

// 柱状图中最大矩形
int largestRectangleArea(vector<int>& heights) {
    heights.push_back(0);               // 哨兵,清空栈
    stack<int> st;
    st.push(-1);                        // 哨兵
    int maxArea = 0;
    for (int i = 0; i < (int)heights.size(); i++) {
        while (st.top() != -1 && heights[st.top()] >= heights[i]) {
            int h = heights[st.top()]; st.pop();
            int w = i - st.top() - 1;
            maxArea = max(maxArea, h * w);
        }
        st.push(i);
    }
    return maxArea;
}

7. 前缀和

// 一维前缀和
class PrefixSum {
    vector<int> pre;
public:
    PrefixSum(vector<int>& nums) {
        pre.resize(nums.size() + 1, 0);
        for (int i = 0; i < (int)nums.size(); i++)
            pre[i+1] = pre[i] + nums[i];
    }
    // 查询区间 [l, r] 的和
    int query(int l, int r) { return pre[r+1] - pre[l]; }
};

// 二维前缀和
class MatrixPrefixSum {
    vector<vector<int>> pre;
public:
    MatrixPrefixSum(vector<vector<int>>& mat) {
        int m = mat.size(), n = mat[0].size();
        pre.assign(m+1, vector<int>(n+1, 0));
        for (int i = 1; i <= m; i++)
            for (int j = 1; j <= n; j++)
                pre[i][j] = pre[i-1][j] + pre[i][j-1]
                           - pre[i-1][j-1] + mat[i-1][j-1];
    }
    // 查询子矩阵 [r1,c1] 到 [r2,c2] 的和
    int query(int r1, int c1, int r2, int c2) {
        return pre[r2+1][c2+1] - pre[r1][c2+1]
             - pre[r2+1][c1] + pre[r1][c1];
    }
};