原题来自某公司的一道面试题:
You have four colored cubes. Each side of each cube is a single color,
and there are four colors: blue (B), red (R), green (G) and yellow (Y)
Describing the six faces as front, back, left, right, top, bottom, the
cube colors are:
Cube Front Back Left Right Top Bottom
1 R B G Y B Y
2 R G G Y B B
3 Y B R G Y R
4 Y G B R R R
The objective is to find ways to stack the four cubes as a vertical
column so that each side of the column is showing all four colors.
Use the language of your choice to write a program to find all
successful permutations.
大概意思是说给定4个指定好颜色的立方体,从第1个立方体开始垂直叠堆起来,即叠堆成拥有4层的垂直立方体,叠堆起来的垂直立方体需要确保每4面(4面即:左、前、右后,上和下这2面可以不考虑)都有不同的颜色。
思路解决:回溯尝试(迭代或者递归)法去尝试枚举所有情况直到满足条件。
关键点:
1、每个立方体共有4*4=16种翻转情况,需要存储下每个立方体的16中翻转情况,便于在不满足条件时换下一种翻转情况尝试;
2、在尝试当前层玩所有翻转情况后依旧不满足条件,则需要有一种回溯机制(即回退到原来的地方),继续尝试下一种可能;
3、定义了一个立方体类(Cube),属性关键的包括左、前、右、后、上、下这6个面的颜色,还有一个包括了16种翻转情况的Cube列表;然后再判断相邻2层的立方体,当有某一面的颜色相同时,就代表不满足条件,否则就代表满足条件;
具体实现代码(java)如下,分别使用了迭代和递归的方式去求解问题。
public class Test { private static final Cube[] cubes = { new Cube(Color.R, Color.B, Color.G, Color.Y, Color.B, Color.Y), new Cube(Color.R, Color.G, Color.G, Color.Y, Color.B, Color.B), new Cube(Color.Y, Color.B, Color.R, Color.G, Color.Y, Color.R), new Cube(Color.Y, Color.G, Color.B, Color.R, Color.R, Color.R), }; static{ for(Cube cube:cubes){ cube.generateRotations(); } } private enum Color { B, R, G, Y; } private static class Cube { private final Color front; private final Color back; private final Color left; private final Color right; private final Color top; private final Color bottom; private String name = ""; private List<Cube> rotations; private int rotationIndex; public Cube(Color front, Color back, Color left, Color right, Color top, Color bottom) { this.front = front; this.back = back; this.left = left; this.right = right; this.top = top; this.bottom = bottom; } public void resetRotation() { rotationIndex = 0; } public boolean hasRotation() { return rotationIndex < rotations.size(); } public Cube rotation() { Cube result = rotations.get(rotationIndex); rotationIndex++; //System.out.println(nextIndex/*+":"+result*/); return result; } public boolean isOneSideColorSame(Cube cube){ return front == cube.front || back == cube.back || left == cube.left || right == cube.right; } @Override public String toString() { // return "Cube [front=" + front + ", back=" + back + ", left=" + left + ", right=" + right + ", top=" + top // + ", bottom=" + bottom + "]"; return "Cube [top=" + top + ", bottom=" + bottom + ", front=" + front + ", back=" + back + ", left=" + left + ", right=" + right + "] "+name; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((back == null) ? 0 : back.hashCode()); result = prime * result + ((bottom == null) ? 0 : bottom.hashCode()); result = prime * result + ((front == null) ? 0 : front.hashCode()); result = prime * result + ((left == null) ? 0 : left.hashCode()); result = prime * result + ((right == null) ? 0 : right.hashCode()); result = prime * result + ((top == null) ? 0 : top.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Cube other = (Cube) obj; if (back != other.back) return false; if (bottom != other.bottom) return false; if (front != other.front) return false; if (left != other.left) return false; if (right != other.right) return false; if (top != other.top) return false; return true; } public void generateRotations() { Set<Cube> rotationSet = new LinkedHashSet<Cube>(); int i = 0; Cube current = null; do { current = i == 0 ? this : current.turnLeft90Degree(); if(rotationSet.add(current)){ current.name = i > 0 ? i + " left90Degree" : ""; //System.out.println("i:"+i+" add "+left90Degree); } Cube down90Degree = current; for(int j=0;j<3;j++){ //内嵌的循环为3是为了减去一次不必要的重复情况 ,因为第一个面在之前已经add了 down90Degree = down90Degree.turnDown90Degree(); if(rotationSet.add(down90Degree)){ down90Degree.name = (i > 0 ? i + " left90Degree and " : "") + (j + 1) + " down90Degree"; //System.out.println("i:"+i+",j:"+j+" add "+down90Degree); } } i++; } while (rotationSet.size() < 16); //立方体总的旋转共能产生4×4=16种情况 rotations = new ArrayList<Cube>(rotationSet); } public Cube turnLeft90Degree(){ return new Cube(right, left, front, back, top, bottom); } public Cube turnDown90Degree(){ return new Cube(top, bottom, left, right, back, front); } } public static void main(String[] args) { solveByIterator(); resetCubesRecursion(); solveByRecursion(); //print4CubeRotations(); } private static void solveByIterator(){ print(solveByIterator(4), "solveByIterator resolution"); } private static List<Cube> solveByIterator(final int level){ List<Cube> result = new ArrayList<Cube>(); boolean isAddNextCube = false; Cube currentCube = cubes[0].rotation(); for(int i=0;i<level;){ if(isAddNextCube){ currentCube = cubes[i].rotation(); isAddNextCube = false; } if(addCube(result, currentCube)){ i++; isAddNextCube = true; }else{ while(!cubes[i].hasRotation()){ //当此层立方体的所有旋转情况都试过后依旧无解 cubes[i].resetRotation(); //重置此层的旋转情况,为下次拿到下一个旋转情况做准备 i--; //返回上一层 if(i < 0){ throw new IllegalArgumentException(level+"层四色立方体无解!"); } result.remove(i); //因为下一层所有情况都尝试依旧无解,故重新清除之前已经存储的的立方体情况 } currentCube = cubes[i].rotation(); //拿到下一个旋转情况再尝试 //失败的话拿另一个立方体尝试,这里没有必要,因为假设立方柱的叠放所拿的立方体顺序为1、2、3、4 } } return result; } private static void resetCubesRecursion(){ for (Cube cube : cubes) { cube.resetRotation(); } } private static void solveByRecursion(){ List<Cube> result = new ArrayList<Cube>(); final int level = 4; if(!solveByRecursion(level, cubes, cubes[0].rotation(), result)){ throw new IllegalArgumentException(level+"层四色立方体无解!"); } print(result, "solveByRecursion resolution"); } private static boolean solveByRecursion(final int level, Cube[] cubes, Cube current, List<Cube> result){ if(!tryAddCubeIterate(current, result)){ return false; } if(result.size() == level){ return true; } if(!solveByRecursion(level, cubes, cubes[result.size()].rotation(), result)){ result.remove(result.size()-1); //返回上一层 if(!cubes[result.size()].hasRotation()){ cubes[result.size()].resetRotation(); return false; } return solveByRecursion(level, cubes, cubes[result.size()].rotation(), result); } return true; } /** 尝试该层的所有翻转情况,还不行则重置此层的旋转情况,为下次拿到下一个旋转情况做准备 */ private static boolean tryAddCubeIterate(Cube current, List<Cube> result){ boolean isAddSucc = addCube(result, current); while(!isAddSucc){ if(!cubes[result.size()].hasRotation()){ cubes[result.size()].resetRotation(); return false; } isAddSucc = addCube(result, cubes[result.size()].rotation()); } return true; } private static boolean addCube(List<Cube> result, Cube cube){ if(!isSatisfy(result, cube)){ return false; } return result.add(cube); } private static boolean isSatisfy(List<Cube> result, Cube cube) { if(result.isEmpty()){ return true; } for(Cube ele:result){ if(ele.isOneSideColorSame(cube)){ return false; } } return true; } private static void print(List<Cube> result, String solveType) { System.out.println("----------------------------------"+solveType+"----------------------------------"); for(int i=0;i<result.size();i++){ Cube ele = result.get(i); System.out.println((i+1)+"_"+ele); } System.out.println("----------------------------------"+solveType+"----------------------------------"); } private static void print4CubeRotations() { for (Cube cube : cubes) { System.out.println(cube.rotations.size()); for (Cube c : cube.rotations) { System.out.println(c); } } } }
代码在求解出一种情况后就会退出,并打印该情况下4个立方体的6个面和到达该面需要做的翻转:
----------------------------------solveByIterator resolution---------------------------------- 1_Cube [top=G, bottom=Y, front=B, back=Y, left=R, right=B] 1 left90Degree and 1 down90Degree 2_Cube [top=B, bottom=B, front=Y, back=G, left=G, right=R] 3 left90Degree and 2 down90Degree 3_Cube [top=R, bottom=Y, front=G, back=R, left=B, right=Y] 3 left90Degree and 2 down90Degree 4_Cube [top=R, bottom=R, front=R, back=B, left=Y, right=G] 1 left90Degree ----------------------------------solveByIterator resolution---------------------------------- ----------------------------------solveByRecursion resolution---------------------------------- 1_Cube [top=G, bottom=Y, front=B, back=Y, left=R, right=B] 1 left90Degree and 1 down90Degree 2_Cube [top=B, bottom=B, front=Y, back=G, left=G, right=R] 3 left90Degree and 2 down90Degree 3_Cube [top=R, bottom=Y, front=G, back=R, left=B, right=Y] 3 left90Degree and 2 down90Degree 4_Cube [top=R, bottom=R, front=R, back=B, left=Y, right=G] 1 left90Degree ----------------------------------solveByRecursion resolution----------------------------------
求解出1种解的情况后,能横向翻转和纵向翻转,实际就有4*2=8种解的情况了。