java实现九宫格解锁_Java计算手机九宫格锁屏图案连接9个点的方案总数
(一)問(wèn)題
九宮格圖案解鎖連接9個(gè)點(diǎn)共有多少種方案?
(二)初步思考
可以把問(wèn)題抽象為求滿足一定條件的1-9的排列數(shù)(類似于“八皇后問(wèn)題”),例如123456789和987654321都是合法的(按照從上到下、從左到右、從1到9編號(hào)),解決此類問(wèn)題一般都用遞歸方法,因?yàn)閱?wèn)題規(guī)模較大,且沒(méi)有明確的計(jì)算方法
(三)深度思考
不難想出下面的簡(jiǎn)單思路:
1.先窮舉,再排除不合法結(jié)果(過(guò)濾窮舉的結(jié)果)
大略估計(jì)一下復(fù)雜度,A99=362880(計(jì)算機(jī)應(yīng)該可以接受),方案總數(shù)不超過(guò)A99,也就是說(shuō)窮舉的結(jié)果是A99,再濾去不合法的結(jié)果即可,理論上此方法可行
2.按條件窮舉(在窮舉的過(guò)程中排除不合法結(jié)果)
1>正常思維
---a.選擇起點(diǎn)位置(i,j)
---b.在起點(diǎn)周圍尋找合法終點(diǎn),規(guī)則如下:(共有12個(gè)方向)
------1.上(i - 1, j)--5.左上斜(i - 1, j - 1)---9.左上長(zhǎng)斜(i - 2, j - 1)
------2.下(i + 1, j) -6.左下斜(i + 1, j - 1) -10.左下長(zhǎng)斜(i + 2, j - 1)
------3.左(i, j - 1)--7.右上斜(i - 1, j + 1)--11.右上長(zhǎng)斜(i - 2, j + 1)
------4.右(i, j + 1) -8.右下斜(i + 1, j + 1)-12.右下長(zhǎng)斜(i + 2, j + 1)
---c.記錄路徑(起點(diǎn) + 終點(diǎn))
---d.判滿,若路徑長(zhǎng)度小于9執(zhí)行e步驟,否則執(zhí)行f步驟
---e.終點(diǎn)變起點(diǎn),返回a步驟
---f.輸出結(jié)果(路徑)
2>逆向思維
---a.排除(1.排除已選擇的點(diǎn)2.排除從起點(diǎn)不能到達(dá)的點(diǎn))得到臨時(shí)剩余點(diǎn)集
---b.在臨時(shí)剩余點(diǎn)集中選擇下一個(gè)點(diǎn)
---c.判滿,路徑長(zhǎng)度小于9,執(zhí)行d步驟,否則執(zhí)行e步驟
---d.執(zhí)行a步驟
---e.輸出結(jié)果(路徑)
P.S.正常思維比較容易理解,逆向思維更容易實(shí)現(xiàn)
(四)編碼
[最初想用方案2的逆向思維方案來(lái)實(shí)現(xiàn),結(jié)果失敗了,原因是遞歸內(nèi)嵌循環(huán),程序執(zhí)行軌跡難以捉摸...頭疼良久之后放棄了,改用方案1,簡(jiǎn)單粗暴]
[核心代碼類]
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class ScreenLock {
//將NUM設(shè)置為待排列數(shù)組的長(zhǎng)度即實(shí)現(xiàn)全排列
private int NUM = 0;
private int count = 0;
private String[] strSource;
String string = null;
private static String[] wrongPos = {//各點(diǎn)對(duì)應(yīng)的不能到達(dá)的位置
"379","8","179",
"6","#","4",
"139","2","137"};
public ScreenLock(String[] strSource)
{//strSource格式為以逗號(hào)分隔的數(shù)字串,如1,2,3,4
//初始化
this.strSource = strSource;
this.NUM = strSource.length;
}
public int getCount()
{
sort(Arrays.asList(strSource), new ArrayList());
return count;
}
private void sort(List datas, List target) {
if (target.size() == NUM) {
StringBuilder sb = new StringBuilder();
for (Object obj : target)
sb.append(obj);
String ans = sb.toString();
if(isValid(ans))
count++;
return;
}
for (int i = 0; i < datas.size(); i++) {
List newDatas = new ArrayList(datas);
List newTarget = new ArrayList(target);
newTarget.add(newDatas.get(i));
newDatas.remove(i);
sort(newDatas, newTarget);
}
}
private boolean isValid(String ans)
{//判斷ans是否合法
for(int i = 0;i < ans.length() - 1;i++)
{
//獲取當(dāng)前位置的字符的值
int currPos = Integer.parseInt(ans.charAt(i) + "");
//獲取路徑子串
String path = ans.substring(0, i + 1);
//獲取錯(cuò)誤值
String wrrPos = wrongPos[currPos - 1];
//獲取可變錯(cuò)誤值
if(currPos != 5)//5不可能變
{
for(int j = 0;j < wrrPos.length();j++)
{
//獲取中間值
String wrong = wrrPos.charAt(j) + "";
int mid = (currPos + Integer.parseInt(wrong)) / 2;
//若中間值已被連接,則錯(cuò)誤終點(diǎn)可用
if(path.contains(mid + ""))
wrrPos = wrrPos.replace(wrong, "#");
//若下一位是錯(cuò)誤值則ans不合法
if(wrrPos.contains(ans.charAt(i + 1) + ""))
return false;
}
}
}
return true;
}
}
[測(cè)試類]
public class CountLockPlans {
public static void main(String[] args) {
//計(jì)算手機(jī)九宮格鎖屏圖案連接9個(gè)點(diǎn)的方案總數(shù)
String s = "1,2,3,4,5,6,7,8,9";
String[] str = s.split(",");
ScreenLock lock = new ScreenLock(str);
int num = lock.getCount();
System.out.println("連接9個(gè)點(diǎn)共有 " + num + " 種方案");
}
}
(五)運(yùn)行結(jié)果
連接9個(gè)點(diǎn)共有 62632 種方案【此結(jié)果是錯(cuò)的,詳情見(jiàn)最下方第(十)點(diǎn)】
(六)程序正確性檢驗(yàn)
1.能否生成1-9的全排列?
注釋掉無(wú)關(guān)代碼,直接輸出所有全排列,同時(shí)計(jì)數(shù),結(jié)果無(wú)誤(全部輸出需要17秒左右)
2.isValid()方法是否能夠正確判斷方案的合法性?
單獨(dú)調(diào)用isValid()傳入各種參數(shù)測(cè)試,結(jié)果無(wú)誤
3.算法邏輯是否無(wú)誤?
求排列的同時(shí)過(guò)濾不合法解,邏輯無(wú)誤
[綜上,理論上輸出的結(jié)果是正確的]
(七)答案正確性確認(rèn)
上網(wǎng)找找有沒(méi)有人算出結(jié)果,濾去所有看起來(lái)不靠譜的答案,選定果殼網(wǎng)答案(據(jù)說(shuō)用了Mathematica,乍看高上大)
文章中的解決思路也是:合法方案數(shù) = 全排列總數(shù) - 不合法方案數(shù)
仔細(xì)看過(guò)文章后發(fā)現(xiàn)原文的結(jié)論可能是錯(cuò)的(雖然不知道其具體算法)
1.從原文的貼圖可以看到先求出了方案總數(shù)985 824(這個(gè)我們不必關(guān)心,只需要關(guān)注連接9個(gè)點(diǎn)的計(jì)算結(jié)果就好)
2.原文貼圖記下不能直接連的點(diǎn)對(duì)(和我們的wrongPos數(shù)組作用類似,用來(lái)排除)
3.接著往下看是:根據(jù)上一步的點(diǎn)對(duì)生成所有不合法方案(仍然不知道具體是怎么算的,但是這里肯定存在漏洞)
原作者的思路應(yīng)該是按照相鄰點(diǎn)來(lái)判斷生成不合法方案(例如:假設(shè)第一位是1那么如果第二位選擇3,則以13開(kāi)頭的所有方案全部PASS掉)
不難發(fā)現(xiàn)這樣一個(gè)BUG:第一位選擇2,第二位選擇1,那么第三位能不能選擇3呢?
實(shí)際情況是Yes,但如果按照上面假設(shè)的判斷方法則是No,因?yàn)?1,3)屬于不合法點(diǎn)對(duì)!
這就又出現(xiàn)新問(wèn)題了,因?yàn)槲覀儼l(fā)現(xiàn)所謂的不合法點(diǎn)對(duì)好像是一個(gè)動(dòng)態(tài)的集合,如果中間點(diǎn)被選了,那么非法點(diǎn)對(duì)就變成合法的了(例如2被選了之后1可以和3連接,3和1也可以連接)
我們的算法會(huì)不會(huì)存在這個(gè)問(wèn)題呢?
private boolean isValid(String ans)
{//判斷ans是否合法
for(int i = 0;i < ans.length() - 1;i++)
{
//獲取當(dāng)前位置的字符的值
int currPos = Integer.parseInt(ans.charAt(i) + "");
//獲取路徑子串
String path = ans.substring(0, i + 1);
//獲取錯(cuò)誤值
String wrrPos = wrongPos[currPos - 1];
//獲取可變錯(cuò)誤值
if(currPos != 5)//5不可能變
{
for(int j = 0;j < wrrPos.length();j++)
{
//獲取中間值
String wrong = wrrPos.charAt(j) + "";
int mid = (currPos + Integer.parseInt(wrong)) / 2;
//若中間值已被連接,則錯(cuò)誤終點(diǎn)可用
if(path.contains(mid + ""))
wrrPos = wrrPos.replace(wrong, "#");
//若下一位是錯(cuò)誤值則ans不合法
if(wrrPos.contains(ans.charAt(i + 1) + ""))
return false;
}
}
}
return true;
}
從上面的代碼不難看出我們的算法已經(jīng)考慮到了這樣的情況(動(dòng)態(tài)修正wrrPos)
(八)思考延伸
按照這樣的方法,我們不難算出一共有多少種方案(從四個(gè)點(diǎn)到九個(gè)點(diǎn)),在此作簡(jiǎn)單分析:
1>9個(gè)點(diǎn)和8個(gè)點(diǎn)的數(shù)目應(yīng)該相等(前8位數(shù)都定了,最后一位也就不能變了)
2>9個(gè)點(diǎn)和7個(gè)點(diǎn)的數(shù)目應(yīng)該是2倍關(guān)系(前7位數(shù)定了,后兩位只有兩種排列方式,如果去掉后2位則前7位數(shù)有一半重復(fù)了)
...
設(shè)總方案數(shù)為 num,連接 i 個(gè)點(diǎn)的方案總數(shù)為 n( i ),例如n( 9 ) = 62632
則:1式:num = n( 4 ) + n( 5 ) + n( 6 ) + n( 7 ) + n( 8 ) + n( 9 )
2式:n( 8 ) = n( 9 ), n( 7 ) = n( 9 ) / 2, n( 6 ) = n( 9 ) / 6, n( 5 ) = n( 9 ) / 24, n( 4 ) = n( 9 ) / 120 [規(guī)律:n( i ) = n( 9 ) / A(9 - i)(9 - i)]
把2式帶入1式即可求得方案總數(shù),在此不再贅述
(九)總結(jié)
探索過(guò)程中雖然走了很多彎路,但也有不少收獲,例如動(dòng)態(tài)不合法判斷的BUG是在嘗試方案2時(shí)發(fā)現(xiàn)的,雖然方案2失敗了,但避免了在方案1中出現(xiàn)類似的問(wèn)題
只要思路明晰,敢于嘗試,絕對(duì)沒(méi)有plain try
(十)BUG修正
文中對(duì)果殼網(wǎng)算法提出的質(zhì)疑是錯(cuò)誤的,原文結(jié)果是對(duì)的
經(jīng)過(guò)驗(yàn)證,本文算法存在BUG,信息如下:
當(dāng)前路徑是 12345687
錯(cuò)誤原因是下一位 9被錯(cuò)誤值#39包含
錯(cuò)誤串為 123456879
BUG分析:出現(xiàn)這個(gè)BUG的原因是對(duì)自己的算法太過(guò)自信,設(shè)計(jì)算法的時(shí)候過(guò)分考慮了算法復(fù)雜度,省掉了一個(gè)不能省的循環(huán)(應(yīng)該先動(dòng)態(tài)修改wrrPos再對(duì)結(jié)果進(jìn)行判斷,原算法把二者放在一個(gè)循環(huán)里了)
現(xiàn)對(duì)isValid方法修改如下:
private static boolean isValid(String ans)
{//判斷ans是否合法
for(int i = 0;i < ans.length() - 1;i++)
{
//獲取當(dāng)前位置的字符的值
int currPos = Integer.parseInt(ans.charAt(i) + "");
//獲取路徑子串
String path = ans.substring(0, i + 1);
//獲取錯(cuò)誤值
String wrrPos = wrongPos[currPos - 1];
//獲取可變錯(cuò)誤值
if(currPos != 5)//5不可能變
{
for(int j = 0;j < wrrPos.length();j++)
{
//獲取中間值
String wrong = wrrPos.charAt(j) + "";
int mid = (currPos + Integer.parseInt(wrong)) / 2;
//若中間值已被連接,則錯(cuò)誤終點(diǎn)可用
if(path.contains(mid + ""))
wrrPos = wrrPos.replace(wrong, "#");
}
//若下一位是錯(cuò)誤值則ans不合法
if(wrrPos.contains(ans.charAt(i + 1) + ""))
return false;
}
}
return true;
}
[與原算法唯一的區(qū)別是把if判斷語(yǔ)句從循環(huán)里拿出來(lái)了,當(dāng)時(shí)想法是為了節(jié)省一個(gè)循環(huán)...結(jié)果,哎...]
修正后運(yùn)行結(jié)果:
連接9個(gè)點(diǎn)共有 140704 種方案
結(jié)論:果殼網(wǎng)的結(jié)論是正確的!之前對(duì)其內(nèi)部算法的猜測(cè)可能有誤,實(shí)屬抱歉。
總結(jié)
以上是生活随笔為你收集整理的java实现九宫格解锁_Java计算手机九宫格锁屏图案连接9个点的方案总数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 实录 | 计算未来轻沙龙:人工智能前沿与
- 下一篇: java美元兑换,(Java实现) 美元