Rust面试宝典第7题:单词接龙
作者:mmseoamin日期:2024-04-27

题目

        字典wordList中从单词beginWord和endWord的转换序列是一个按下述规格形成的序列:beginWord -> s1 -> s2 -> ... -> sk。序列满足以下三个条件:

        1、每一对相邻的单词只差一个字母。

        2、对于1 <= i <= k时,每个si都在wordList中。注意:beginWord不需要在wordList中。

        3、sk == endWord。

        给你两个单词beginWord和endWord,还有一个字典wordList,返回从beginWord到endWord的最短转换序列 中的单词数目 。如果不存在这样的转换序列,返回0。

        示例 1:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是"hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度为5。

        示例 2:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:"cog"不在字典中,所以无法进行转换。

解析

        这道题目主要考察的是图搜索算法,特别是广度优先搜索(BFS)算法的应用。同时,它也涉及到了数据结构的运用,比如:队列、集合等。此外,还需要理解题目中给出的转换规则,并能够将这些规则转化为图搜索中的节点和边的关系。具体来说,主要涉及以下的知识点。

        1、图的遍历算法:该问题可以转化为图论中的问题,其中每个单词是一个节点。如果两个单词之间只有一个字符不同,则它们之间存在一条边。因此,考察了对图遍历算法的理解和应用。

        2、广度优先搜索(BFS):为了找到从beginWord到endWord的最短路径,广度优先搜索是一个有效的方法。它逐层遍历图,直到找到目标节点。

        3、数据结构与算法设计:需要合理地设计数据结构(比如:队列、哈希集合等)来高效地进行广度优先搜索。

        4、字符串操作:在处理单词转换时,需要进行字符串的比较和修改操作。

        我们可以将这个问题转化为图搜索问题:将每个单词看作图中的一个节点,如果两个单词之间只有一个字母的差异,并且这两个单词都在给定的单词列表中,那么我们就在这两个单词之间画一条边。这样,问题就转化为了在给定的图中找到从beginWord到endWord的最短路径。

        为了找到最短路径,我们可以使用广度优先搜索(BFS)算法。BFS是一种用于遍历或搜索图的算法,它从图的某一节点(源节点)出发,访问最靠近源节点的所有邻居节点,然后对每个邻居节点进行相同的操作,直到找到目标节点或遍历完整个图。在BFS中,我们通常使用一个队列来保存待访问的节点,并使用一个集合或数组来记录已经访问过的节点,以避免重复访问。

        根据上面的算法思路,我们给出了下面的示例代码。

use std::collections::{HashSet, VecDeque};
use std::iter::FromIterator;
fn ladder_length(begin_word: String, end_word: String, word_list: Vec) -> i32 {
    let word_set: HashSet> = word_list
        .iter()
        .map(|word| word.chars().collect())
        .collect();
    if !word_set.contains(&end_word.chars().collect::>()) {
        return 0;
    }
    let mut queue = VecDeque::<(Vec, i32)>::new();
    let mut visited = HashSet::new();
    let begin_word_chars: Vec = begin_word.chars().collect();
    let begin_word_chars_clone = begin_word_chars.clone();
    queue.push_back((begin_word_chars, 1));
    visited.insert(begin_word_chars_clone);
    while let Some((word_chars, length)) = queue.pop_front() {
        let word: String = String::from_iter(word_chars.iter().cloned());
        if word == end_word {
            return length;
        }
        for (i, ch) in word_chars.iter().enumerate() {
            for c in 'a'..='z' {
                let mut new_word_chars = word_chars.clone();
                new_word_chars[i] = c;
                let new_word_chars_clone = new_word_chars.clone();
                if word_set.contains(&new_word_chars) && 
                    !visited.contains(&new_word_chars)     {
                    queue.push_back((new_word_chars, length + 1));
                    visited.insert(new_word_chars_clone);
                }
            }
        }
    }
    0
}
fn main() {
    let word_list1 = vec![
        "hot".to_string(),
        "dot".to_string(),
        "dog".to_string(),
        "lot".to_string(),
        "log".to_string(),
        "cog".to_string(),
    ];
    let len1 = ladder_length("hit".to_string(), "cog".to_string(), word_list1);
    println!("{}", len1);
    let word_list2 = vec![
        "hot".to_string(),
        "dot".to_string(),
        "dog".to_string(),
        "lot".to_string(),
        "log".to_string(),
    ];
    let len2 = ladder_length("hit".to_string(), "cog".to_string(), word_list2);
    println!("{}", len2);
}

        在上述的示例代码中,我们实现了一个名为ladder_length的函数。该函数用于计算从起始单词(begin_word)通过每次只改变一个字符的方式,变换到目标单词(end_word)所需的最少步骤数。这种变换需要保证每一步产生的单词都在给定的单词列表(word_list)中。这实际上是在解决一个经典的图搜索问题,其中每个单词代表图中的一个节点,如果两个单词之间只有一个字符不同,则它们之间存在一条边。

        首先,将单词列表转换为一个HashSet,以便快速检查一个单词是否在给定的单词列表中。检查目标单词是否在单词列表中,如果不在,则直接返回0,因为无法达到目标。然后,初始化一个队列和一个用于记录访问过的单词的HashSet,并将起始单词(转换为字符向量后)和步数1一起放入队列,并将起始单词标记为已访问。

        接着,使用while循环和pop_front方法从队列中取出单词和步数。如果取出的单词就是目标单词,直接返回步数。否则,尝试将该单词的每个字符替换为'a'到'z'中的每个字符,生成新的单词,并检查这些新单词是否在单词列表中且未被访问过。满足条件的新单词将被加入队列,并标记为已访问。

        最后,如果队列为空时仍未找到目标单词,函数返回0,表示无法从起始单词变换到目标单词。

总结

        上述源码实现是一个典型的广度优先搜索实现,用于解决单词变换问题。它通过维护一个队列和一个访问集合来有效地探索单词空间,并找到从起始单词到目标单词的最短路径。