字节跳动一道笔试题让我纠结了许久
题目如下
给你3个无刻度的水桶,问能不能弄出k升的水,如果能,最小需要多少步,不能打印no,
输入:4个数,a代表水桶一容量,b水桶二,c水桶3,k要称出的容量
输出:no或者最少次数
例如:8,3,5,4 输出6
[0,0,5] -> [0,3,2] -> [0,0,2] -> [0,2,0] -> [0,2,5] -> [0,3,4]
例如: 8,4,6,5 输出no
因为偶数不能称出奇数
基础知识
以前做过这样类似的题,如何判断能称出来呢,存在这样的x,y,z整数使得
k = x * a+y * b+z * c;
之前想遍历出a,b,c从一个范围到另一个范围,得出x,y,z 根据x,y,z,来判断多少次,可是我根本就判断不出来
第二次解决方法
广义优先和动态规划
* 每个桶都有两个选择,倒水和加水,利用广义优先,记录出第一步所有的3个桶状态
* 第二步在第一步基础上接着这样做,直到出现k升水
* 为了防止出现重复的,我们必须存下每次操作所有桶出现的状态,每一次操作判断该状态是否存在
* 存在就不做了,所以,查询操作是极度多的,优化查找是优化这个算法的核心
* 我们利用HashMap存储,k是状态,v是次数,为了让出现的桶状态如4,7,8 和后续出现的为一个我们
* 将状态用一个3位数表示,即a100+b10+c表示。
* 每次出现一个这样的数,判断包含否,不包含加入hashtable
代码有详细的解释
这个需要极大细心,每步都很相似又有些不同,思路可以,调试了半天
/**
* @time201.09.10
* @author 明行
*题目:给3个无刻度水桶,a,b,c,能否称出k升水,如果能输入最小多少次,不能输出no
* 输入,4个数,分别是a,b,c,k 输出no或者最小次数
*/
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Scanner;
public class Get_Water {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int a = sc.nextInt();
int b = sc.nextInt();
int c = sc.nextInt();
int k = sc.nextInt();
// 先作预判断看能不能称出来
if(k > a + b + c) System.out.println("no");
else {
//如果3个桶都是偶数,称奇数水,则不能
if(((a&1)==0) && ((b&1)==0) && ((c&1)==0) && ((k&1)==1)) {
System.out.println("no");
}
else {
getResult(a,b,c,k);
}
}
}
/*解题思路
* 广义优先和动态规划
* 每个桶都有两个选择,倒水和加水,利用广义优先,记录出第一步所有的3个桶状态
* 第二步在第一步基础上接着这样做,直到出现k升水
* 为了防止出现重复的,我们必须存下每次操作所有桶出现的状态,每一次操作判断该状态是否存在
* 存在就不做了,所以,查询操作是极度多的,优化查找是优化这个算法的核心
* 我们利用HashMap存储,k是状态,v是次数,为了让出现的桶状态如4,7,8 和后续出现的为一个我们
* 将状态用一个3位数表示,即a*100+b*10+c表示。
* 每次出现一个这样的数,判断包含否,不包含加入hashtable
*/
private static void getResult(int a, int b, int c, int k) {
/* 选择HashMap原因,其实后面发现选择HashSet更好
HashTable较适应于多线程 但被淘汰了改用ConCurrentHashMap
* 对于集合里的值增删查和get的时间复杂度分析:顺序增删查改,_ 代表没有该方法获无意义
* Array 数组 O(n) O(n) O(n) O(1)
Listed list 链表 O(n) O(n) O(n) O(1)
HashMap 哈希表 O(1) O(1) O(1) _
HashTable 哈希表 O(1) O(1) O(1) _
TreeMap 红黑树有序 O(logn) O(logn) O(logn)
TreeSet 红黑树有序 O(logn) O(logn) O(logn)
HashSet 哈希表 O(1) O(1) O(1) _
LinkedHashSe 链表实现的哈希表 O(1) O(1) O(1) _
*/
/* 动态规划加广义优先,所以应该用队列来做。*/
HashMap<Integer,Integer> map = new HashMap<>();
LinkedList<int[]> q = new LinkedList<>();
int[] csh = new int[3]; //用一个长度为3的数组记录每个桶的状态
map.put(0, 0); // 放入初始状态
q.offer(csh);
int res = -1;
/* 每一层操作完,步数加一 */
int i = 1; //记录第一层的个数
int step = 0; //记录步数
// 只要q1为空,所有情况都遍历完了
while(!q.isEmpty()) {
// 每次弹出,需要考虑引用类型的问题,所以,做副本
// 难点二,做几个副本,
int[] temp = q.poll(); // temp是开始的基础,每次在他上面做副本,进行下一步操作
// 先判断弹出的节点是否满足情况,满足就可以返回了
if(heli(temp,k)) {
res = step;break;
}
int[] fuben = temp.clone();
// 按顺序对a,b,c3个桶做倒水或加水操作
// 如果为0,只能加水,为a只能倒水
//其他,0-a之间,加水没有意义,所以也是只能到水
//倒水有三个选择,到掉和导入其他两个桶里
/**注:step指的是弹出的节点所用的步数,如果在次基础上多做一步要加一 **/
/**桶一操作**/
if(fuben[0] == 0) {
// 判断map是否存在该状态
int zt = a * 100 + fuben[1] * 10 + fuben[2];
if(!map.containsKey(</