# 0365. 水壶问题
## 题目地址(365. 水壶问题)
<https://leetcode-cn.com/problems/water-and-jug-problem/>
## 题目描述
```
<pre class="calibre18">```
有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?
如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。
你允许:
装满任意一个水壶
清空任意一个水壶
从一个水壶向另外一个水壶倒水,直到装满或者倒空
示例 1: (From the famous "Die Hard" example)
输入: x = 3, y = 5, z = 4
输出: True
示例 2:
输入: x = 2, y = 6, z = 5
输出: False
```
```
## BFS(超时)
## 前置知识
- BFS
- 最大公约数
## 公司
- 阿里
- 百度
- 字节
### 思路
两个水壶的水我们考虑成状态,然后我们不断进行倒的操作,改变状态。那么初始状态就是(0 0) 目标状态就是 (any, z)或者 (z, any),其中any 指的是任意升水。
已题目的例子,其过程示意图,其中括号表示其是由哪个状态转移过来的:
0 0 3 5(0 0) 3 0 (0 0 )0 5(0 0) 3 2(0 5) 0 3(0 0) 0 2(3 2) 2 0(0 2) 2 5(2 0) 3 4(2 5) bingo
### 代码
```
<pre class="calibre18">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span>:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">canMeasureWater</span><span class="hljs-params">(self, x: int, y: int, z: int)</span> -> bool:</span>
<span class="hljs-keyword">if</span> x + y < z:
<span class="hljs-keyword">return</span> <span class="hljs-keyword">False</span>
queue = [(<span class="hljs-params">0</span>, <span class="hljs-params">0</span>)]
seen = set((<span class="hljs-params">0</span>, <span class="hljs-params">0</span>))
<span class="hljs-keyword">while</span>(len(queue) > <span class="hljs-params">0</span>):
a, b = queue.pop(<span class="hljs-params">0</span>)
<span class="hljs-keyword">if</span> a ==z <span class="hljs-keyword">or</span> b == z <span class="hljs-keyword">or</span> a + b == z:
<span class="hljs-keyword">return</span> <span class="hljs-keyword">True</span>
states = set()
states.add((x, b))
states.add((a, y))
states.add((<span class="hljs-params">0</span>, b))
states.add((a, <span class="hljs-params">0</span>))
states.add((min(x, b + a), <span class="hljs-params">0</span> <span class="hljs-keyword">if</span> b < x - a <span class="hljs-keyword">else</span> b - (x - a)))
states.add((<span class="hljs-params">0</span> <span class="hljs-keyword">if</span> a + b < y <span class="hljs-keyword">else</span> a - (y - b), min(b + a, y)))
<span class="hljs-keyword">for</span> state <span class="hljs-keyword">in</span> states:
<span class="hljs-keyword">if</span> state <span class="hljs-keyword">in</span> seen:
<span class="hljs-keyword">continue</span>;
queue.append(state)
seen.add(state)
<span class="hljs-keyword">return</span> <span class="hljs-keyword">False</span>
```
```
**复杂度分析**
- 时间复杂度:由于状态最多有O((x+1)∗(y+1))O((x + 1) \* (y + 1))O((x+1)∗(y+1)) 种,因此总的时间复杂度为O(x∗y)O(x \* y)O(x∗y)。
- 空间复杂度:我们使用了队列来存储状态,set 存储已经访问的元素,空间复杂度和状态数目一致,因此空间复杂度是O(x∗y)O(x \* y)O(x∗y)。
上面的思路很直观,但是很遗憾这个算法在 LeetCode 的表现是 TLE(Time Limit Exceeded)。不过如果你能在真实面试中写出这样的算法,我相信大多数情况是可以过关的。
我们来看一下有没有别的解法。实际上,上面的算法就是一个标准的 BFS。如果从更深层次去看这道题,会发现这道题其实是一道纯数学问题,类似的纯数学问题在 LeetCode 中也会有一些,不过大多数这种题目,我们仍然可以采取其他方式 AC。那么让我们来看一下如何用数学的方式来解这个题。
## 数学法 - 最大公约数
### 思路
这是一道关于`数论`的题目,确切地说是关于`裴蜀定理`(英语:Bézout's identity)的题目。
摘自wiki的定义:
```
<pre class="calibre18">```
对任意两个整数 a、b,设 d是它们的最大公约数。那么关于未知数 x和 y的线性丢番图方程(称为裴蜀等式):
ax+by=m
有整数解 (x,y) 当且仅当 m是 d的整数倍。裴蜀等式有解时必然有无穷多个解。
```
```
因此这道题可以完全转化为`裴蜀定理`。还是以题目给的例子`x = 3, y = 5, z = 4`,我们其实可以表示成`3 * 3 - 1 * 5 = 4`, 即`3 * x - 1 * y = z`。我们用a和b分别表示3 升的水壶和5升的水壶。那么我们可以:
- 倒满a(**1**)
- 将a倒到b
- 再次倒满a(**2**)
- 再次将a倒到b(a这个时候还剩下1升)
- 倒空b(**-1**)
- 将剩下的1升倒到b
- 将a倒满(**3**)
- 将a倒到b
- b此时正好是4升
上面的过程就是`3 * x - 1 * y = z`的具体过程解释。
**也就是说我们只需要求出x和y的最大公约数d,并判断z是否是d的整数倍即可。**
### 代码
代码支持:Python3,JavaScript
Python Code:
```
<pre class="calibre18">```
<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span>:</span>
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">canMeasureWater</span><span class="hljs-params">(self, x: int, y: int, z: int)</span> -> bool:</span>
<span class="hljs-keyword">if</span> x + y < z:
<span class="hljs-keyword">return</span> <span class="hljs-keyword">False</span>
<span class="hljs-keyword">if</span> (z == <span class="hljs-params">0</span>):
<span class="hljs-keyword">return</span> <span class="hljs-keyword">True</span>
<span class="hljs-keyword">if</span> (x == <span class="hljs-params">0</span>):
<span class="hljs-keyword">return</span> y == z
<span class="hljs-keyword">if</span> (y == <span class="hljs-params">0</span>):
<span class="hljs-keyword">return</span> x == z
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">GCD</span><span class="hljs-params">(a, b)</span>:</span>
smaller = min(a, b)
<span class="hljs-keyword">while</span> smaller:
<span class="hljs-keyword">if</span> a % smaller == <span class="hljs-params">0</span> <span class="hljs-keyword">and</span> b % smaller == <span class="hljs-params">0</span>:
<span class="hljs-keyword">return</span> smaller
smaller -= <span class="hljs-params">1</span>
<span class="hljs-keyword">return</span> z % GCD(x, y) == <span class="hljs-params">0</span>
```
```
JavaScript:
```
<pre class="calibre18">```
<span class="hljs-title">/**
* @param {number} x
* @param {number} y
* @param {number} z
* @return {boolean}
*/</span>
<span class="hljs-keyword">var</span> canMeasureWater = <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">x, y, z</span>) </span>{
<span class="hljs-keyword">if</span> (x + y < z) <span class="hljs-keyword">return</span> <span class="hljs-params">false</span>;
<span class="hljs-keyword">if</span> (z === <span class="hljs-params">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-params">true</span>;
<span class="hljs-keyword">if</span> (x === <span class="hljs-params">0</span>) <span class="hljs-keyword">return</span> y === z;
<span class="hljs-keyword">if</span> (y === <span class="hljs-params">0</span>) <span class="hljs-keyword">return</span> x === z;
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">GCD</span>(<span class="hljs-params">a, b</span>) </span>{
<span class="hljs-keyword">let</span> min = <span class="hljs-params">Math</span>.min(a, b);
<span class="hljs-keyword">while</span> (min) {
<span class="hljs-keyword">if</span> (a % min === <span class="hljs-params">0</span> && b % min === <span class="hljs-params">0</span>) <span class="hljs-keyword">return</span> min;
min--;
}
<span class="hljs-keyword">return</span> <span class="hljs-params">1</span>;
}
<span class="hljs-keyword">return</span> z % GCD(x, y) === <span class="hljs-params">0</span>;
};
```
```
实际上求最大公约数还有更好的方式,比如辗转相除法:
```
<pre class="calibre18">```
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">GCD</span><span class="hljs-params">(a, b)</span>:</span>
<span class="hljs-keyword">if</span> b == <span class="hljs-params">0</span>: <span class="hljs-keyword">return</span> a
<span class="hljs-keyword">return</span> GCD(b, a % b)
```
```
**复杂度分析**
- 时间复杂度:O(log(max(a,b)))O(log(max(a, b)))O(log(max(a,b)))
- 空间复杂度:空间复杂度取决于递归的深度,因此空间复杂度为 O(log(max(a,b)))O(log(max(a, b)))O(log(max(a,b)))
## 关键点分析
- 数论
- 裴蜀定理
更多题解可以访问我的LeetCode题解仓库:<https://github.com/azl397985856/leetcode> 。 目前已经接近30K star啦。
大家也可以关注我的公众号《脑洞前端》获取更多更新鲜的LeetCode题解
![](https://img.kancloud.cn/77/1d/771d6f53e2a51febbcb6fa97f2899ac3_1586x578.jpg)
- Introduction
- 第一章 - 算法专题
- 数据结构
- 基础算法
- 二叉树的遍历
- 动态规划
- 哈夫曼编码和游程编码
- 布隆过滤器
- 字符串问题
- 前缀树专题
- 《贪婪策略》专题
- 《深度优先遍历》专题
- 滑动窗口(思路 + 模板)
- 位运算
- 设计题
- 小岛问题
- 最大公约数
- 并查集
- 前缀和
- 平衡二叉树专题
- 第二章 - 91 天学算法
- 第一期讲义-二分法
- 第一期讲义-双指针
- 第二期
- 第三章 - 精选题解
- 《日程安排》专题
- 《构造二叉树》专题
- 字典序列删除
- 百度的算法面试题 * 祖玛游戏
- 西法的刷题秘籍】一次搞定前缀和
- 字节跳动的算法面试题是什么难度?
- 字节跳动的算法面试题是什么难度?(第二弹)
- 《我是你的妈妈呀》 * 第一期
- 一文带你看懂二叉树的序列化
- 穿上衣服我就不认识你了?来聊聊最长上升子序列
- 你的衣服我扒了 * 《最长公共子序列》
- 一文看懂《最大子序列和问题》
- 第四章 - 高频考题(简单)
- 面试题 17.12. BiNode
- 0001. 两数之和
- 0020. 有效的括号
- 0021. 合并两个有序链表
- 0026. 删除排序数组中的重复项
- 0053. 最大子序和
- 0088. 合并两个有序数组
- 0101. 对称二叉树
- 0104. 二叉树的最大深度
- 0108. 将有序数组转换为二叉搜索树
- 0121. 买卖股票的最佳时机
- 0122. 买卖股票的最佳时机 II
- 0125. 验证回文串
- 0136. 只出现一次的数字
- 0155. 最小栈
- 0167. 两数之和 II * 输入有序数组
- 0169. 多数元素
- 0172. 阶乘后的零
- 0190. 颠倒二进制位
- 0191. 位1的个数
- 0198. 打家劫舍
- 0203. 移除链表元素
- 0206. 反转链表
- 0219. 存在重复元素 II
- 0226. 翻转二叉树
- 0232. 用栈实现队列
- 0263. 丑数
- 0283. 移动零
- 0342. 4的幂
- 0349. 两个数组的交集
- 0371. 两整数之和
- 0437. 路径总和 III
- 0455. 分发饼干
- 0575. 分糖果
- 0874. 模拟行走机器人
- 1260. 二维网格迁移
- 1332. 删除回文子序列
- 第五章 - 高频考题(中等)
- 0002. 两数相加
- 0003. 无重复字符的最长子串
- 0005. 最长回文子串
- 0011. 盛最多水的容器
- 0015. 三数之和
- 0017. 电话号码的字母组合
- 0019. 删除链表的倒数第N个节点
- 0022. 括号生成
- 0024. 两两交换链表中的节点
- 0029. 两数相除
- 0031. 下一个排列
- 0033. 搜索旋转排序数组
- 0039. 组合总和
- 0040. 组合总和 II
- 0046. 全排列
- 0047. 全排列 II
- 0048. 旋转图像
- 0049. 字母异位词分组
- 0050. Pow(x, n)
- 0055. 跳跃游戏
- 0056. 合并区间
- 0060. 第k个排列
- 0062. 不同路径
- 0073. 矩阵置零
- 0075. 颜色分类
- 0078. 子集
- 0079. 单词搜索
- 0080. 删除排序数组中的重复项 II
- 0086. 分隔链表
- 0090. 子集 II
- 0091. 解码方法
- 0092. 反转链表 II
- 0094. 二叉树的中序遍历
- 0095. 不同的二叉搜索树 II
- 0096. 不同的二叉搜索树
- 0098. 验证二叉搜索树
- 0102. 二叉树的层序遍历
- 0103. 二叉树的锯齿形层次遍历
- 105. 从前序与中序遍历序列构造二叉树
- 0113. 路径总和 II
- 0129. 求根到叶子节点数字之和
- 0130. 被围绕的区域
- 0131. 分割回文串
- 0139. 单词拆分
- 0144. 二叉树的前序遍历
- 0150. 逆波兰表达式求值
- 0152. 乘积最大子数组
- 0199. 二叉树的右视图
- 0200. 岛屿数量
- 0201. 数字范围按位与
- 0208. 实现 Trie (前缀树)
- 0209. 长度最小的子数组
- 0211. 添加与搜索单词 * 数据结构设计
- 0215. 数组中的第K个最大元素
- 0221. 最大正方形
- 0229. 求众数 II
- 0230. 二叉搜索树中第K小的元素
- 0236. 二叉树的最近公共祖先
- 0238. 除自身以外数组的乘积
- 0240. 搜索二维矩阵 II
- 0279. 完全平方数
- 0309. 最佳买卖股票时机含冷冻期
- 0322. 零钱兑换
- 0328. 奇偶链表
- 0334. 递增的三元子序列
- 0337. 打家劫舍 III
- 0343. 整数拆分
- 0365. 水壶问题
- 0378. 有序矩阵中第K小的元素
- 0380. 常数时间插入、删除和获取随机元素
- 0416. 分割等和子集
- 0445. 两数相加 II
- 0454. 四数相加 II
- 0494. 目标和
- 0516. 最长回文子序列
- 0518. 零钱兑换 II
- 0547. 朋友圈
- 0560. 和为K的子数组
- 0609. 在系统中查找重复文件
- 0611. 有效三角形的个数
- 0718. 最长重复子数组
- 0754. 到达终点数字
- 0785. 判断二分图
- 0820. 单词的压缩编码
- 0875. 爱吃香蕉的珂珂
- 0877. 石子游戏
- 0886. 可能的二分法
- 0900. RLE 迭代器
- 0912. 排序数组
- 0935. 骑士拨号器
- 1011. 在 D 天内送达包裹的能力
- 1014. 最佳观光组合
- 1015. 可被 K 整除的最小整数
- 1019. 链表中的下一个更大节点
- 1020. 飞地的数量
- 1023. 驼峰式匹配
- 1031. 两个非重叠子数组的最大和
- 1104. 二叉树寻路
- 1131.绝对值表达式的最大值
- 1186. 删除一次得到子数组最大和
- 1218. 最长定差子序列
- 1227. 飞机座位分配概率
- 1261. 在受污染的二叉树中查找元素
- 1262. 可被三整除的最大和
- 1297. 子串的最大出现次数
- 1310. 子数组异或查询
- 1334. 阈值距离内邻居最少的城市
- 1371.每个元音包含偶数次的最长子字符串
- 第六章 - 高频考题(困难)
- 0004. 寻找两个正序数组的中位数
- 0023. 合并K个升序链表
- 0025. K 个一组翻转链表
- 0030. 串联所有单词的子串
- 0032. 最长有效括号
- 0042. 接雨水
- 0052. N皇后 II
- 0084. 柱状图中最大的矩形
- 0085. 最大矩形
- 0124. 二叉树中的最大路径和
- 0128. 最长连续序列
- 0145. 二叉树的后序遍历
- 0212. 单词搜索 II
- 0239. 滑动窗口最大值
- 0295. 数据流的中位数
- 0301. 删除无效的括号
- 0312. 戳气球
- 0335. 路径交叉
- 0460. LFU缓存
- 0472. 连接词
- 0488. 祖玛游戏
- 0493. 翻转对
- 0887. 鸡蛋掉落
- 0895. 最大频率栈
- 1032. 字符流
- 1168. 水资源分配优化
- 1449. 数位成本和为目标值的最大数字
- 后序