不久之前在codewars上发现一道关于数数的题,当时没有做出来,然后过了两天翻juejin,看见了一篇讲简单的动态规划算法的文章,突然给了我一点灵感,然后就尝试着用这种办法解决掉了这个问题。话不多说,来看一下这道题:
这里有一张图片,但是不知道为什么不能显示,作为一个前端,简直不能忍,愤然打开控制台。。。 然后。。。决定还是找一张图吧。 大概解释一下这个题。 你可能已经很熟悉将几何手势锁屏作为智能手机的安全防范措施,为了打开手机,你需要一直在屏幕上滑动手指将屏幕上中网格的点连接起来绘制成正确的图案,下面这张图片是7个点的例子(然而并没有图。。。)。这道题,你要做的是完成这个函数,这个函数要传入两个参数,一个是对应网格的其中一个字符,一个是需要连接的网格上点的个数,该函数必须返回从给定点开始的并且连接次数是给定长度的所有情况的数量。注意这里连接点只能用直线:
·水平的(类似A-B)
·垂直的(类似D-G)
·对角线(类似B-I或者I-E)
·经过已经使用的点(类似G-C经过E)
测试的例子包含一些组合数量的示例,以帮助您检查代码。
额外选项:
你可能很好奇,对于安卓的锁屏手势,有效的必须是4到9个点,并且总共有389112个可能的有效组合。
好,现在应该差不多明白这个题了,其实就是以给的点开始然后根据给的数量按照规则去连接,最后返回一共多少种情况。我们先分析一下这道题,首先,我们要找到他的参数是什么,我们就以给出的测试的例子为例, 第一个参数是D,第二个参数是3,我们看见了D最先就要知道他在图中的位置, 所以我们先创建一个9宫格
var originDots = [
['A','B','C'],
['D','E','F'],
['G','H','I'],
]
复制代码
这样我们就能看出哪些点能够连接起来,然后还要知道的是哪些点已经被连接了,所以我们要创建一个和这个一样的网格,来表示哪些点用过,哪些点没用过,
var checkedArr = [
[0,0,0],
[0,0,0],
[0,0,0]
]
复制代码
我们用这个来表示初始化的状态,然后根据给的点将该点的位置变为已使用,
var lo = []
originDots.forEach((item, index)=>{
item.forEach((itemA, indexA) =>{
if (itemA === firstDot) {
lo.push(index)
lo.push(indexA)
}
})
})
// var checkedArr = [
// [0,0,0],
// [1,0,0],
// [0,0,0]
// ]
复制代码
类似于这样,接下来我们就需要知道,哪些点能够作为满足条件的下一环被连起来,这个时候我们就需要记录一下当前的这个点的位置,以及连接这个位置所需要的满足条件。
var lastX = 0;
var lastY = 0;
function canCheck(x, y){
//同一行
if(x == lastX && Math.abs(y-lastY) > 1){
if(checkedArr[x][1] == 0){
return false;
}
}
//同一列
if(y == lastY && Math.abs(x-lastX) > 1){
if(checkedArr[1][y] == 0){
return false;
}
}
//对角
if((x+lastX)/2==1 && (y+lastY)/2==1 && x != lastX){
if(checkedArr[1][1] == 0){
return false;
}
}
return true;
}
复制代码
好,接下来要做的就是寻找满足条件的点,所以我们就要遍历我们的网格,只要是没有被使用过的就可以成为备选,然后记得我们的次数,不要选满网格,
function search(startX,startY,len){
//判断是否可用
if(!canCheck(startX,startY)){
return 0;
}
//选择状态
checkedArr[startX][startY] = 1;
//找到了一个
if(len == 1){
//恢复未选择状态
checkedArr[startX][startY] = 0;
return 1;
}
if (len > 9) {
return 0
}
var count = 0;
for(var x=0;x<=2;x++){
for(var y=0;y<=2;y++){
if(checkedArr[x][y] == 0){
lastX = startX;
lastY = startY;
count += search(x,y,len - 1);
}
}
}
checkedArr[startX][startY] = 0;
return count;
}
复制代码
ok,这个地方要注意的一下就是checkedArr[startX][startY] = 0 ,这块可以都认为是假设,所以在每次尝试之后要记得重置一下状态。最后,完整代码
var originDots = [
['A','B','C'],
['D','E','F'],
['G','H','I'],
]
var checkedArr = [
[0,0,0],
[0,0,0],
[0,0,0]
]
var lastX = 0;
var lastY = 0;
function countPatternsFrom(firstDot, length) {
// Your code here
var lo = []
originDots.forEach((item, index)=>{
item.forEach((itemA, indexA) =>{
if (itemA === firstDot) {
lo.push(index)
lo.push(indexA)
}
})
})
return exe(lo[0],lo[1],length)
}
function exe(x,y,len){
init(x,y);
return search(x,y,len);
}
function search(startX,startY,len){
//判断是否可用
if(!canCheck(startX,startY)){
return 0;
}
//选择状态
checkedArr[startX][startY] = 1;
//找到了一个
if(len == 1){
//恢复未选择状态
checkedArr[startX][startY] = 0;
return 1;
}
if (len > 9) {
return 0
}
var count = 0;
for(var x=0;x<=2;x++){
for(var y=0;y<=2;y++){
if(checkedArr[x][y] == 0){
lastX = startX;
lastY = startY;
count += search(x,y,len - 1);
}
}
}
checkedArr[startX][startY] = 0;
return count;
}
function canCheck(x, y){
//同一行
if(x == lastX && Math.abs(y-lastY) > 1){
if(checkedArr[x][1] == 0){
return false;
}
}
//同一列
if(y == lastY && Math.abs(x-lastX) > 1){
if(checkedArr[1][y] == 0){
return false;
}
}
//对角
if((x+lastX)/2==1 && (y+lastY)/2==1 && x != lastX){
if(checkedArr[1][1] == 0){
return false;
}
}
return true;
}
function init(x,y){
checkedArr = [
[0,0,0],
[0,0,0],
[0,0,0]
];
lastX = x;
lastY = y;
}
console.log(countPatternsFrom('D', 3)); // 37
复制代码
整个代码没什么亮点,随便一个人都能写出来,不过最重要的应该是这个思路,一开始看到这个题的时候,我的思维一直都停在用乘法来解决这个问题,就像数学的排列组合那样,所以根本就想不到可以用加法来解决这件事,最后要感谢一下juejin.im/post/5a29d5… 这篇文章,如果不是看了这个,我不知道我什么时候才能突破之前的思维模式,所以大家一定要多读书,多看报。。。