题目描述:
假设有一只兔子,有4个排成一排的洞,编号为1和4。兔子每天晚上跳到相邻的一个洞里住,2号洞可以跳1号和3号洞,4号洞只能跳3号洞。而猎人每天白天检查其中的一个洞。猎人告诉你每天检查的洞的编号,分析一下是否一定能抓到兔子。如果能抓到,找出最优路径。
这道题思路:想办法把兔子逼到最后一个洞或者第一个洞,这样它下一步只只有一个洞可跳自然就找到了,列举探索的思路也很重要,要分别列出当前可能藏的洞和当前没找到下一次可能藏的洞,找准这两个变量很关键,最终是要把兔子逼到第一个洞或者最后一个洞,所以下一次可能藏的洞会越来越少,所以猎人选择洞的时候也要考虑这一点(猎人选择所要找的洞,下一次可能藏的洞相比找之前可能藏的洞应该会少,直到最终剩下一个洞)。
例如:先选择1,兔子不在1,那么当前可能在的洞2,3,4,下一次可能藏的洞为1,2,3,4。这样跟下一次比较洞没有减少。所以想着第一步不应该选1。按照每找一次下一次可能藏的洞越来越少思路。实际我们代码中找的时候肯定是按顺序找,列出所有可能找的过程,所以我们代码就要决定哪一步是不需要的,找准条件筛选。
BFS(广度优先算法)
此题的思想和BFS(广度优先算法)类似,简单说一下。BFS(广度优先算法)是一个针对图和树的遍历算法。简单的说,BFS是从根节点开始,沿着树的宽度遍历树的节点,如果所有节点均被访问,则算法中止;最初用于解决迷宫最短路径和网络路由等问题。
BFS操作步骤如下:
1、把起始点放入queue;
2、重复下述2步骤,直到queue为空为止:
1) 从queue中取出队列头的点;并删除队列首节点。
2) 找出与此点邻接的且尚未遍历的点,进行标记,然后全部放入queue中。
使用该算法关键的数据结构为队列。新加入的节点一般要是未处理过的,所以某些情况下最初要对所有节点进行标记。 (1)将起始节点1放入队列中,标记为已遍历:
(2)从queue中取出队列头的节点1,找出与节点1邻接的节点2,3,标记为已遍历,然后放入queue中。
(3)从queue中取出队列头的节点2,找出与节点2邻接的节点1,4,5,由于节点1已遍历,排除;标记4,5为已遍历,然后放入queue中。以此类推.........
要标志节点是否访问过,用数组是一种很快速的方法,但有时数据量太大,很难用一个大数组来记录时,采用hash,set是最好的做法。实际上visit数组在这里也是充当hash,set的作用。python中暂时采用集合set。还可以利用字典dict标记(充当hash)。
猎人抓兔子代码如下:
import queue
def get_new_set_hole(search_hole, cur_hole):
"""
:param search_hole: 要寻找的洞
:param cur_hole: 当前兔子可能在的洞
:return:
"""
# 利用集合代表当查看search_hole后,没有抓到兔子,兔子可能藏身的洞
set_hole = set()
# 在当前可能存在的洞找相邻的洞即为下一次可能藏身的洞
for hole in cur_hole:
# 右邻的洞,没有抓到兔子,所以!=search_hole
if hole+1 != search_hole and hole+1 <= 4:
set_hole.add(hole+1)
# 左邻的洞
if hole-1 != search_hole and hole-1 >= 1:
set_hole.add(hole-1)
return set_hole
def main():
"""
1.首先将初始情况加入队列,当队列不为空时:
2.如果已经得到了结果,那就结束,跳转到(7.)
3.取出队列的第一个值
4.分别检查4个洞
5.如果检查之后兔子的藏身洞变少,那就将这个洞加入到队列中
6.如果检查后兔子没有藏身之处,则已经找到结果,
将结果赋值给path同时设置已找到结果标志,并跳出循环
7.输出最后记录的结果
8.根据得到的结果,找出每一步后兔子藏身之处
"""
queue_hole = queue.Queue()
# 记录最终的找寻过程
path = []
# 找到就退出循环标志
flag = False
# 三个参数分别表猎人要找的洞,当前可能藏身的洞,空列表记录猎人找洞的记录
# 初始状态还没开始找,所以暂时赋下面的值,第一个参数一般是1-4循环尝试,
# 这里初始赋值随便赋一个不影响的值
queue_hole.put((0, {1, 2, 3, 4}, []))
while not queue_hole.empty():
if flag:
break
cur, cur_hole_set, record = queue_hole.get()
for hole in range(1, 5):
copy_record = record[:]
next_hole_set = get_new_set_hole(hole, cur_hole_set)
if len(next_hole_set) <= len(cur_hole_set):
copy_record.append(hole)
queue_hole.put((hole, next_hole_set, copy_record))
if not next_hole_set:
record.append(hole)
path = copy_record
flag = True
break
print(path)
例子:LeetCode 127 单词接龙
题目描述:
字典wordList中从单词beginWord和endWord的转换序列是一个按下述规格形成的序列beginWord -> s1 -> s2 -> ... -> sk:
每一对相邻的单词只差一个字母。
对于 1 <= i <= k 时,每个 si 都在 wordList 中。注意,beginWord 不需要在 wordList 中。sk == endWord
给你两个单词beginWord和endWord和一个字典wordList,返回从beginWord 到endWord的最短转换序列 中的 单词数目。如果不存在这样的转换序列,返回 0 。
要求:①每次只能变换一个字母。②每个字符串长度相同
③每次转换的字符串必须在wordList中。④如果没有这种转换,返回0。⑤没有重复的
示例:
输入:beginWord = "hit", endWord = "cog", wordList = ["hot", "dot", "dog", "lot", "log", "cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。输入:beginWord = "hit", endWord = "cog", wordList = ["hot", "dot", "dog", "lot", "log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。
解题思路:
1、本题要求的是最短转换序列的长度,看到最短首先想到的就是广度优先搜索。想到广度优先搜索自然而然的就能想到图,但是本题并没有直截了当的给出图的模型,因此我们需要把它抽象成图的模型。
2、我们可以把每个单词都抽象为一个点,如果两个单词可以只改变一个字母进行转换,那么说明他们之间有一条双向边。因此我们只需要把满足转换条件的点相连,就形成了一张图。
3、基于该图,我们以 beginWord 为图的起点,以 endWord 为终点进行广度优先搜索,寻找 beginWord 到 endWord 的最短路径。
由图可得显然有两条路径满足最短路径。
本题跟二叉树的层次遍历类似,想像有一颗二叉树,广度优先搜索就是把二叉树拆成一层一层的,广度优先。(深度优先搜索就是直接一路找到最下面,如果没找到然后再返回来,深度优先)。
因为此题只要求最短路径长度,所以只要遍历到该层时及时记录该层的层数(深度deep)即可。所以差不多就是个找寻满足条件的单词的问题,找到单词之后及时入队列和出队列。
按照分层思路来:那么每层满足条件的单词和层数如下:同一个单词出现在两层因为不止一条路径
hit 1 i变为o 到2
hot 2 h变为d和l到 3
dot 3 d变为l,但lot在第三层已经访问过了,所以不到4,t变为g, 到4
lot 3 t变为g 到4 ,由于l可以变为d,但是dot访问过了。所以4没有dot
dog 4 ..............
log 4
cog 5
import queue
def word_chain(begin_word, end_word, word_list):
"""
:param begin_word: 开始单词
:param end_word: 结束单词
:param word_list: 单词表
:return:
"""
if end_word not in word_list:
return 0
# 构造一个字典,key为'*ot','h*t','ho*'这样的形式(key为word_list中单词变化而来)
# 然后把满足只变化一个字母的单词归并到同一个key的value中。
# 例如:每个单词都有len(word)中形式,hot: *ot, h*t, ho*,分别当做key,然后然后选取满足条件的value
# {'*ot': ['hot', 'dot', 'lot'], 'h*t': ['hot'], 'ho*': ['hot']}
word_dict = dict()
for word in word_list:
for i in range(len(word)):
key = "{0}*{1}".format(word[:i], word[i+1:])
if key in word_dict:
word_dict[key].append(word)
else:
word_dict[key] = [word]
queue_data = queue.Queue()
queue_data.put((begin_word, 1))
# 集合标记单词是否访问过
visited = set()
while not queue_data.empty():
word, steps = queue_data.get()
print(word, steps)
if word not in visited:
visited.add(word)
if word == end_word:
return steps
for i in range(len(word)):
s = "{0}*{1}".format(word[:i], word[i+1:])
for j in word_dict.get(s, []):
# 如果j在visited中,说明j已经被访问过了,不需要再次进行访问。
if j not in visited:
queue_data.put((j, steps + 1))
return 0