路径之谜
题目描述
小明冒充 XX 星球的骑士,进入了一个奇怪的城堡。
城堡里边什么都没有,只有方形石头铺成的地面。
假设城堡地面是 n×nn×n 个方格。如下图所示。
按习俗,骑士要从西北角走到东南角。可以横向或纵向移动,但不能斜着走,也不能跳跃。每走到一个新方格,就要向正北方和正西方各射一箭。(城堡的西墙和北墙内各有 nn 个靶子)同一个方格只允许经过一次。但不必走完所有的方格。如果只给出靶子上箭的数目,你能推断出骑士的行走路线吗?有时是可以的,比如上图中的例子。
本题的要求就是已知箭靶数字,求骑士的行走路径(测试数据保证路径唯一)
输入描述
第一行一个整数 NN (0≤N≤200≤N≤20),表示地面有 N×NN×N 个方格。
第二行 NN 个整数,空格分开,表示北边的箭靶上的数字(自西向东)
第三行 NN 个整数,空格分开,表示西边的箭靶上的数字(自北向南)
输出描述
输出一行若干个整数,表示骑士路径。
为了方便表示,我们约定每个小格子用一个数字代表,从西北角开始编号: 0,1,2,3 ⋯⋯
比如,上图中的方块编号为:
0 1 2 3
4 5 6 7
8 9 10 11
12 13 14 15
输入输出样例
示例
输入
4
2 4 3 4
4 3 3 3
输出
0 4 5 1 2 3 7 11 10 9 13 14 15
运行限制
- 最大运行时间:5s
- 最大运行内存: 256M
看到这题,有关路径,那肯定是用dfs或者bfs,由于对dfs更熟悉,使用使用深度优先搜索
dfs的基本模板:
public static void dfs(...)
{
if(终止条件)
{将满足条件的路径存储
return;
}
for(int i=index;i<n;i++){
将满足条件的点加入当前这条路径中;
dfs(....)
将满足条件的点移除当前这条路径中;(回溯)
}
有关路径图形,就联想到之前做过的岛屿数量,都是要向四个方向走,即for循环
int[][] dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int[] b : dir) {
int nexti = i + b[0];
int nextj = j + b[1];
}
了解了一下题目大概的意思,以及大概的思路,来确定递归三部曲:
- 传入的参数:
图形的构造,还有解决这题所特有的参数,他要射箭,两个方向,我们是通过箭的数量来判断他走的是那条路,所以要将这两个方向的箭的数量以数组的形式存储传入,最后终止条件肯定要比较箭的数量,如果两个方向的数量都满足条件则将此条路径输出,又因为这是一个图的结构,我肯定要传入我的初始位置(或者说每一次递归时的初始位置),所以要传入i,j,来确定当前走到哪了,且不能重复走之前走过的格子,所以需要使用used[][] boolean数组来确定是否被走过
综上:传入的参数有(这个图的大小,目标两个方向的箭数,当前两个方向的箭数,i,j,used,存储的路径)
- 终止条件:
依题意得,我们的目标是从左上角走到右下角,所以当i==N-1,j==N-1(N表示图形的边长格子数)时,return;且当前两个方向的箭数与目标两个方向的箭数相等时(
Arrays.equals(c,m) && Arrays.equals(d,n)
)将此条路径输出(注意输出格式,不能以list格式输出了
for(int lj:result) System.out.print(lj+" ");这样才是正确的
)
- 单层递归逻辑:
因为向四个方向走,难免会越界,所以应该在向四个方向走完之后判断该位置是否合法,要是不和发就不执行,合法便执行
for (int[] b : dir) {
int nexti = i + b[0];
int nextj = j + b[1];
if(!(nexti<0||nextj<0||nexti>=N||nextj>=N||used[nexti][nextj])) {
m[nexti] ++;
n[nextj] ++ ;
list.add(nexti * N + nextj);
used[nexti][nextj] = true;
backtracking(N, c, d, nexti, nextj, used, list, m, n);
used[nexti][nextj] = false;
m[nexti]--;
n[nextj]--;
list.remove(list.size() - 1);
}}
这里要注意 int nexti = i + b[0]; int nextj = j + b[1];,不能直接给i赋值要用nexti和nextj才可以,不然到时侯每次的方向都是在上次改变之后的基础上,并不是我们想要走的方向,还要注意回溯操作,要将我在for循环以后, backtracking前的操作都反向来一遍zh
import java.util.*;
public class a {
public static List<Integer> result=new ArrayList<>();
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int N=sc.nextInt();
int[] c=new int[N];
int[] d=new int[N];
for(int i=0;i<N;i++)
{
c[i]=sc.nextInt();
}
for(int i=0;i<N;i++)
{
d[i]=sc.nextInt();
}
find(N,d,c);
}
public static void find(int N,int[] c,int[]d)
{
List<Integer> list=new ArrayList<>();
//List<Integer> result=new ArrayList<>();
boolean[][] used = new boolean[N][N];
int[] m=new int[N];
int[] n=new int[N];
m[0]+=1;
n[0]+=1;
list.add(0);
used[0][0]=true;
backtracking(N,c,d,0,0,used,list,m,n);
}
public static void backtracking(int N,int[] c,int[]d,int i,int j,boolean[][] used,
List<Integer> list,int[] m,int[]n)
{
if(i*N+j==N*N-1 )
{
if( Arrays.equals(c,m) && Arrays.equals(d,n))
{result= new ArrayList<>(list);
for(int lj:result) System.out.print(lj+" ");
}
return;
}
int[][] dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int[] b : dir) {
int nexti = i + b[0];
int nextj = j + b[1];
if(!(nexti<0||nextj<0||nexti>=N||nextj>=N||used[nexti][nextj])) {
m[nexti] ++;
n[nextj] ++ ;
list.add(nexti * N + nextj);
used[nexti][nextj] = true;
backtracking(N, c, d, nexti, nextj, used, list, m, n);
used[nexti][nextj] = false;
m[nexti]--;
n[nextj]--;
list.remove(list.size() - 1);
}
}
}
}
这是我的答题代码,通过了28%,可见思路没有问题(这里注意要初始化(0,0)时的used值,和在此位置上两个方向的箭数各加一),但是其余的都超时了,因此,我们还需要做剪枝操作
剪枝
for循环这里剪枝不了,他固定死了一定时四个方向,在终止条件那里一开始考虑从步数出发,因为你走的步数是可以从某个方向的箭数来确定的,就是每个格子固定方向的箭数之和,但是效果微乎其微,然后考虑在单层递归逻辑下功夫,当你走到这个位置之后如果在还没有添加走到此方向的箭数时已经超过了目标箭数,则不进行操作,主要的思想就是单个地比较箭数,放在终止条件是不行的(不知道为什么啊,有大佬解答吗),主要是这个方法,像我之前和起来是不够的
import java.util.*;
public class a {
public static List<Integer> result=new ArrayList<>();
public static void main(String[] args)
{
Scanner sc=new Scanner(System.in);
int N=sc.nextInt();
int[] c=new int[N];
int[] d=new int[N];
for(int i=0;i<N;i++)
{
c[i]=sc.nextInt();
}
for(int i=0;i<N;i++)
{
d[i]=sc.nextInt();
}
find(N,d,c);
}
public static void find(int N,int[] c,int[]d)
{
List<Integer> list=new ArrayList<>();
//List<Integer> result=new ArrayList<>();
boolean[][] used = new boolean[N][N];
int[] m=new int[N];
int[] n=new int[N];
m[0]+=1;
n[0]+=1;
list.add(0);
used[0][0]=true;
backtracking(N,c,d,0,0,used,list,m,n);
}
public static void backtracking(int N,int[] c,int[]d,int i,int j,boolean[][] used,
List<Integer> list,int[] m,int[]n)
{
if(i*N+j==N*N-1 )//放这里没有用,改变不了什么
{
if( Arrays.equals(c,m) && Arrays.equals(d,n))
{result= new ArrayList<>(list);
for(int lj:result) System.out.print(lj+" ");
}
return;
}
int[][] dir = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
for (int[] b : dir) {
int nexti = i + b[0];
int nextj = j + b[1];
if(!(nexti<0||nextj<0||nexti>=N||nextj>=N||used[nexti][nextj])) {
if (m[nexti] < c[nexti] && n[nextj] < d[nextj]) {//这个起作用,单个单个看
m[nexti]++;
n[nextj]++;
list.add(nexti * N + nextj);
used[nexti][nextj] = true;
backtracking(N, c, d, nexti, nextj, used, list, m, n);
used[nexti][nextj] = false;
m[nexti]--;
n[nextj]--;
list.remove(list.size() - 1);
}
}
}
}
}
总体就是这样