No.126 LeetCode題目 “成語接龍II”

首先透露一下,本人在2018年參加南京大學本科生開放日(保研夏令營)的機試時,此題目爲三道機試題中的一題,十個測試樣例,看你AC了幾個樣例。當時基本沒怎麼準備,看到題目時一臉懵,今天竟然又讓我在LeetCode上碰到了。

題目描述

給定兩個單詞(beginWordendWord)和一個字典 wordList,找出所有從 beginWordendWord 的最短轉換序列。轉換需遵循如下規則:

  1. 每次轉換隻能改變一個字母。
  2. 轉換過程中的中間單詞必須是字典中的單詞。

說明:

  • 如果不存在這樣的轉換序列,返回一個空列表。
  • 所有單詞具有相同的長度。
  • 所有單詞只由小寫字母組成。
  • 字典中不存在重複的單詞。
  • 你可以假設 beginWordendWord 是非空的,且二者不相同。

示例 1:

輸入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]

輸出:
[
  ["hit","hot","dot","dog","cog"],
  ["hit","hot","lot","log","cog"]
]

示例 2:

輸入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]

輸出: []

解釋: endWord "cog" 不在字典中,所以不存在符合要求的轉換序列。

解題思路

本題目採用廣度優先搜索(BFS)的方法,因此利用隊列來存儲單詞序列。
在這裏插入圖片描述
爲方便起見,我們利用哈希表給單詞標號,操作單詞編號,發現到達終點時再輸出單詞序列。

爲了保留相同長度的多條路徑,我們採用 cost 數組,其中 cost[i] 表示 beginWord 對應的點到第i個點的代價(即轉換次數)。初始情況下其所有元素初始化爲無窮大。

  • 若該節點爲終點,則將其路徑轉換爲對應的單詞存入答案;
  • 若該節點不爲終點,則遍歷和它連通的節點(假設爲 next )中滿足 cost[next] >= cost[now] + 1的加入隊列,並更新 cost[next] = cost[now] + 1。如果 cost[next] < cost[now] + 1,說明這個節點已經被訪問過,不需要再考慮。

以上思路參考:LeetCode官方

具體實現細節可見代碼及其註釋。

注:
bool checkDifference(string& str1, string& str2)函數的參數需要加引用,不然會超出時間限制。

具體代碼

class Solution {
public:
  //判斷兩個字符串是否相等
  bool checkDifference(string& str1, string& str2) {
    int difference = 0;
    for (int i = 0; i<str1.size() && difference <= 1; i++) {
      if (str1[i] != str2[i]) difference++;
    }
    return difference == 1;
  }
  vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
    unordered_map<string, int>wordId;//利用哈希表記錄單詞表中的詞
    vector<string>words;//記錄所有的單詞
    vector<vector<int>>edge;//記錄每個單詞相鄰的單詞id
    int id = 0;//單詞的編號
          //利用哈希表記錄單詞
    for (const string& word : wordList) {
      if (!wordId.count(word)) {
        wordId[word] = id++;
        words.push_back(word);
      }
    }
    //如果最終的單詞不在wordId中,直接返回{}
    if (wordId.count(endWord) == 0) return {};
    //如果起始單詞不在wordId中,將其加入
    if (wordId.count(beginWord) == 0) {
      wordId[beginWord] = id++;
      words.push_back(beginWord);
    }
    //爲edge賦值(找相鄰單詞)
    edge.resize(words.size());//重新賦值edge數組大小
    for (int i = 0; i<words.size(); i++) {
      for (int j = i + 1; j<words.size(); j++) {
        if (checkDifference(words[i], words[j])) {
          edge[i].push_back(j);
          edge[j].push_back(i);
        }
      }
    }
    //尋找全部路徑,利用廣度優先搜索
    int dest = wordId[endWord];
    queue<vector<int>>que;//定義一個隊列用於存放當前的所有路徑
    vector<vector<string>>ans;//存放最終的答案
    vector<int>cost(words.size(), INT_MAX);//找代價最小的路徑,先賦值爲最大
    cost[wordId[beginWord]] = 0;//將起始點的代價設爲0
    que.push(vector<int>{wordId[beginWord]});//將起始點放進que
    while (que.empty() == 0) {
      vector<int>now = que.front();//當前隊列中的遍歷順序
      que.pop();
      int last = now.back();//當前的隊尾元素
                  //如果隊尾元素就是終點單詞
      if (last == dest) {
        vector<string>tmp;
        //將now中的id映射成word後放入ans中
        for (int i = 0; i<now.size(); i++)
          tmp.push_back(words[now[i]]);
        ans.push_back(tmp);
      }
      //如果不是終點單詞還要繼續查找
      else {
        for (int i = 0; i<edge[last].size(); i++) {
          int next = edge[last][i];
          //如果下一個相鄰單詞的代價大於當前單詞(保證最短路徑)
          if (cost[last] + 1 <= cost[next]) {
            cost[next] = cost[last] + 1;
            //將新的序列加入到隊列中進行判斷。
            vector<int>tmp(now);
            tmp.push_back(next);
            que.push(tmp);
          }
        }
      }
    }
    return ans;
  }
};

性能結果

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章