有一个3升水壶和一个5升水壶, 如何精确取出4升水?
问题背景
这个问题来自于电影Die Hard Ⅲ(虎胆龙威3) 片段, 剧中, 主人公必须在规定时间内使用3加仑水桶和5加仑水桶盛出4加仑水, 否则, 恐怖分子将引爆炸弹
为了方便以下探讨问题使用单位“升”
那么如何使用这两个水桶盛水呢?
以下是一个参考方案(用一个数对表示两个水桶的水量)假设最开始为**(0,0)**
- 大桶装满5升水 (0,5)
- 把5升水倒进3升水桶里, 留下2升 (3,2)
- 倒空小桶的水(不需要了) (0,2)
- 把2升水转移到小桶里 (2,0)
- 加满大桶 (2,5)
- 从大桶往小桶加水直至小桶正好填满, 此时大桶剩余4升 (3,4)
将这个问题一般化
假设有两个水桶容量为x
和y
升, 请问能否准确的到target
升的水
由于没有精确测量体积的工具, 所以我们只有如下操作
- 装满任意一个水壶
- 清空任意一个水壶
- 将一个水壶倒入另一个水壶, 直至水壶已满, 或者水壶已空
解答思路
最开始的状态为(0,0)
然后对于最开始的状态可以进行的所有操作全部模拟一遍, 并记录操作之后的所有状态
对于产生的每一个状态, 都重复以上操作, 将所有可能的操作全部模拟一遍
如果遇到了之前已经遇到过的状态, 就不用重复模拟一遍, 直接跳过
对于每一个状态, 操作是重复的, 那么应该使用递归还是栈呢?
由于无法预估到底多少次能够找到答案, 所以使用递归方式比较危险, 容易导致栈溢出, 因此使用栈来模拟递归, 使用栈保存水桶状态, 同时, 在增减水的过程中, 会出现重复的状态, 用集合保存所有模拟过的状态, 遇到集合中存在的状态就直接跳过
返回值为布尔值, 表示是否可以达成目标
具体代码实现
1 |
|
几个小问题
1. set判断的是vector 对象的地址还是值
C++中, set会判断vector
的值, 自动比对是否一致, 但是在Java中会比较对象的地址, 此时想使用的set中的contains()方法的话, 可以使用如下方法
1 |
|
2. 为什么重写equals()
方法时一定要重写hashCode()
方法呢?
在Java中,
HashMap
和HashSet
类需要使用hashCode
值确定对象在内存中的位置, 如果要在HashMap
和HashSet
对象中寻找是否存在一个对象, 那么首先会检查对象的hashCode
值存不存在, 然后再调用equals()
方法来比较和已经存在的对象是否相等如果重写了
equals()
方法时没有重写hashCode()
方法, 那么在比较时压根找不到集合中已经存在的相同的对象, 导致程序认为集合中不存在要找的对象
Objects.hash()
方法的参数是若干个Object对象
它的定义是public static int hash(Object... values)
参考链接
3. 递归多少层会栈溢出呢?
递归的层数导致栈溢出的具体限制因系统而异, 但通常在几千到几百万层之间这取决于计算机的内存大小、操作系统和编程语言的实现方式
为了避免栈溢出, 可以考虑以下方法
- 优化递归算法:使用尾递归, 以减少栈帧的累积
- 增加栈大小:某些编程语言和环境允许你手动设置栈的大小如果递归算法需要更深的栈, 可以尝试增加栈的大小
- 使用循环:修改算法的具体实现方法, 改成使用循环迭代思考具体的循环条件, 或者使用直接用栈模拟递归过程
4. 什么是尾递归
在递归时, 通常会发生, a函数在自己内部调用了2次或者多次a函数本身, 此时递归使用的系统栈空间称指数增加, 容易发生栈溢出, 所以如果只调用一次的话, 可以减少一定的系统栈的占用, 此时如果调用语句时
return
语句, 那么a函数调用自己之后就无需保留自己的状态, 系统也不需要为程序保留旧的a函数, 只需要管理新的a函数即可, 大大减少了系统栈的开销给尾递归下一个定义, 指一个函数在调用自身之后不再执行任何其他操作,而是将返回值直接传递给函数调用的上级
下面演示一下如何将一个不是尾递归的函数改造成尾递归
改造前
1 |
|
改造后
1 |
|