leetcode 135. Candy

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?

Example 1:

Input: [1,0,2]
Output: 5
Explanation: You can allocate to the first, second and third child with 2, 1, 2 candies respectively.

Example 2:

Input: [1,2,2]
Output: 4
Explanation: You can allocate to the first, second and third child with 1, 2, 1 candies respectively.
             The third child gets 1 candy because it satisfies the above two conditions.

 

=============================2018.12.21更新============================

从左到右贪心的策略选择,注意后面下降的峰值,影响到之前那一个上升时的峰值。

class Solution {
    public int candy(int[] ratings) {
        int cur=1,sum=1,downpeak=1,uppeak=1;//下降时的downpeak,uppeak是上升时顶峰,cur为当前大小
        for(int i=1;i<ratings.length;i++){
            if(ratings[i]>ratings[i-1]){
                downpeak=0;
                cur++;
                uppeak=cur;
                sum+=cur;
            }else if(ratings[i]<ratings[i-1]){
                if(cur==1){
                    downpeak++;
                    sum+=downpeak;
                    if(downpeak==uppeak){
                        downpeak++;
                        sum++;
                    }
                }else{
                    downpeak=1;
                    sum+=downpeak;
                    cur=1;
                }
            }else{
                downpeak=1;
                uppeak=1;
                sum++;
                cur=1;
            }
        }
        return sum;
    }
}

=================================================================

思路:观察可看出:从左到右遍历。新加入的孩子分3中情况:

1.新加入的孩子i的级别如果比左边孩子级别大,每次初始当前孩子的糖果数val[i-1]+1。这样能使当前糖果总数最少。(证明:当前点与该递增子序列第一个开始递增的点之间的点的和是最小,子序列第一个递增点之前的所有点的和又是最小的。下同)

2.新加入的孩子i的级别如果比左边孩子级别小,每次初始当前孩子的糖果数1。这样能使糖果总数最少,导致左边孩子糖果数的更新。

3.新加入的孩子i的级别如果与左边孩子相等,每次初始当前孩子的糖果数1。这样能使糖果总数最少,不会导致左边孩子糖果数的更新。

开始的想法是定义一个val[i]数组记录每个孩子的糖果数,遇到上面第二种情况,会导致之前孩子的糖果数更新,所以时间复杂度为O(n^2),空间复杂度O(n),虽然AC了,但时间太长了。

(未改进的代码)

 public int candy(int[] ratings) {
        int[] val=new int[ratings.length];
        int sum=0;
        val[0]=1;
        for(int i=1;i<ratings.length;i++){
            if(ratings[i]<=ratings[i-1]){
                val[i]=1;
                for(int j=0;i-j-1>=0;j++){//整理之前的数的值
                    if(ratings[i-j]<ratings[i-j-1]&&val[i-j]>=val[i-j-1]){
                        val[i-j-1]++;
                    }else{
                        break;                       
                    }
                }
            }
            else{
                val[i]=val[i-1]+1;
            }
        }
        for(int i=0;i<val.length;i++){
            sum+=val[i];
        }
        return sum;
    }

 

后来再次观察得,其实不用维护一个数组保存每个孩子的糖果数。后面孩子糖果数的更新取决于前面某个孩子的最高峰值。只需保存,当前孩子单调递减是左边的峰值high,和当前孩子与左边某个峰值的距离dis,这两个值决定了之间孩子的糖果数的更新情况,如果dis==high的时候要更新峰值high。还需保存当前糖果和sum。

分3种情况:

1.当孩子级别递增时,根据每个孩子的糖果数比前面那个孩子的+1,更新sum,并记录最高峰值。

2.当孩子级别递减时,因为孩子每次糖果初始值为1,导致左边孩子糖果数的更新,根据左边的峰值high和dis,更新sum。

3.当孩子级别从递减,转变为递增;或孩子级别与前一个相等时。初始化峰值high,距离dis,需定义一个标记位rise和第一种情况做区分。当rise==false就是第2或第3种情况,rise==true就是第一种情况。别忘了更新sum。

时间复杂度O(n),空间复杂度O(1)。

(改进后的代码)

public int candy(int[] ratings) {
        int high=1;
        int dis=0;//下降时到峰值的距离
        int sum=1;
        boolean rise=true;//true
        for(int i=1;i<ratings.length;i++){//上升
            if(ratings[i]>ratings[i-1]){
                if(rise){//保持上升
                   high++;
                   sum+=high;//每次更新high
                }else{//前面下降碰到上升,初始化
                    rise=true;
                    high=2;
                    sum+=2;
                    dis=0;
                }
            }
            else if(ratings[i]<ratings[i-1]){//下降
                rise=false;//下降标志位
                dis++;
                if(dis==high){
                    high++;//之前最大高度增加1 
                    sum+=dis+1;
                }else{
                    sum+=dis;
                }
            }else{//当ratings[i]==ratings[i-1]时,看成 ratings[i]之后是下降状态
               rise=false;
               high=1;
               sum+=1;
               dis=0;
            }
        }
        return sum;
    }

 

看到网上还有两种思路,这两种贪心思想需要慢慢领会。参考了http://blog.sina.com.cn/s/blog_6dad631f0102wzhm.html

1.从权值最小的小孩开始分起,每分到一个小孩时,看他是否比身边小孩的权值大,如果大,则比身边小孩最多糖数再多1;如果小,则不变。参考http://www.cnblogs.com/shadowmydx/p/4914215.html

2.先初始化candy数组为1; 对rating数组从左到右扫描,如果遇到rating[j]=candy[j+1],则让candy[j+1]=candy[j]+1; 对rating数组从右到左扫描,如果遇到rating[j-1]>rating[j]且candy[j-1]<=candy[j],则让candy[j-1]=candy[j]+1;这样左右邻居都满足要求了。参考leetcode官方解答https://leetcode.com/articles/candy/#approach-4-single-pass-approach-with-constant-space-accepted。又看了下官方解答第四种思路和上述解法差不多。

第二种方法难以想到,需要慢慢体会。

### 贪心算法在 LeetCode 上的应用 贪心算法是一种通过局部最优选择来达到全局最优解的方法。其核心思想是在每一步都做出当前状态下最好的选择,从而希望最终能够得到整体的最优解[^1]。 以下是基于 Python 的几个经典贪心算法题目及其解决方案: --- #### 题目 1: **LeetCode 455. Assign Cookies** 给定两个数组 `g` 和 `s`,分别表示孩子的胃口值和饼干大小。每个孩子最多只能吃一块饼干,求最大满足的孩子数量。 ##### 解法 先对两个数组进行排序,然后从小到大分配饼干给尽可能多的孩子。 ```python def findContentChildren(g, s): g.sort() s.sort() i, j = 0, 0 count = 0 while i < len(g) and j < len(s): if s[j] >= g[i]: count += 1 i += 1 j += 1 return count ``` 此方法利用了贪心策略,在每次循环中优先考虑最小需求的孩子并匹配最合适的饼干[^3]。 --- #### 题目 2: **LeetCode 135. Candy** 有 n 个小孩站在一条直线上,每个小孩有一个评分值。分发糖果的要求是:如果某个小孩的评分高于相邻的小孩,则该小孩获得更多的糖果;至少每人一颗糖果。 ##### 解法 两次遍历数组,一次从前向后,另一次从后向前,确保左右两侧的关系都被满足。 ```python def candy(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 i in range(n - 2, -1, -1): if ratings[i] > ratings[i + 1]: candies[i] = max(candies[i], candies[i + 1] + 1) return sum(candies) ``` 这种方法通过两轮扫描实现了局部最优条件下的全局最优解。 --- #### 题目 3: **LeetCode 621. Task Scheduler** 给定一组任务字符以及冷却时间 `n`,计算完成所有任务所需的最少单位时间数。 ##### 解法 统计频率最高的任务数目,并根据这些任务之间的间隔安排其他任务。 ```python from collections import Counter def leastInterval(tasks, n): task_counts = list(Counter(tasks).values()) max_freq = max(task_counts) max_count = task_counts.count(max_freq) intervals = (max_freq - 1) * (n + 1) + max_count return max(len(tasks), intervals) ``` 上述代码的关键在于理解如何合理填充高频任务之间的时间间隙。 --- #### 总结 解决贪心类问题时,通常需要明确以下几个方面: - 是否可以通过逐步优化子结构解决问题? - 如何定义“局部最优”,它是否能导向“全局最优”? 此外,清晰表达逻辑流程有助于构建完整的解决方案[^2]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值