C++算法(动态规划)

1. 斐波那契数列(Fibonacci Sequence)

问题描述:F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)

// 方法1:递归(效率低,存在大量重复计算)
// 时间复杂度:O(2^n),空间复杂度:O(n)
int fibonacci1(int n) {
    if (n <= 1) return n;
    return fibonacci1(n - 1) + fibonacci1(n - 2);
}

// 方法2:记忆化搜索
// 时间复杂度:O(n),空间复杂度:O(n)
int fibonacci2(int n, vector<int>& memo) {
    if (n <= 1) return n;
    if (memo[n] != -1) return memo[n];
    memo[n] = fibonacci2(n - 1, memo) + fibonacci2(n - 2, memo);
    return memo[n];
}

// 方法3:动态规划(自底向上)
// 时间复杂度:O(n),空间复杂度:O(n)
int fibonacci3(int n) {
    if (n <= 1) return n;
    vector<int> dp(n + 1);
    dp[0] = 0;
    dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

// 方法4:空间优化版本
// 时间复杂度:O(n),空间复杂度:O(1)
int fibonacci4(int n) {
    if (n <= 1) return n;
    int prev2 = 0, prev1 = 1;
    for (int i = 2; i <= n; i++) {
        int curr = prev1 + prev2;
        prev2 = prev1;
        prev1 = curr;
    }
    return prev1;
}

// 方法5:矩阵快速幂
// 时间复杂度:O(log n),空间复杂度:O(1)
int fibonacci5(int n) {
    if (n <= 1) return n;
    // 使用矩阵 [[1,1],[1,0]]^n 计算
    // 实现略
}

2. 0/1 背包问题(0/1 Knapsack)

问题描述:给定 n 个物品,每个物品有重量和价值,背包容量为 W,求最大价值

// 方法1:二维 DP
// 时间复杂度:O(n*W),空间复杂度:O(n*W)
int knapsack01_2D(vector<int>& weights, vector<int>& values, int W) {
    int n = weights.size();
    vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));
    
    // dp[i][w] 表示前 i 个物品,容量为 w 时的最大价值
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= W; w++) {
            // 不选第 i 个物品
            dp[i][w] = dp[i - 1][w];
            
            // 选第 i 个物品(如果能放下)
            if (w >= weights[i - 1]) {
                dp[i][w] = max(dp[i][w], 
                              dp[i - 1][w - weights[i - 1]] + values[i - 1]);
            }
        }
    }
    
    return dp[n][W];
}

// 方法2:一维 DP(空间优化)
// 时间复杂度:O(n*W),空间复杂度:O(W)
int knapsack01_1D(vector<int>& weights, vector<int>& values, int W) {
    int n = weights.size();
    vector<int> dp(W + 1, 0);
    
    for (int i = 0; i < n; i++) {
        // 必须从后向前遍历,避免重复使用物品
        for (int w = W; w >= weights[i]; w--) {
            dp[w] = max(dp[w], dp[w - weights[i]] + values[i]);
        }
    }
    
    return dp[W];
}

// 回溯找出选择的物品
vector<int> knapsackWithItems(vector<int>& weights, vector<int>& values, int W) {
    int n = weights.size();
    vector<vector<int>> dp(n + 1, vector<int>(W + 1, 0));
    
    // 填充 DP 表
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= W; w++) {
            dp[i][w] = dp[i - 1][w];
            if (w >= weights[i - 1]) {
                dp[i][w] = max(dp[i][w], 
                              dp[i - 1][w - weights[i - 1]] + values[i - 1]);
            }
        }
    }
    
    // 回溯找出选择的物品
    vector<int> selected;
    int w = W;
    for (int i = n; i > 0 && w > 0; i--) {
        if (dp[i][w] != dp[i - 1][w]) {
            selected.push_back(i - 1);
            w -= weights[i - 1];
        }
    }
    
    return selected;
}

// 使用示例
int main() {
    vector<int> weights = {2, 3, 4, 5};
    vector<int> values = {3, 4, 5, 6};
    int W = 8;
    cout << "最大价值: " << knapsack01_1D(weights, values, W) << endl;
}

3. 最长递增子序列(Longest Increasing Subsequence, LIS)

问题描述:找出数组中最长的严格递增子序列的长度

// 方法1:动态规划
// 时间复杂度:O(n²),空间复杂度:O(n)
int lengthOfLIS_DP(vector<int>& nums) {
    if (nums.empty()) return 0;
    
    int n = nums.size();
    vector<int> dp(n, 1);  // dp[i] 表示以 nums[i] 结尾的 LIS 长度
    int maxLen = 1;
    
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                dp[i] = max(dp[i], dp[j] + 1);
            }
        }
        maxLen = max(maxLen, dp[i]);
    }
    
    return maxLen;
}

// 方法2:贪心 + 二分查找(最优解)
// 时间复杂度:O(n log n),空间复杂度:O(n)
int lengthOfLIS_Binary(vector<int>& nums) {
    if (nums.empty()) return 0;
    
    vector<int> tails;  // tails[i] 表示长度为 i+1 的 LIS 的最小尾部元素
    
    for (int num : nums) {
        // 二分查找第一个大于等于 num 的位置
        auto it = lower_bound(tails.begin(), tails.end(), num);
        
        if (it == tails.end()) {
            tails.push_back(num);  // num 比所有元素都大
        } else {
            *it = num;  // 替换为更小的元素
        }
    }
    
    return tails.size();
}

// 打印 LIS(使用 DP 方法)
vector<int> printLIS(vector<int>& nums) {
    if (nums.empty()) return {};
    
    int n = nums.size();
    vector<int> dp(n, 1);
    vector<int> parent(n, -1);
    int maxLen = 1, maxIndex = 0;
    
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j] && dp[j] + 1 > dp[i]) {
                dp[i] = dp[j] + 1;
                parent[i] = j;
            }
        }
        if (dp[i] > maxLen) {
            maxLen = dp[i];
            maxIndex = i;
        }
    }
    
    // 回溯构建 LIS
    vector<int> lis;
    for (int i = maxIndex; i != -1; i = parent[i]) {
        lis.push_back(nums[i]);
    }
    reverse(lis.begin(), lis.end());
    
    return lis;
}

4. 最长公共子序列(Longest Common Subsequence, LCS)

问题描述:找出两个字符串的最长公共子序列长度

// 方法1:二维 DP
// 时间复杂度:O(m*n),空间复杂度:O(m*n)
int longestCommonSubsequence(string text1, string text2) {
    int m = text1.length(), n = text2.length();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    
    // dp[i][j] 表示 text1[0..i-1] 和 text2[0..j-1] 的 LCS 长度
    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] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    
    return dp[m][n];
}

// 方法2:空间优化(一维 DP)
// 时间复杂度:O(m*n),空间复杂度:O(min(m,n))
int longestCommonSubsequence_Optimized(string text1, string text2) {
    if (text1.length() < text2.length()) {
        swap(text1, text2);
    }
    
    int m = text1.length(), n = text2.length();
    vector<int> dp(n + 1, 0);
    
    for (int i = 1; i <= m; i++) {
        int prev = 0;
        for (int j = 1; j <= n; j++) {
            int temp = dp[j];
            if (text1[i - 1] == text2[j - 1]) {
                dp[j] = prev + 1;
            } else {
                dp[j] = max(dp[j], dp[j - 1]);
            }
            prev = temp;
        }
    }
    
    return dp[n];
}

// 打印 LCS
string printLCS(string text1, string text2) {
    int m = text1.length(), n = text2.length();
    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 (text1[i - 1] == text2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1] + 1;
            } else {
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
    }
    
    // 回溯构建 LCS
    string lcs;
    int i = m, j = n;
    while (i > 0 && j > 0) {
        if (text1[i - 1] == text2[j - 1]) {
            lcs = text1[i - 1] + lcs;
            i--;
            j--;
        } else if (dp[i - 1][j] > dp[i][j - 1]) {
            i--;
        } else {
            j--;
        }
    }
    
    return lcs;
}

5. 最长公共子串(Longest Common Substring)

问题描述:找出两个字符串的最长公共连续子串长度

// 方法1:二维 DP
// 时间复杂度:O(m*n),空间复杂度:O(m*n)
int longestCommonSubstring(string text1, string text2) {
    int m = text1.length(), n = text2.length();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    int maxLen = 0;
    
    // dp[i][j] 表示以 text1[i-1] 和 text2[j-1] 结尾的最长公共子串长度
    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;
                maxLen = max(maxLen, dp[i][j]);
            }
        }
    }
    
    return maxLen;
}

// 方法2:空间优化
// 时间复杂度:O(m*n),空间复杂度:O(n)
int longestCommonSubstring_Optimized(string text1, string text2) {
    int m = text1.length(), n = text2.length();
    vector<int> dp(n + 1, 0);
    int maxLen = 0;
    
    for (int i = 1; i <= m; i++) {
        int prev = 0;
        for (int j = 1; j <= n; j++) {
            int temp = dp[j];
            if (text1[i - 1] == text2[j - 1]) {
                dp[j] = prev + 1;
                maxLen = max(maxLen, dp[j]);
            } else {
                dp[j] = 0;
            }
            prev = temp;
        }
    }
    
    return maxLen;
}

// 打印最长公共子串
string printLongestCommonSubstring(string text1, string text2) {
    int m = text1.length(), n = text2.length();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    int maxLen = 0, endIndex = 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;
                if (dp[i][j] > maxLen) {
                    maxLen = dp[i][j];
                    endIndex = i - 1;
                }
            }
        }
    }
    
    return text1.substr(endIndex - maxLen + 1, maxLen);
}

6. 编辑距离(Levenshtein Distance)

问题描述:将字符串 s1 转换为 s2 所需的最少操作次数(插入、删除、替换)

// 时间复杂度:O(m*n),空间复杂度:O(m*n)
int minDistance(string word1, string word2) {
    int m = word1.length(), n = word2.length();
    vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
    
    // 初始化边界
    for (int i = 0; i <= m; i++) dp[i][0] = i;
    for (int j = 0; j <= n; j++) dp[0][j] = j;
    
    // dp[i][j] 表示 word1[0..i-1] 转换为 word2[0..j-1] 的最少操作数
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1[i - 1] == word2[j - 1]) {
                dp[i][j] = dp[i - 1][j - 1];  // 不需要操作
            } else {
                dp[i][j] = min({
                    dp[i - 1][j] + 1,      // 删除
                    dp[i][j - 1] + 1,      // 插入
                    dp[i - 1][j - 1] + 1   // 替换
                });
            }
        }
    }
    
    return dp[m][n];
}

// 空间优化版本
// 时间复杂度:O(m*n),空间复杂度:O(n)
int minDistance_Optimized(string word1, string word2) {
    int m = word1.length(), n = word2.length();
    vector<int> dp(n + 1);
    
    for (int j = 0; j <= n; j++) dp[j] = j;
    
    for (int i = 1; i <= m; i++) {
        int prev = dp

剩余60%内容,订阅专栏后可继续查看/也可单篇购买

C++八股文全集 文章被收录于专栏

本专栏系统梳理C++技术面试核心考点,涵盖语言基础、面向对象、内存管理、STL容器、模板编程及经典算法。从引用指针、虚函数表、智能指针等底层原理,到继承多态、运算符重载等OOP特性从const、static、inline等关键字辨析,到动态规划、KMP算法、并查集等手写实现。每个知识点以面试答题形式呈现,注重原理阐述而非冗长代码,帮助你快速构建完整知识体系,从容应对面试官提问,顺利拿下offer。

全部评论
点赞 回复 分享
发布于 02-03 09:09 上海

相关推荐

评论
1
1
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务