212.Word Search II

Tags: [graph], [trie], [prefix_tree], [tree], [graph], [DFS], [backtracking], [pruning]

Com: {g}

Link: https://leetcode.com/problems/word-search-ii/\#/description

Given a 2D board and a list of words from the dictionary, find all words in the board.

Each word must be constructed from letters of sequentially adjacent cell, where "adjacent" cells are those horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

For example,
Given words=["oath","pea","eat","rain"]and board=

[
  ['o','a','a','n'],
  ['e','t','a','e'],
  ['i','h','k','r'],
  ['i','f','l','v']
]

Return["eat","oath"].

Note:
You may assume that all inputs are consist of lowercase letters a-z.

click to show hint.

You would need to optimize your backtracking to pass the larger test. Could you stop backtracking earlier?

If the current candidate does not exist in all words' prefix, you could stop backtracking immediately. What kind of data structure could answer such query efficiently? Does a hash table work? Why or why not? How about a Trie? If you would like to learn how to implement a basic trie, please work on this problem:Implement Trie (Prefix Tree)first.


Solution: Trie, Prefix Tree, DFS, Backtracking

class Solution(object):
    class TrieNode(object):
        def __init__(self):
            self.end_of_w = False
            self.children = {}


    def findWords(self, board, words):
        """
        :type board: List[List[str]]
        :type words: List[str]
        :rtype: List[str]
        """
        if not board or not board[0] or not words:
            return []

        trie_root = self.TrieNode()
        for w in words:
            self.insert_w(trie_root, w)

        rows, cols = len(board), len(board[0])
        visited = [[False] * cols for _ in xrange(rows)]
        rest = set()
        buff = []

        for r in xrange(rows):
            for c in xrange(cols):
                self.dfs(board, rows, cols, r, c, trie_root, buff, rest, visited)

        return list(rest)

    def insert_w(self, root, w):
        curr = root
        for i in xrange(len(w)):
            c = w[i]
            if c not in curr.children:
                curr.children[c] = self.TrieNode()

            curr = curr.children[c]

        curr.end_of_w = True

    def dfs(self, board, rows, cols, row, col, trie_root, buff, rest, visited):
        if board[row][col] not in trie_root.children:
            return

        buff.append(board[row][col])
        visited[row][col] = True
        next_node = trie_root.children[board[row][col]]
        if next_node.end_of_w:
            rest.add(''.join(buff))

        # iterate top, left, bottom, right:
        directions = ((-1, 0), (0, -1), (1, 0), (0, 1))
        for d in directions:
            r, c = row + d[0], col + d[1]
            if 0 <= r < rows and 0 <= c < cols and not visited[r][c]:
                self.dfs(board, rows, cols, r, c, next_node, buff, rest, visited)

        visited[row][col] = False
        buff.pop()

Revelation:

  • There is a new way to design Trie (prefix tree).
  • Using Trie to do pruning when we do DFS and backtracking.

Note:

  • Time complexity = O(n * k + (rows * cols) * (rows * cols)) = O(n*k + (rows * cols)^2), n is the number of words, k is the max length of words.

Time Limited Exceeded:

class Solution(object):
    class Trie(object):
        class TrieNode(object):
            def __init__(self):
                self.end_of_w = False
                self.children = {}

        def __init__(self, words):
            self.root = self.TrieNode()
            for w in words:
                self.__add_w(w)

        def __add_w(self, w):
            curr = self.root
            for i in xrange(len(w)):
                c = w[i]
                if c not in curr.children:
                    curr.children[c] = self.TrieNode()

                curr = curr.children[c]
                if i == len(w) - 1:
                    curr.end_of_w = True

        def search(self, w):
            # return -1: not found
            # return 0: found prefix
            # return 1: found word

            curr = self.root
            for i in xrange(len(w)):
                c = w[i]
                if c not in curr.children:
                    return -1

                curr = curr.children[c]

            return 1 if curr.end_of_w else 0

        def delete(self, w):
            if not w:
                return True

            stack = []
            curr = self.root
            for c in w:
                if c not in curr.children:
                    return False

                stack.append(curr)
                curr = curr.children[c]

            curr.end_of_w = False
            index = len(w) - 1
            while stack:
                curr = stack.pop()
                if len(curr.children[w[index]].children) == 0:
                    del curr.children[w[index]]

                index -= 1

            return True


    def findWords(self, board, words):
        """
        :type board: List[List[str]]
        :type words: List[str]
        :rtype: List[str]
        """
        if not board or not board[0] or not words:
            return []

        trie = self.Trie(words)

        num_of_rows = len(board)
        num_of_cols = len(board[0])
        visited = [[False for _ in xrange(num_of_cols)] for _ in xrange(num_of_rows)]

        rest = set()
        buff = []
        for row in xrange(num_of_rows):
            for col in xrange(num_of_cols):
                self.dfs(board, num_of_rows, num_of_cols, row, col, buff, rest, visited, trie)

        return list(rest)

    def dfs(self, board, num_of_rows, num_of_cols, row, col, buff, rest, visited, trie):
        buff.append(board[row][col])
        visited[row][col] = True

        w = ''.join(buff)
        search_rest = trie.search(w)
        if search_rest == -1:
            visited[row][col] = False
            buff.pop()
            return
        elif search_rest == 1:
            rest.add(w)
            trie.delete(w)

        # iterate four directions
        # top, left, bottom, right
        directions = ((-1, 0), (0, -1), (1, 0), (0, 1))
        for d in directions:
            r, c = row + d[0], col + d[1]
            if 0 <= r < num_of_rows and 0 <= c < num_of_cols and not visited[r][c]:
                self.dfs(board, num_of_rows, num_of_cols, r, c, buff, rest, visited, trie)

        visited[row][col] = False
        buff.pop()

Revelation:

  • Remember the way how to delete the word from Trie.

Time Limited Exceeded:

class Solution(object):
    def findWords(self, board, words):
        """
        :type board: List[List[str]]
        :type words: List[str]
        :rtype: List[str]
        """
        if not board or not board[0] or not words:
            return []

        num_of_rows = len(board)
        num_of_cols = len(board[0])
        visited = [[False for _ in xrange(num_of_cols)] for _ in xrange(num_of_rows)]
        rest = []
        for w in set(words):
            if self.search(w, board, num_of_rows, num_of_cols, visited):
                rest.append(w)

        return rest

    def search(self, w, board, num_of_rows, num_of_cols, visited):
        for row in xrange(num_of_rows):
            for col in xrange(num_of_cols):
                if board[row][col] != w[0]:
                    continue

                if self.dfs(board, num_of_rows, num_of_cols, row, col, w, 0, visited):
                    return True

        return False

    def dfs(self, board, num_of_rows, num_of_cols, row, col, w, index, visited):
        # base case
        if index == len(w) - 1:
            return True

        visited[row][col] = True

        # iterate four directions:
        # top, left, bottom, right
        directions = ((-1, 0), (0, -1), (1, 0), (0, 1))
        for d in directions:
            r, c = row + d[0], col + d[1]
            if 0 <= r < num_of_rows and 0 <= c < num_of_cols and not visited[r][c] and board[r][c] == w[index + 1]:
                if self.dfs(board, num_of_rows, num_of_cols, r, c, w, index + 1, visited):
                    visited[row][col] = False
                    return True

        visited[row][col] = False
        return False

results matching ""

    No results matching ""