1. 程式人生 > 實用技巧 >[LeetCode-135]分發糖果

[LeetCode-135]分發糖果

老師想給孩子們分發糖果,有 N個孩子站成了一條直線,老師會根據每個孩子的表現,預先給他們評分。

你需要按照以下要求,幫助老師給這些孩子分發糖果:

每個孩子至少分配到 1 個糖果。
相鄰的孩子中,評分高的孩子必須獲得更多的糖果。
那麼這樣下來,老師至少需要準備多少顆糖果呢?

示例 1:

輸入: [1,0,2]
輸出: 5
解釋: 你可以分別給這三個孩子分發 2、1、2 顆糖果。

示例 2:

輸入: [1,2,2]
輸出: 4
解釋: 你可以分別給這三個孩子分發 1、2、1 顆糖果。
第三個孩子只得到 1 顆糖果,這已滿足上述兩個條件。

這道題做的時間比較長,主要原因沒有進行問題分解

解體思路是採用貪心演算法,也就是求解問題不在整體上對問題進行考慮,而是做出多次區域性最優解,最後將這些區域性最優解組合得到最終解

假如學生A和學生B相鄰,A在B的左邊,我們可以將問題問題分解為兩部分

  • ratingB > ratingA,此時A的糖果要比B的少
  • ratingB < ratingA,此時A的糖果要比B的多

我們不妨將這兩種情況作為分發糖果的兩個子問題,並且這兩個子問題是互斥的

左規則:對於ratingA > ratingB,我們可以將rating從左到右遍歷,如果rating[i] > rating[i-1],那麼rating[i] = rating[i-1] + 1,否則rating[i] = 1

不需要考慮左規則的學生,我們給他們分配最少1個糖果就可以了

右規則:對於ratingA < ratingB,我們可以將rating從右到左遍歷,如果rating[i] > rating[i+1],那麼rating[i] = rating[i+1] + 1,否則rating[i] = 1

同時滿足左規則和右規則時,我們可以相信糖果分配是合理的
但是我們還需要考慮糖果分配數量最優解的問題,也就是用最少的糖果滿足題目條件
所以我們不能簡單的將左規則和右規則分配的糖果相加得到最終解

我們可以這樣思考最優解的問題:

  • 最優解的糖果肯定是不能比左規則、右規則少的,否則就會破壞這兩個條件其中一個
  • 左規則和右規則之間有什麼聯絡?

我們前面提到過不可能出現兩個學生同時滿足左規則和右規則的情況
所以說如果A的糖果比B的多,此時左規則分配的糖果為x1和y1,有x1<y1
同時A不需要滿足右規則,也就是右規則分配的糖果x2=1,至於y2取任意之即可
那麼我們就可以從這個關係切入,找出問題最優解
我們取x = max(x1, x2=1),y = max(y1, y2)
x1=x<y,滿足左規則,同時A和B又不需要考慮右規則
而且“最優解的糖果肯定是不能比左規則、右規則少的”,所以x、y就是問題的最優解

再來看時間複雜度,我們的左規則、右規則遍歷了兩邊輸入陣列,最後將左規則和右規則結果遍歷得到最終結果,所以時間複雜度O(n)
空間複雜度的話,實現左規則和右規則需要建立兩個陣列,所以空間複雜度為O(n)

下面是程式碼

/*
 * @lc app=leetcode.cn id=135 lang=java
 *
 * [135] 分發糖果
 */

// @lc code=start
class Solution {
    public int candy(int[] ratings) {
        int[] leftCount = new int[ratings.length];
        int[] rightCount = new int[ratings.length];
        int len = ratings.length;

        //左規則
        for(int i=0;i<len;i++){
            if(i>0&&ratings[i]>ratings[i-1]){
                leftCount[i] = leftCount[i-1]+1;
            }else{
                leftCount[i] = 1;
            }
        }
        
        //右規則
        for(int i=len-1;i>=0;i--){
            if(i<len-1&&ratings[i]>ratings[i+1]){
                rightCount[i] = rightCount[i+1]+1;
            }else{
                rightCount[i] = 1;
            }
        }

        //取最大值得到最終解
        int ret = 0;
        for(int i=0;i<len;i++){
            ret += Math.max(leftCount[i], rightCount[i]);
        }
        return ret;
    }
}
// @lc code=end