Java计算手机九宫格锁屏图案连接9个点的方案总数
(一)问题
九宫格图案解锁连接9个点共有多少种方案?
(二)初步思考
可以把问题抽象为求满足一定条件的1-9的排列数(类似于“八皇后问题”),例如123456789和987654321都是合法的(按照从上到下、从左到右、从1到9编号),解决此类问题一般都用递归方法,因为问题规模较大,且没有明确的计算方法
(三)深度思考
不难想出下面的简单思路:
1.先穷举,再排除不合法结果(过滤穷举的结果)
大略估计一下复杂度,A99=362880(计算机应该可以接受),方案总数不超过A99,也就是说穷举的结果是A99,再滤去不合法的结果即可,理论上此方法可行
2.按条件穷举(在穷举的过程中排除不合法结果)
1>正常思维
---a.选择起点位置(i,j)
---b.在起点周围寻找合法终点,规则如下:(共有12个方向)
------1.上(i - 1, j)--5.左上斜(i - 1, j - 1)---9.左上长斜(i - 2, j - 1)
------2.下(i + 1, j) -6.左下斜(i + 1, j - 1) -10.左下长斜(i + 2, j - 1)
------3.左(i, j - 1)--7.右上斜(i - 1, j + 1)--11.右上长斜(i - 2, j + 1)
------4.右(i, j + 1) -8.右下斜(i + 1, j + 1)-12.右下长斜(i + 2, j + 1)
---c.记录路径(起点 + 终点)
---d.判满,若路径长度小于9执行e步骤,否则执行f步骤
---e.终点变起点,返回a步骤
---f.输出结果(路径)
2>逆向思维
---a.排除(1.排除已选择的点2.排除从起点不能到达的点)得到临时剩余点集
---b.在临时剩余点集中选择下一个点
---c.判满,路径长度小于9,执行d步骤,否则执行e步骤
---d.执行a步骤
---e.输出结果(路径)
P.S.正常思维比较容易理解,逆向思维更容易实现
(四)编码
[最初想用方案2的逆向思维方案来实现,结果失败了,原因是递归内嵌循环,程序执行轨迹难以捉摸...头疼良久之后放弃了,改用方案1,简单粗暴]
[核心代码类]
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; public class ScreenLock {
//将NUM设置为待排列数组的长度即实现全排列
private int NUM = 0;
private int count = 0;
private String[] strSource;
String string = null; private static String[] wrongPos = {//各点对应的不能到达的位置
"379","8","179",
"6","#","4",
"139","2","137"}; public ScreenLock(String[] strSource)
{//strSource格式为以逗号分隔的数字串,如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++)
{
//获取当前位置的字符的值
int currPos = Integer.parseInt(ans.charAt(i) + "");
//获取路径子串
String path = ans.substring(0, i + 1);
//获取错误值
String wrrPos = wrongPos[currPos - 1];
//获取可变错误值
if(currPos != 5)//5不可能变
{
for(int j = 0;j < wrrPos.length();j++)
{
//获取中间值
String wrong = wrrPos.charAt(j) + "";
int mid = (currPos + Integer.parseInt(wrong)) / 2;
//若中间值已被连接,则错误终点可用
if(path.contains(mid + ""))
wrrPos = wrrPos.replace(wrong, "#"); //若下一位是错误值则ans不合法
if(wrrPos.contains(ans.charAt(i + 1) + ""))
return false;
}
}
} return true;
}
}
特别说明:上面代码中用到的全排列算法来自http://blog.csdn.net/sunyujia/article/details/4124011 (尊重前辈的劳动成果)
[测试类]
public class CountLockPlans { public static void main(String[] args) {
//计算手机九宫格锁屏图案连接9个点的方案总数
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个点共有 " + num + " 种方案");
} }
(五)运行结果
连接9个点共有 62632 种方案【此结果是错的,详情见最下方第(十)点】
(六)程序正确性检验
1.能否生成1-9的全排列?
注释掉无关代码,直接输出所有全排列,同时计数,结果无误(全部输出需要17秒左右)
2.isValid()方法是否能够正确判断方案的合法性?
单独调用isValid()传入各种参数测试,结果无误
3.算法逻辑是否无误?
求排列的同时过滤不合法解,逻辑无误
[综上,理论上输出的结果是正确的]
(七)答案正确性确认
上网找找有没有人算出结果,滤去所有看起来不靠谱的答案,选定果壳网答案(据说用了Mathematica,乍看高上大)
文章中的解决思路也是:合法方案数 = 全排列总数 - 不合法方案数
原文链接:http://www.guokr.com/article/49408/
仔细看过文章后发现原文的结论可能是错的(虽然不知道其具体算法)
1.从原文的贴图可以看到先求出了方案总数985 824(这个我们不必关心,只需要关注连接9个点的计算结果就好)
2.原文贴图记下不能直接连的点对(和我们的wrongPos数组作用类似,用来排除)
3.接着往下看是:根据上一步的点对生成所有不合法方案(仍然不知道具体是怎么算的,但是这里肯定存在漏洞)
原作者的思路应该是按照相邻点来判断生成不合法方案(例如:假设第一位是1那么如果第二位选择3,则以13开头的所有方案全部PASS掉)
不难发现这样一个BUG:第一位选择2,第二位选择1,那么第三位能不能选择3呢?
实际情况是Yes,但如果按照上面假设的判断方法则是No,因为(1,3)属于不合法点对!
这就又出现新问题了,因为我们发现所谓的不合法点对好像是一个动态的集合,如果中间点被选了,那么非法点对就变成合法的了(例如2被选了之后1可以和3连接,3和1也可以连接)
我们的算法会不会存在这个问题呢?
private boolean isValid(String ans)
{//判断ans是否合法
for(int i = 0;i < ans.length() - 1;i++)
{
//获取当前位置的字符的值
int currPos = Integer.parseInt(ans.charAt(i) + "");
//获取路径子串
String path = ans.substring(0, i + 1);
//获取错误值
String wrrPos = wrongPos[currPos - 1];
//获取可变错误值
if(currPos != 5)//5不可能变
{
for(int j = 0;j < wrrPos.length();j++)
{
//获取中间值
String wrong = wrrPos.charAt(j) + "";
int mid = (currPos + Integer.parseInt(wrong)) / 2;
//若中间值已被连接,则错误终点可用
if(path.contains(mid + ""))
wrrPos = wrrPos.replace(wrong, "#"); //若下一位是错误值则ans不合法
if(wrrPos.contains(ans.charAt(i + 1) + ""))
return false;
}
}
} return true;
}
从上面的代码不难看出我们的算法已经考虑到了这样的情况(动态修正wrrPos)
(八)思考延伸
按照这样的方法,我们不难算出一共有多少种方案(从四个点到九个点),在此作简单分析:
1>9个点和8个点的数目应该相等(前8位数都定了,最后一位也就不能变了)
2>9个点和7个点的数目应该是2倍关系(前7位数定了,后两位只有两种排列方式,如果去掉后2位则前7位数有一半重复了)
...
设总方案数为 num,连接 i 个点的方案总数为 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 [规律:n( i ) = n( 9 ) / A(9 - i)(9 - i)]
把2式带入1式即可求得方案总数,在此不再赘述
(九)总结
探索过程中虽然走了很多弯路,但也有不少收获,例如动态不合法判断的BUG是在尝试方案2时发现的,虽然方案2失败了,但避免了在方案1中出现类似的问题
只要思路明晰,敢于尝试,绝对没有plain try
(十)BUG修正
文中对果壳网算法提出的质疑是错误的,原文结果是对的
经过验证,本文算法存在BUG,信息如下:
当前路径是 12345687
错误原因是下一位 9被错误值#39包含
错误串为 123456879
BUG分析:出现这个BUG的原因是对自己的算法太过自信,设计算法的时候过分考虑了算法复杂度,省掉了一个不能省的循环(应该先动态修改wrrPos再对结果进行判断,原算法把二者放在一个循环里了)
现对isValid方法修改如下:
private static boolean isValid(String ans)
{//判断ans是否合法
for(int i = 0;i < ans.length() - 1;i++)
{
//获取当前位置的字符的值
int currPos = Integer.parseInt(ans.charAt(i) + "");
//获取路径子串
String path = ans.substring(0, i + 1);
//获取错误值
String wrrPos = wrongPos[currPos - 1];
//获取可变错误值
if(currPos != 5)//5不可能变
{
for(int j = 0;j < wrrPos.length();j++)
{
//获取中间值
String wrong = wrrPos.charAt(j) + "";
int mid = (currPos + Integer.parseInt(wrong)) / 2;
//若中间值已被连接,则错误终点可用
if(path.contains(mid + ""))
wrrPos = wrrPos.replace(wrong, "#");
} //若下一位是错误值则ans不合法
if(wrrPos.contains(ans.charAt(i + 1) + ""))
return false;
}
} return true;
}
[与原算法唯一的区别是把if判断语句从循环里拿出来了,当时想法是为了节省一个循环...结果,哎...]
修正后运行结果:
连接9个点共有 140704 种方案
结论:果壳网的结论是正确的!之前对其内部算法的猜测可能有误,实属抱歉。
Java计算手机九宫格锁屏图案连接9个点的方案总数的更多相关文章
- 【Android】Android 手机忘记锁屏密码的解决办法
对于忘记Android锁屏密码的筒子们,除重新刷包或者wipe data外,还可用adb来删除密码: 方法很简单 1.手机连接电脑,不要打开数据连接,在充电模式下进行. 2.在电脑上,解压adb.zi ...
- iOS开发——使用技术OC篇&简单九宫格锁屏功能的实现与封装
简单九宫格锁屏功能的实现与封装 首先来看看最后的实现界面. 在这开始看下面的内容之前希望你能先大概思考活着回顾一下如果 你会怎么做,只要知道大概的思路就可以. 由于iphone5指纹解锁的实现是的这个 ...
- jQuery仿Android锁屏图案应用插件
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- jQuery仿Android锁屏图案应用
在线演示 本地下载
- [android] 手机卫士设备管理权限锁屏
设备管理员 Device Admin 获取DevicePolicyManager对象,通过getSystemService(DEVICE_POLICY_MANAGER),设备策略管理器 调用Devic ...
- android------锁屏(手机启动出现锁屏界面)
以前用过一个红包锁屏的软件,第一次打开手机出现锁屏,滑动领取收益,当时觉得这功能不错,就查阅资料,写了一个案例, apk运行流程: 进入软件--->启动服务--->关闭手机(可先退出应用) ...
- [cocos2d-x·解Bug]关于cocos2d-x游戏在android锁屏状态下播放Bgm的解决方法
最近<宠物联萌>在三星App上发布遇到一个问题:如果用户在锁定屏幕时解锁解到一半时取消解锁,这时用cocos2d-x开发的游戏就会出现游戏Bgm会恢复播放,但手机屏幕仍然是锁屏状态的Bug ...
- Android端恶意锁屏勒索应用分析
一.前言 5月12日,一场全球性互联网灾难悄然而至,一款名为WannaCRY的PC端恶意勒索软件利用NSA泄漏的危险漏洞“永恒之蓝”,给100多个国家和地区10万台电脑造成了巨大的损失.到2017年为 ...
- WP8开发札记(二)让程序支持锁屏运行
我们都知道WP8是采用墓碑机制来管理应用程序的. 当我们把手机锁屏了,默认情况下,App就触发了deactivated 事件,准备被墓碑化了. 但是,有些时间我们不想让程序就此终止运行.(比如说,你开 ...
随机推荐
- TIME_WAIT状态的作用
TIME_WAIT状态: 主动关闭的那端最后经历的状态,一般为2MSL秒(1~4分钟). 两个原因: 保证当最后一个ack丢失后,能收到对端重传的fin包. 保证ack包消失,不会影响下一个连接. 关 ...
- JAVA 系统变量之System.getenv()和System.getProperty() 用法
Java提供了System类的静态方法getenv()和getProperty()用于返回系统相关的变量与属性,getenv方法返回的变量大多于系统相关,getProperty方法返回的变量大多与ja ...
- 简单的几个Boost定时器
boost的asio库里有几个定时的器,老的有 deadline_timer , 还有三个可配合 C++11 的 chrono 使用的 high_resolution_timer . steady_ ...
- 团队项目:二次开发--v.2.1--软件工程
原先代码,对于基本对象的Get,Set方法构造函数等方法与实现基本功能的方法统一放到了一起,容易造成代码不清晰,别人比较难阅读的情况.而且其中代码冗余比较多. 改进代码,进行了层次的分析,将基本对象与 ...
- CString 作为参数执行都不执行
static int LoadUsbSDK(CString curpath ) win10系统下 CString 函数加载dll直接跳过 加载dll最好不要带参数
- [Jmeter]通过批处理调用java,java从CSV动态读取登录的用户名和密码,并将其作为参数组合成字符串,写入外部.bat文件,然后通过Java执行这个外部批处理文件
问题1:怎样通过批处理调用java代码? 问题2:怎样通过java从CSV文件获取到用户名和密码存入变量? 问题3:怎样将获取到的用户名和密码组合成字符串,写入外部批处理文件? 问题4:怎样在批处理文 ...
- Java NIO系列教程(十一) Java NIO 与 IO
Java NIO系列教程(十一) Java NIO与IO 当学习了 Java NIO 和 IO 的 API 后,一个问题马上涌入脑海: 我应该何时使用 IO,何时使用 NIO 呢?在本文中,我会尽量清 ...
- linq 使用or构建动态查询
You can certainly do it within a Where clause (extension method). If you need to build a complex que ...
- AJAX初尝试——ACM/ICPC类比赛气球管理系统
很早之前做过一个,白板没界面,20秒暴力刷新,数据库每个team一个n列的对应n个题目的标记项,只能对单个比赛暴力把全部user_id导入单独的气球表(也就是cid=1000用这个表的话,cid100 ...
- HTTP Error 403.14问题处理
打开目录浏览后,点击启用.