day31 | 貪心01

0.引言

● 理論基礎
● 455.分發(fā)餅干
● 376. 擺動序列
● 53. 最大子序和

0.理論基礎

貪心算法一般分為如下四步:

  • 將問題分解為若干個子問題
  • 找出適合的貪心策略
  • 求解每一個子問題的最優(yōu)解
  • 將局部最優(yōu)解堆疊成全局最優(yōu)解

這個四步其實過于理論化了,我們平時在做貪心類的題目 很難去按照這四步去思考,真是有點“雞肋”。

做題的時候,只要想清楚 局部最優(yōu) 是什么,如果推導出全局最優(yōu),其實就夠了。

455.# 分發(fā)餅干

Category Difficulty Likes Dislikes
algorithms Easy (56.72%) 685 -

假設你是一位很棒的家長,想要給你的孩子們一些小餅干。但是,每個孩子最多只能給一塊餅干。

對每個孩子 i,都有一個胃口值 g[i]這是能讓孩子們滿足胃口的餅干的最小尺寸;并且每塊餅干 j,都有一個尺寸 s[j]。如果 s[j] >= g[i],我們可以將這個餅干 j 分配給孩子 i ,這個孩子會得到滿足。你的目標是盡可能滿足越多數(shù)量的孩子,并輸出這個最大數(shù)值。

示例 1:

輸入: g = [1,2,3], s = [1,1]
輸出: 1
解釋: 
你有三個孩子和兩塊小餅干,3個孩子的胃口值分別是:1,2,3。
雖然你有兩塊小餅干,由于他們的尺寸都是1,你只能讓胃口值是1的孩子滿足。
所以你應該輸出1。

示例 2:

輸入: g = [1,2], s = [1,2,3]
輸出: 2
解釋: 
你有兩個孩子和三塊小餅干,2個孩子的胃口值分別是1,2。
你擁有的餅干數(shù)量和尺寸都足以讓所有孩子滿足。
所以你應該輸出2.

提示:

  • 1 <= g.length <= 3 * 10<sup>4</sup>
  • 0 <= s.length <= 3 * 10<sup>4</sup>
  • 1 <= g[i], s[j] <= 2<sup>31</sup> - 1

Discussion | Solution

貪心思想

大尺寸的餅干既可以滿足胃口大的孩子也可以滿足胃口小的孩子,那么就應該優(yōu)先滿足胃口大的。

這里的局部最優(yōu)就是大餅干喂給胃口大的,充分利用餅干尺寸喂飽一個,全局最優(yōu)就是喂飽盡可能多的小孩。

image.png

先排序,再分發(fā)。

/*
 * @lc app=leetcode.cn id=455 lang=cpp
 *
 * [455] 分發(fā)餅干
 */

// @lc code=start
class Solution {
 public:
  int findContentChildren(vector<int>& g, vector<int>& s) {
    sort(g.begin(), g.end());
    sort(s.begin(), s.end());

    int child = 0;  // child s下標
    int candy = 0;  // candy g下標
    // 小餅干先喂飽小胃口
    while (child < g.size() && candy < s.size()) {
      if (s[candy] >= g[child]) {  // 滿足當前孩子
        child++;
      }
      candy++;
    }

    return child;  // 孩子滿足的個數(shù)
  }
};
// @lc code=end

376. # 擺動序列

Category Difficulty Likes Dislikes
algorithms Medium (47.17%) 912 -

如果連續(xù)數(shù)字之間的差嚴格地在正數(shù)和負數(shù)之間交替,則數(shù)字序列稱為** 擺動序列 。**第一個差(如果存在的話)可能是正數(shù)或負數(shù)。僅有一個元素或者含兩個不等元素的序列也視作擺動序列。

  • 例如, [1, 7, 4, 9, 2, 5] 是一個 擺動序列 ,因為差值 (6, -3, 5, -7, 3) 是正負交替出現(xiàn)的。

  • 相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5] 不是擺動序列,第一個序列是因為它的前兩個差值都是正數(shù),第二個序列是因為它的最后一個差值為零。

子序列 可以通過從原始序列中刪除一些(也可以不刪除)元素來獲得,剩下的元素保持其原始順序。

給你一個整數(shù)數(shù)組 nums ,返回 nums 中作為 **擺動序列 **的 最長子序列的長度 。

示例 1:

輸入:nums = [1,7,4,9,2,5]
輸出:6
解釋:整個序列均為擺動序列,各元素之間的差值為 (6, -3, 5, -7, 3) 。

示例 2:

輸入:nums = [1,17,5,10,13,15,10,5,16,8]
輸出:7
解釋:這個序列包含幾個長度為 7 擺動序列。
其中一個是 [1, 17, 10, 13, 10, 16, 8] ,各元素之間的差值為 (16, -7, 3, -3, 6, -8) 。

示例 3:

輸入:nums = [1,2,3,4,5,6,7,8,9]
輸出:2

解題思路:

  • 局部最優(yōu):刪除單調(diào)坡度上的節(jié)點(不包括單調(diào)坡度兩端的節(jié)點),那么這個坡度就可以有兩個局部峰值。

  • 整體最優(yōu):整個序列有最多的局部峰值,從而達到最長擺動序列。
    -局部最優(yōu)推出全局最優(yōu),并舉不出反例,那么試試貪心!

image.png

實際操作上,其實連刪除的操作都不用做,因為題目要求的是最長擺動子序列的長度,所以只需要統(tǒng)計數(shù)組的峰值數(shù)量就可以了(相當于是刪除單一坡度上的節(jié)點,然后統(tǒng)計長度)

這就是貪心所貪的地方,讓峰值盡可能的保持峰值,然后刪除單一坡度上的節(jié)點

在計算是否有峰值的時候,大家知道遍歷的下標i ,計算prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i]),如果prediff < 0 && curdiff > 0 或者 prediff > 0 && curdiff < 0 此時就有波動就需要統(tǒng)計。

這是我們思考本題的一個大題思路,但本題要考慮三種情況:

    1. 情況一:上下坡中有平坡
    1. 情況二:數(shù)組首尾兩端
    1. 情況三:單調(diào)坡中有平坡
/*
 * @lc app=leetcode.cn id=376 lang=cpp
 *
 * [376] 擺動序列
 */

// @lc code=start
class Solution {
 public:
  int wiggleMaxLength(vector<int>& nums) {
    if (nums.size() <= 1) return nums.size();
    int cur_diff = 0;  // 當前一對差值
    int pre_diff = 0;  // 前一對差值
    int result = 1;  // 記錄峰值個數(shù),序列默認序列最右邊有一個峰值
    for (int i = 0; i < nums.size() - 1; i++) {
      cur_diff = nums[i + 1] - nums[i];
      // 出現(xiàn)峰值
      if ((pre_diff <= 0 && cur_diff > 0) || (pre_diff >= 0 && cur_diff < 0)) {
        result++;
        pre_diff = cur_diff;  // 注意這里,只在擺動變化的時候更新prediff
      }
    }
    return result;
  }
};

// @lc code=end

其他參考:

在這個問題中,up 和 down 分別表示以當前元素為結(jié)尾的最長上升子序列和最長下降子序列的長度。這里的上升和下降子序列指的是擺動序列的一部分,即一個子序列中相鄰元素之間的差值嚴格正負交替。

  • up:以當前元素結(jié)尾的最長上升子序列的長度。這意味著在這個子序列中,當前元素的值大于前一個元素,同時滿足擺動序列的性質(zhì)(差值嚴格正負交替)。
  • down:以當前元素結(jié)尾的最長下降子序列的長度。這意味著在這個子序列中,當前元素的值小于前一個元素,同時滿足擺動序列的性質(zhì)(差值嚴格正負交替)。

在遍歷數(shù)組的過程中,我們比較相鄰的元素,根據(jù)它們的大小關系更新 up 和 down 的值。當當前元素大于前一個元素時,我們更新 up 的值為 down + 1,表示當前元素可以接在一個下降子序列的后面,形成一個更長的上升子序列。反之,如果當前元素小于前一個元素,我們更新 down 的值為 up + 1,表示當前元素可以接在一個上升子序列的后面,形成一個更長的下降子序列。如果當前元素等于前一個元素,我們不需要更新 up 和 down,因為它們不會對擺動序列產(chǎn)生影響。

/*
 * @lc app=leetcode.cn id=376 lang=cpp
 *
 * [376] 擺動序列
 */

// @lc code=start
class Solution {
 public:
  int wiggleMaxLength(vector<int>& nums) {
    int n = nums.size();
    if (n < 2) return n;  // 特判
    // 有上坡才有下坡
    int up = 1, down = 1;  // 初始值都為1
    for (int i = 1; i < n; i++) {
      if (nums[i] > nums[i - 1]) {
        up = down + 1;
      } else if (nums[i] < nums[i - 1]) {
        down = up + 1;
      }
    }
    return max(up, down);  // 返回較大值
  }
};

// @lc code=end

53. # 最大子數(shù)組和

Category Difficulty Likes Dislikes
algorithms Medium (54.85%) 5970 -

給你一個整數(shù)數(shù)組 nums ,請你找出一個具有最大和的連續(xù)子數(shù)組(子數(shù)組最少包含一個元素),返回其最大和。

**子數(shù)組 **是數(shù)組中的一個連續(xù)部分。

示例 1:

輸入:nums = [-2,1,-3,4,-1,2,1,-5,4]
輸出:6
解釋:連續(xù)子數(shù)組 [4,-1,2,1] 的和最大,為 6 。

示例 2:

輸入:nums = [1]
輸出:1

示例 3:

輸入:nums = [5,4,-1,7,8]
輸出:23

提示:

  • 1 <= nums.length <= 10<sup>5</sup>
  • -10<sup>4</sup> <= nums[i] <= 10<sup>4</sup>

進階:如果你已經(jīng)實現(xiàn)復雜度為 O(n) 的解法,嘗試使用更為精妙的 分治法 求解。


Discussion | Solution

貪心

  • 如果 -2 1 在一起,計算起點的時候,一定是從1開始計算,因為負數(shù)只會拉低總和,這就是貪心貪的地方!

  • 局部最優(yōu):當前“連續(xù)和”為負數(shù)的時候立刻放棄,從下一個元素重新計算“連續(xù)和”,因為負數(shù)加上下一個元素 “連續(xù)和”只會越來越小。

  • 全局最優(yōu):選取最大“連續(xù)和”

/*
 * @lc app=leetcode.cn id=53 lang=cpp
 *
 * [53] 最大子數(shù)組和
 */

// @lc code=start
class Solution {
 public:
  int maxSubArray(vector<int>& nums) {
    int max_sum = nums[0], cur_sum = nums[0];
    for (int i = 1; i < nums.size(); ++i) {
      // 如果 -2 1 在一起,計算起點的時候,一定是從1開始計算,
      // 因為負數(shù)只會拉低總和,這就是貪心貪的地方!
      cur_sum = max(cur_sum + nums[i], nums[i]);
      max_sum = max(max_sum, cur_sum);
    }
    return max_sum;
  }
};
// @lc code=end
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容