《代码随想录》Ⅷ 贪心算法 135. 分发糖果

《代码随想录》Ⅷ 贪心算法 135. 分发糖果

努力学习!

题目:力扣链接

  • n​ 个孩子站成一排。给你一个整数数组 ratings​ 表示每个孩子的评分。

    你需要按照以下要求,给这些孩子分发糖果:

    • 每个孩子至少分配到 1​ 个糖果。
    • 相邻两个孩子评分更高的孩子会获得更多的糖果。

    请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目

一、思想

这道题的核心思想是使用贪心算法,通过两次遍历数组,分别从左到右和从右到左,确保每个孩子都能满足以下条件:

  1. 每个孩子至少分配到1个糖果:初始时,每个孩子都分配1个糖果。

  2. 相邻两个孩子评分更高的孩子会获得更多的糖果

    • 从左到右遍历时,如果当前孩子的评分比前一个孩子高,则当前孩子的糖果数比前一个孩子多1。
    • 从右到左遍历时,如果当前孩子的评分比后一个孩子高,则当前孩子的糖果数比后一个孩子多1。

通过这种方式,可以确保每个孩子都能满足条件,并且总糖果数是最小的。

二、代码

/**
 * 该函数用于计算给定评分数组的糖果数量。
 * 评分数组中的每个元素代表一个孩子的评分,糖果数量表示为每个孩子得到的糖果数。
 * 规则是:如果一个孩子的评分比邻座的孩子高,则他必须得到比邻座孩子更多的糖果。
 * 这个函数使用了贪婪算法,从左右两边同时遍历数组,最终得到每个孩子应得的糖果数,并求和。
 *
 * @param ratings 孩子的评分数组
 * @return 总的糖果数量
 */
class Solution
{
public:
    /**
     * 计算孩子们的糖果数量
     * @param ratings 孩子的评分数组
     * @return 总的糖果数量
     */
    int candy(vector<int> &ratings)
    {
        // 初始化总的糖果数量为0
        int res = 0;
        // 初始化每个孩子应得的糖果数为1
        vector<int> candyVec(ratings.size(), 1);
        // 从左到右遍历数组,如果当前孩子的评分比前一个孩子高,则当前孩子的糖果数为前一个孩子的糖果数+1
        for (int i = 1; i < ratings.size(); ++i) {
            if (ratings[i - 1] < ratings[i]) {
                candyVec[i] = candyVec[i - 1] + 1;
            }
        }
        // 从右到左遍历数组,如果当前孩子的评分比后一个孩子高,则当前孩子的糖果数为后一个孩子的糖果数+1的最大值
        for (int i = ratings.size() - 2; i >= 0; --i) {
            if (ratings[i] > ratings[i + 1]) {
                candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
            }
        }

        // 计算总的糖果数量
        for (int candy : candyVec) {
            res += candy;
        }
        return res;
    }
};

三、代码解析

1. 算法工作原理分解
1.1 初始化糖果数组
  • 目的:确保每个孩子至少分配到1个糖果。
  • 实现:创建一个与ratings​数组大小相同的candyVec​数组,初始值均为1。
1.2 从左到右遍历
  • 目的:确保如果当前孩子的评分比前一个孩子高,则当前孩子的糖果数比前一个孩子多1。

  • 实现

    • 从第二个孩子开始遍历,如果ratings[i] > ratings[i - 1]​,则candyVec[i] = candyVec[i - 1] + 1​。
1.3 从右到左遍历
  • 目的:确保如果当前孩子的评分比后一个孩子高,则当前孩子的糖果数比后一个孩子多1。

  • 实现

    • 从倒数第二个孩子开始遍历,如果ratings[i] > ratings[i + 1]​,则candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1)​。
1.4 计算总糖果数
  • 目的:计算所有孩子糖果数的总和。
  • 实现:遍历candyVec​数组,累加每个孩子的糖果数。
2. 关键点说明
2.1 从左到右遍历的意义
  • 从左到右遍历时,确保每个孩子比前一个评分更高的孩子获得更多的糖果。这满足了从左到右的评分规则。
2.2 从右到左遍历的意义
  • 从右到左遍历时,确保每个孩子比后一个评分更高的孩子获得更多的糖果。这满足了从右到左的评分规则。
2.3 为什么需要两次遍历
  • 单独从左到右或从右到左遍历都无法完全满足条件。例如,如果只从左到右遍历,可能会忽略从右到左的评分规则。因此,需要两次遍历来确保所有条件都被满足。
2.4 贪心算法的应用
  • 局部最优:每次遍历时,确保当前孩子满足与相邻孩子的糖果数关系。
  • 全局最优:通过两次遍历,确保所有孩子都满足条件,并且总糖果数是最小的。

四、复杂度分析

  • 时间复杂度O(n)

    • 其中n​是孩子的数量。算法需要遍历两次数组,因此总的时间复杂度为O(n)​。
  • 空间复杂度O(n)

    • 需要额外的candyVec​数组来存储每个孩子的糖果数,因此空间复杂度为O(n)​。

白展堂:人生就是这样,苦和累你总得选一样吧?哪有什么好事都让你一个人占了呢。 ——《武林外传》

### 关于贪心算法的讲解 贪心算法是一种在每一步选择中都采取当前状态下最好或最优的选择,从而希望最终结果也是全局最优的一种算法策略[^1]。 对于某些特定问题而言,这种局部最优解能够直接导向全局最优解。然而,并不是所有的优化问题都能通过这种方法求得最精确的结果,但在很多情况下可以获得接近最优解的有效方案。 #### 示例一:最大和转换后的数组元素(Java) 考虑这样一个例子,在给定整数列表`nums`以及一个非负整数`k`的情况下,允许执行最多`k`次操作来改变任意数量的数值符号。目标是在不超过`k`次翻转的前提下最大化所有元素之和: ```java class Solution { public int largestSumAfterKNegations(int[] nums, int k) { Arrays.sort(nums); int count = 0; for (int i = 0; i < nums.length; i++) { if (k > 0 && nums[i] < 0) { nums[i] = -nums[i]; k--; } count += nums[i]; } Arrays.sort(nums); return count - ((k % 2 == 0) ? 0 : 2 * nums[0]); } } ``` 这段代码实现了上述逻辑,其中先对输入数组进行了升序排列以便优先处理负值较大的项,之后再根据剩余的操作次数决定是否调整最小正值以进一步提升总和[^2]。 #### 示例二:分配最少糖果数目 另一个典型的应用场景涉及向一群孩子分发糖果,条件是一个孩子的评分高于其左侧邻居,则该名学生应获得更多的糖果。这里采用了一种简单直观的方法——每当遇到更高的分数就增加一颗糖的数量直到遍历结束整个序列为止[^3]。 ```python def distribute_candies(ratings): n = len(ratings) candies = [1]*n for i in range(1,n): if ratings[i]>ratings[i-1]: candies[i]=candies[i-1]+1 for j in reversed(range(n-1)): if ratings[j]>ratings[j+1] and candies[j]<=candies[j+1]: candies[j]=candies[j+1]+1 return sum(candies) ``` 此Python函数展示了如何利用两次扫描过程分别从前至后和从后往前更新每个位置上的最低需求量,确保满足题目要求的同时使得总的糖果消耗达到最小化。 #### 示例三:寻找合适的起始站点完成环形路线旅行 最后来看一个更复杂的案例—解决“加油站”问题。假设存在一系列相连的服务区构成闭合路径,车辆可以在任一站加油并继续行驶直至下一个目的地。为了判断能否顺利完成一圈旅程,可以通过计算各段行程结束后所剩燃油量来进行评估。具体做法是从第一个节点开始累积净增益(`gas-cost`),只要中途未曾跌入负区间即表明可以从起点出发成功返回原点;反之则需重新选定其他候选作为新的出发点尝试验证[^4]。 ```cpp bool canCompleteCircuit(vector<int>& gas, vector<int>& cost) { int total_tank = 0, curr_tank = 0, starting_station = 0; for (size_t i=0 ; i<gas.size() ; ++i){ total_tank += gas[i]-cost[i]; curr_tank += gas[i]-cost[i]; // If one couldn't get here, if(curr_tank < 0){ // Start over from next station. starting_station=i+1; curr_tank=0; } } return total_tank >= 0 && starting_station != gas.size(); } ``` 以上三个实例均体现了不同形式下的贪心思维模式及其应用技巧,它们共同之处在于总是倾向于做出当下看来最佳的动作,进而逐步构建出完整的解决方案框架。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值