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?
首先关于题意,higher rating get more candies, 但是如果是equal呢,可以比它的邻居多,也可以比它的邻居少。 也就是说,假设 rating=[2,4,4,4,2],那么结果可以是[1,2,1,2,1].
分析一下知道,需要对于连续上升和下降趋势计数。对于平行的趋势,中间的都可以设为1。初始状态可以看作平行趋势。对于平行趋势到上升(或下降)趋势时的变化,其实就和初始一样。
平行状态:每个children可以只给一颗糖。比如rating=[4,4,2],那么结果就是[1,1,2];
下降状态:下降状态需要设置上升趋势的初始计数为2(这是因为下降实际是以1,2,...,n再翻过来去计数的,翻过来之后最后一个是1,所以上升的初始应该是2),这样才能正确地从下降到上升的转换。比如rating=[3,1,4],计数是[1,2,2],最终结果是[2,1,2];
上升状态:上升状态需要设置下降趋势的初始计数为1。为了确保下降的计数翻转过来之后,下降的第一个点会比上升的最后一个点的糖数少。我们必须在计算下降趋势的时候,如果大于或等于之前上升趋势的最大趋势,我们就要在最终的计数加1。比如rating=[1,3,6,5,4,3,2,1],计数是[1,2,3,1,2,3,4,5],下降趋势部分翻转过来就是[1,2,3,5,4,3,2,1],当下降计数>=3时就可累加1,这一部分1可以看作是累加到上升趋势的最后一个数上,也就是[1,2,6,5,4,3,2,1]。
初始状态:对于N>1,第一个可以直接先计着,从2开始,将第i元素与第i-1元素比较判断处于哪个状态。初始状态和平行状态一样。根据上面的分析,上升趋势初始值为2,下升趋势初始值为1. 前一个上升趋势的最大值为1.
1 class Solution { 2 public: 3 int candy(vector<int> &ratings) { 4 int n = ratings.size(); 5 if (n <= 1) { 6 return n; 7 } 8 9 int sum = 1, increasing = 2, decreasing = 1, maxIncreasing = 1; 10 11 for (int i = 1; i < n; ++i) { 12 if (ratings[i] > ratings[i - 1]) { 13 maxIncreasing = increasing; 14 sum += increasing++; 15 decreasing = 1; 16 } else if (ratings[i] == ratings[i - 1]) { 17 increasing = 2; 18 decreasing = 1; 19 maxIncreasing = 1; 20 sum++; 21 } else { 22 sum += decreasing++; 23 if (decreasing > maxIncreasing) { 24 sum++; 25 } 26 increasing = 2; 27 } 28 } 29 30 return sum; 31 } 32 };
时间复杂度O(n),空间复杂度O(1)。
第二次刷,照着思路写,卡在第16行了。应该只要是上升趋势就要更新pre. pre就是上升趋势的最大值。pre的初始值就应该很大。 120ms
1 class Solution { 2 public: 3 int candy(vector<int> &ratings) { 4 int n = ratings.size(); 5 if (n <= 1) return n; 6 7 int sum = 1, pre = ratings.size() + 1, candy = 1, state = -1; 8 9 for (int i = 1; i < ratings.size(); ++i) { 10 if (ratings[i - 1] < ratings[i]) { 11 if (state == -1 || state == 1) { 12 candy++; 13 } else { 14 candy = 2; 15 } 16 pre = candy; 17 state = 1; 18 } else if (ratings[i - 1] > ratings[i]) { 19 if (state == -1 || state == 0) { 20 candy++; 21 if (candy >= pre) sum++; 22 } else { 23 candy = 1; 24 } 25 state = 0; 26 } else { 27 candy = 1; 28 state = -1; 29 pre = ratings.size() + 1; 30 } 31 sum += candy; 32 } 33 34 return sum; 35 } 36 };
Method II
网上有另一种解决思路,需要扫两遍,一遍是从左到右扫,一遍是从右到左扫,这样得到两个最小糖数组,最后对这两个数据每个位置求一下最大值就可以了。这样的时间复杂度仍是O(n),空间复杂度也是O(n)。思想和Trapping Rain Water类似。
和z神讨论了一下,z神给出了一个不需要O(n)空间的做法,想想也是。
有两个指针,左指针指向的是当前位置左边连续递减的边界位置;右指针指向的是当前位置右边连续递减的边界位置;
每个位置的糖数由max(cur - left, right - cur) + 1决定;
但是要额外注意一下相等的情况。如果ratings[i] == ratings[i-1]的话,那么left=cur; 如果ratings[i]==ratings[i+1]的话,那么right=cur;
左指针的更新在O(n)可以做到,右指针的更新最怀情况是O(2n)。
对于右边的递减序列,右指针的更新只在right<=cur的时候才有必要更新。如果right>cur,证明当前还在递减序列当中,所以right不变。(Line14)
1 class Solution { 2 public: 3 int candy(vector<int> &ratings) { 4 int n = ratings.size(); 5 if (n <= 1) return n; 6 ratings.push_back(ratings.back() + 1); 7 int sum = 0, l = 0, r = 0, candy1 = 0; 8 for (int i = 0; i < n; ++i) { 9 if (i == 0 || ratings[i] <= ratings[i - 1]) { 10 l = i; 11 } 12 if (ratings[i] <= ratings[i + 1]) { 13 r = i; 14 } else if (r <= i) { 15 r = i; 16 for (int j = i + 1; ratings[j] < ratings[j - 1]; ++j) r++; 17 } 18 sum += max(r - i, i - l) + 1; 19 } 20 ratings.pop_back(); 21 return sum; 22 } 23 };
136ms
如果没有Line 14,就是1780ms。
第三次刷,最容易想出来的还是正反向扫一遍的做法。算是一次过吧。--i老是容易写成++i。
1 class Solution { 2 public: 3 int candy(vector<int> &ratings) { 4 if (ratings.empty()) return 0; 5 int n = ratings.size(); 6 vector<int> dp(n, 0); 7 for (int i = 1; i < n; ++i) { 8 if (ratings[i] > ratings[i - 1]) { 9 dp[i] = dp[i - 1] + 1; 10 } 11 } 12 13 int min = 0, right = 0; 14 for (int i = n - 1; i >= 0; --i) { 15 if (i != n - 1 && ratings[i] > ratings[i + 1]) { 16 right++; 17 } else { 18 right = 0; 19 } 20 min += max(dp[i], right) + 1; 21 } 22 return min; 23 } 24 };