Рубрики
Без рубрики

День 9 – Слово Лестница

Проблема дана два слова Beglword и EndWord, а словарь Wordlist, верните … Теги с лецкодом, Python.

Январь LeetCoding Challenge 2021 (33 частью серии)

Эта проблема

Учитывая два слова Начальное слово и Endword и словарь Wordlist верните длину кратчайшей последовательности трансформации от Начальное слово к Endword Таким, что:

  • Только одно письмо может быть изменено за раз.
  • Каждое преобразованное слово должно существовать в списке слов.

Вернуть 0. Если такая последовательность преобразования нет.

Пример 1:

Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog", return its length 5.

Пример 2:

Input: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.

Ограничения:

  • 1 длиной
  • EndWord.Length.Length
  • 1 длиной
  • WordList [i] .length.length
  • Начальное слово , Endword и Wordlist [я] состоят из строчных английских букв.
  • начало
  • Все строки в Wordlist являются Уникальный Отказ

Мои тесты

import pytest
from .Day9 import Solution

s = Solution()


@pytest.mark.parametrize(
    "beginWord,endWord,wordList,expected",
    [
        ("hit", "cog", ["hot", "dot", "dog", "lot", "log", "cog"], 5),        
        ("hit", "cog", ["hot", "dot", "dog", "lot", "log"], 0),
        ("count", "court", ["mount", "point"], 0),
    ],
)
def test_ladder_length(beginWord, endWord, wordList, expected):
    assert s.ladderLength(beginWord, endWord, wordList) == expected

Мое решение

from typing import List, Dict
import collections


def getLadderLength(
    queue,
    visited: Dict[str, int],
    otherVisited: Dict[str, int],
    wordLength: int,
    combos: Dict[str, List[str]],
):
    word, level = queue.popleft()
    for i in range(wordLength):
        testWord = word[:i] + "*" + word[i + 1 :]
        if testWord in combos:
            for w in combos[testWord]:
                if w in otherVisited:
                    return level + otherVisited[w]
                if w not in visited:
                    visited[w] = level + 1
                    queue.append((w, level + 1))
    return 0


def preProcessNeighbors(wordList: List[str], wordLength: int):
    combos = {}
    for word in wordList:
        for i in range(wordLength):
            testWord = word[:i] + "*" + word[i + 1 :]
            if testWord not in combos:
                combos[testWord] = []
            combos[testWord].append(word)
    return combos


class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        if endWord not in wordList:
            return 0
        wl = len(beginWord)
        combos = preProcessNeighbors(wordList, wl)

        qBegin = collections.deque([(beginWord, 1)])
        qEnd = collections.deque([(endWord, 1)])

        visitedBegin = {beginWord: 1}
        visitedEnd = {endWord: 1}
        ladderLength = 0

        while qBegin and qEnd:
            ladderLength = getLadderLength(qBegin, visitedBegin, visitedEnd, wl, combos)
            if ladderLength > 0:
                return ladderLength
            ladderLength = getLadderLength(qEnd, visitedEnd, visitedBegin, wl, combos)
            if ladderLength > 0:
                return ladderLength

        return 0

Анализ

Мой комментарий

После этого я в настоящее время переживаю высокий уровень ненависти к проблемам кодирования. Я на самом деле решил проблему, похожую на это некоторое время назад Таким образом, я даже имел выгоду от работы от памяти в некоторых аспектах, но он все еще ехал мне немного сумасшедший.

Глядя на проблему, которую мы знаем, нам нужно создать путь слов, идущих от начало к Endword Отказ Глядя на примере, что он может быть интуитивно понятен, чтобы вообразить, чтобы представить итерацию по поводу списка, проверьте, какие слова различаются на одну букву, подсчитайте преобразования по пути. Это быстро разрушается, как только вы измените пример, чтобы быть немного сложнее. Моя следующая интуиция была возможности иметь карту различных путей и вернуть максимально короткую. Это было бы слишком грязно, хотя. Что бы вы использовали в качестве ключа? Вы должны получить доступ к каждому списку слишком часто?

Если нам нужен путь, и нам нужно отслеживать длину пути, то структура данных нам нужна, явно график. Каждый узел – это слово с уровнем, узел находится на пути между началом и концом. Каждый сосед у узла должен содержать другое слово, которое отличается только одной буквой. Таким образом, мы придерживаемся требования, чтобы каждая трансформация меняет только одну букву.

Первое, что мы делаем, это проверить, если Endword в списке вообще и возврат 0. если не.

Тогда мы должны предварительно назначить график.

def preProcessNeighbors(wordList: List[str], wordLength: int):
    combos = {}
    for word in wordList:
        for i in range(wordLength):
            testWord = word[:i] + "*" + word[i + 1 :]
            if testWord not in combos:
                combos[testWord] = []
            combos[testWord].append(word)
    return combos

Wordlength в значительной степени постоянен на основе ограничений:

  • EndWord.Length.Length
  • WordList [i] .length.length

Мы повторяем каждое слово в нашем списке слов. Затем мы генерируем версии слова, заменяющие каждую букву с помощью * Отказ Так например, скажем, слово это может Отказ Мы генерируем 3 слова: * a , C * N , CA * Отказ Мы собираемся использовать их в качестве клавиш и создать структуру данных, которая выглядит так:

{
  "*an": ["can"],
  "c*n": ["can"],
  "ca*": ["can"]
}

Что это использование?

Представьте, что у нас есть список слов, как это:

["hot", "dot", "dog", "cog"]

Если мы используем предварительную обработку выше, мы получим структуру данных, как это:

{
  "*ot": ["hot", "dot"],
  "h*t": ["hot"],
  "ho*": ["hot"],
  "d*t": ["dot"],
  "do*": ["dot", "dog"],
  "*og": ["dog", "cog"],
  "c*g": ["cog"],
  "co*": ["cog"]
}

Это придет довольно удобно, когда мы пытаемся выяснить преобразования.

Следующий шаг – настроить 2 очереди, чтобы позволить нам сделать BFS найти кратчайший путь. В моем решении я инициирую поиск с начала, так и конца. Честно говоря, мне не произошло бы, чтобы сделать это таким образом, кроме того, когда я сталкивался с такой проблемой раньше, и увидел это как форму оптимизации. Я попробовал с этого, и без этого я также делаю поиск только с самого начала. В анализе лецкода не было очень разных Но я использую двойной поиск, поскольку это то, что я узнал, и это не делает его намного сложнее.

Таким образом, следующий шаг, установка 2 очередей и 2 карты для отслеживания посещаемых слов (вы можете заменить это только для прохождения только в одну сторону):

qBegin = collections.deque([(beginWord, 1)])
qEnd = collections.deque([(endWord, 1)])

visitedBegin = {beginWord: 1}
visitedEnd = {endWord: 1}

Пересекать очереди, используя цикл while с беседка :

while qBegin and qEnd:            

Получить слово и текущий уровень в очереди:

word, level = queue.popleft()

Итайте за каждую букву в этом слове и генерируйте клавиши для использования в этой карте, которую мы предварительно обработаны ранее:

word, level = queue.popleft()
    for i in range(wordLength):
        testWord = word[:i] + "*" + word[i + 1 :]

Теперь мы проверяем эту карту например Если мы сгенерировали слово, как «* OT», основываясь на нашем примере ранее, мы вернемся в список, как: [«Горячий», «Точка»]

Отзывать:

{
  "*ot": ["hot", "dot"],
  etc...
}

Так что в следующем коде:

if testWord in combos:
            for w in combos[testWord]:
                if w in otherVisited:
                    return level + otherVisited[w]
                if w not in visited:
                    visited[w] = level + 1
                    queue.append((w, level + 1))

Мы будем итерации за этот список. Если мы обнаружили, что Слово уже посетило в другом поискоре, мы бы знали, что это был частью пути, и мы бы наш ответ Уровень возврата + другиеVisized [w] Отказ

Если бы он не посетил, мы бы помемли его, как посещено на этом конце посетил [W] + 1 и добавьте слово в очередь queue.append ((w, уровень + 1)) и продолжить поиск.

Сложность времени здесь в основном длина каждого слова в квадрате, умноженном на количество слов. Итак, если n – количество слов SAND K – длина слова, у нас будет:

O (k ^ 2 * n)

Космическая сложность в основном одинакова. Мы сохранили K ^ 2 * N на этой карте.

Мое решение здесь не очень здорово. Кодированная кодировка грязная, и производительность, безусловно, может быть оптимизирована намного больше (ударить только 50% других представлений). Другие вещи, которые беспокоит меня, как имя метода, которое я использовал getladderlength Не имеет смысла, но мой мозг обжаривается в этот момент, так что оставляя его здесь. Я надеюсь, что я никогда не получаю так в интервью!

Январь LeetCoding Challenge 2021 (33 частью серии)

Оригинал: “https://dev.to/ruarfff/day-9-word-ladder-11ic”