题目描述
X 星球的流行宠物是青蛙,一般有两种颜色:白色和黑色。
X 星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去。
如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙。
∗ W W W B B B ∗WWWBBB ∗WWWBBB
其中,
W 字母表示白色青蛙,
B 表示黑色青蛙,
∗ 表示空杯子。
X 星的青蛙很有些癖好,它们只做 3 个动作之一:
跳到相邻的空杯子里。
隔着 1 只其它的青蛙(随便什么颜色)跳到空杯子里。
隔着 2 只其它的青蛙(随便什么颜色)跳到空杯子里。
对于上图的局面,只要 1 步,就可跳成下图局面:
W W W ∗ B B B WWW∗BBB WWW∗BBB
本题的任务就是已知初始局面,询问至少需要几步,才能跳成另一个目标局面。
输入描述
输入为 2 行,2 个串,表示初始局面和目标局面。我们约定,输入的串的长度不超过 15。
输出描述
输出要求为一个整数,表示至少需要多少步的青蛙跳。
输入输出样例
示例
输入
※WWBB
WWBB※
输出
2
解题思路
先来看看 ∗ W W W B B B ∗WWWBBB ∗WWWBBB 怎么变成 W W W ∗ B B B WWW∗BBB WWW∗BBB 。
很简单,第三个 W 的青蛙向左跳了2格,占了 ∗ ∗ ∗ 的位置,而跳走后,原来的位置变为 ∗ ∗ ∗ 。
再看看 ∗ W W B B *WWBB ∗WWBB 怎么变为 W W B B ∗ WWBB* WWBB∗
至少需要两步: ∗ W W B B *WWBB ∗WWBB → W W ∗ B B WW*BB WW∗BB → W W B B ∗ WWBB* WWBB∗
对于已知初始状态和结束状态的题目,简单粗暴的解题思路无非两种,一种枚举,一种遍历,把所有的可能性都试一遍,而找出其中 路径最短 / 步骤最少 的,无疑要使用遍历算法,那么优先考虑 广度优先BFS 或 深度优先DFS。
假设起始局面为 ∗ W W B B *WWBB ∗WWBB,目标局面为 W W B B ∗ WWBB* WWBB∗。
- 初始时,队列 Q = [[‘ ∗ W W B B *WWBB ∗WWBB’, 0]],集合 aset = {‘ ∗ W W B B *WWBB ∗WWBB’}。
- 从队列取出 [‘ ∗ W W B B *WWBB ∗WWBB’, 0],对其应用各种移动方式:
当移动方式为 1 时,得到新局面 W ∗ W B B W*WBB W∗WBB,步数变为 1。检查发现 W ∗ W B B W*WBB W∗WBB 不在 aset 中,将其加入 aset 并把 [‘ W ∗ W B B W*WBB W∗WBB’, 1] 加入队列 Q。
类似地,依次应用其他移动方式生成新局面并处理。 - 继续从队列中取出元素进行探索,直到找到目标局面 W W B B ∗ WWBB* WWBB∗,此时输出的步数即为从起始局面到目标局面的最少步数。
通过这种逐层扩展搜索空间的方式,使用 广度优先搜索BFS 能够保证第一次找到目标状态时所经过的路径是最短的。
算法步骤
- 读取用户输入的初始局面和目标局面。
- 定义青蛙可能的移动方式。
- 使用集合 aset 记录已经访问过的局面,避免重复搜索。
- 初始化队列 Q,将初始局面和步数 0 加入队列。
- 不断从队列中取出元素,尝试所有可能的移动方式,得到新的局面。
- 如果新的局面等于目标局面,输出步数并结束搜索。
- 如果新的局面未被访问过,将其加入已访问集合和队列,继续搜索。
代码展示
感谢 @朱恒彤 同学提供的代码。
python">import os
import sys# 从用户输入中读取初始局面和目标局面,start 是起始状态,end 是目标状态
start = input()
end = input()# 定义青蛙可能的移动方式
# 这是一个列表,表示在字符串中可以移动的位数(向右或向左),
# 其中正数表示向右移动,负数表示向左移动。例如,1 表示向右移动 1 格,-2 表示向左移动 2 格
move = [1, -1, 2, -2, 3, -3]# aset 是一个集合,集合的特点是元素具有唯一性,用于记录已经访问过的状态,以避免重复访问。
# 初始时将起始状态加入集合
aset = {start}# 定义广度优先搜索函数
def bfs():# 初始化队列,队列中的每个元素是一个列表,包含当前局面和到达该局面所需的步数# 初始时队列中只有一个元素,即起始状态和对应的步数 0,说明步数是从 0 开始的Q = [[start, 0]]# 当队列不为空时,继续搜索while Q:# 从队列中取出队首元素# 运用了 while Q 循环。只要 Q 不为空,就会从队列中取出一个 old# old 是一个列表,old[0] 是当前局面,old[1] 是到达该局面所需的步数old = Q.pop(0)# 遍历所有可能的移动方式for i in move:# 将当前局面的字符串转换为字符列表,因为字符串是不可变类型,而列表是可变类型,方便进行字符交换操作a = list(old[0])# 获取到达当前局面所需的步数b = old[1]# 找到空位(用 '*' 表示)在字符列表中的索引c = a.index('*')# 计算空位移动后的新索引d = c + i# 检查新索引是否在合法范围内if 0 <= d < len(start):# 交换空位和新索引位置的字符a[c] = a[d]# 将新的字符位置的值转移到 '*' 的位置,把原来 '*' 的位置设置为新的字符a[d] = '*'# 将字符列表转换回字符串,重新形成新的状态字符,得到新的局面e = ''.join(a)# 步数加 1b += 1# 如果新的局面等于目标局面,输出当前步数并结束搜索if e == end:print(b)return# 如果新的局面未被访问过,将其加入已访问集合aset,表示为已访问,并加入队列Q,,表示加入新的局面和对应的步数,然后继续搜索if e not in aset:aset.add(e)Q.append([e, b])# 调用广度优先搜索函数开始搜索
bfs()