热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【每日算法Day69】面试经典题:分发糖果问题

题目链接LeetCode135.分发糖果[1]题目描述老师想给孩子们分发糖果,有个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们

题目链接

LeetCode 135. 分发糖果[1]

题目描述

老师想给孩子们分发糖果,有 N 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

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

  • 每个孩子至少分配到 1 个糖果。
  • 相邻的孩子中,评分高的孩子必须获得更多的糖果。

那么这样下来,老师至少需要准备多少颗糖果呢?

示例1

输入:
[1,0,2]
输出:
5
解释:
你可以分别给这三个孩子分发 2、1、2 颗糖果。

示例2

输入:
[1,2,2]
输出:
4
解释:
你可以分别给这三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这已满足上述两个条件。

题解

这题虽然难度定义成困难,但其实代码不长,思路也比较简单清晰。

首先明确一下题目中的两个条件,我们可以把所有人的分数在坐标轴中连起来,这样就形成了一个波形图(图片来自官方题解):

v2-f60d1385c7bbcdaacee85c6a6a957b68_b.jpg


那么这就像一座一座山峰一样,在谷底(左右两边分数都大于等于它)糖果数一定是 1 。从谷底往两侧扩展,糖果数逐渐加 1 就行了。

要注意的一点是图中 pt.13 那个位置也是一个谷底,因为它右边的分数等于它。

那么问题的关键就是如何找到这些谷底了。

两次遍历

首先初始化所有人都分到 1 个糖果。

然后从左向右遍历一次所有分数,如果发现分数小于等于前一个人分数,那暂时不用管,因为你从左向右是没法知道下坡的点距离谷底还有多远的。而如果发现分数大于前一个人分数,那么就在前一个人糖果数基础上,再多分一个给他,因为是上坡,所以谷底一定在左边已经遍历过了,是知道距离的。

接着就是遍历下坡了,也就是从右向左遍历所有分数,同理如果发现分数大于后一个人分数,那么就在后一个人糖果数基础上,再多分一个给他。

但是这里要处理一个冲突,那就是峰顶既是上坡,又是下坡,其实只要两次遍历完取上坡和下坡中糖果数较大的那个就行了。

总结一下就是一次从左向右遍历所有上坡,一次从右向左遍历所有下坡,峰顶取两次较大值

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

一次遍历

从上面方法中可以看出,本题求解的难点就在于从左向右遍历的时候,下坡到底有多长没法知道,必须全部遍历完了才能知道。还有就是山峰的值必须看左右两边的上坡下坡有多长。

为了解决这个问题,我们可以用变量 up 记录上坡的长度,down 记录下坡的长度。

当遇到谷底的时候,就表明一座山遍历结束了,那么我们只需要比较 updown 的大小就知道峰顶取值了。

而如何判断一座山遍历结束呢?假设现在遍历到了第 i 个学生,我们再用两个变量,ns 表示 i-1i 的变化趋势(1:上升,-1:下降,0:不变),os 表示前一个时刻 i-2i-1 的变化趋势。那么谷底只有下面三种情况:

  • os <0ns > 0 。也就是前一个时刻在下降&#xff0c;当前时刻上升了&#xff0c;那显然第 i-1 个学生是谷底。
  • os <0ns &#61; 0 。也就是前一个时刻在下降&#xff0c;当前时刻不变&#xff0c;这种情况下第 i-1 个学生也是谷底&#xff0c;因为根据题意&#xff0c;他的糖果数没必要比第 i 个学生多。
  • os > 0ns &#61; 0 。也就是前一个时刻在上升&#xff0c;当前时刻不变。这种情况下&#xff0c;山峰只有上坡&#xff08;到峰顶 i-1 结束&#xff09;&#xff0c;没有下坡&#xff0c;所以这座山也遍历结束了&#xff0c;得计算糖果数了。

这座山峰的的糖果数可以表示为三部分之和&#xff1a;上坡、下坡和峰顶。上坡就是 1&#43;2&#43;\ldots&#43;up&#xff0c;下坡就是 1&#43;2&#43;\ldots&#43;down&#xff0c;峰顶就是 \max{\{up, down\}}&#43;1 。算完了之后&#xff0c;这座山峰就再也不用考虑了&#xff0c; updown 都清零。

在具体实现的时候&#xff0c;这个方法细节有点多&#xff0c;一不小心就会错&#xff0c;直接看代码注释吧。

继续看下面这张图&#xff1a;

v2-f60d1385c7bbcdaacee85c6a6a957b68_b.jpg

贴一段官方的样例解释&#xff1a;

v2-be70e4bcf9709e45abe0b310bbccce90_b.jpg


时间复杂度 O(n) &#xff0c;空间复杂度 O(1)

单调栈

我们用一个单调栈来保存单调下降的得分&#xff0c;也就是下坡。

当遍历到第 i 个学生时&#xff0c;如果栈顶元素 j 的得分小于等于 i 的得分&#xff0c;也就是遇到谷底了&#xff0c;那么就出栈&#xff0c;直到栈空。

最后用一个很大的数将栈里所有元素顶出来就行了。

时间复杂度 O(n) &#xff0c;空间复杂度 O(n)

小结

第一种解法最容易理解和实现&#xff0c;也不用考虑什么特殊情况。但是后两种方法处理起来就稍稍有点麻烦了&#xff0c;需要结合样例和代码来理解。但是本质上都是一样的&#xff0c;都是从谷底&#xff08;糖果数为 1&#xff09;开始&#xff0c;向两周扩展。方法一是先从每个谷底向右边上坡扩展&#xff0c;再向左边下坡扩展。方法二是计算出相邻两个谷底之间的上坡下坡长度&#xff0c;然后直接计算。第三个方法是从每个谷底先向左边下坡扩展&#xff0c;再向右边上坡扩展。

代码

两次遍历&#xff08;c&#43;&#43;&#xff09;

class Solution {
public:int candy(vector<int>& ratings) {int n &#61; ratings.size();vector<int> res(n, 1);for (int i &#61; 1; i < n; &#43;&#43;i) {if (ratings[i] > ratings[i-1]) {res[i] &#61; res[i-1] &#43; 1;}}for (int i &#61; n-2; i >&#61; 0; --i) {if (ratings[i] > ratings[i&#43;1]) {res[i] &#61; max(res[i], res[i&#43;1]&#43;1);}}return accumulate(res.begin(), res.end(), 0);}
};

一次遍历&#xff08;c&#43;&#43;&#xff09;

class Solution {
public:int count(int n) {return n*(n&#43;1)/2;}int candy(vector<int>& ratings) {int n &#61; ratings.size();int sum &#61; 0;int up &#61; 0, down &#61; 0;int os &#61; 0, ns &#61; 0;for (int i &#61; 1; i < n; &#43;&#43;i) {ns &#61; ratings[i]>ratings[i-1] ? 1 : (ratings[i]<ratings[i-1] ? -1 : 0);// 这座山峰遍历结束&#xff0c;计算糖果数。
if ((os < 0 && ns >&#61; 0) || os > 0 && ns &#61;&#61; 0) {// 这里看似好像峰顶没有加 1&#xff0c;其实是 count(down) 减去了 1。
// 因为谷底是共享的&#xff0c;所以将谷底给了下一座山峰的上坡。
sum &#43;&#61; count(up) &#43; count(down) &#43; max(up, down);up &#61; down &#61; 0;}if (ns > 0) up&#43;&#43;;else if (ns < 0) down&#43;&#43;;// 如果是平原&#xff0c;说明谷底不会共享&#xff0c;之前少加的 1 再补上。
else if (!ns) sum&#43;&#43;;os &#61; ns;}// 最后一座山峰循环里不会计算到&#xff0c;再加上。
sum &#43;&#61; count(up) &#43; count(down) &#43; max(up, down) &#43; 1;return sum;}
};

单调栈&#xff08;c&#43;&#43;&#xff09;

class Solution {
public:int candy(vector<int>& ratings) {ratings.push_back(INT_MAX);int n &#61; ratings.size();vector<int> res(n, 1);stack<int> st;int sum &#61; 0;for (int i &#61; 0; i < n; &#43;&#43;i) {if (!st.empty() && ratings[i] >&#61; ratings[st.top()]) {while (!st.empty()) {int j &#61; st.top();st.pop();if (j < n-1 && ratings[j] > ratings[j&#43;1]) {res[j] &#61; max(res[j], res[j&#43;1]&#43;1);}if (j > 0 && ratings[j] > ratings[j-1]) {res[j] &#61; max(res[j], res[j-1]&#43;1);}sum &#43;&#61; res[j];}}st.push(i);}return sum;}
};

参考资料

[1]

LeetCode 135. 分发糖果: leetcode-cn.com/problem



推荐阅读
  • 生成式对抗网络模型综述摘要生成式对抗网络模型(GAN)是基于深度学习的一种强大的生成模型,可以应用于计算机视觉、自然语言处理、半监督学习等重要领域。生成式对抗网络 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 云原生边缘计算之KubeEdge简介及功能特点
    本文介绍了云原生边缘计算中的KubeEdge系统,该系统是一个开源系统,用于将容器化应用程序编排功能扩展到Edge的主机。它基于Kubernetes构建,并为网络应用程序提供基础架构支持。同时,KubeEdge具有离线模式、基于Kubernetes的节点、群集、应用程序和设备管理、资源优化等特点。此外,KubeEdge还支持跨平台工作,在私有、公共和混合云中都可以运行。同时,KubeEdge还提供数据管理和数据分析管道引擎的支持。最后,本文还介绍了KubeEdge系统生成证书的方法。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 也就是|小窗_卷积的特征提取与参数计算
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了卷积的特征提取与参数计算相关的知识,希望对你有一定的参考价值。Dense和Conv2D根本区别在于,Den ... [详细]
  • 闭包一直是Java社区中争论不断的话题,很多语言都支持闭包这个语言特性,闭包定义了一个依赖于外部环境的自由变量的函数,这个函数能够访问外部环境的变量。本文以JavaScript的一个闭包为例,介绍了闭包的定义和特性。 ... [详细]
  • WebSocket与Socket.io的理解
    WebSocketprotocol是HTML5一种新的协议。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送 ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
author-avatar
heqiuhao
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有