前不久,一位朋友向我推薦了一款名爲《Quixel》(中文名《快速像素》)的遊戲,遊戲界面如圖:
遊戲規則如下:
1.用手指點按方格,遊戲認爲該方格被塗黑;
2.每行/列中的數字,爲該行/列中連續塗黑的格子的數量(比如出現3,則爲該行/列中有3個連續塗黑的格子;如果出現22,則爲先有兩個連續的格子,然後中間空出至少1個格子後,再有兩個連續塗黑的格子,3個數字的以此類推);
3.當塗黑的格子滿足所有行列的數字要求時,則認爲是勝利,遊戲則顯示出塗黑格子所組成的圖案,下圖爲上一張圖過關的結果:
當然,這種5*5的格子是很簡單的,可是我的朋友給我分享這個遊戲的時候,已經玩到400多關了,是一個10*10的大棋盤,難度更高。我對這種解迷類遊戲還是比較感興趣的,於是研究了一會,得出一個結論:如果我能把所有的格子從全白到全部塗黑都過一遍,那一定有一個結果的。然後便突然想到:咦?這不就是枚舉嘛!所以我就想,是不是我可以寫一個程序,遍歷出所有結果,然後用電腦去計算出一個正確的結果呢?
說幹就幹,那首先要做的,就是提煉遊戲的規則,然後根據規則,去進行編程,於是規則提煉如下:
1.格子有兩種狀態,塗黑與不塗黑,這裏我對應爲數字1和0;
2.被塗黑的格子要同時滿足行列上的要求。
規則提煉好以後,接下來就是做了,當然最簡單也是最笨的方法,就是從格子全部爲0循環到全部爲1,然後嘛,總有一款適合你~言歸正傳,上面那種方法消耗的時間過大,所以不予考慮,於是經過一番思考,我想出了一個比較容易實現的算法:
1.將棋盤視爲二維數組,數組中每個元素只有0和1兩種狀態;
2.先計算出每一行中符合要求的一維數組,分別放入一個小容器中,然後將小容器放到一個大容器中,這樣,小容器存放的是每行符合要求的一維數組,而大容器,則可以理解爲是整個棋盤,放的是所有行的可能;
3.依次從小容器中取出一條記錄,然後進行排列組合,按列取,去校驗是否符合列上的數字,如果每一列都驗證通過,則停止排列組合,輸出該二維數組。
寫到這裏,相信很多人已經有思路了,只要去按照上面的規則去完成就好,那麼這裏我在做的時候還遇到一個小問題,那就是,如果我的棋盤只是5*5,或僅僅是10*10,我只要有多少行,就去寫多少個for循環的嵌套就好,但實際情況是,隨着關數的增加,棋盤也會越來越大,所以在如何動態進行for循環的嵌套,我給出的方案是——遞歸。
好了,說到這裏,基本上邏輯的東西就完成了,下面我把代碼貼一下,大家感興趣的可以看一下,如果哪位朋友有更優化的解決方案,也歡迎提出,我這裏就算是拋磚引玉啦
public class mainLogic {
//橫行數
private static int[] xArray = {2, 3, 3, 4, 6, 5, 5, 23, 6, 4};
//豎列數
private static int[] yArray = {2, 4, 22, 6, 6, 5, 3, 5, 5, 3};
private static int CURRENT_COUNT = 0;
private static boolean IS_FIND_RESULT = false;
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
public static void main(String[] args) {
int max_Length = 10; //行數
Date startTime = new Date();
System.out.println("開始計算滿足要求的橫列數組集合");
List<List<String>> allXList = getXLists(max_Length);
System.out.println("計算完成------" + dateFormat.format(new Date()));
List<String[][]> resultList = new ArrayList<>();
String[][] resultArray = new String[max_Length][max_Length];
int sumNum = getSumNum(allXList);
System.out.println("開始驗證數據,共計驗證" + sumNum + "種組合------" + dateFormat.format(new Date()));
caluResult(resultArray, allXList, 0, max_Length);
Date endTime = new Date();
System.out.println("共有" +sumNum+"種組合");
displayStringArray(resultArray);
System.out.println("開始時間:" + dateFormat.format(startTime));
System.out.println("結束時間:" + dateFormat.format(endTime));
}
public static int getSumNum(List<List<String>> allList){
int sum = 1;
for (int i=0; i<allList.size(); i++){
int count = allList.get(i).size();
sum *= count;
}
return sum;
}
/**
* 輸出二維數組
* @param binaryString
*/
public static void displayStringArray(String[][] binaryString){
int columns = binaryString[0].length;
for(int j=0; j<columns; j++){
for (int k=0; k<columns; k++){
System.out.print(binaryString[j][k]);
}
System.out.println();
}
}
/**
* 獲取所有橫列中符合要求的橫列數組集合
* @param num 行數/列數/每行二進制字符串的長度
* @return 返回結果爲所有符合橫列要求的集合的集合,有多少行,返回的集合中就有多少個元素
*/
public static List<List<String>> getXLists(int num){
double pow = Math.pow(2.0, Double.valueOf(num));
int decimalNum = new Double(pow).intValue();
List<List<String>> allList = new ArrayList<List<String>>();
for(int i=0; i<num; i++){
//用於存放符合第i個標準數的集合
List<String> xList = new ArrayList<String>();
//如果該行標準數爲0,則記錄爲全部爲0的字符串
if (xArray[i] == 0){
String zeroString = "";
for (int j=0; j<num; j++){
zeroString += "0";
}
xList.add(zeroString);
}
else {
for (int j = 0; j < decimalNum; j++) {
String binaryString = padLeft(j, num);
if (check(binaryString, xArray[i])) {
xList.add(binaryString);
}
}
}
allList.add(xList);
}
return allList;
}
public static void caluResult(String[][] resultArray, List<List<String>> allList, int currentLevel, int sumLevel){
if (currentLevel == sumLevel){
CURRENT_COUNT++;
System.out.println("當前完成第" + CURRENT_COUNT + "種排列組合------" + dateFormat.format(new Date()));
boolean isPass = stepCheck(resultArray, sumLevel, yArray);
if (isPass){
System.out.println("符合要求");
IS_FIND_RESULT = true;
}
else {
System.out.println("不符合要求");
}
return;
}
for (int i=0; i<allList.get(currentLevel).size(); i++){
if (IS_FIND_RESULT){
break;
}
//因爲二維數組爲n*n數組,所以可以用層數=列數,可以直接使用,n*m數組則不可直接使用
for (int j=0; j<sumLevel; j++){
resultArray[currentLevel][j] = allList.get(currentLevel).get(i).substring(j, j+1);
}
int newLevel = currentLevel + 1;
caluResult(resultArray, allList, newLevel, sumLevel);
}
}
/**
* 十進制數轉爲二進制數並補充0
* @param decimalNum 輸入的十進制數
* @param stringLength 輸出的字符串長度
* @return
*/
public static String padLeft(int decimalNum, int stringLength){
char[] chars = new char[stringLength];
for (int i=0; i<stringLength; i++){
chars[stringLength - 1 - i] = (char)((decimalNum >> i & 1) + '0');
}
return new String(chars);
}
/**
* 該方法用於校驗數組是否符合規定
* 比如標準數爲32,則規則爲,有三個連續的1,在若干個位置後,還有兩個連續的1,最後是若干個0
* 比如 0001110110 符合規則, 01110110100不符合規則
* @param binaryString 01字符串
* @param standrdNum 標準數
* @return
*/
public static boolean check(String binaryString, int standrdNum){
List<Integer> standrdList = new ArrayList<Integer>();
String standrdString = String.valueOf(standrdNum);
for (int i=0; i<standrdString.length(); i++){
standrdList.add(Integer.valueOf(standrdString.substring(i, i+1)));
}
//用於記錄遍歷01字符串的位置
int currentIndex = 0;
//取出第i位,判斷當前需要有多少個連續的1
boolean isEqualsToStand = true;
for (int i=0; i<standrdString.length(); i++){
//記錄是否爲第一次檢查到01數組裏的"1"
boolean isFirstTime = true;
//記錄01數組中當前連續的1的數量
int countOfOne = 0;
for (int j=currentIndex; j<binaryString.length(); j++){
//如果當前沒有遍歷到"1",則跳到下一位
String tempString = binaryString.substring(j, j+1);
if (isFirstTime && binaryString.substring(j, j+1).equals("0")){
currentIndex++;
continue;
}
//如果已經遍歷到"1",但當前位置爲"0",則結束
if (!isFirstTime && binaryString.substring(j, j+1).equals("0")){
break;
}
//遍歷到"1",計數器+1,並設定爲已經遍歷到1
else{
currentIndex++;
isFirstTime = false;
countOfOne++;
}
}
//如果符合當前規定的連續的"1"的數量,則繼續
if (countOfOne == standrdList.get(i)){
continue;
}
//如果不符合,則退出
else{
isEqualsToStand = false;
break;
}
}
//判斷剩下的字符串是否全部爲0
if (currentIndex < binaryString.length()){
for (int i=currentIndex; i<binaryString.length(); i++){
if (binaryString.substring(i, i+1).equals("1")){
isEqualsToStand = false;
break;
}
}
}
return isEqualsToStand;
}
/**
* 對生成對每一個二維數組進行校驗
* @param unCheckStringArray
* @param columns
* @param yArray
* @return
*/
public static boolean stepCheck(String[][] unCheckStringArray, int columns, int[] yArray){
boolean isPass = true;
for (int i=0; i<columns; i++){
String binaryString = "";
//對待檢查待二維數組進行按列取操作
for(int j=0; j< columns; j++){
binaryString += unCheckStringArray[j][i];
}
boolean columnResult = check(binaryString, yArray[i]);
if (!columnResult){
isPass = false;
break;
}
}
return isPass;
}
}
貼一下輸出結果:
其實從結果不難看出,對於10*10的數組,計算量還是很大的,所以目前來說,只能算是一個半成品,優化的空間一定存在,如果有哪位朋友有更好的想法,歡迎提出~