【算法】【算法杂谈】分糖果问题

文章介绍了如何解决一个关于按规则分配糖果的算法问题,提出了爬坡思想,分别给出了Java语言的原问题和进阶问题的解决方案。在原问题中,确保每个孩子至少一个糖果且得分高的孩子糖果多;进阶问题增加了相同得分孩子糖果数必须一样的规则。代码示例展示了如何找出单调递增和递减区间,以及如何计算所需的最少糖果数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

当前所有算法都使用测试用例运行过,但是不保证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
再次感谢左大神对我算法的指点迷津!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

元空间

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值