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

 

猜你喜欢

转载自blog.csdn.net/qq_36347365/article/details/83104108