211. Design Add and Search Words Data Structure

안창범·2023년 9월 10일
0

LeetCode Top Interview 150

목록 보기
24/27

문제

https://leetcode.com/problems/design-add-and-search-words-data-structure/description/?envType=study-plan-v2&envId=top-interview-150

해결 방법 1

  • 208. Implement Trie (Prefix Tree) 이 문제의 해결방법과 마찬가지로 Trie를 사용하여 해결
  • .이 입력된 경우 .에 a ~ z를 넣어보고 단어가 존재하면 true return
  • 시간을 줄이기 위해 .이 2개 이상 있는 경우(문제에선 .이 최대 2개이긴 함) 이전의 .에 특정 알파벳을 넣었을 때, 다음 .이 나오기 전까지의 문자열이 startsWith이 true로 return 되어야 다음 . 진행
    • ex) b..을 체크하는 경우 먼저 ba.이 생성 됨 => ba로 시작하는 단어가 있는 경우에만 baa ~ baz를 진행하고 ba로 시작하는 단어가 없는 경우에는 bb.으로 넘어감

코드

class WordDictionary {

    private Node root;

    public WordDictionary() {
        this.root = new Node();
    }
    
    public void addWord(String word) {
        Node current = this.root;

        for (char c : word.toCharArray()) {
            int idx = c - 'a';
            if (current.children[idx] == null) {
                current.children[idx] = new Node();
            }
            current = current.children[idx];
        }
        current.isEndOfWord = true;
    }
    
    public boolean search(String word) {
        if (!word.contains(".")) {
            return detailSearch(word);
        } else {
            return makeWordAndSearch(word);
        }
    }

    private boolean makeWordAndSearch(String word) {
        int dotIdx = word.indexOf(".");
        for (char c = 'a' ; c <= 'z' ; c ++) {

            String newWord = word.substring(0, dotIdx) + c + word.substring(dotIdx + 1);
            if (newWord.contains(".")) {
                if (startsWith(newWord.split("\\.")[0])) {
                    if (makeWordAndSearch(newWord)) {
                        return true;
                    }
                }
            } else {
                if (detailSearch(newWord)) {
                    return true;
                }
            }
        }
        return false;
    }

    static Node current;

    public boolean detailSearch(String word) {
        if (!startsWith(word)) return false;
        return current.isEndOfWord;
    }
    
    public boolean startsWith(String prefix) {
        current = this.root;

        for (char c : prefix.toCharArray()) {
            int idx = c - 'a';
            if (current.children[idx] == null) return false;

            current = current.children[idx];
        }
        return true;
    }
}

class Node {
    Node[] children;
    boolean isEndOfWord;

    Node() {
        this.children = new Node[26];
        this.isEndOfWord = false;
    }
}

결과

  • startsWith을 체크하는 로직을 추가하기 전보다 시간이 약 3배 정도 빨라졌지만, 그럼에도 시간적으로 더 효율적인 방법이 있는 것 같다고 생각함

해결 방법 2

  • 문자열 'b.d'가 주어졌다고 가정했을 때, 전에는 bad ~ bzd까지 모든 경우를 확인해서 시간이 오래 걸렸다고 생각함
  • 이 문제를 해결하기 위해 현재 Node가 b일 때, '.'을 만나게 되는데 이 경우 a~z까지 하나씩 넣는 것이 아닌 현재 Node의 children이 null이 아닌 경우에만 체크하는 방식으로 수정함
    • 이전에 bcx, bck, bez가 삽입되었다면, b Node에서 children[c], children[e]만 null이 아니기 때문에 c, e만 체크
  • 주의해야 할 점은 'a'만 삽입되어 있고 '.a'를 체크하는 경우를 생각해 보자
    • 처음에 .을 만나면 a로 이동하게 됨 (이전에 a가 삽입되어 있기 때문)
    • .을 a로 가정한 경우 다음을 체크해야되는데 a 다음은 없음
    • 이 경우에 a는 endOfWord이기 때문에 true를 return 하는 문제가 발생했음
    • 이를 해결하기 위해 현재 word에서 체크하는 idx와 이전에 체크한 횟수 checkCnt를 추가하여 둘이 다르면 false를 return 하도록 하여 해결
      • 위의 상황에서 '.a'는 idx는 2이지만 checkCnt는 1이기 때문에 false return

코드

class WordDictionary {

    private Node root;

    public WordDictionary() {
        this.root = new Node();
    }
    
    public void addWord(String word) {
        Node current = this.root;

        for (char c : word.toCharArray()) {
            int idx = c - 'a';
            if (current.children[idx] == null) {
                current.children[idx] = new Node();
            }
            current = current.children[idx];
        }
        current.isEndOfWord = true;
    }
    
    static Node current;
    public boolean search(String word) {
        current = this.root;
        return detailSearch(word, 0, 0);
    }

    public boolean detailSearch(String word, int startIdx, int checkCnt) {

        int idx = startIdx;
        for (; idx < word.length() ; idx ++) {
            char c = word.charAt(idx);
            if (c == '.') {
                for (int j = 0 ; j < 26 ; j ++) {
                    if (current.children[j] != null) {
                        Node temp = current;
                        current = current.children[j];
                        if (detailSearch(word, idx + 1, checkCnt + 1)) {
                            return true;
                        }
                        current = temp;
                    }
                }
            } else {
                if (current.children[c - 'a'] == null) return false;
                current = current.children[c - 'a'];
                checkCnt ++;
            }
        }

        if (idx == checkCnt) {
            return current.isEndOfWord;
        } else {
            return false;
        }
    }
    
}

class Node {
    Node[] children;
    boolean isEndOfWord;

    Node() {
        this.children = new Node[26];
        this.isEndOfWord = false;
    }
}

결과

  • 해결방법 1에 비해 시간을 많이 줄일 수 있었음

0개의 댓글

관련 채용 정보