算法刷题

🧮 算法基础

本笔记总结了常见算法与数据结构的基础知识与典型题目,适合用于复习与面试准备。


一、数组与链表

1. 排序

📘 复杂度与应用场景

算法 平均复杂度 最坏复杂度 稳定性 适用场景
冒泡排序 O(n²) O(n²) 数据量小、稳定性要求高
选择排序 O(n²) O(n²) 数据量小,交换次数少
插入排序 O(n²) O(n²) 基本有序数组
希尔排序 O(n¹·³)~O(n²) O(n²) 中等规模数组
快速排序 O(n log n) O(n²) 工业界通用排序
归并排序 O(n log n) O(n log n) 稳定排序、外部排序
堆排序 O(n log n) O(n log n) 需要原地排序、Top K 场景

🔸 快速排序(分治思想,原地划分,适合 Top-K、排序优化等)

  • 数组中的第 K 个最大元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int quickFind(vector<int>& nums, int l, int r, int k) {
    if (l == r) return nums[l];
    int i = l - 1, j = r + 1, pivot = nums[ l + r >> 1];
    while(i < j) { // 均不含等于号
    while(nums[++i] < pivot);
    while(nums[--j] > pivot);
    if (i < j) swap(nums[i], nums[j]);
    }
    if (k <= j) return quickFind(nums, l, j, k); // 这里是小于等于
    return quickFind(nums, j+1, r, k);
    }
    int findKthLargest(vector<int>& nums, int k) {
    return quickFind(nums, 0, nums.size() - 1, nums.size() - k);
    }

🔸 归并排序

  • 逆序对的数量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    using LL = long long;
    LL merge_sort(vector<int>& record, vector<int>& tmp, int l, int r){
    if (l >= r) return 0;
    int mid = l + r >> 1;
    LL res = merge_sort(record, tmp, l, mid) + merge_sort(record, tmp, mid + 1, r);
    int i = l, j = mid + 1, k = 0;
    while (i <= mid && j <=r) { // 合并两个有序的数组,这里合并的是两边的值
    if(record[i] <= record[j]) tmp[ k++ ] = record[ i++ ];
    else { // 可以产生逆序对的情况
    tmp[ k++ ] = record[ j++ ];
    res += mid - i + 1;
    }
    }
    while( i <= mid ) tmp[k++] = record[i++];
    while( j <= r ) tmp[k++] = record[j++];
    for(int i = l, j = 0; i <= r; i++, j++) record[i] = tmp[j];
    return res;
    }
    int reversePairs(vector<int>& record) {
    int N = record.size();
    vector<int> tmp(N);
    return merge_sort(record, tmp, 0, record.size() - 1);
    }

2. 双指针

🚀 快慢指针

典型应用:遍历、过滤、删除重复元素、判断链表特性等。

  1. 数组移动删除元素

    这种问题一般需要从一端开始,设立两个指针i,j;j走的快一些,循环终止条件为j走到列表末尾。i一般用来控制删除后元素应该放在的正确位置

  • 删除有序数组中的重复项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int removeDuplicates(vector<int>& nums) {
    int N = nums.size();
    int i = 1, j = 1; // 根据判断条件要求,需要从下标1开始,为0时不会产生重复项,不考虑
    while(j < N) {
    if (nums[j] != nums[j - 1]) {
    nums[i] = nums[j];
    i++;
    }
    j++;
    }
    return i;
    }
  • 移除元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int removeElement(vector<int>& nums, int val) {
    int N = nums.size();
    int i = 0, j = 0; // 需要从0开始,不然开头重复删除不了
    while(j < N) {
    if (nums[j] != val) {
    nums[i] = nums[j];
    i++;
    }
    j++;
    }
    return i;
    }
  • 移动零

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    void moveZeroes(vector<int>& nums) {
    int N = nums.size();
    int i = 0, j = 0; // 下标从0开始,
    while (j < N) {
    if (nums[j] != 0) {
    swap(nums[i], nums[j]);
    i++;
    }
    j++;
    }
    }
  1. 链表的删除合并
  • 删除排序链表中的重复元素

    1
    2
    3
    4
    5
    6
    7
    8
    ListNode* deleteDuplicates(ListNode* head) {
    auto cur = head;
    while (cur && cur->next) { // 如果cur->next为空,说明已经到最后一个节点,不需要判断重复了
    if (cur->val == cur->next->val) { cur->next = cur->next->next; }
    else { cur = cur->next; }
    }
    return head;
    }
  • 删除链表的倒数第 N 个结点/训练计划 II

    设置一个快指针,先走N次,此时fast相较于slow为倒数第N个节点,fast走到最后,此时slow的位置为倒数第N个节点的位置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    auto dummy = new ListNode(-1, head);
    auto slow = dummy, fast = dummy;
    while (n--) {
    fast = fast->next;
    if (!fast) return nullptr; // 长度不够长
    }
    while(fast->next) {
    fast = fast->next;
    slow = slow->next;
    }
    slow->next = slow->next->next;
    return dummy->next;
    }
  • 环形链表/环形链表 II(检测与定位环起点)

    设置一个slow和fast指针,slow一次走一格,fast一次走两格,如果有环必会相遇。需要找到节点位置的情况,这时从头节点开始,slow与head相遇的位置为环的入口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    ListNode* detectCycle(ListNode* head) {
    auto slow = head, fast = head;
    while (fast && fast->next) {
    slow = slow->next;
    fast = fast->next->next;
    if (slow == fast) {
    auto start = head;
    while (start != slow) { // 找环入口的方式
    slow = slow->next;
    start = start->next;
    }
    return start;
    }
    }
    return nullptr;
    }
  • 相交链表

    p1从headA出发走到末尾,再走一遍链表B,这里不相交会分别从链表A和链表B走一轮,最后在末尾nullptr结束,相交的情况会同时达到相交的节点

    1
    2
    3
    4
    5
    6
    7
    8
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    auto p1 = headA, p2 = headB;
    while(p1 != p2) {
    p1 = p1 ? p1->next : headA;
    p2 = p2 ? p2->next : headB;
    }
    return p1;
    }
  • 合并两个有序链表/合并 K 个升序链表

    从两个虚拟头节点出发,分别比较节点值,小于则加入新的链表。合并K个可两两合并。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    ListNode* merge(ListNode* list1, ListNode* list2){
    auto dummy = new ListNode(-1);
    auto cur = dummy;
    while(list1 && list2){
    if (list1->val < list2->val){ cur->next = list1; list1 = list1->next; }
    else { cur->next = list2; list2 = list2->next; }
    cur = cur->next;
    }
    if (list1) cur->next = list1; // 处理没有完全走完某个列表的情况
    if (list2) cur->next = list2;
    return dummy->next;
    }
    ListNode* mergeKLists(vector<ListNode*>& lists) {
    ListNode* list = nullptr;
    for(int i = 0; i < lists.size(); i++){ list = merge(list, lists[i]); }
    return list;
    }
  • 分隔链表(一分为二)

    与上面类似,这里是反过来,从一个链表出发,小于值加入到新的链表,大于加入到另外一个。这里有个坑,会成环,因此第二个链表末尾要置空

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    ListNode* partition(ListNode* head, int x) {
    auto dummy1 = new ListNode(-1);
    auto dummy2 = new ListNode(-1);
    auto p1 = dummy1, p2 = dummy2, cur = head;
    while (cur) {
    if (cur->val < x) {
    p1->next = cur;
    p1 = p1->next;
    }
    else {
    p2->next = cur;
    p2 = p2->next;
    }
    cur = cur->next;
    }
    p2->next = nullptr; // 去除环
    p1->next = dummy2->next; // 两个有序链表拼接起来
    return dummy1->next;
    }
  • 链表的中间结点

    很简单给两个指针,一个一次走一个节点,一个走两次,慢的为中间节点

    1
    2
    3
    4
    5
    6
    7
    8
    ListNode* middleNode(ListNode* head) {
    auto slow = head, fast = head;
    while(fast && fast->next){ // 需要控制走两次的边界条件,一般都是这种形式
    slow = slow->next;
    fast = fast->next->next;
    }
    return slow;
    }

⚖️ 左右指针

典型应用:区间搜索、反转、回文、二分查找。

  • 反转字符串

    从两边向中间走,交换值即可

    1
    2
    3
    4
    void reverseString(vector<char>& s) {
    int i = 0, j = s.size() - 1;
    while(i < j) { swap(s[i++], s[j--]); }
    }
  • 验证回文串/回文链表

    从两边走,验证是否相等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    bool isPalindrome(string s) {
    std::string sraw;
    for (auto c : s) {
    if (isalnum(c)) sraw += tolower(c); //用到一些判断的api可以记住
    }
    int n = sraw.size();
    int l = 0, r = n - 1;
    while(l < r) {
    if (sraw[l++] != sraw[r--]) return false;
    }
    return true;
    }

    回文链表可以先存储值再判断是否是回文,这里可使用递归写法。

  • 最长回文子串

    需要从每个位置向两边扩散找最大子串,这里有子串为奇数和偶数两种可能,因此需要选两者较大的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    int expand(string& s, int i, int j) {
    while (i >= 0 && j < s.size()) {
    if (s[i] != s[j]) break; // 这里i--不能直接写进来,计数值公式会改变
    i--;
    j++;
    }
    return j - i - 1; // l与r是不满足条件后的值,实际长度是((r-1)-(l+1))+1 = r -l -1
    }
    string longestPalindrome(string s) {
    int max_len = 0, start = 0;
    for (int i = 0; i < s.size(); i++) {
    int len = max(expand(s, i, i), expand(s, i, i+1));
    if (len > max_len) {
    max_len = len;
    start = i - (len - 1 >> 1); // 注意实际开始位置的计算方式
    }
    }
    return s.substr(start, max_len);
    }
  • 二分查找:

    1. 二分查找

      需要注意是左侧、右侧边界的查找方案是有所不同的,下面是最通用的版本,左侧右侧边界的查找需要拓展,这一部分内容在第2题体现。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      int search(vector<int>& nums, int target) {
      int i = 0, j = nums.size() - 1;
      while (i <= j) { // 注意边界条件和取舍
      int mid = (j - i) / 2 + i;
      if (nums[mid] < target) i = mid + 1;
      else if (nums[mid] > target) j = mid - 1;
      else return mid;
      }
      return -1;
      }
    2. 在排序数组中查找元素的第一个和最后一个位置(左边界 / 右边界)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      class Solution {
      public:
      int binary_search(vector<int>& nums, int target, bool lower) {
      int left = 0, right = (int)nums.size() - 1, ans = (int)nums.size();
      while (left <= right) {
      int mid = (left + right) / 2;
      if (nums[mid] > target || (lower && nums[mid] >= target)) {
      right = mid - 1;
      ans = mid;
      } else {
      left = mid + 1;
      }
      }
      return ans;
      }

      vector<int> searchRange(vector<int>& nums, int target) {
      int leftIdx = binarySearch(nums, target, true);
      int rightIdx = binarySearch(nums, target, false) - 1;
      if (leftIdx <= rightIdx && rightIdx < nums.size() &&
      nums[leftIdx] == target && nums[rightIdx] == target) {
      return vector<int>{leftIdx, rightIdx};
      }
      return vector<int>{-1, -1};
      }
      };
  • 滑动窗口

    ​ 这种题一般用到单调队列和单调栈


3. 前缀差分

📊 前缀和(应用:查询区间和、子数组和)

  • 区域和检索 - 数组不可变

    给一个左边界left和右边界right,计算left到right的和,前缀和为一个数组,s[j]记录0到j-1的和。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    class NumArray {
    private:
    std::vector<int> sums;
    public:
    NumArray(vector<int>& nums) {
    sums.resize(nums.size() + 1);
    sums[0] = 0;
    for (int i = 1; i <= nums.size(); i++)
    sums[i] = sums[i - 1] + nums[i - 1]; // S[i] = S[i-1] + nums[i-1],从1开始
    }
    int sumRange(int left, int right) { return sums[right + 1] - sums[left]; }
    // S[right+1]才是从0到right的和
    };
  • 二维区域和检索 - 矩阵不可变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class NumMatrix {
    public:
    vector<vector<int>> sums;
    NumMatrix(vector<vector<int>>& matrix) {
    int m = matrix.size();
    if (m > 0) {
    int n = matrix[0].size();
    sums.resize(m, vector<int>(n + 1));
    for (int i = 0; i < m; i++) {
    for (int j = 0; j < n; j++) {
    sums[i][j + 1] = sums[i][j] + matrix[i][j];
    }
    }
    }
    }
    int sumRegion(int row1, int col1, int row2, int col2) {
    int sum = 0;
    for (int i = row1; i <= row2; i++) { sum += sums[i][col2 + 1] - sums[i][col1]; }
    return sum;
    }
    };

⚙️ 差分(应用:批量更新区域值,同时加减)

  • 数组差分

    差分是为了批量更新,比如某区间,同时加上某个数,同时减去某个数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    #include <iostream>
    using namespace std;
    const int N = 1e6 + 10;
    int a[N], s[N];

    int main(){
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; i++) {
    scanf("%d", &a[i]);
    s[i] = a[i] - a[i-1];
    }
    while (m--){
    int l, r, c;
    scanf("%d%d%d", &l, &r, &c);
    s[l] += c; s[r + 1] -= c; // 更新操作
    }
    for(int i = 1; i <= n; i++) a[i] = a[i-1] + s[i]; // 还原公式,从1开始
    for(int i = 1; i <= n; i++) printf("%d ", a[i]);
    return 0;
    }
  • 矩阵差分

  • 拼车


二、栈与队列

  • 用栈实现队列

    用两个栈,一个栈控制入,一个栈控制出,每当要出队,把控制入的栈元素依次从栈顶到栈尾出栈放入到另外一个栈

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    class MyQueue {
    public:
    MyQueue() {}
    void push(int x) { st1.push(x); }
    int pop() {
    auto res = peek();
    st2.pop();
    return res;
    }
    int peek() {
    if (st2.empty()) {
    while (!st1.empty()) {
    st2.push(st1.top());
    st1.pop();
    }
    }
    return st2.top();
    }
    bool empty() { return st1.empty() && st2.empty(); }
    private:
    std::stack<int> st1,st2;
    };
  • 用队列实现栈

    只需要用一个队列,加入元素时,先获得当前元素个数n,这个数入队后,将前n个元素依次出队再入队

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    class MyStack {
    public:
    std::queue<int> q;
    MyStack() {}
    void push(int x) {
    int n = q.size();
    q.push(x);
    while (n--) {
    auto front = q.front();
    q.pop();
    q.push(front);
    }
    }
    int pop() {
    auto res = q.front();
    q.pop();
    return res;
    }
    int top() { return q.front(); }
    bool empty() { return q.empty(); }
    };
  • 最小栈

    用辅助栈记录最小值即可

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class MinStack {
    public:
    std::stack<int> stk;
    std::stack<int> min_stk;
    MinStack() {}
    void push(int val) {
    stk.push(val);
    min_stk.push(min(min_stk.top(), val));
    }
    void pop() {
    stk.pop();
    min_stk.pop();
    }
    int top() { return stk.top(); }
    int getMin() { return min_stk.top(); }
    };
  • 面试题 16.26. 计算器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class Solution {
    public:
    int calculate(string s) {
    vector<int> stk;
    char preSign = '+';
    int num = 0;
    int n = s.length();
    for (int i = 0; i < n; ++i) {
    if (isdigit(s[i])) {
    num = num * 10 + int(s[i] - '0');
    }
    if (!isdigit(s[i]) && s[i] != ' ' || i == n - 1) {
    switch (preSign) {
    case '+':
    stk.push_back(num);
    break;
    case '-':
    stk.push_back(-num);
    break;
    case '*':
    stk.back() *= num;
    break;
    default:
    stk.back() /= num;
    }
    preSign = s[i];
    num = 0;
    }
    }
    return accumulate(stk.begin(), stk.end(), 0);
    }
    };
  • 224. 基本计算器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    class Solution {
    public:
    int calculate(string s) {
    stack<int> ops;
    ops.push(1);
    int sign = 1;
    int ret = 0;
    int n = s.length();
    int i = 0;
    while (i < n) {
    if (s[i] == ' ') { i++;}
    else if (s[i] == '+') { sign = ops.top(); i++; }
    else if (s[i] == '-') { sign = -ops.top(); i++; }
    else if (s[i] == '(') { ops.push(sign); i++; }
    else if (s[i] == ')') { ops.pop(); i++; }
    else {
    long num = 0;
    while (i < n && s[i] >= '0' && s[i] <= '9') {
    num = num * 10 + s[i] - '0';
    i++;
    }
    ret += sign * num;
    }
    }
    return ret;
    }
    };
  • 单调栈

    ​ et. 给一个序列,找出每一个数左边/右边,离它最近的数

    • 每日温度

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Solution {
      public:
      vector<int> dailyTemperatures(vector<int>& temperatures) {
      int N = temperatures.size();
      vector<int> ans(N);
      stack<int> st;
      for (int i = N - 1; i >= 0; i--) { // 右往左
      while(!st.empty() && temperatures[i] >= temperatures[st.top()]) {
      st.pop(); // 大于栈顶元素,栈顶出栈,保持栈的单调
      }
      ans[i] = st.empty() ? 0 : (st.top() - i); //栈空:没有值,不空:第i天到栈顶天数
      st.push(i);
      }
      return ans;
      }
      };
    • 下一个更大元素 I

      nums1是子集,先遍历nums2,并记录所有的数,然后根据nums1的值找结果

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      class Solution {
      public:
      vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
      int N = nums2.size();
      stack<int> st;
      unordered_map<int, int> ans_mp;
      for (int i = N - 1; i >= 0; i--){
      int num = nums2[i];
      while(!st.empty() && num >= st.top()) {
      st.pop();
      }
      ans_mp[num] = st.empty() ? -1 : st.top();
      st.push(num);
      }
      vector<int> ans(nums1.size());
      for (int i = 0; i < nums1.size(); ++i) {
      ans[i] = ans_mp[nums1[i]];
      }
      return ans;
      }
      };
    • 下一个更大元素 II

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Solution {
      public:
      vector<int> nextGreaterElements(vector<int>& nums) {
      int n = nums.size();
      vector<int> ans(n, -1);
      stack<int> stk;
      for (int i = 0; i < n * 2 - 1; i++) {
      while (!stk.empty() && nums[stk.top()] < nums[i % n]) {
      ans[stk.top()] = nums[i % n];
      stk.pop();
      }
      stk.push(i % n);
      }
      return ans;
      }
      };
  • 单调队列

    这种题一般采用双端队列,因为要操作尾部的删减

    • 滑动窗口最大值

      从头部到尾部递减,头部是最大值

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      class Solution {
      public:
      vector<int> maxSlidingWindow(vector<int>& nums, int k) {
      vector<int> ans;
      deque<int> dq;
      int n = nums.size();
      for(int i = 0; i < n; i++){
      while(!dq.empty() && nums[dq.back()] <= nums[i] ){
      dq.pop_back();
      }
      dq.push_back(i);
      if (i >= dq.front() + k) dq.pop_front(); // 去除窗口已经过了的数据
      if (i >= k - 1) ans.push_back(nums[dq.front()]); // 满足k个输出依次
      }
      return ans;
      }
      };
    • 设计自助结算系统

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      class Checkout {
      public:
      deque<int> dq;
      queue<int> q;
      Checkout() {}
      int get_max() {
      if (dq.empty()) return -1;
      return dq.front();
      }
      void add(int value) { // 保持递减,注意这里是小于
      q.push(value);
      while(!dq.empty() && dq.back() < value) { dq.pop_back(); }
      dq.push_back(value);
      }
      int remove() { // dq是否pop,根据是否最大值是队列最前面,由于递减,因此删除后dq还是存的最大值
      if (q.empty()) return -1;
      int ans = q.front();
      if (ans == dq.front()) { dq.pop_front(); }
      q.pop();
      return ans;
      }
      };

三、二叉树

  1. 遍历方式

    • 二叉树的前序遍历/二叉树的后序遍历/二叉树的中序遍历

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      class Solution {
      public:
      void preOrder(TreeNode* root, vector<int>& order) { // 换顺序即可完成前、中、后序
      if (root == nullptr) return;
      order.push_back(root->val);
      preOrder(root->left, order);
      preOrder(root->right, order);
      }
      vector<int> preorderTraversal(TreeNode* root) {
      vector<int> ans;
      preOrder(root, ans);
      return ans;
      }
      };
    • 二叉树的层序遍历/104. 二叉树的最大深度

      使用队列记录辅助

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      class Solution {
      public:
      vector<vector<int>> levelOrder(TreeNode* root) {
      vector<vector<int>> ans;
      if (root == nullptr) return ans;
      queue<TreeNode*> q;
      q.push(root);
      while (!q.empty()) { // 可记录访问层数获得最大深度
      auto sz = q.size();
      vector<int> level;
      while (sz--) {
      auto nd = q.front();
      if (nd->left) q.push(nd->left);
      if (nd->right) q.push(nd->right);
      level.push_back(nd->val);
      q.pop();
      }
      ans.push_back(level);
      }
      return ans;
      }
      };
    • N 叉树的前序遍历/N 叉树的后序遍历

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      class Solution {
      public:
      void traversal(Node* root, vector<int>& ans) {
      if(root == nullptr) return;
      ans.push_back(root->val); // 控制访问位置可实现前序后序的遍历
      for (auto child : root->children) { traversal(child, ans);}
      }
      vector<int> preorder(Node* root) {
      vector<int> ans;
      traversal(root, ans);
      return ans;
      }
      };
    • N 叉树的层序遍历

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      class Solution {
      public:
      vector<vector<int>> levelOrder(Node* root) {
      vector<vector<int>> ans;
      if (root == nullptr) return ans;
      queue<Node*> q;
      q.push(root);
      while(!q.empty()) {
      auto size = q.size();
      vector<int> level;
      for(auto i = 0; i < size; i++){
      auto node = q.front();
      level.push_back(node->val);
      for (auto child : node->children){ q.push(child); }
      q.pop();
      }
      ans.push_back(level);
      }
      return ans;
      }
      };

      112. 路径总和

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      class Solution {
      public:
      bool hasPathSum(TreeNode *root, int sum) {
      if (root == nullptr) {
      return false;
      }
      if (root->left == nullptr && root->right == nullptr) {
      return sum == root->val;
      } // 叶子节点
      return hasPathSum(root->left, sum - root->val) ||
      hasPathSum(root->right, sum - root->val);
      }
      };
      // 队列做法
      class Solution {
      public:
      bool hasPathSum(TreeNode* root, int targetSum) {
      if(root == nullptr) return false;
      queue<TreeNode*> q_node;
      queue<int> q_sum;
      q_node.push(root);
      q_sum.push(root->val);
      while(!q_node.empty()) {
      auto node = q_node.front();
      auto val = q_sum.front();
      q_node.pop();
      q_sum.pop();
      if (node->left == nullptr && node->right == nullptr && val == targetSum) {
      return true;
      }
      if (node->left) {
      q_node.push(node->left);
      q_sum.push(node->left->val + val);
      }
      if (node->right) {
      q_node.push(node->right);
      q_sum.push(node->right->val + val);
      }
      }
      return false;
      }
      };
  2. 树的特性

    • 543. 二叉树的直径

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      class Solution {
      public:
      int ans;
      int depth(TreeNode* root) {
      if (root == nullptr) return 0;
      auto l_dep = depth(root->left); //左子树的深度
      auto r_dep = depth(root->right); //右子树子树的深度
      ans = max(ans, l_dep + r_dep + 1); // 直径最大值,记录下来
      return max(l_dep, r_dep) + 1; // 当前节点深度
      }
      int diameterOfBinaryTree(TreeNode* root) {
      ans = 1;
      depth(root);
      return ans - 1;
      }
      };
    • 236. 二叉树的最近公共祖先

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      class Solution {
      public:
      TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
      if (root == nullptr || root == p || root == q) return root; //满足条件,往上返回
      TreeNode* left = lowestCommonAncestor(root->left, p, q);
      TreeNode* right = lowestCommonAncestor(root->right, p, q);
      if (left && right) return root; // 左右都找到的情况,当前节点是最近公共祖先
      return left ? left : right;
      }
      };
    • 235. 二叉搜索树的最近公共祖先

      上题可解,利用二叉搜索树性质优化

    • 222. 完全二叉树的节点个数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      class Solution {
      public:
      int countNodes(TreeNode* root) {
      if (root == nullptr) return 0; // 空节点返回0
      int left_num = countNodes(root->left);
      int right_num = countNodes(root->right);
      return left_num + right_num + 1; // 左子树个数加右子树个数
      }
      };
  3. 树的构造与操作

  4. 树的序列化与反序列化

  5. 二叉搜索树


四、BFS与DFS

  • 广度优先

    AcWing 844. 走迷宫

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    #include <iostream>
    #include <algorithm>
    #include <queue>
    #include <cstring>
    using namespace std;

    typedef pair<int, int> PII;
    const int N = 110;
    int n, m;
    int g[N][N], d[N][N];

    int bfs(){
    queue<PII> q;
    memset(d, -1, sizeof d);
    d[0][0] = 0;
    int dx[4] = {-1, 0, 1, 0};
    int dy[4] = {0, 1, 0, -1};
    q.push({0, 0});
    while(q.size()){
    PII t = q.front();
    q.pop();
    for(int i = 0; i < 4; i++){
    int x = t.first + dx[i], y = t.second + dy[i];
    if (x >= 0 && x < n && y >= 0 && y < m && !g[x][y] && d[x][y] == -1){
    d[x][y] = d[t.first][t.second] + 1;
    q.push({x, y});
    }
    }
    }
    return d[n-1][m-1];
    }

    int main(){
    cin >> n >> m;
    for(int i = 0 ; i < n; i++)
    for (int j = 0; j < m; j++)
    cin >> g[i][j];
    cout << bfs() << endl;
    return 0;
    }

    1091. 二进制矩阵中的最短路径

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class Solution {
    public:
    int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
    if (grid[0][0] == 1) { return -1; }
    int n = grid.size();
    vector<vector<int>> visited(n, vector<int>(n, 0));
    vector<vector<int>> dist(n, vector<int>(n, 0));
    visited[0][0] = 1, dist[0][0] = 1;
    queue<pair<int, int>> q;
    int dx[8] = {-1, 0, 1, 0, -1, -1, 1, 1};
    int dy[8] = {0, 1, 0, -1, -1, 1, -1, 1};
    q.push({0, 0});
    while(!q.empty()) {
    auto [x, y] = q.front();
    // std::cout << "x: " << x << " y: " << y << endl;
    q.pop();
    if (x == n - 1 && y == n - 1) { return dist[x][y]; }
    for (int i = 0; i < 8; i++){
    auto nx = x + dx[i];
    auto ny = y + dy[i];
    // std::cout << "nx: " << nx << " ny: " << ny << endl;
    if (nx >= 0 && nx < n && ny >= 0 && ny < n && grid[nx][ny] != 1 && visited[nx][ny] == 0) {
    visited[nx][ny] = 1;
    dist[nx][ny] = dist[x][y] + 1;
    q.push({nx, ny});
    }
    }
    }
    return -1;
    }
    };

    AcWing 845. 八数码

    1926. 迷宫中离入口最近的出口

  • 深度优先

    AcWing 842. 排列数字

  • 树的DFS与BFS

    AcWing 846. 树的重心

    AcWing 847. 图中点的层次

  • 回溯法

    37. 解数独

    51. N 皇后

    52. N皇后 II

    77. 组合

    可参考代码随想录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    class Solution {
    public:
    vector<vector<int>> result;
    vector<int> path;
    void backtracking(int n, int k, int startIdx) {
    if (path.size() == k) {
    result.push_back(path);
    return;
    }
    for (int i = startIdx; i <= n - (k - path.size()) + 1; i++) {
    path.push_back(i); // 处理节点
    backtracking(n, k, i + 1);
    path.pop_back(); // 回溯,撤销处理的节点
    }
    }
    vector<vector<int>> combine(int n, int k) {
    backtracking(n, k, 1);
    return result;
    }
    };

五、图论

  1. 图遍历

    207. 课程表/210. 课程表 II

    785. 判断二分图/AcWing 860. 染色法判定二分图

    AcWing 861. 二分图的最大匹配

  2. 最小生成树(Kruskal、Prim)

    AcWing 858. Prim算法求最小生成树

    AcWing 859. Kruskal算法求最小生成树

  3. 最短路径

    AcWing 850. Dijkstra求最短路 I/AcWing 850. Dijkstra求最短路 II

    AcWing 854. Floyd求最短路

    AcWing 851. spfa求最短路

  4. 并查集(Union-Find)

    AcWing 837. 连通块中点的数量

  5. 拓扑排序(检测有向图环)

    AcWing 848. 有向图的拓扑序列


六、动态规划与贪心

🎒 背包问题

​ 背包/完全背包/多重背包

📈 线性DP

💡 贪心算法


七、其他常见题型

  1. 区间合并

    56. 合并区间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution {
    public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
    vector<vector<int>> ans; // 先排序然后比较是否能合并
    std::sort(intervals.begin(), intervals.end(),
    [](vector<int>& a, vector<int>& b) { return b[0] > a[0]; });
    for (auto& interval : intervals) {
    if (ans.empty()) { ans.push_back(interval); continue; }
    if (interval[0] <= ans.back()[1]) {
    ans.back()[1] = max(interval[1], ans.back()[1]);
    } else { ans.push_back(interval); }
    }
    return ans;
    }
    };
  2. 反转链表

    206. 反转链表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Solution {
    public:
    ListNode* reverseList(ListNode* head) {
    ListNode* pre = nullptr;
    auto cur = head;
    while (cur) {
    auto nxt = cur->next;
    cur->next = pre;
    pre = cur;
    cur = nxt;
    }
    return pre;
    }
    };

    25. K 个一组翻转链表

    92. 反转链表 II

  3. 遍历问题

    151. 反转字符串中的单词

    48. 旋转图像

    54. 螺旋矩阵/59. 螺旋矩阵 II

    61. 旋转链表

  4. LRU缓存机制

    146. LRU缓存

  5. 数的和

    1. 两数之和

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Solution {
    public:
    vector<int> twoSum(vector<int>& nums, int target) {
    auto n = nums.size();
    std::unordered_map<int, int> st;
    for (auto i = 0; i < n; i++) { // 边添加边枚举
    if (st.count(target - nums[i])) { return {st[target - nums[i]], i}; }
    else { st.insert({nums[i], i}); }
    }
    return {};
    }
    };

    15. 三数之和

    167. 两数之和 II - 输入有序数组

    18. 四数之和

  6. 岛屿

    200. 岛屿数量

    从一个岛向周围扩散,直到这个岛淹没

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    class Solution {
    public:
    void dfs(vector<vector<char>>& grid, int x, int y){
    grid[x][y] = '0';
    int m = grid.size(), n = grid[0].size();
    int dx[4] = {-1, 0, 1, 0};
    int dy[4] = {0, 1, 0, -1};
    for(int i = 0; i < 4; i++){
    for(int j = 0; j < 4; j++){
    int nx = x + dx[i];
    int ny = y + dy[i];
    if (nx >= 0 & nx < m && ny >= 0 && ny < n && grid[nx][ny] == '1'){
    dfs(grid, nx, ny);
    }
    }
    }
    }
    int numIslands(vector<vector<char>>& grid) {
    int ans = 0;
    int m = grid.size(), n = grid[0].size();
    for(int i = 0; i < m; i++){
    for(int j = 0; j < n; j++){
    if (grid[i][j] == '1'){
    dfs(grid, i, j);
    ans++;
    }
    }
    }
    return ans;
    }
    };

    695. 岛屿的最大面积

    与上题类似,这里淹没岛屿时要返回面积。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    class Solution {
    public:
    int dfs(vector<vector<int>>& grid, int x, int y){
    grid[x][y] = 0;
    int m = grid.size(), n = grid[0].size();
    int dx[4] = {-1, 0, 1, 0};
    int dy[4] = {0, 1, 0, -1};
    int ans = 1;
    for(int i = 0; i < 4; i++){
    for(int j = 0; j < 4; j++){
    int nx = x + dx[i];
    int ny = y + dy[i];
    if (nx >= 0 & nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1){
    ans += dfs(grid, nx, ny);
    }
    }
    }
    return ans;
    }
    int maxAreaOfIsland(vector<vector<int>>& grid) {
    int ans = 0;
    int m = grid.size(), n = grid[0].size();
    for(int i = 0; i < m; i++){
    for(int j = 0; j < n; j++){
    if (grid[i][j] == 1){
    ans = max(ans, dfs(grid, i, j));
    }
    }
    }
    return ans;
    }
    };
  7. 接雨水

    11. 盛最多水的容器

    42. 接雨水

    动态规划求解,盛水的容量=两边最高柱子高度-当前柱子的高度

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Solution {
    public:
    int trap(vector<int>& height) {
    const int N = height.size();
    vector<int> leftMax(N, 0);
    vector<int> rightMax(, 0);
    leftMax[0] = height[0];
    for (int i = 1; i < N; i++) { leftMax[i] = max(leftMax[i - 1], height[i]);}
    rightMax[N - 1] = height[N - 1];
    for (int i = N - 2; i >= 0; i--) { rightMax[i] = max(rightMax[i + 1], height[i]); }
    int ans = 0;
    for (int i = 0; i < N; i++) { ans += min(leftMax[i], rightMax[i]) - height[i]; }
    return ans;
    }
    };
  8. 打家劫舍

    198. 打家劫舍

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Solution {
    public:
    int rob(vector<int>& nums) {
    if (nums.size() == 0) return 0;
    if (nums.size() == 1) return nums[0];
    vector<int> dp(nums.size());
    dp[0] = nums[0];
    dp[1] = max(nums[0], nums[1]);
    for (int i = 2; i < nums.size(); i++) {
    dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
    }
    return dp[nums.size() - 1];
    }
    };