Leetcode 799.香槟塔:动态规划+递归
香槟塔:动态规划+递归
题目来源:Leetcode 22/11/20每日一题:799.香槟塔
https://leetcode.cn/problems/champagne-tower
我们把玻璃杯摆成金字塔的形状,其中 第一层 有 1 个玻璃杯, 第二层 有 2 个,依次类推到第 100 层,每个玻璃杯 (250ml) 将盛有香槟。
从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了,任何溢出的香槟都会立刻等流量的流向左右两侧的玻璃杯。当左右两边的杯子也满了,就会等流量的流向它们左右两边的杯子,依次类推。(当最底层的玻璃杯满了,香槟会流到地板上)
例如,在倾倒一杯香槟后,最顶层的玻璃杯满了。倾倒了两杯香槟后,第二层的两个玻璃杯各自盛放一半的香槟。在倒三杯香槟后,第二层的香槟满了 - 此时总共有三个满的玻璃杯。在倒第四杯后,第三层中间的玻璃杯盛放了一半的香槟,他两边的玻璃杯各自盛放了四分之一的香槟。
现在当倾倒了非负整数杯香槟后,返回第 i 行 j 个玻璃杯所盛放的香槟占玻璃杯容积的比例( i 和 j 都从0开始)。
示例 1:
输入: poured(倾倒香槟总杯数) = 1, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.00000
解释: 我们在顶层(下标是(0,0))倒了一杯香槟后,没有溢出,因此所有在顶层以下的玻璃杯都是空的。
示例 2:
输入: poured(倾倒香槟总杯数) = 2, query_glass(杯子的位置数) = 1, query_row(行数) = 1
输出: 0.50000
解释: 我们在顶层(下标是(0,0)倒了两杯香槟后,有一杯量的香槟将从顶层溢出,位于(1,0)的玻璃杯和(1,1)的玻璃杯平分了这一杯香槟,所以每个玻璃杯有一半的香槟。
示例 3:
输入: poured = 100000009, query_row = 33, query_glass = 17
输出: 1.00000
解释
因为下层杯子中的量是从上层溢出的,即与上层有关的。很自然的会想到dp。
如何定义dp数组?只需一个一维数组即可,长度为0~要查询的杯子的位置。要查询的杯子的位置是什么?观察香槟塔的摆放方式,可以看到,第一层1个杯子,第二层2个杯子...第n层n个杯子,每层杯子数量构成了一个等差数列,那么计算要查询的杯子位置,只需计算它所在层数前的所有层的杯子总数+它在本层中所在的位置即可:
\(dp[i], 0 <= i <= (query\_row * (query\_row + 1) / 2 + query\_glass + 1)\)
下面,只需要按照层级来进行dfs即可,边界条件是:
- 当前层<最大层(即目标杯子所在的层):递归
- 当前层=最大层:结束递归
对于每一层,需要从这一层的杯子开始位置,一直遍历到杯子结束的位置。某一层杯子开始位置是:\(curStart = cur * (cur + 1) / 2\),每一层的杯子个数是:\(cur+1\),其中\(cur\)表示当前所在的层数,\(cur\)从0开始。
对于某一层的某一个杯子,假设其所在层数的起始坐标为\(curStart\),它位于所在层的第\(i\)个位置(\(i\)从0开始),进行如下处理:
- 如果\(dp[curStart + i]> 1.0\),那么该杯子中的香槟会溢出,此时记录溢出值:\(more = dp[curStart + i] - 1.0\),并将该杯子的香槟量设置为1。
- 对于溢出的香槟more,应该平分给其下层的两个“孩子”。
如何确定两个孩子的坐标?观察可得,如果一个杯子在某一层的第\(i\)个位置,那么它的孩子在其下一层的第\(i\)个位置和第\(i+1\)个位置。
如下图,以2号为例,其孩子为4号和5号,2号位于第1层(层数从0开始)的第1个(位置从0开始)位置,而4号位于第2层的第1个位置,5号位于第2层的第2个位置。

那么数值上就容易确定了,假设当前节点位于第\(cur\)层的第\(i\)个,那么其两个孩子的坐标分别为:
\(leftChild = (cur + 2) * (cur + 1) / 2 + i\),
\(rightChild = leftChild + 1\)。
确定了两个孩子坐标后,只需要将\(more\)平分给两个孩子即可。别忘记判断孩子坐标是否越界。
实现
代码如下:
/**
* 时间2ms,超过98.42%
* 空间41.6MB,超过93.69%
*/
class Solution {
public double champagneTower(int poured, int query_row, int query_glass) {
int n = query_row * (query_row + 1) / 2 + query_glass + 1;
double[] dp = new double[n];
dp[0] = poured;
cal(dp, query_row, 0);
return Math.min(dp[n-1], 1.0);
}
public void cal(double[] dp, int max, int cur) {
if(cur < max) {
int curStart = cur * (cur + 1) / 2;
for(int i = 0; i <= cur; ++i) {
if(dp[i + curStart] > 1.0) {
double more = dp[i + curStart] - 1.0;
dp[i + curStart] = 1.0;
int leftChild = (cur + 2) * (cur + 1) / 2 + i;
int rightChild = leftChild + 1;
if(leftChild < dp.length) dp[leftChild] += more / 2;
if(rightChild < dp.length) dp[rightChild] += more / 2;
}
}
cal(dp, max, cur + 1);
}
}
}
复杂度分析
时间复杂度O(n),需要遍历一遍杯子;空间复杂度O(n),需要一个dp数组和递归所用到的栈。
Leetcode 799.香槟塔:动态规划+递归的更多相关文章
- Java实现 LeetCode 799 香槟塔 (暴力模拟)
799. 香槟塔 我们把玻璃杯摆成金字塔的形状,其中第一层有1个玻璃杯,第二层有2个,依次类推到第100层,每个玻璃杯(250ml)将盛有香槟. 从顶层的第一个玻璃杯开始倾倒一些香槟,当顶层的杯子满了 ...
- LeetCode探索初级算法 - 动态规划
LeetCode探索初级算法 - 动态规划 今天在LeetCode上做了几个简单的动态规划的题目,也算是对动态规划有个基本的了解了.现在对动态规划这个算法做一个简单的总结. 什么是动态规划 动态规划英 ...
- 从"汉诺塔"经典递归到JS递归函数
前言 参考<JavaScript语言精粹> 递归是一种强大的编程技术,他把一个问题分解为一组相似的子问题,每一问题都用一个寻常解去解决.递归函数就是会直接或者间接调用自身的一种函数,一般来 ...
- [Python3 练习] 005 汉诺塔1 递归解法
题目:汉诺塔 I (1) 描述 传说,在世界中心贝拿勒斯(在印度北部)的圣庙外有左中右三根足够长的柱子(塔) 左边柱子上套着 64 片金片,金片按"上小下大"排,其余两根是空柱子 ...
- 用C语言实现汉诺塔自动递归演示程序
用C语言实现汉诺塔自动递归演示程序 程序实现效果 1.变界面大小依照输入递归数改变. 2.汉诺塔自动移动演示. 3.采用gotoxy实现流畅刷新. 4.保留文字显示递归流程 程序展示及实现 githu ...
- Leetcode题目70.爬楼梯(动态规划+递归-简单)
题目描述: 假设你正在爬楼梯.需要 n 阶你才能到达楼顶. 每次你可以爬 1 或 2 个台阶.你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数. 示例 1: 输入: 2 输出: 2 ...
- [LeetCode] Champagne Tower 香槟塔
We stack glasses in a pyramid, where the first row has 1 glass, the second row has 2 glasses, and so ...
- 小旭讲解 LeetCode 53. Maximum Subarray 动态规划 分治策略
原题 Given an integer array nums, find the contiguous subarray (containing at least one number) which ...
- leetcode刷题-- 5. 动态规划
动态规划思路 参考 状态转移方程: 明确「状态」-> 定义dp数组/函数的含义 -> 明确「选择」-> 明确 base case 试题 53最大子序和 题目描述 53 给定一个整数数 ...
- [leetcode] 题型整理之动态规划
动态规划属于技巧性比较强的题目,如果看到过原题的话,对解题很有帮助 55. Jump Game Given an array of non-negative integers, you are ini ...
随机推荐
- Keepalived之简单有效的配置
1.简介 官网地址:https://www.keepalived.org/ 源码包下载地址:https://www.keepalived.org/download.html Keepalived是一种 ...
- Elasticsearch:Java 运用示例
在今天的文章中,我们来介绍如何使用Java来访问Elasticsearch. 首先,我们必须在我们的系统中安装Elasticsearch. Maven 配置 针对Java的开发,我们必须在pom.xm ...
- PVC-U排水管及管件
- Beats:使用 Filebeat 导入 JSON 格式的日志文件
转载自:https://blog.csdn.net/UbuntuTouch/article/details/108504014 在今天的文章中,我来用另外的一种方式来展示如何导入一个 JSON 格式的 ...
- Docker搭建自己的Gitlab CI Runner
转载自:https://cloud.tencent.com/developer/article/1010595 1.Gitlab CI介绍 CI:持续集成,我们通常使用CI来做一些自动化工作,比如程序 ...
- jquery修改图片src
jquery修改图片src,会重新请求此图片url,重新加载图片.方法如下: $("#<img的id>").attr("src", src_valu ...
- 动态编译库 Natasha 5.0 兼容版本发布
Natasha 5.0 版本已于 2022/10/10 日发布, 此次大版本更迭带来了兼容性支持, 目前 Natasha 可以兼容 standard2.0 及 coreapp3.1 以上版本. 下载使 ...
- el-cascader中最后一级显示为空在前端处理数据
el-cascader中最后一级显示为空是因为从后端接口获取的数据最后一个children为空 <el-cascader :options="treeDeptData" st ...
- LeetCode题目答案及理解汇总(持续更新)
面试算法题 dfs相关 全排列 #include<bits/stdc++.h> using namespace std; const int N = 10; //用一个path数组来存储每 ...
- 『现学现忘』Git分支 — 39、Git中分支与对象的关系
目录 1.Git对象之间的关系 2.提交对象与分支的关系 (1)提交对象与分支的关系 (2)分支说明 (3)HEAD与分支的关系 1.Git对象之间的关系 我们之前学了Git的三个对象:提交对象.树对 ...