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?
我一开始的思路是:
首先,将ratings数组按rating的值进行排序分层,做成一个multimap,每个rating作为key,value是对应的所有数组下标,用一个list封装。
之后,再根据rating的值从低到高给各个孩子分配最少的糖果数,初始值为1,并且保证如果该孩子的rating比其邻居的高,则必须比邻居的糖果数要多1。
因为要实现对rating的排序分层,我使用了TreeMap。
public int candy(int[] ratings) {
if (ratings.length == 0) return 0;
int[] candies = new int[ratings.length];
int count = 0;
// Key: rating value; Value: all the corresponding child indices
TreeMap<Integer, ArrayList<Integer>> tm = new TreeMap<Integer, ArrayList<Integer>>();
for (int i = 0; i < ratings.length; i++) {
if (!tm.containsKey(ratings[i])) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(i);
tm.put(ratings[i], list);
} else {
ArrayList<Integer> list = tm.get(ratings[i]);
list.add(i);
}
}
// traverse the tree map by the rating level
Set<Integer> set = tm.keySet();
for (int rating : set) {
ArrayList<Integer> list = tm.get(rating);
for (int index : list) {
candies[index] = 1;
// check if the candies are more than the left neighbor
if (index - 1 > 0 && ratings[index] > ratings[index - 1]
&& candies[index] <= candies[index - 1]) {
candies[index] = candies[index - 1] + 1;
}
// check if the candies are more than the right neighbor
if (index + 1 < ratings.length
&& ratings[index] > ratings[index + 1]
&& candies[index] <= candies[index + 1]) {
candies[index] = candies[index + 1] + 1;
}
count += candies[index];
}
}
return count;
}
按照这样的思路,总共需要遍历原来rating数组一次来构造TreeMap,然后再遍历一次TreeMap,计算分配给每个孩子的糖果数,并计算其总数。总共两轮遍历,感觉时间复杂度应该是O(N),可是为什么结果还是超时?
琢磨了一段时间,发现TreeMap由于具有排序时间,并不像HashMap那样具有O(1)的插入时间,而是像其他具有排序性质的数据结构一样,需要O(lgN)的时间才能完成插入操作。所以,这个实现的性能瓶颈在于构造TreeMap,一共需要O(N*lgN)的时间。
所以得另外转换思路了。网上有一种巧妙的解法,只需要两次对数组进行两次扫描。
1)从左往右扫描,如果某个孩子的糖果数少于rating低的左邻居,那么就让他持有比左邻居多1的糖果数。
2)从右往左扫描,如果某个孩子的糖果数少于rating低的优邻居,那么就让他持有比右邻居多1的糖果数。
然后对数组求和即可。
public int candy(int[] ratings) {
if (ratings.length == 0)
return 0;
int[] candies = new int[ratings.length];
for (int i = 1; i < ratings.length; i++) {
if (ratings[i] > ratings[i - 1])
candies[i] = candies[i - 1] + 1;
}
for (int i = ratings.length - 2; i >= 0; i--) {
if (ratings[i] > ratings[i + 1])
candies[i] = Math.max(candies[i], candies[i + 1] + 1);
}
int ret = ratings.length;
for (int i : candies) {
ret += i;
}
return ret;
}
当然,这里最后一步求和其实不需要另外扫描一遍数组,完全可以跟第二次扫描合并。
另外网上还发现了一种有趣的一维DP解法:
1)如果比前一个孩子rating高,必须比前一个孩子多给1个糖;
2)如果跟前一个孩子rating相同,给一个糖就可以了(并不需要跟前一个孩子所持糖果数相等);
3)如果比前一个孩子rating低,给一个糖,然后再多给前面孩子一个糖。另外,还有继续一直检查是否需要多给更前面的孩子一个糖,直到遇到情况1或情况2。
public int candy(int[] ratings) {
if (ratings.length == 0)
return 0;
int[] d = new int[ratings.length];
d[0] = 1;
for (int i = 1; i < ratings.length; i++) {
if (ratings[i] == ratings[i - 1])
d[i] = 1;
else if (ratings[i] > ratings[i - 1])
d[i] = d[i - 1] + 1;
else {// should give less candies than the previous child
d[i] = 1;
if (d[i - 1] == 1) {
int j = i;
while (j > 0 && ratings[j - 1] > ratings[j]
&& d[j - 1] == d[j]) {
// only push backwards when the ratings are different
// but the # of candies are the same
d[j - 1]++;
j--;
}
}
}
}
int sum = 0;
for (int i = 0; i < ratings.length; i++) {
sum += d[i];
}
return sum;
}