17年网易笔试-跳石板:广度优先搜索

前言

17年网易笔试有一道编程题目《跳石板》,newcoder OJ链接如下:
跳石板-牛客网
题目描述如下:

小易来到了一条石板路前,每块石板上从1挨着编号为:1、2、3…….
这条石板路要根据特殊的规则才能前进:对于小易当前所在的编号为K的 石板,小易单次只能往前跳K的一个约数(不含1和K)步,即跳到K+X(X为K的一个非1和本身的约数)的位置。 小易当前处在编号为N的石板,他想跳到编号恰好为M的石板去,小易想知道最少需要跳跃几次可以到达。
例如:
N = 4,M = 24:
4->6->8->12->18->24
于是小易最少需要跳跃5次,就可以从4号石板跳到24号石板

没有实战过图搜索的题,考试中没啥思路,下来赶紧看看了解答,随后总结为此文。

广度优先搜索

Breadth First Search (BFS)是一种图搜索算法,BFS首先从源定点s开始,沿着s节点的宽度遍历节点,当所有跟s节点临接的节点都被访问过以后,再继续向下一层访问。
如下图所示
17年网易笔试-跳石板:广度优先搜索_第1张图片
遍历顺序即为:1-2-3-4-5-6-7-8-9-10-11-12

想法一

首先最朴素的想法就是枚举N的所有约数(除了1和其本身),然后将所有约数与X的和作为N的子孙,构建一棵树以后,第一个值等于M的叶子节点的深度,即为最小跳跃次数。
节点可以通过一个类来定义,比如:

class Node{
    int val;
    Node parent;
}

或者

class Node{
    int val;
    int depth; 
}

这样通过一个队列Q一步步广搜即可,但是实现想法以后,不论如何增加限制条件都Timeout,当M大于1000以后就基本上GG了,速度非常非常慢,为什么呢?OK,我们换个思路,继续。

想法二

既然简单的使用构建搜索树的方法行不通,我们换个思路。既然是广度搜索,每次遍历一层,那么我们就尝试每次循环记录一层的数字进行计算。
1、声明一个List curr存储一层节点,通过约数,计算下一层节点(比如一开始List只有一个数字N=4,通过计算约数,下一层就是有一个数字4+2=6)。
2、通过一个临时List tmp来存储计算的加过约数的和。
3、下一个循环用tmp替换curr。
类似于一下结构

List curr = new ArrayList<>();
curr.add(N);
while(flag){
    List tmp = new ArrayList<>();
    tmp.add(所有求得的约数);
    curr = tmp;
}

这时输出每一次循环的curr发现,每一层有很多重复数值的节点,显然这些重复数值的节点再次计算是很浪费时间的。比如:

从n=4开始,第三层:
[8, 9, 8]
这就有两个8,那么这两个8继续分别向下计算,显然重复,因此可以合并一层中所有重复的值。
[8, 9]

OK,让我们上传OJ运行试试看…Sad,仍然Timeout。这是为什么呢~

想法三

在想法二的基础上,把M设置的大一些,不断地输出每一层的值就会发现,除了一层中可能出现的重复情况,各个层之间也会出现很多的相同值得节点,对呀,这就像题目中说的上楼梯,每一层楼梯都是固定的,到每一层阶梯的最小值通过广搜已经计算过了,再重复计算并没有什么用啊!这就回归到了DP的思想了。
因此最终改良版本,只需定义一个合适大小的数组,这个数组大小大于M,初始值设置为-1,表示没有到过,对所有没有到过的,只记录一次首次到达的前进次数(层数)即可。随后这个位置的儿子节点都无需再遍历了!
OK,最终代码如下,终于通过,感人,TAT

import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;

public class Main {
    public void run(){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();

        int[] dst = new int[2*m];
        for(int i=0;i1;

        dst[n] = 0;

        Queue Q = new LinkedList<>();
        Q.offer(n);

        while(!Q.isEmpty()){
            int x = Q.poll();
            for(int i=2; i*i <= x ; i++){
                if(x%i == 0){
                    if(x+i<= m && dst[x+i] == -1) {
                        Q.offer(x + i);
                        dst[x+i] = dst[x] + 1;
                    }
                    if(x+x/i <= m && dst[x+x/i] == -1){
                        Q.offer( x + x/i);
                        dst[x+ x/i] = dst[x] + 1;
                    }
                }
            }
        }

        System.out.println(dst[m]);

        sc.close();
    }

    public static void main(String[] args){
        new Main().run();
    }
}

约数的计算

通过上一节的程序我们可以看到,计算x的约数无需从1遍历到x,而是只需从1遍历到sqrt(x)
随后,x的约数就是2~sqrt(x)之间的i,还有对应的x/i
这样就降低了搜索空间。

总结

1、算法复杂度太高,首先一点就是能不能减少无谓的重复计算?
2、减少重复计算的方式就是拿空间换时间,通过记录来避免重复部分的计算。

你可能感兴趣的:(algorithm,oj,java)