1. 贪心算法 (Greedy Algorithm)

核心思想: 每一步都做出局部最优选择,希望最终得到全局最优解。

适用场景: 具有「最优子结构」和「贪心选择性质」的问题。

示例:活动选择问题

#include <vector>
#include <algorithm>

struct Activity {
    int start, end;
};

// 按结束时间排序
int maxActivities(std::vector<Activity>& acts) {
    std::sort(acts.begin(), acts.end(), 
        [](const Activity& a, const Activity& b) {
            return a.end < b.end;
        });
    
    int count = 1;
    int last_end = acts[0].end;
    
    for (size_t i = 1; i < acts.size(); i++) {
        if (acts[i].start >= last_end) {
            count++;
            last_end = acts[i].end;
        }
    }
    return count;
}

示例:区间调度问题(最少会议室)

#include <vector>
#include <queue>
#include <algorithm>

int minMeetingRooms(std::vector<std::vector<int>>& intervals) {
    if (intervals.empty()) return 0;
    
    std::sort(intervals.begin(), intervals.end());
    
    // 最小堆,存储每个会议室的结束时间
    std::priority_queue<int, std::vector<int>, std::greater<int>> pq;
    pq.push(intervals[0][1]);
    
    for (size_t i = 1; i < intervals.size(); i++) {
        // 最早结束的会议已经结束
        if (intervals[i][0] >= pq.top()) {
            pq.pop();
        }
        pq.push(intervals[i][1]);
    }
    return pq.size();
}

2. 动态规划 (Dynamic Programming)

核心思想: 将复杂问题分解为子问题,存储子问题的解以避免重复计算。

关键要素: 状态定义、状态转移方程、初始状态、边界条件。

示例:斐波那契数列(记忆化搜索)

#include <vector>

// 自顶向下:记忆化搜索
int fibMemo(int n, std::vector<int>& memo) {
    if (n <= 1) return n;
    if (memo[n] != -1) return memo[n];
    memo[n] = fibMemo(n - 1, memo) + fibMemo(n - 2, memo);
    return memo[n];
}

int fib(int n) {
    std::vector<int> memo(n + 1, -1);
    return fibMemo(n, memo);
}

示例:0/1 背包问题

#include <vector>
#include <algorithm>

// dp[i][w] = 前i个物品,容量为w时的最大价值
int knapsack01(const std::vector<int>& weight,
               const std::vector<int>& value, 
               int capacity) {
    int n = weight.size();
    std::vector<std::vector<int>> dp(n + 1, 
        std::vector<int>(capacity + 1, 0));
    
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= capacity; w++) {
            if (weight[i-1] <= w) {
                dp[i][w] = std::max(
                    dp[i-1][w],                          // 不选
                    dp[i-1][w - weight[i-1]] + value[i-1]  // 选
                );
            } else {
                dp[i][w] = dp[i-1][w];
            }
        }
    }
    return dp[n][capacity];
}

// 空间优化版本
int knapsack01Opt(const std::vector<int>& weight,
                  const std::vector<int>& value, 
                  int capacity) {
    std::vector<int> dp(capacity + 1, 0);
    
    for (size_t i = 0; i < weight.size(); i++) {
        // 倒序遍历,避免重复计算
        for (int w = capacity; w >= weight[i]; w--) {
            dp[w] = std::max(dp[w], dp[w - weight[i]] + value[i]);
        }
    }
    return dp[capacity];
}

示例:最长公共子序列 (LCS)

#include <string>
#include <vector>

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

示例:最长递增子序列 (LIS)

#include <vector>
#include <algorithm>

// O(n^2) 解法
int lengthOfLIS_n2(const std::vector<int>& nums) {
    int n = nums.size();
    std::vector<int> dp(n, 1);
    int maxLen = 1;
    
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[j] < nums[i]) {
                dp[i] = std::max(dp[i], dp[j] + 1);
            }
        }
        maxLen = std::max(maxLen, dp[i]);
    }
    return maxLen;
}

// O(n log n) 解法:二分优化
int lengthOfLIS(const std::vector<int>& nums) {
    std::vector<int> tails;
    
    for (int num : nums) {
        auto it = std::lower_bound(tails.begin(), tails.end(), num);
        if (it == tails.end()) {
            tails.push_back(num);
        } else {
            *it = num;
        }
    }
    return tails.size();
}

3. 分治算法 (Divide and Conquer)

核心思想: 将问题分解为若干子问题,递归解决后合并结果。

示例:归并排序

#include <vector>

void merge(std::vector<int>& nums, int left, int mid, int right) {
    std::vector<int> temp(right - left + 1);
    int i = left, j = mid + 1, k = 0;
    
    while (i <= mid && j <= right) {
        if (nums[i] <= nums[j]) {
            temp[k++] = nums[i++];
        } else {
            temp[k++] = nums[j++];
        }
    }
    while (i <= mid) temp[k++] = nums[i++];
    while (j <= right) temp[k++] = nums[j++];
    
    for (int p = 0; p < k; p++) {
        nums[left + p] = temp[p];
    }
}

void mergeSort(std::vector<int>& nums, int left, int right) {
    if (left >= right) return;
    
    int mid = left + (right - left) / 2;
    mergeSort(nums, left, mid);
    mergeSort(nums, mid + 1, right);
    merge(nums, left, mid, right);
}

示例:快速排序

#include <vector>
#include <algorithm>

int partition(std::vector<int>& nums, int low, int high) {
    int pivot = nums[high];
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        if (nums[j] <= pivot) {
            i++;
            std::swap(nums[i], nums[j]);
        }
    }
    std::swap(nums[i + 1], nums[high]);
    return i + 1;
}

void quickSort(std::vector<int>& nums, int low, int high) {
    if (low < high) {
        int pi = partition(nums, low, high);
        quickSort(nums, low, pi - 1);
        quickSort(nums, pi + 1, high);
    }
}

示例:计算逆序对数量

#include <vector>

long long mergeCount(std::vector<int>& nums, int left, int mid, int right) {
    std::vector<int> temp(right - left + 1);
    int i = left, j = mid + 1, k = 0;
    long long invCount = 0;
    
    while (i <= mid && j <= right) {
        if (nums[i] <= nums[j]) {
            temp[k++] = nums[i++];
        } else {
            temp[k++] = nums[j++];
            invCount += (mid - i + 1);  // 左边剩余元素都大于nums[j]
        }
    }
    while (i <= mid) temp[k++] = nums[i++];
    while (j <= right) temp[k++] = nums[j++];
    
    for (int p = 0; p < k; p++) {
        nums[left + p] = temp[p];
    }
    return invCount;
}

long long countInversions(std::vector<int>& nums, int left, int right) {
    if (left >= right) return 0;
    
    int mid = left + (right - left) / 2;
    long long count = 0;
    count += countInversions(nums, left, mid);
    count += countInversions(nums, mid + 1, right);
    count += mergeCount(nums, left, mid, right);
    return count;
}

4. 回溯算法 (Backtracking)

核心思想: 通过尝试所有可能的候选解来找出所有解,并在发现候选解不满足条件时回退。

示例:全排列

#include <vector>

void backtrack(std::vector<int>& nums, int start,
               std::vector<std::vector<int>>& result) {
    if (start == nums.size()) {
        result.push_back(nums);
        return;
    }
    
    for (int i = start; i < nums.size(); i++) {
        std::swap(nums[start], nums[i]);
        backtrack(nums, start + 1, result);
        std::swap(nums[start], nums[i]);  // 回溯
    }
}

std::vector<std::vector<int>> permute(std::vector<int>& nums) {
    std::vector<std::vector<int>> result;
    backtrack(nums, 0, result);
    return result;
}

示例:N 皇后问题

#include <vector>
#include <string>

class Solution {
    std::vector<std::vector<std::string>> result;
    
    bool isValid(std::vector<std::string>& board, 
                 int row, int col) {
        int n = board.size();
        // 检查列
        for (int i = 0; i < row; i++) {
            if (board[i][col] == 'Q') return false;
        }
        // 检查左上对角线
        for (int i = row - 1, j = col - 1; 
             i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] == 'Q') return false;
        }
        // 检查右上对角线
        for (int i = row - 1, j = col + 1; 
             i >= 0 && j < n; i--, j++) {
            if (board[i][j] == 'Q') return false;
        }
        return true;
    }
    
    void backtrack(std::vector<std::string>& board, int row) {
        if (row == board.size()) {
            result.push_back(board);
            return;
        }
        
        int n = board[row].size();
        for (int col = 0; col < n; col++) {
            if (!isValid(board, row, col)) continue;
            
            board[row][col] = 'Q';
            backtrack(board, row + 1);
            board[row][col] = '.';  // 撤销选择
        }
    }
    
public:
    std::vector<std::vector<std::string>> solveNQueens(int n) {
        std::vector<std::string> board(n, std::string(n, '.'));
        backtrack(board, 0);
        return result;
    }
};

示例:子集生成

#include <vector>

void backtrack(const std::vector<int>& nums, int start,
               std::vector<int>& path,
               std::vector<std::vector<int>>& result) {
    result.push_back(path);  // 每个节点都是一个子集
    
    for (int i = start; i < nums.size(); i++) {
        path.push_back(nums[i]);
        backtrack(nums, i + 1, path, result);
        path.pop_back();  // 回溯
    }
}

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

核心思想: 在有序数组中,每次将搜索区间减半。

时间复杂度: O(log n)

示例:基础二分查找

#include <vector>

int binarySearch(const std::vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
            return mid;
        } else if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return -1;  // 未找到
}

示例:查找左边界

#include <vector>

int findLeftBound(const std::vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    // 检查边界
    if (left >= nums.size() || nums[left] != target) {
        return -1;
    }
    return left;
}

示例:查找右边界

#include <vector>

int findRightBound(const std::vector<int>& nums, int target) {
    int left = 0, right = nums.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (nums[mid] <= target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    // 检查边界
    if (right < 0 || nums[right] != target) {
        return -1;
    }
    return right;
}

示例:二分答案(寻找平方根)

#include <algorithm>

int mySqrt(int x) {
    if (x < 2) return x;
    
    int left = 1, right = x / 2;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        long long square = (long long)mid * mid;
        
        if (square == x) {
            return mid;
        } else if (square < x) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return right;  // right 是最后一个满足 mid^2 <= x 的值
}

6. 双指针技术

示例:快慢指针(检测链表环)

struct ListNode {
    int val;
    ListNode *next;
    ListNode(int x) : val(x), next(nullptr) {}
};

bool hasCycle(ListNode *head) {
    if (!head || !head->next) return false;
    
    ListNode *slow = head;
    ListNode *fast = head->next;
    
    while (fast && fast->next) {
        if (slow == fast) return true;
        slow = slow->next;
        fast = fast->next->next;
    }
    return false;
}

示例:滑动窗口(最小覆盖子串)

#include <string>
#include <unordered_map>
#include <climits>

std::string minWindow(std::string s, std::string t) {
    std::unordered_map<char, int> need, window;
    for (char c : t) need[c]++;
    
    int left = 0, right = 0;
    int valid = 0;
    int start = 0, len = INT_MAX;
    
    while (right < s.size()) {
        char c = s[right++];
        if (need.count(c)) {
            window[c]++;
            if (window[c] == need[c]) valid++;
        }
        
        while (valid == need.size()) {
            if (right - left < len) {
                start = left;
                len = right - left;
            }
            char d = s[left++];
            if (need.count(d)) {
                if (window[d] == need[d]) valid--;
                window[d]--;
            }
        }
    }
    return len == INT_MAX ? "" : s.substr(start, len);
}

算法选择指南

问题类型推荐算法典型示例
最优子结构 + 贪心选择贪心活动选择、Huffman编码
重叠子问题动态规划背包问题、LCS、LIS
可分解子问题分治归并排序、快速排序
搜索所有可能解回溯全排列、N皇后、子集
有序数组查找二分查找查找元素、查找边界
数组/字符串处理双指针链表环、滑动窗口

复杂度对比

算法时间复杂度空间复杂度特点
贪心因问题而异O(1) ~ O(n)快但不保证全局最优
动态规划因问题而异O(n) ~ O(n²)保证最优,但空间开销大
分治O(n log n)O(log n) ~ O(n)递归实现简洁
回溯指数级O(n)枚举所有可能
二分查找O(log n)O(1)要求有序