【专题】搜索题型(BFS+DFS)

【专题】搜索题型(BFS+DFS)

一、回溯法

回溯算法 都可以抽象成一个树状结构,可以将其抽象成一个【n叉树问题】 。如果满足递归的条件,树枝可以无限增加,直到找到所需要数据为止;如果不满足,树枝则会折断。树的深度取决于要搜索问题的层数树的宽度取决于每个节点处理集合的大小

排列问题:N个数按一定规则全排列,有几种排列方式

组合问题:N个数里面按一定规则找出k个数的集合

子集问题:一个N个数的集合里有多少符合条件的子集

1. 回溯模版------排列问题

核心点排列与顺序有关

主旨展现

排列要求数字不重复------每次选择的数字需要打标记------vis数组

要输出当前排列------记录路径------path数组。

回溯:先打标记,记录路径、然后下一层,回到上一层,清除标记。

题目描述

给定一个整数 m,将数字 1∼m 排成一排,将会有很多种排列方法。

现在,请你按照字典序将所有的排列方法输出。

python 复制代码
n=int(input())

# 标记数组:表示数字是否被用过
vis = [0] * (n+1)
# 存储当前路径(排列结果)
li=[]

def dfs(depth):
    # depth:第depth个数字
    if depth == n:
        print(li)
        return

    # 遍历每一种可能(边界到n为止)
    for i in range(1,n+1):
        if vis[i] == 0:
            li.append(i)
            vis[i]=1 # 标志为1,表示已经用过
            dfs(depth+1) # 进入下一层
            vis[i]=0 # 回溯:撤销选择(标志位0)
            li.pop() # 回溯:撤销选择(移除数据)

dfs(0)
--------------------------------------
输入:3
输出:
	[1, 2, 3]
    [1, 3, 2]
    [2, 1, 3]
    [2, 3, 1]
    [3, 1, 2]
    [3, 2, 1]
--------------------------------------
类似题目:素数环【环】

输入正整数 n,把整数 1,2,...,n 组成一个环,使得相邻两个整数之和均为素数。

输入格式

输入正整数 n。

输出格式

输出所有满足条件的环。

python 复制代码
import math

n = int(input())

# 存储当前路径(组合结果)
li = []
# 标记数组:表示数字是否被用过
vis=[0]*(n+1)
# 标志
flag=0


# 函数:判断是否为质数,是则返回1,不是则返回0
def prime(n):
    # 质数从2开始
    if n<=1:
        return 0
    sqrt = int(math.sqrt(n))
    for i in range(2,sqrt+1):
        if n%i==0:
            return 0
    return 1

# 组合模型
def dfs(depth):
    # 初始化
    global flag
    flag=0
    # 找到一组组合方式
    if depth==n:
        # 对于环而言,先判断首项和尾部
        if prime(li[0] + li[-1])==1:
            # 满足条件,则对从头开始进行判断
            for i in range(1,len(li)):
                if prime(li[i]+li[i-1])==1:
                    flag=1
                else:
                    flag=0
                  	break
        if flag==1:
            for i in range(len(li)):
                if i<len(li)-1:
                    print(li[i],end=' ')
                else:
                    print(li[i])
        return
    for i in range(1,n+1):
        if vis[i]==0:
            li.append(i)
            vis[i]=1
            dfs(depth+1)
            vis[i]=0
            li.pop()

dfs(0) # 初始调用:深度0,起始值1
----------------------------------------
输入:6
输出:
    1 4 3 2 5 6
    1 6 5 2 3 4
    2 3 4 1 6 5
    2 5 6 1 4 3
    3 2 5 6 1 4
    3 4 1 6 5 2
    4 1 6 5 2 3
    4 3 2 5 6 1
    5 2 3 4 1 6
    5 6 1 4 3 2
    6 1 4 3 2 5
    6 5 2 3 4 1
----------------------------------------

2. 回溯模版------组合问题

核心点组合与顺序无关

题目描述

排列与组合是常用的数学方法,其中组合就是从 n 个元素中抽出 r 个元素(不分顺序且 rn ),我们可以简单地将 n 个元素理解为自然数 1,2,...,n ,从中任取 r 个数。

现要求你输出所有组合。

例如 n =5,r=3,所有组合为:

123,124,125,134,135,145,234,235,245,345。

输入格式

一行两个自然数 n ,r (1<n <21,0≤rn)。

输出格式

所有的组合,每一个组合占一行且其中的元素按由小到大的顺序排列,每个元素占三个字符的位置,所有的组合也按字典顺序。

python 复制代码
n,k=list(map(int,input().split()))

# 存储当前路径(组合结果)
li = []
# 标记数组:表示数字是否被用过
vis=[0]*(n+1)

def dfs(depth,val):
    # 终止条件:已选k个元素
    if depth == k:
        for i in li:
            print('{:>3}'.format(i), end='')
        print()
        return
    # 进行讲解,该思想能用到哪些类似的题目中
    for i in range(val,n+1):
        if vis[i]==0:
            li.append(i)
            vis[i]=1
            dfs(depth+1,i+1) # 下一层只能选比i大的元素
            vis[i]=0 # 回溯:撤销选择(标志位0)
            li.pop() # 回溯:撤销选择(移除数据)

dfs(0,1) # 初始调用:深度0,起始值1
-------------------------------------------
输入:5 3
输出:
	  1  2  3
      1  2  4
      1  2  5
      1  3  4
      1  3  5
      1  4  5
      2  3  4
      2  3  5
      2  4  5
      3  4  5
-------------------------------------------
组合问题

给定两个正整数n、k,假设序列S=[1,2,3,...,n],求从S中任选k个的所有可能结果【组合】。

python 复制代码
n,size=list(map(int,input().split()))

# 全局变量result来存储所有找到的组合
result=[]

# 临时列表temp来存储当前正在构建的组合
# index表示当前索引
def dfs(index,temp):
    # 如果当前组合的长度等于size,将其加入结果中
    if len(temp)==size:
        result.append(temp[:]) # 【重点】
        return
    # 如果当前索引超过了n,返回
    if index>n:
        return
    # 选择当前索引,继续递归
    temp.append(index)
    dfs(index+1,temp)
    # 不选择当前索引,继续递归
    temp.pop()
    dfs(index+1,temp)

dfs(1,[])

for element in result:
    print(" ".join(map(str,element)))
-------------------------------------------
输入:
	4 2
输出:
	1 2
    1 3
    1 4
    2 3
    2 4
    3 4
-------------------------------------------

3. 回溯模版------子集问题

子集的问题相对于组合和排列用的更多,子集的模版特别简单直接看代码

python 复制代码
n=int(input())
li = list(map(int,input().split()))

# lis 存放结果
lis=[]

def dfs(depth):
    if depth==n:
        print(lis)
        return
    # 选择第depth个元素
    lis.append(li[depth])
    dfs(depth+1)
    lis.pop()

    # 不选择第depth个元素
    dfs(depth+1)

dfs(0)
-------------------------------------
输入:
    3
    1 2 3
输出:
    [1, 2, 3]
    [1, 2]
    [1, 3]
    [1]
    [2, 3]
    [2]
    [3]
    []
-------------------------------------
类似题目:调配料

题目描述

Perket 是一种流行的美食。为了做好 Perket,厨师必须谨慎选择食材,以在保持传统风味的同时尽可能获得最全面的味道。你有 n 种可支配的配料。对于每一种配料,我们知道它们各自的酸度 s 和苦度 b。当我们添加配料时,总的酸度为每一种配料的酸度总乘积;总的苦度为每一种配料的苦度的总和。

众所周知,美食应该做到口感适中,所以我们希望选取配料,以使得酸度和苦度的绝对差最小。

另外,我们必须添加至少一种配料,因为没有任何食物以水为配料的。

输入格式

第一行一个整数 n,表示可供选用的食材种类数。

接下来 n 行,每行 2 个整数 s**ib**i ,表示第 i 种食材的酸度和苦度。

输出格式

一行一个整数,表示可能的总酸度和总苦度的最小绝对差。

python 复制代码
n=int(input())
a=[]
b=[]
for i in range(n):
    x,y=list(map(int,input().split()))
    a.append(x)
    b.append(y)

# 初始化:记录 酸程度 和 苦程度
suan=1
ku=0

def bfs(depth):
    global suan
    global ku
    global mins
    for i in range(depth,n):
        suan=suan*a[i]
        ku=ku+b[i]
        mins=min(mins,abs(suan-ku))
        bfs(i+1)
        suan=suan/a[i]
        ku=ku-b[i]


mins=abs(a[0]-b[0])
bfs(0)
print(int(mins))
--------------------------------------
输入:
	4
    1 7
    2 6
    3 8
    4 9
输出:
	1
--------------------------------------

二、棋盘问题

1、N皇后问题

在n×n的国际棋盘上摆放n个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

python 复制代码
def NQueen(n):
    def backtrack(row):
        # 内部的函数需要修改外部函数的变量时,就可以使用nonlocal关键字来指明这个变量(不是全局变量)
        nonlocal counter # 记录成功放置N个皇后的方案总数
        # 退出条件(遍历到第n行):说明所有行都成功放置了皇后,此时计数器counter加一。
        if row == n:
            counter += 1
            return
        for col in range(n):
            if (col not in columns) and (row-col not in diagonals1) and (row+col not in diagonals2):
                # 将当前位置添加进来
                columns.add(col)
                diagonals1.add(row-col)
                diagonals2.add(row+col)
                # 回溯:如果可以,则标记该列为已占用,并递归到下一行。
                backtrack(row + 1)
                # 用于尝试列的下一个位置
                columns.remove(col)
                diagonals1.remove(row-col)
                diagonals2.remove(row+col)

    counter = 0
    columns = set() # 存储列位置
    diagonals1 = set() # 存储主对角线: row - col
    diagonals2 = set() # 存储辅对角线:row + col
    backtrack(0)
    return counter

n = int(input())
print(NQueen(n))
-----------------------
输入:8
输出:92
-----------------------
2、八皇后问题

在8×8的国际棋盘上摆放了8个皇后,判断其是否是一个合法的摆放方式,即任意两个皇后都不能处于同一行、同一列或同一斜线上。

python 复制代码
def f(board):
    rows=set() #存储行位置
    cols = set() # 存储列位置
    main_diag=set() #存储主对角线: row - col
    anti_diag=set() #存储辅对角线:row + col

    for row in range(8):
        for col in range(8):
            # 判断若是皇后
            if board[row][col] == 1:
                if (row in rows) or (col in cols) or ((row-col) in main_diag) or ((row+col) in anti_diag):
                    return 0
                rows.add(row)
                cols.add(col)
                main_diag.add(row-col)
                anti_diag.add(row+col)
    return 1

board = [list(map(int,input().split())) for _ in range(8)]

# 输出
if f(board)==0:
    print("NO")
else:
    print("YES")
-------------------------
输入:
0 0 0 0 0 1 0 0
0 1 0 0 0 0 0 0
0 0 0 0 0 0 1 0
1 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 0 0 0 0 1
0 0 0 0 1 0 0 0
0 0 1 0 0 0 0 0
输出:
YES
-------------------------

三、联通块问题

1、小怂爱水洼

问题描述

小怂喜欢收集水洼中的水,他每到一个水量不为零的小水洼中就会收集里面的所有水。

小怂去到了一个大小为 N×M 的水洼上,水洼上的每一块小水洼水量为 ai,j(i∈[1,n],j∈[1,m])。小怂的起始点是水洼中水量不为 0 的任意一个小水洼。假设小怂的起始点是 (1,1),他可以移动无数次,每次移动只能移动到当前水洼上下左右四个方向的相邻小水洼上,并且需要满足相邻小水洼水量大于 0,即如果新的小水洼水量为零,小怂就不能走到这个小水洼上。特别地,小怂可以重复走到某块小水洼,但是小水洼中的水只能被收集一次;如果起始点的水洼中有水,他会收集那些水。

值得注意的是:每块上下左右相连且水量不为 0 的小水洼会合成一块大水洼,小怂每到一块新的大水洼,他之前收集到的水量会变为 0。

求解小怂在大水洼中可以收集到的最大水量。

思路讲解

复制代码
1 2 0
3 4 0
0 0 5

第一个大水洼由 (1,1), (1,2), (2,1), (2,2) 四个小水洼组成,水量总和为 10。

第二个大水洼由 (3,3) 一个小水洼组成,水量总和为 5。

因此小怂在大水洼中可以获得的最大水量是 10。

python 复制代码
n,m=list(map(int,input().split()))
pool = [list(map(int, input().split())) for _ in range(n)]

# 记录是否走过:0表示没有走过;1表示走过
visit = [[0] * m for _ in range(n)]
# 位移偏量:上下左右
direction = [[0,1],[0,-1],[1,0],[-1,0]]
ans=0

def bfs(sx, sy):
    water = 0  # 初始化当前连通区域的水量为0
    queue = [[sx, sy]]  # 创建一个【队列】,用于BFS,初始时将起始格子加入队列
    while queue:  # 当队列不为空时,继续搜索
        x, y = queue.pop(0)  # 弹出队列的第一个元素(如:sx,sy),获取其坐标【当前正在访问的格子】
        water += pool[x][y]  # 将当前格子的水量加入到water中
        for dx, dy in direction:  # 遍历四个方向
            xx, yy = x + dx, y + dy  # 计算新坐标
            if xx < n and xx>=0 and yy < m and yy>=0:  # 判断新坐标是否在网格范围内
                # 如果新坐标对应的格子未被访问过且水量不为0
                if visit[xx][yy]==0 and pool[xx][yy] != 0:  
                    visit[xx][yy] = 1  # 标记为已访问
                    queue.append([xx, yy])  # 将新坐标加入队列,继续搜索
    return water  # 返回当前连通区域的水量


for i in range(n):
    for j in range(m):
        # 若这个格子没有做过 并且 对应值不为0
        if visit[i][j]==0 and pool[i][j] != 0:
            visit[i][j]=1
            ans=max(ans,bfs(i,j))

print(ans)
------------------------------------------
输入:
	3 3
    1 2 0
    3 4 0
    0 0 5
输出:
	10
------------------------------------------
2、全球变暖

问题描述

你有一张某海域 N ×N 像素的照片,. 表示海洋、 # 表示陆地,如下所示:

复制代码
.......
.##....
.##....
....##.
..####.
...###.
.......

其中 "上下左右" 四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座岛屿。

由于全球变暖导致了海面上升,科学家预测未来几十年,岛屿边缘一个像素的范围会被海水淹没。具体来说如果一块陆地像素与海洋相邻(上下左右四个相邻像素中有海洋),它就会被淹没。

例如上图中的海域未来会变成如下样子:

复制代码
.......
.......
.......
.......
....#..
.......
.......

请你计算:依照科学家的预测,照片中有多少岛屿会被完全淹没。

解题思路

python 复制代码
n = int(input())
a=[list(input()) for _ in range(n)]

# 记录是否走过:0表示没有走过;1表示走过
visited = [[0]*n for _ in range(n)]
# 位移偏量:上下左右
direction = [[0,1],[0,-1],[1,0],[-1,0]]
# 记录岛屿的数量
count=0

def dfs(x,y):
    global flag # 表示是否会被淹掉:0表示会被淹,1表示不会被淹
    ans=0 # 岛屿格子数
    queue = [[x, y]]  # 创建一个【队列】,用于BFS,初始时将起始格子加入队列
    while queue:
        x, y = queue.pop(0)  # 弹出队列的第一个元素(如:sx,sy),获取其坐标【当前正在访问的格子】
        # 【当前正在访问的格子】不会被淹没
        if a[x - 1][y] == '#' and a[x + 1][y] == '#' and a[x][y - 1] == '#' and a[x][y + 1] == '#':
            flag = 1
        for dx, dy in direction:  # 遍历四个方向
            xx, yy = x + dx, y + dy  # 计算新坐标
            if a[xx][yy]=='#' and visited[xx][yy]==0:  # 判断新坐标是否在网格范围内
                visited[xx][yy] = 1  # 标记为已访问
                queue.append([xx, yy])  # 将新坐标加入队列,继续搜索


for i in range(n):
    for j in range(n):
        if visited[i][j] == 0 and a[i][j] == '#':
            visited[i][j]=1
            flag=0
            dfs(i,j)
            if flag==0: # 被淹掉
                count+=1

print(count)
---------------------------------------------
输入:
	7
    .......
    .##....
    .##....
    ....##.
    ..####.
    ...###.
    .......
输入:
	1 # 被淹掉的岛屿数量为1
---------------------------------------------
3、填涂染色

问题描述

由数字 0 组成的方阵中,有一任意形状的由数字 1 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 2。例如:6×6 的方阵(n=6),涂色前和涂色后的方阵如下:

如果从某个 0 出发,只向上下左右 4 个方向移动且仅经过其他 0 的情况下,无法到达方阵的边界,就认为这个 0 在闭合圈内 。闭合圈不一定是环形的,可以是任意形状,但保证闭合圈内的 0 是连通的(两两之间可以相互到达)。

解题思路:从边缘DFS,把圈外的0都记录下【visited=1】,最后没记录的就是圈内的0

python 复制代码
n=int(input())
grid=[list(map(int,input().split())) for _ in range(n)]

# 记录圈外的0
visited = [[0]*n for _ in range(n)]
# 位移偏量:上下左右
direction = [[0,1],[0,-1],[1,0],[-1,0]]

# 标记圈外0
def dfs(x,y):
    queue=[[x,y]]
    while queue:
        x1,y1=queue.pop(0)
        # 遇到边界1,停止标记
        if grid[x1][y1]==1:
            return
        for dx,dy in direction:
            xx,yy=x1+dx,y1+dy
            # 判断新坐标是否在网格范围内
            if xx>=0 and xx<n and yy>=0 and yy<n:
                # 格子是0【可通过】 并且 没有访问过
                if grid[xx][yy] == 0 and visited[xx][yy]==0:
                    visited[xx][yy]=1
                    queue.append([xx, yy])  # 将新坐标加入队列,继续搜索

# 从矩阵的四个边界上的每一个位置开始进行搜索
for i in range(n):
    dfs(0, i)
    dfs(n-1, i)
    dfs(i, 0)
    dfs(i, n-1)

for i in range(n):
    for j in range(n):
        # 若不是圈外 并且 访问的是0
        if visited[i][j]==0 and grid[i][j]==0:
            grid[i][j]=2

for i in range(n):
    for j in range(n):
        if j<n-1:
            print(grid[i][j],end=' ')
        else:
            print(grid[i][j])
------------------------------------------------
输入:
	6
    0 0 0 0 0 0
    0 0 1 1 1 1
    0 1 1 0 0 1
    1 1 0 0 0 1
    1 0 0 0 0 1
    1 1 1 1 1 1
输出:
	0 0 0 0 0 0
    0 0 1 1 1 1
    0 1 1 2 2 1
    1 1 2 2 2 1
    1 2 2 2 2 1
    1 1 1 1 1 1
------------------------------------------------
4、岛屿数量

题目描述:给定一个由 1(陆地)和 0(水)组成的矩阵,你需要计算岛屿的数量。岛屿由水平方向或垂直方向上相邻的陆地连接而成,并且四周都是水域。你可以假设矩阵外均被水包围。

例如下图所示,含有3个岛屿数量。

python 复制代码
n, m = map(int, input().split())
# 邻接矩阵【二维数组】
grid=[list(map(int,input().split())) for _ in range(n)]
# 位移偏量:上下左右
direction = [[0, 1], [1, 0], [0, -1], [-1, 0]]
# 记录是否走过:0表示没有走过;1表示走过
visited = [[0] * m for _ in range(n)]

num=0

def bfs(x,y):
    queue=[[x,y]]
    while queue:
        x1,y1=queue.pop(0)
        if grid[x1][y1]==0:
            return
        for dx,dy in direction:
            xx=x1+dx
            yy=y1+dy
            # 判断新坐标是否在网格范围内
            if xx < n and xx>=0 and yy < m and yy>=0:
                # 如果下一个位置是未访问过的陆地,则标记为已访问,并继续深度优先搜索
                if grid[xx][yy]==1 and visited[xx][yy]==0:
                    visited[xx][yy]=1
                    queue.append([xx,yy])

for i in range(n):
    for j in range(m):
        if visited[i][j]==0 and grid[i][j]==1:
            num += 1 # 发现新的陆地区域,计数器加1
            bfs(i,j)
print(num)
-----------------------------------
输入:
	4 5
    1 1 0 0 0
    1 1 0 0 0
    0 0 1 0 0
    0 0 0 1 1
输出:
	3
-----------------------------------

四、搜索问题

1、扫雷游戏

扫雷游戏是一款十分经典的单机小游戏。

在 n 行 m 列的雷区中有一些格子含有地雷(称之为地雷格),其他格子不含地雷(称之为非地雷格)。

玩家翻开一个非地雷格时,该格将会出现一个数字------提示周围格子中有多少个是地雷格。

游戏的目标是在不翻出任何地雷格的条件下,找出所有的非地雷格。

现在给出 n 行 m 列的雷区中的地雷分布,要求计算出每个非地雷格周围的地雷格数。

注:一个格子的周围格子包括其:左上、上、右上、左、右、左下、下、右下八个方向上与之直接相邻的格子。

基本思想

python 复制代码
n,m=list(map(int,input().split()))

# 存放信息
g=[input() for _ in range(n)]

# 位移偏量
dx = [-1, -1, -1, 0, 0, 1, 1, 1]
dy = [-1, 0, 1, -1, 1, -1, 0, 1]

def check(x,y):
    cnt=0
    # 遍历八个方向(位移偏量)
    for i in range(0,8):
        a=x+dx[i] # 各个方向上对应的x
        b=y+dy[i] #各个方向上对应的 y
        # 边界性
        if a<0 or a>n-1 or b<0 or b>m-1:
            continue
        if g[a][b]=='*':
            cnt+=1
    return cnt


# 遍历每一个点,搜索附近的地雷数
for i in range(0,n):
    for j in range(0,m):
        if g[i][j]=='*':
            print('*',end='')
        else:
            ans=check(i,j)
            print(ans,end='')
    if i<n-1:
        print()
-----------------------------------
输入:
	3 3
    *??
    ???
    ?*?
输出:
	*10
    221
    1*1
-----------------------------------

2、走迷宫

题目描述

给定一个 N×M 的网格迷宫G。G的每个格子要么是道路,要么是障碍物(道路用 1 表示,障碍物用 0 表示)。

已知迷宫的入口位置为 (x1,y1),出口位置为 (x2,y2)。问从入口走到出口,最少要走多少个格子。

输入描述

输入第 1 行包含两个正整数 N,M,分别表示迷宫的大小。

接下来输入一个 N×M 的矩阵。若 Gi,j=1表示其为道路,否则表示其为障碍物。

最后一行输入四个整数 x1,y1,x2,y2,表示入口的位置和出口的位置。

输出描述

输出仅一行,包含一个整数表示答案。

若无法从入口到出口,则输出 −1。

python 复制代码
n,m=map(int,input().split())
a=[list(map(int,input().split())) for i in range(n)]
x1,y1,x2,y2=list(map(int,input().split()))
# 从 0 开始的索引,因此将这些坐标减去 1 以转换为索引
x1, y1, x2, y2 = x1 - 1, y1 - 1, x2 - 1, y2 - 1

# 记录是否走过:0表示没有走过;1表示走过
visit = [[0] * m for _ in range(n)]
# 位移偏量:上下左右
direction = [[0,1],[0,-1],[1,0],[-1,0]]

def bfs():
    queue = [[x1, y1, 0]]
    while queue:
        x,y,z=queue.pop(0)
        # 到达目的地
        if x==x2 and y==y2:
            return z
        # 遍历四个方向
        for dx,dy in direction:
            # 计算新坐标
            xx = x + dx
            yy = y + dy
            # 判断新坐标是否在网格范围内 并且 新坐标为道路(值为1) 并且 标志是未走过的(值为0)
            if xx < n and xx >= 0 and yy < m and yy >= 0 and a[xx][yy]==1 and visit[xx][yy]==0:
                visit[xx][yy]=1 # 已经访问了,标记为1
                queue.append([xx,yy,z+1])
    return -1


print(bfs())
-------------------------------------------
输入:
	5 5 
    1 0 1 1 0
    1 1 0 1 1 
    0 1 0 1 1
    1 1 1 1 1
    1 0 0 0 1
    1 1 5 5 
输出:
	8
-------------------------------------------

3、马的遍历

题目描述

有一个 n ×m 的棋盘,在某个点 (x ,y) 上有一个马,要求你计算出马到达棋盘上任意一个点最少要走几步。

输入格式

输入只有一行四个整数,分别为 n ,m ,x ,y

输出格式

一个 n ×m 的矩阵,代表马到达某个点最少要走几步(不能到达则输出 −1)。

python 复制代码
n,m,x,y=list(map(int,input().split()))

# 从 0 开始的索引,因此将这些坐标减去 1 以转换为索引
x,y=x-1,y-1
# 棋盘
board = [[-1]*m for _ in range(n)]
board[x][y]=0 # 【起始位置】设置为0
# 记录是否走过:0表示没有走过;1表示走过
visit = [[0] * m for _ in range(n)]
visit[x][y] = 1  # 【标记起点】为已访问
# 位移偏量:马走日字格
direction = [[-2,-1],[-2,1],[-1,-2],[-1,2],[1,-2],[1,2],[2,-1],[2,1]]

def bfs(x1,y1):
    queue=[[x1,y1,0]]
    while queue:
        x, y, z = queue.pop(0)
        for dx,dy in direction:
            xx=x+dx
            yy=y+dy
            if xx < n and xx >= 0 and yy < m and yy >= 0 and visit[xx][yy] == 0:
                visit[xx][yy] = 1  # 标记为已访问
                if board[xx][yy] == -1 or board[xx][yy] > z + 1:  # 更新最短步数
                    board[xx][yy] = z + 1
                queue.append([xx, yy, z + 1])  # 加入队列继续搜索
    return -1

bfs(x, y)  # 从起点开始搜索
# 输出结果矩阵
for i in range(n):
    for j in range(m):
        print(board[i][j], end=' ')
    print()  # 换行输出每一行的结果
--------------------------------------------------
输入:
	3 3 1 1
输出:
	0 3 2 
    3 -1 1 
    2 1 4 
--------------------------------------------------

4、蜂巢

蜂巢由大量的六边形拼接而成, 定义蜂巢中的方向为: 0 表示正西方向, 1 表示西偏北 60度,2 表示东偏北 60度,3 表示正东, 4 表示东偏南 60度,5 表示西 偏南 60度。

对于给定的一点 O, 我们以 O 为原点定义坐标系, 如果一个点 A 由 O 点 先向 d 方向走 p 步再向 (d+2) mod 6 方向 ( d 的顺时针 120度 方向) 走 q 步到达, 则这个点的坐标定义为 (d,p,q)。在蜂窝中, 一个点的坐标可能有多种。

下图给出了点 B(0,5,3) 和点 C(2,3,2) 的示意。

给定点 (d1,p1,q1) 和点 (d2,p2,q2), 请问他们之间最少走多少步可以到达?

输入格式

输入一行包含 6 个整数 d1,p1,q1,d2,p2,q2 表示两个点的坐标, 相邻两个整 数之间使用一个空格分隔。

输出格式

输出一行包含一个整数表示两点之间最少走多少步可以到达。

python 复制代码
d1, p1, q1, d2, p2, q2 = map(int, input().split())
x = [-1, -0.5, 0.5, 1, 0.5, -0.5]   #0-5 六个方向移动单位长度,分别对应的x轴变化情况
y = [0, 1, 1, 0, -1, -1]           #0-5 六个方向移动单位长度,分别对应的y轴变化情况

#计算第一个点(x1, y2)对应原点o的坐标
x1 = x[d1]*p1 + x[(d1+2)%6]*q1
y1 = y[d1]*p1 + y[(d1+2)%6]*q1

#计算第二个点(x2, y2)对应原点o的坐标
x2 = x[d2]*p2 + x[(d2+2)%6]*q2
y2 = y[d2]*p2 + y[(d2+2)%6]*q2

#计算两个点在x轴上和y轴上的差值的绝对值
disX, disy = abs(x2-x1), abs(y2-y1)

#情况1 第一个点直接可以斜着到达第二个点
if disy*0.5 >= disX:
    print(int(disy))
#情况2 第一个点必须分别沿x轴和y轴(先x或先y不影响结果)各走一段,才能到第二个点
else:
    # print(disy+disX-0.5*disy)
    print(int(0.5 * disy + disX))
------------------------------------------------------
输入:0 5 3 2 3 2
输出:7
------------------------------------------------------
相关推荐
谢道韫6665 分钟前
37-串联所有单词的子串
开发语言·算法·c#
爱编程的小新☆7 分钟前
2025年第十六届蓝桥杯省赛JavaB组真题回顾
算法·职场和发展·蓝桥杯
callJJ9 分钟前
Dijkstra算法求解最短路径—— 从零开始的图论讲解(2)
java·数据结构·算法·intellij-idea·图论·dijkstra·图搜索算法
uhakadotcom19 分钟前
入门教程:Keras和PyTorch深度学习框架对比
后端·算法·面试
PHASELESS41141 分钟前
Java二叉树深度解析:结构、算法与应用实践指南
java·开发语言·数据结构·算法
牧木江41 分钟前
【从C到C++的算法竞赛迁移指南】第二篇:动态数组与字符串完全攻略 —— 写给C程序员的全新世界
c语言·c++·经验分享·笔记·算法
前端 贾公子1 小时前
力扣 283 移动零的两种高效解法详解
算法
学习2年半2 小时前
回溯算法:List 还是 ArrayList?一个深拷贝引发的思考
数据结构·算法·list
烁3472 小时前
每日一题(小白)暴力娱乐篇30
java·数据结构·算法·娱乐
Wils0nEdwards5 小时前
Leetcode 独一无二的出现次数
算法·leetcode·职场和发展