最大子数组和算法全解析:从暴力枚举到动态规划优化

引言

在算法和数据结构的学习中,最大子数组和问题是一个经典且重要的问题。它不仅是面试中的高频题目,更是理解算法优化思想的绝佳案例。本文将从最基础的暴力解法开始,逐步讲解优化思路,最后深入分析最优的动态规划解法(Kadane算法)。

问题定义

给定一个整数数组nums,找出一个连续子数组(至少包含一个元素),使得其元素之和最大,并返回这个最大和。

示例

cpp 复制代码
输入:[-2, 1, -3, 4, -1, 2, 1, -5, 4]
输出:6
解释:连续子数组 [4, -1, 2, 1] 的和最大,为 6

算法一:原始暴力解法(O(n³))

算法思想

最直观的思路是枚举所有可能的子数组,然后计算每个子数组的和,找出最大值。

时间复杂度分析

  • 子数组数量:n + (n-1) + ... + 1 = n(n+1)/2

  • 计算每个子数组和:每次需要O(n)时间

  • 总时间复杂度:O(n³)

代码实现

cpp 复制代码
#include <iostream>
#include <climits>
using namespace std;

int main() {
    int nums[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int n = sizeof(nums) / sizeof(nums[0]);
    
    int maxSum = INT_MIN;  // 初始化为最小整数
    
    // 三层循环枚举所有子数组
    for (int i = 0; i < n; i++) {        // 子数组起始位置
        for (int j = i; j < n; j++) {    // 子数组结束位置
            int sum = 0;
            for (int k = i; k <= j; k++) {  // 计算子数组和
                sum += nums[k];
            }
            if (sum > maxSum) {  // 更新最大值
                maxSum = sum;
            }
        }
    }
    
    cout << "最大子数组和: " << maxSum << endl;  // 输出: 6
    return 0;
}

算法分析

  1. 优点:思路最简单直观,容易理解

  2. 缺点:时间复杂度极高,无法处理大规模数据

  3. 适用场景:仅用于教学理解,实际应用不推荐

算法二:优化暴力解法(O(n²))

优化思路

观察原始暴力解法,发现存在大量重复计算。计算nums[i...j]的和时,其实等于nums[i...j-1]的和加上nums[j]。我们可以利用这个性质,避免重复计算。

时间复杂度分析

  • 外层循环:n次

  • 内层循环:平均n/2次

  • 总时间复杂度:O(n²)

代码实现

cpp 复制代码
#include <iostream>
#include <climits>
using namespace std;

int main() {
    int nums[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int n = sizeof(nums) / sizeof(nums[0]);
    
    int maxSum = INT_MIN;
    
    // 两层循环枚举子数组
    for (int i = 0; i < n; i++) {        // 起始位置i
        int sum = 0;  // 以i为起点的子数组和
        for (int j = i; j < n; j++) {    // 结束位置j
            sum += nums[j];  // 在上一次结果基础上累加
            if (sum > maxSum) {  // 更新最大值
                maxSum = sum;
            }
        }
    }
    
    cout << "最大子数组和: " << maxSum << endl;  // 输出: 6
    return 0;
}

优化效果

  1. 去掉了最内层的循环

  2. 时间复杂度从O(n³)降低到O(n²)

  3. 减少了重复计算

算法三:动态规划(Kadane算法,O(n))

动态规划思想

动态规划是解决最优化问题的强大工具,其核心思想是:

  1. 最优子结构:问题的最优解包含子问题的最优解

  2. 重叠子问题:在求解过程中会重复计算相同的子问题

  3. 状态转移方程:定义状态之间的关系

Kadane算法原理

Kadane算法是动态规划在最大子数组和问题上的最优实现。

核心洞察

任何子数组都有一个结尾位置。如果我们能求出

以每个位置结尾的最大子数组和,那么全局最大值就是这些值中的最大值。

状态定义

定义状态dp[i]:以nums[i]结尾的最大子数组和。

状态转移方程
cpp 复制代码
dp[i] = max(dp[i-1] + nums[i], nums[i])

解释

  • nums[i]接在以nums[i-1]结尾的最大子数组后面

  • 或者从nums[i]开始一个新的子数组

  • 取两者中的较大值

空间优化

由于dp[i]只依赖于dp[i-1],我们可以用O(1)的空间:

cpp 复制代码
cur = max(cur + nums[i], nums[i])
best = max(best, cur)

贪心选择性质

Kadane算法本质上是贪心算法:

  • 如果当前子数组和是正数,就保留它(对后续有正贡献)

  • 如果是负数,就抛弃它,从当前元素重新开始

时间复杂度

  • 只需遍历数组一次

  • 每次操作O(1)时间

  • 总时间复杂度:O(n)

  • 空间复杂度:O(1)

代码实现

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int nums[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int n = sizeof(nums) / sizeof(nums[0]);
    
    int cur = nums[0];   // 以当前元素结尾的最大子数组和
    int best = nums[0];  // 全局最大值
    
    for (int i = 1; i < n; i++) {
        // 状态转移方程
        cur = max(cur + nums[i], nums[i]);
        
        // 更新全局最大值
        best = max(best, cur);
    }
    
    cout << "最大子数组和: " << best << endl;  // 输出: 6
    return 0;
}

算法执行过程详解

以数组[-2, 1, -3, 4, -1, 2, 1, -5, 4]为例:

cpp 复制代码
i=0: cur=-2, best=-2
i=1: cur=max(-2+1,1)=1, best=1
i=2: cur=max(1-3,-3)=-2, best=1
i=3: cur=max(-2+4,4)=4, best=4
i=4: cur=max(4-1,-1)=3, best=4
i=5: cur=max(3+2,2)=5, best=5
i=6: cur=max(5+1,1)=6, best=6
i=7: cur=max(6-5,-5)=1, best=6
i=8: cur=max(1+4,4)=5, best=6
最终结果:6

算法四:扩展 - 记录最大子数组位置

在实际应用中,我们不仅需要知道最大和,还需要知道具体是哪个子数组具有最大和。

算法思路

我们在Kadane算法的基础上进行扩展,记录以下信息:

  1. cur:以当前元素结尾的最大子数组和

  2. best:全局最大子数组和

  3. curStart:当前子数组的起始位置

  4. bestStart:全局最大子数组的起始位置

  5. bestEnd:全局最大子数组的结束位置

代码实现

cpp 复制代码
#include <iostream>
#include <algorithm>
using namespace std;

int main() {
    int nums[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int n = sizeof(nums) / sizeof(nums[0]);
    
    int cur = nums[0], best = nums[0];
    int curStart = 0, bestStart = 0, bestEnd = 0;
    
    for (int i = 1; i < n; i++) {
        if (cur + nums[i] > nums[i]) {
            // 情况1:延续之前的子数组
            cur = cur + nums[i];
            // 注意:这里不更新curStart,因为延续了之前的子数组
        } else {
            // 情况2:从当前元素重新开始
            cur = nums[i];
            curStart = i;  // 更新当前子数组的起始位置
        }
        
        if (cur > best) {
            // 找到了新的最大子数组
            best = cur;
            bestStart = curStart;
            bestEnd = i;
        }
    }
    
    cout << "最大子数组和: " << best << endl;
    cout << "最大子数组起始位置: " << bestStart << endl;
    cout << "最大子数组结束位置: " << bestEnd << endl;
    cout << "最大子数组: [";
    
    for (int i = bestStart; i <= bestEnd; i++) {
        cout << nums[i];
        if (i < bestEnd) cout << ", ";
    }
    cout << "]" << endl;
    
    return 0;
}

执行过程详解

以数组[-2, 1, -3, 4, -1, 2, 1, -5, 4]为例:

  1. i=1 : 比较cur+nums[1] = -2+1 = -1nums[1]=1,选择重新开始,cur=1, curStart=1

  2. i=2 : 比较1-3=-2-3,选择延续,cur=-2

  3. i=3 : 比较-2+4=24,选择重新开始,cur=4, curStart=3

  4. i=4 : 比较4-1=3-1,选择延续,cur=3

  5. i=5 : 比较3+2=52,选择延续,cur=5,更新best=5, bestStart=3, bestEnd=5

  6. i=6 : 比较5+1=61,选择延续,cur=6,更新best=6, bestStart=3, bestEnd=6

  7. i=7 : 比较6-5=1-5,选择延续,cur=1

  8. i=8 : 比较1+4=54,选择延续,cur=5

最终结果:最大和6,子数组[4, -1, 2, 1]

算法对比分析

算法 时间复杂度 空间复杂度 核心思想 适用场景 推荐度
原始暴力 O(n³) O(1) 枚举所有子数组 教学理解 ★☆☆☆☆
优化暴力 O(n²) O(1) 利用累加避免重复计算 小规模数据 ★★☆☆☆
动态规划 O(n) O(1) 状态转移,贪心选择 实际应用 ★★★★★

从暴力到动态规划的思考过程

1. 理解问题本质

首先明确问题的要求:找到连续子数组的最大和。最直接的想法是枚举所有可能性。

2. 优化重复计算

观察暴力解法,发现大量重复计算。计算nums[i...j]的和时,可以利用nums[i...j-1]的结果,从而将时间复杂度从O(n³)降低到O(n²)。

3. 发现最优子结构

这是关键一步。我们意识到:任何子数组都有一个结尾位置。如果我们能求出以每个位置结尾的最大子数组和,那么全局最大值就是这些值的最大值。

4. 定义状态和状态转移

定义状态dp[i]为以nums[i]结尾的最大子数组和。状态转移方程为:

cpp 复制代码
dp[i] = max(dp[i-1] + nums[i], nums[i])

这个方程的含义是:以nums[i]结尾的最大子数组和,要么是把nums[i]接在前面最大子数组的后面,要么是nums[i]自己单独作为一个子数组。

5. 空间优化

由于dp[i]只依赖于dp[i-1],我们不需要存储整个dp数组,只需要一个变量来记录前一个状态即可。

动态规划算法的正确性证明

1. 最优子结构

设数组为A[0..n-1],最大子数组为A[i..j]。那么对于任意的k(i ≤ k ≤ j),A[i..k]的和必定是A[i..j]的子问题的最优解。换句话说,问题的全局最优解包含子问题的最优解。

2. 无后效性

dp[i](以A[i]结尾的最大子数组和)只依赖于dp[i-1]A[i],不依赖于dp[i-2]等更早的状态。这保证了动态规划的正确性。

3. 贪心选择性质

状态转移方程dp[i] = max(dp[i-1] + A[i], A[i])体现了贪心选择:

  • 如果dp[i-1]为正,说明前面的子数组有正贡献,应该保留

  • 如果dp[i-1]为负,说明前面的子数组是负担,应该抛弃

实际应用场景

  1. 股票交易:计算一段时间内股票的最大收益

  2. 信号处理:寻找信号中的最强信号段

  3. 数据分析:找出连续时间段内的最大变化

  4. 图像处理:扩展到二维的最大子矩阵和

特殊情况和边界条件处理

1. 空数组处理

cpp 复制代码
if (n == 0) {
    cout << "数组为空" << endl;
    return 0;
}

2. 全负数数组

Kadane算法能正确处理全负数数组,因为每次都会比较cur + nums[i]nums[i],当cur是负数时,nums[i]可能更大。

3. 整数溢出

如果数组元素值很大,可能需要使用long long类型:

cpp 复制代码
long long cur = nums[0], best = nums[0];

算法性能比较

小规模数据(n=100)

  • 原始暴力解法:约0.001秒

  • 优化暴力解法:约0.0001秒

  • Kadane算法:约0.00001秒

中等规模数据(n=1000)

  • 原始暴力解法:约1秒

  • 优化暴力解法:约0.01秒

  • Kadane算法:约0.0001秒

大规模数据(n=10000)

  • 原始暴力解法:不可行

  • 优化暴力解法:约1秒

  • Kadane算法:约0.001秒

学习收获

通过解决最大子数组和问题,我们可以学到:

  1. 从简单到复杂的思考过程:从暴力解法开始,逐步优化

  2. 识别重复计算:观察并消除重复计算是优化的关键

  3. 动态规划思想:定义状态,找到状态转移方程

  4. 空间优化技巧:利用状态依赖关系减少空间使用

  5. 贪心选择性质:在某些问题中,贪心选择能保证最优解

总结

最大子数组和问题是一个完美的算法教学案例,它展示了从朴素解法到高效算法的优化过程:

  1. 暴力解法帮助我们理解问题本质

  2. 优化暴力解法展示了如何消除重复计算

  3. 动态规划解法体现了最优子结构和状态转移的核心思想

Kadane算法 是这个问题的最优解,它简单、高效、易于实现。记住它的核心思想:如果当前子数组和是正数,就保留它;如果是负数,就抛弃它,从当前元素重新开始

在实际编程中,当你遇到需要寻找连续子数组最大和的问题时,Kadane算法应该是你的首选。它不仅效率高,而且实现简单,是动态规划思想的完美体现。

通过这个问题的学习,我们不仅掌握了一个具体问题的解法,更重要的是学会了如何分析问题、优化算法的思考方法,这将在解决更复杂的问题时发挥重要作用。



扩展练习

  1. 尝试修改算法,使其能够处理环形数组的最大子数组和

  2. 实现二维矩阵的最大子矩阵和算法

  3. 思考如何修改算法,使其能够返回所有具有相同最大和的子数组

  4. 尝试解决最大子数组积问题(需要考虑正负号)

掌握这些扩展问题,将进一步加深你对动态规划思想的理解。

最大子数组和问题的扩展解法

扩展一:环形数组的最大子数组和

问题描述

给定一个环形数组(首尾相连),找出一个连续子数组,使其和最大。

算法思路

环形数组的最大子数组和有两种情况:

  1. 最大子数组不跨越数组边界(即普通的最大子数组)

  2. 最大子数组跨越数组边界(由数组末尾一部分和开头一部分组成)

对于第二种情况,可以转化为:数组总和减去最小子数组和

特殊情况处理

如果数组全为负数,则最大子数组和就是数组中的最大元素(单个元素)

时间复杂度

O(n)

代码实现
cpp 复制代码
#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;

int maxCircularSubarray(int nums[], int n) {
    if (n == 0) return 0;
    
    // 情况1:最大子数组不跨越边界
    int max_no_wrap = INT_MIN;
    int cur_max = nums[0];
    int best_max = nums[0];
    
    for (int i = 1; i < n; i++) {
        cur_max = max(cur_max + nums[i], nums[i]);
        best_max = max(best_max, cur_max);
    }
    max_no_wrap = best_max;
    
    // 情况2:最大子数组跨越边界
    // 先计算数组总和
    int total_sum = 0;
    for (int i = 0; i < n; i++) {
        total_sum += nums[i];
    }
    
    // 计算最小子数组和
    int min_no_wrap = INT_MAX;
    int cur_min = nums[0];
    int best_min = nums[0];
    
    for (int i = 1; i < n; i++) {
        cur_min = min(cur_min + nums[i], nums[i]);
        best_min = min(best_min, cur_min);
    }
    min_no_wrap = best_min;
    
    // 跨越边界的最大子数组和 = 总和 - 最小子数组和
    int max_wrap = total_sum - min_no_wrap;
    
    // 特殊情况:如果数组全为负数
    if (max_no_wrap < 0) {
        return max_no_wrap;  // 返回最大的负数
    }
    
    // 返回两种情况的最大值
    return max(max_no_wrap, max_wrap);
}

int main() {
    // 测试用例1:普通环形数组
    int nums1[] = {5, -3, 5};
    int n1 = sizeof(nums1) / sizeof(nums1[0]);
    cout << "环形数组 [5,-3,5] 的最大子数组和: " 
         << maxCircularSubarray(nums1, n1) << endl;  // 输出: 10
    
    // 测试用例2:全负数数组
    int nums2[] = {-1, -2, -3};
    int n2 = sizeof(nums2) / sizeof(nums2[0]);
    cout << "环形数组 [-1,-2,-3] 的最大子数组和: " 
         << maxCircularSubarray(nums2, n2) << endl;  // 输出: -1
    
    // 测试用例3:包含0的数组
    int nums3[] = {0, 5, -2, 3, -1};
    int n3 = sizeof(nums3) / sizeof(nums3[0]);
    cout << "环形数组 [0,5,-2,3,-1] 的最大子数组和: " 
         << maxCircularSubarray(nums3, n3) << endl;  // 输出: 8
    
    return 0;
}

扩展二:二维矩阵的最大子矩阵和

问题描述

给定一个M×N的整数矩阵,找出一个子矩阵,使其元素之和最大。

算法思路

将二维问题转化为一维问题:

  1. 固定矩阵的上下边界

  2. 将上下边界之间的列求和,得到一个一维数组

  3. 对这个一维数组使用Kadane算法求最大子数组和

  4. 遍历所有可能的上下边界组合

时间复杂度

O(M² × N) 或 O(M × N²),取决于如何固定边界

代码实现
cpp 复制代码
#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;

// 一维数组的Kadane算法
int kadane1D(int arr[], int n) {
    int cur = arr[0], best = arr[0];
    for (int i = 1; i < n; i++) {
        cur = max(cur + arr[i], arr[i]);
        best = max(best, cur);
    }
    return best;
}

// 二维矩阵的最大子矩阵和
int maxSubmatrixSum(int matrix[][4], int rows, int cols) {
    int maxSum = INT_MIN;
    
    // 固定上边界
    for (int top = 0; top < rows; top++) {
        // 临时数组,存储当前上下边界之间每列的和
        int temp[4] = {0};
        
        // 固定下边界
        for (int bottom = top; bottom < rows; bottom++) {
            // 更新temp数组:加上当前行的元素
            for (int j = 0; j < cols; j++) {
                temp[j] += matrix[bottom][j];
            }
            
            // 对temp数组使用Kadane算法
            int currentMax = kadane1D(temp, cols);
            maxSum = max(maxSum, currentMax);
        }
    }
    
    return maxSum;
}

int main() {
    // 测试用例
    int matrix[3][4] = {
        {1, 2, -1, -4},
        {-8, -3, 4, 2},
        {3, 8, 10, 1}
    };
    
    int rows = 3, cols = 4;
    cout << "二维矩阵最大子矩阵和: " 
         << maxSubmatrixSum(matrix, rows, cols) << endl;  // 输出: 29
    
    return 0;
}

扩展三:返回所有具有相同最大和的子数组

问题描述

找出所有和最大的子数组,而不仅仅是其中一个。

算法思路

修改Kadane算法,在找到新的最大值时记录位置,在遇到相同最大值时也记录位置。

时间复杂度

O(n)

代码实现
cpp 复制代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 存储子数组信息的结构体
struct Subarray {
    int start;
    int end;
    int sum;
};

vector<Subarray> findAllMaxSubarrays(int nums[], int n) {
    vector<Subarray> result;
    
    if (n == 0) return result;
    
    int cur = nums[0];
    int curStart = 0;
    int bestSum = nums[0];
    
    // 初始子数组
    result.push_back({0, 0, bestSum});
    
    for (int i = 1; i < n; i++) {
        if (cur + nums[i] > nums[i]) {
            cur = cur + nums[i];
        } else {
            cur = nums[i];
            curStart = i;
        }
        
        if (cur > bestSum) {
            // 找到更大的和,清空之前的结果
            bestSum = cur;
            result.clear();
            result.push_back({curStart, i, bestSum});
        } else if (cur == bestSum) {
            // 找到相同的和,添加到结果中
            result.push_back({curStart, i, bestSum});
        }
    }
    
    return result;
}

int main() {
    int nums[] = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
    int n = sizeof(nums) / sizeof(nums[0]);
    
    vector<Subarray> allMaxSubarrays = findAllMaxSubarrays(nums, n);
    
    cout << "最大和: " << allMaxSubarrays[0].sum << endl;
    cout << "所有最大子数组:" << endl;
    
    for (const auto& sub : allMaxSubarrays) {
        cout << "  [";
        for (int i = sub.start; i <= sub.end; i++) {
            cout << nums[i];
            if (i < sub.end) cout << ", ";
        }
        cout << "]" << endl;
    }
    
    return 0;
}

扩展四:最大子数组积

问题描述

给定一个整数数组,找出一个连续子数组,使其乘积最大。

算法思路

由于负数的存在,需要同时记录最大乘积和最小乘积(因为负数乘以负数得到正数)。

状态定义:

  • maxProd[i]:以nums[i]结尾的最大乘积

  • minProd[i]:以nums[i]结尾的最小乘积

状态转移:

复制代码
maxProd[i] = max(nums[i], maxProd[i-1] * nums[i], minProd[i-1] * nums[i])
minProd[i] = min(nums[i], maxProd[i-1] * nums[i], minProd[i-1] * nums[i])
时间复杂度

O(n)

代码实现
cpp 复制代码
#include <iostream>
#include <algorithm>
#include <climits>
using namespace std;

int maxProductSubarray(int nums[], int n) {
    if (n == 0) return 0;
    
    int maxProd = nums[0];  // 当前最大乘积
    int minProd = nums[0];  // 当前最小乘积
    int result = nums[0];   // 全局最大乘积
    
    for (int i = 1; i < n; i++) {
        // 保存临时值,因为maxProd和minProd会相互影响
        int tempMax = maxProd;
        int tempMin = minProd;
        
        // 状态转移
        maxProd = max({nums[i], tempMax * nums[i], tempMin * nums[i]});
        minProd = min({nums[i], tempMax * nums[i], tempMin * nums[i]});
        
        // 更新全局最大值
        result = max(result, maxProd);
    }
    
    return result;
}

int main() {
    // 测试用例1:包含负数
    int nums1[] = {2, 3, -2, 4};
    int n1 = sizeof(nums1) / sizeof(nums1[0]);
    cout << "数组 [2,3,-2,4] 的最大子数组积: " 
         << maxProductSubarray(nums1, n1) << endl;  // 输出: 6
    
    // 测试用例2:包含多个负数
    int nums2[] = {-2, 0, -1};
    int n2 = sizeof(nums2) / sizeof(nums2[0]);
    cout << "数组 [-2,0,-1] 的最大子数组积: " 
         << maxProductSubarray(nums2, n2) << endl;  // 输出: 0
    
    // 测试用例3:全负数
    int nums3[] = {-2, -3, -1};
    int n3 = sizeof(nums3) / sizeof(nums3[0]);
    cout << "数组 [-2,-3,-1] 的最大子数组积: " 
         << maxProductSubarray(nums3, n3) << endl;  // 输出: 6
    
    return 0;
}

扩展练习算法对比总结

扩展问题 核心算法 时间复杂度 空间复杂度 关键点
环形数组 两次Kadane算法 O(n) O(1) 考虑跨越边界的情况
二维矩阵 Kadane + 压缩 O(M²×N) O(N) 将二维压缩为一维
所有最大子数组 扩展Kadane O(n) O(k) 记录所有最优解
最大子数组积 双状态DP O(n) O(1) 同时记录最大和最小乘积

学习收获

通过这些扩展问题的学习,我们可以深入理解:

  1. 问题转化思想:将复杂问题转化为已知问题

  2. 空间优化技巧:在动态规划中优化空间复杂度

  3. 多状态管理:同时维护多个状态(如最大乘积和最小乘积)

  4. 边界情况处理:考虑各种特殊输入

  5. 算法扩展性:如何扩展基础算法解决更复杂问题

实际应用

  1. 环形数组:循环时间序列分析

  2. 二维矩阵:图像处理、数据分析

  3. 所有最大子数组:找出所有最优解

  4. 最大子数组积:信号处理、金融分析

进一步思考

  1. 如何优化二维矩阵算法的时间复杂度?

  2. 如何处理浮点数数组的最大子数组积?

  3. 如何找到乘积最大的子数组(而不仅仅是乘积)?

  4. 如何扩展到更高维度的数组?

这些扩展问题展示了动态规划思想的强大和灵活,是算法学习的重要进阶内容。

相关推荐
没头脑的男大2 小时前
关于删除列表的那些事儿
算法
Book思议-2 小时前
【数据结构实战】线性表的应用
c语言·数据结构·算法·链表
qq_461489332 小时前
C++与Qt图形开发
开发语言·c++·算法
richu2 小时前
结合数学思维来深入内存理解哈希散列的实现原理和处理冲突的逻辑
数据结构·哈希冲突
Yzzz-F2 小时前
Problem - 2194E - Codeforces
算法
像污秽一样2 小时前
算法设计与分析-习题12.2
算法·迭代改进·分支界限
x_xbx2 小时前
LeetCode:83. 删除排序链表中的重复元素
算法·leetcode·链表
_小草鱼_3 小时前
【搜索与图论】DFS算法(深度优先搜索)
算法·深度优先·图论·回溯·递归
I_LPL3 小时前
hot100 栈专题
算法·