开始学习贪心算法

455. 分发饼干 - 力扣(LeetCode)

cpp 复制代码
class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int ans = 0, index = s.size()-1;
        for(int i = g.size() -1; i >= 0; i--){
            if(index >= 0 && s[index] >= g[i]){
                ans++;
                index--;
            }
        }
        return ans;
    }
};

376. 摆动序列 - 力扣(LeetCode)

cpp 复制代码
class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int ans = 1;
        int pre = 0;// 维护差值
        for(int i = 1; i < nums.size(); i++){
            int dif = nums[i] - nums[i-1];
            if(dif > 0 && pre <= 0){
                ans++;
                pre = 1;
            } else if(dif < 0 && pre >= 0){
                ans++;
                pre= -1;
            }
        }
        return ans;
    }
};

53. 最大子数组和 - 力扣(LeetCode)

cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0, mn = 0, ans = INT_MIN;
        for(auto x : nums){
            pre += x; // 计算每个位置的前缀和
            ans = max(ans, pre - mn);
            mn = min(mn, pre);
        }
        return ans;
    }
};

好的,我们把"前缀和解法"的这几行代码逐句吃透,讲清楚每个变量代表什么、不变量是什么、更新顺序为何这样,再走一个完整例子。

复制代码
long long pref = 0, mn = 0, ans = LLONG_MIN;
for (int x : nums) {
    pref += x;                // 1) 累加到当前位置的前缀和 S[j]
    ans = max(ans, pref - mn);// 2) 用 S[j] 减"历史最小前缀"得到当前最优子数组和
    mn  = min(mn, pref);      // 3) 更新历史最小前缀,为下一步做准备
}
return (int)ans;

1) 三个变量各自的含义

  • pref:到当前下标 j前缀和 S[j]S[j]。

  • mn:从开头到"上一位置"为止的历史最小前缀和 min⁡i≤j−1S[i]\min_{i\le j-1} S[i]。初始化为 0,对应"空前缀"(让子数组可以从 0 开始)。

  • ans:目前为止见到的最大子数组和

关键恒等式:任意区间 (i,j](i, j] 的和 = S[j]−S[i]S[j] - S[i]。

所以固定 j 时,要让区间和最大,就让减去的 S[i]S[i] 尽量小 ⇒ 用"到目前为止最小的前缀和"。

2) 为何先算 ans 再更新 mn

顺序必须是:

  1. 先用旧的 mn(只含直到 j-1 的前缀)计算 pref - mn;这对应某个非空区间 (i,j](i, j]。

  2. 再把当前 pref 纳入 mn 的候选,供下一个 j+1 使用。

若反过来先更新 mn=min(mn,pref),那 mn 可能等于 prefpref - mn 变 0,会把"空区间"当成候选,破坏正确性。

3) 为什么这样就对?(不变量 + 上界可达)

对每个位置 j,这一轮循环后满足:

  • pref 恰是 S[j]S[j];

  • mn 成为 min⁡i≤jS[i]\min_{i\le j} S[i](已纳入当前前缀);

  • ans 等于 max⁡t≤j max⁡i<=t−1(S[t]−min⁡k≤t−1S[k])\max_{t\le j}\ \max_{i< = t-1}(S[t]-\min_{k\le t-1}S[k]),也就是前 j 个位置里"每个 t 的最佳子数组"的最大值。递推到最后就是全局最优。

全负数组 也没问题:

第一个数 x<0 时,pref=x, ans=max(-inf, x-0)=x;随后 mn=min(0,x)=x,后续不会把空区间当解,最终会选到"绝对值最小的那个负数"。

4) 一个完整示例走表(LeetCode 经典)

nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4],最优是 [4,-1,2,1],和为 6。

j x pref(=S[j]) mn(用前) pref-mn ans(更后) mn(更新后)
0 -2 -2 0 -2 -2 -2
1 1 -1 -2 1 1 -2
2 -3 -4 -2 -2 1 -4
3 4 0 -4 4 4 -4
4 -1 -1 -4 3 4 -4
5 2 1 -4 5 5 -4
6 1 2 -4 6 6 -4
7 -5 -3 -4 1 6 -4
8 4 1 -4 5 6 -4
  • 第 6 步得到 ans=6,对应 S[6]-mn = 2 - (-4),也就是区间 (idx=2, 6] = [4,-1,2,1]

5) 和 Kadane 的关系

Kadane 通过"以 i 结尾 的最优"和"全局最优"递推;

前缀和法通过"到 j 的前缀 "与"历史最小前缀"做差。本质相同:

max⁡i<j(S[j]−S[i])≡max⁡_j{S[j]−min⁡i<jS[i]}.\max_{i<j}(S[j]-S[i]) \equiv \max\j\{S[j]-\min{i<j}S[i]\}.

6) 复杂度 & 小坑

  • 时间 O(n),空间 O(1)

  • long long 防止相加溢出;返回时转回 int(题目范围安全)。

  • 这题是连续子数组(subarray),不是子序列(subsequence)。

如果你想把"具体子数组的左右下标"也返回,我可以在这版里再加上"记录达到 mn 的位置"和"刷新 ans 时的右端点",一眼就能定位区间。