前言
当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~
在此感谢左大神让我对算法有了新的感悟认识!
问题介绍
原问题
一群孩子站成一排,每一个人都有一个得分,现在根据得分开始按照规则发糖果:
1、每一个孩子不管得分多少,至少都是一个糖果
2、任意相邻和孩子之间,得分多的孩子糖果一定比得分少的糖果多
问:求最少需要多少糖果可以满足上述规则?
如:arr = [1,2,1] 代表三个孩子的得分,结果为 4,糖果数量[1,2,1]
进阶问题
现在加一条规则:
3、相同分数的相邻孩子之间分的糖果必须一样
问:最少需要多少糖果可以满足上述规则
解决方案
原问题:
介绍一种爬坡思想解决当前问题:
1、第一个规则作用就是所有点的起点必须是1,从1开始
2、想要满足第二个规则:
- 从index = 0 开始,找到第一个非单调递减区间的起点,如果是[1,2,3,4,4,4,5],那么arr[3]就是单调递增的终点,非单调递增的起点,即使是[1,2,3,4,4,4,5,6,7],也是一样。
- 非单调递增的起点开始往回找到上一个非单调递增的起点(需要游标追踪),之间使用等差数列求和就是这个区间内最少的糖果数量,记录为左半边lcands
- 接下来找单调递减的终点,单调递增的起点,再使用上面的办法求得糖果数,rcands,那么现在存在一个冲突的点,也就是坡顶处,这个地方需要以左边递增的最大值(lbase,如[1,2,3,4,2,1],arr[3]为坡顶lbase为4,rbase为3,也叫坡度),右边递减时的第一个值,坡度中,较大的为准,因此需要减去一个较小的。
- 接下来继续寻找下一个单调递增的终点 ,非单调递增的起点计算即可,特别注意:[4,4,4]区间每一个4都自成一个非单调递增的起点,因此他们都是起点,最后需要的糖果数就是[1,1,1]也就是3
进阶问题
加的这条规则其实更改的判断就是找到“单调递增的终点,非单调递增的起点”这个判断变成了 :
先找到左边的lbase,再找右边的rbase,其中,相同的值出现次数记录下来,res同样先加上,之后等右边的递减计算出来之后,一起结算即可。具体看代码实现
代码编写
java语言版本
原问题:
原问题:
/**
* 二轮测试:分糖果问题(原问题)
* 1、首先分爬坡
* 2、左右坡度合一
* @param arr
* @return
*/
public static int distributeCandyCp1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}else if (arr.length == 1){
return 1;
}
int len = arr.length;
// 找到下一个爬坡的起点
int index = getNextStartCp1(arr, 0);
int res = getRightCandy(0, index++);
// 左边的糖果数量最大值
int lbase = 1;
// 右边糖果数量最大值
int rbase = 0;
while (index < len) {
if (arr[index] > arr[index - 1]) {
// 上坡过程
res += ++lbase;
index ++;
}else if (arr[index] < arr[index-1]) {
// 下坡过程,直接找到下一个上坡点
int next = getNextStartCp1(arr, index-1);
// 右边需要的糖果
int rcand = getRightCandy(index - 1, next);
rbase = next - index + 1;
res += rcand - (lbase > rbase ? rbase : lbase);
lbase = 1;
index = next+1;
}else {
// 相等时相当于下一坡
res += 1;
lbase = 1;
index ++;
}
}
return res;
}
/**
* 计算下坡需要的糖果数量[...3,2,1]
* 等差数列求和公式
* @param left
* @param right
* @return
*/
private static int getRightCandy(int left, int right) {
int len = right - left + 1;
return (len * (len + 1))/2;
}
/**
* 找到下一个爬坡起点
* @param arr
* @param start
* @return
*/
private static int getNextStartCp1(int[] arr, int start) {
for (int i = start; i < arr.length; i++) {
if (arr[i] < arr[i + 1]) {
return i;
}
}
return arr.length-1;
}
进阶问题:
/**
* 二轮测试:分糖果问题(进阶问题)
* 1、首先分爬坡
* 2、左右坡度合一
* @param arr
* @return
*/
public static int distributeCandyCp2(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}else if (arr.length == 1){
return 1;
}
int len = arr.length;
// 找到下一个爬坡的起点
int index = getNextStartCp2(arr, 0);
int[] data = getRightCandyCp2(arr, 0, index++);
int res = data[0];
// 左边的糖果数量最大值
int lbase = 1;
// 右边糖果数量最大值
int rbase = data[1];
// 计算有多少个相同的
int same = 1;
while (index < len) {
if (arr[index] > arr[index - 1]) {
// 上坡过程
res += ++lbase;
same = 1;
index ++;
}else if (arr[index] == arr[index - 1]) {
res += lbase;
same++;
index ++;
} else if (arr[index] < arr[index-1]) {
// 下坡过程,直接找到下一个上坡点
int next = getNextStartCp2(arr, index-1);
// 右边需要的糖果
int[] resAndRcand = getRightCandyCp2(arr, index - 1, next);
int rcand = resAndRcand[0];
rbase = resAndRcand[1];
if (rbase < lbase) {
res += rcand - rbase;
}else {
res += rcand - same * lbase - rbase + same * rbase;
}
lbase = 1;
index = next+1;
}
}
return res;
}
/**
* 计算下坡需要的糖果数量[...3,3,2,2,1]
* 等差数列求和公式
* @param left
* @param right
* @return
*/
private static int[] getRightCandyCp2(int[] arr, int left, int right) {
int base = 1;
int res = 0;
for (int i = right; i >= left; i--) {
if (i == right || arr[i] == arr[i + 1]) {
res += base;
}else {
res += ++base;
}
}
return new int[]{res, base};
}
/**
* 找到下一个爬坡起点
* @param arr
* @param start
* @return
*/
private static int getNextStartCp2(int[] arr, int start) {
for (int i = start; i < arr.length-1; i++) {
if (arr[i] < arr[i + 1]) {
return i;
}
}
return arr.length-1;
}
public static void main(String[] args) {
System.out.println(distributeCandyCp2(new int[]{0,1,2,3,3,3,2,2,2,2,2,1,1}));
}
c语言版本
正在学习中
c++语言版本
正在学习中
思考感悟
这里有几个地方可以理解一下:
1、坡度的概念,比如一个序列:1234,这里的坡度就是4,也就是说lbase就是从1加到4的,同样4321的递减序列坡度也是4,从4减到1的,上坡和下坡相交的时候,坡顶需要按照坡度高的来,否则坡度高的没办法分,会有人分到负的糖果。
2、进阶问题和原问题的区别其实不大,只是原问题的坡顶是一个点,进阶问题的坡顶是一条线,如果你看懂了这道题,我觉得坡顶一条线这句话应该就比较形象了,same恰好就是记录这条线的长度的,为了能够将这条线清算的时候直接当做点来处理,无非就是减去一条线(和:samelbase或者samerbase)和减去一个点(lbase或者rbase)的区别。
写在最后
方案和代码仅提供学习和思考使用,切勿随意滥用!如有错误和不合理的地方,务必批评指正~
如果需要git源码可邮件给2260755767@qq.com
再次感谢左大神对我算法的指点迷津!