1. 程式人生 > >Leecode:135. Candy(week7—-hard)

Leecode:135. Candy(week7—-hard)

題目

示例

分析

題解

其他演算法

改進

空間複雜度為O(1),時間複雜度O(n)的演算法

小結

參考


題目

There are N children standing in a line. Each child is assigned a rating value.

You are giving candies to these children subjected to the following requirements:

  • Each child must have at least one candy.
  • Children with a higher rating get more candies than their neighbors.

What is the minimum candies you must give?

示例

分析

  1. 對每一個小孩子的要求是:
    1. 每一個小孩子至少要有一個糖果
    2. 等級比相鄰的孩子(左右兩邊)高的要求獲得更多的糖果
  2. 可以知道,這個是一個相鄰關聯的題目,初步思考會認為首先需要找到圖中的最小值,採用遞迴找區域性最小值的方法來計算每一個孩子的糖果,但是這個可程式設計性並不好。
  3. 根據等級最高,可以發現每一個點的值由其與左右兩端的大小關係而決定。也就是說
    1. rating[n] > rating[n - 1],則arr[n] = arr[n - 1] + 1;
    2. rating[n] > rating[n + 1], 則arr[n] = arr[n + 1] + 1;
  4. 上面的公式很清楚是正確的,但是實現起來似乎會比較難,因為第2條涉及倒敘,那麼不妨就從尾到頭遍歷一遍?用兩個陣列(數值都初始化為1)的陣列分別進行上面不同兩個方向的遍歷。然後最後的結果是將這兩個陣列相同的位置上最大的數值相加。原因:
    1. 因為從頭到尾以及從尾到頭的兩趟遍歷修改的數值基本都是不同的,因為一個相當於正序的遞增序列,一個相當於正序的遞減序列,相互交叉的部分只是轉折點,而轉折點的數值只由左右兩側的最大值所決定,所以綜合以上的說法可以知道,每個小孩子的糖果數目為max(arr[n], arr2[n])

題解

程式碼如下:36ms

class Solution {
public:
    int candy(vector<int>& ratings) {
        int size_ = ratings.size();
        vector<int> arr = vector<int>();
        vector<int> arr2 = vector<int>();
        for(int i = 0; i < size_; i++){
            arr.push_back(1);
            arr2.push_back(1);
        }
        for(int i = 1; i < size_; i++){
            if(ratings[i] > ratings[i-1]){
                arr[i]  = arr[i - 1] +1;
            }
        }
        for(int i = size_ - 2; i >= 0; i--){
            if(ratings[i] > ratings[i+1]){
                arr2[i] = arr2[i + 1] + 1;
            }
        }
        int result = 0;
        for(int i = 0; i < size_; i++){
            result += max(arr[i], arr2[i]);
        }

        return result;
    }
};

其他演算法

改進

  • 只採用一個數組:
    • 上面的演算法使用的方法是使用兩個陣列,但是在分析的過程中我們不難看見因為兩趟遍歷的方向是不同的,但是依據的規則是一樣的,因此修改的點基本不交差,只有在轉折點才交差,而轉折點的數值是由左右的兩邊的最大值所決定的,主要的擔心前後兩趟遍歷的結果不一樣,所以根據上面所以的內容,可以總結出
    • 從尾到頭的遍歷時:rating[n] > rating[n+1] , 則arr[n] = max(arr[n], arr[n+1] + 1)
    • 修改後的程式碼如下:20ms
class Solution {
public:
    int candy(vector<int>& ratings) {
        int result = 0;
        int size_ = ratings.size();
        vector<int> arr = vector<int>();
        arr.push_back(1);
        for(int i = 1; i < size_; i++){
            arr.push_back(1);
            if(ratings[i] > ratings[i-1]){
                arr[i]  = arr[i - 1] +1;
            }
        }
        for(int i = size_ - 2; i >= 0; i--){
            if(ratings[i] > ratings[i+1]){
                arr[i] = max(arr[i], arr[i + 1] + 1);
            }
            result += arr[i + 1];
        }
        result += arr[0];
        return result;
    }
};

空間複雜度為O(1),時間複雜度O(n)的演算法

  • 因為當為遞增序列的時候,則糖果的數量是1、2、3...到n的,而為遞減序列時、則糖果的數量是從m、m-1...到1的,所以只需要找到每一個遞增或遞減區間的起始位置,然後利用公式n(n+1)/2就可以計算出這個區間中糖果數目,然後在轉折點的位置單獨進行更新就可以了。
  • 所以這個演算法中需要使用的變數為
    • old_slop 用於記錄讀取當前節點之前的狀態,也就是相等或者上升或者下降
    • new_slop則是用於記錄讀取當前節點的當前狀態
    • up用於記錄上升區間的長度大小
    • down用於記錄下降區間的長度大小
  • 程式碼如下:16ms
class Solution {
public:
    int cal(int size){
        return size*(size + 1)/2;
    }

    int candy(vector<int>& ratings) {
        int result = 0;
        int up = 0;
        int down = 0;
        int new_slop = 0;
        int old_slop = 0;
        for (int i = 1; i < ratings.size(); ++i)
        {
            new_slop = (ratings[i] > ratings[i - 1]) ? 1 : (ratings[i] < ratings[i - 1] ? -1 : 0);
            if((new_slop == 0 && old_slop > 0) || (new_slop >= 0 && old_slop < 0)){
                result += (cal(up) + cal(down) + max(up, down));
                up = 0;
                down = 0;
            }
            if(new_slop > 0){
                up++;
            }
            else if(new_slop < 0){
                down++;
            }
            else{
                result++;
            }
            old_slop = new_slop;
        }
        result += (cal(up) + cal(down) + max(up, down) + 1);
        return result;
    }
};

小結

這一道一開始看感覺很簡單,但是越考慮就會覺得越複雜,所以要把公式列出來之後在紙上進行模擬運算也就可以得出比較好的解決方式

參考

Candy_solution