가사검색

가사검색

2021.04.20

Solution1

Hashing & Slicing & BruteForce

  1. query를 슬라이싱합니다.
    1. ? 가 접미사인 경우, ? 전까지 슬라이싱 합니다.
    2. ? 가 접두사인 경우, 뒤집어서 ? 전까지 슬라이싱 합니다.
    3. 전부 ? 인 경우, IsAll 변수에 표시합니다.
  2. words와 query를 비교합니다.
    1. 길이를 비교합니다. (다르면 더이상 확인할 필요 없습니다. => 거짓)
    2. query가 전부 ? 인 경우, 더이상 확인할 필요 없습니다. => 참
    3. words의 접두사 / 접미사와 슬라이싱한 query를 query의 길이만큼 전부 비교합니다.
  3. 이미 확인된 녀석은 Dictionary로 값을 기억하고 있다가 해당 쿼리가 다시 나타나면 그 값을 그대로 사용합니다.

결과 : 효율성 테스트케이스 1개 통과 못함 (2번 케이스)

import copy
def checkStr(word, query):
    word = word[:len(query)]
    if query == word:
        return True
    else:
        return False

def solution(words, queries):
    answer = []
    Checked = {}

    for q in queries:
        back_q = copy.deepcopy(q)
        if q in Checked:
            answer.append(Checked[q])
            continue
        cnt = 0
        IsReverse = False
        IsAll = False
        len_q = len(q)
        idx = q.find('?')
        if idx == 0:
            IsReverse = True
            q = q[::-1]
            idx = q.find('?')
            if idx == 0:
                IsReverse = False
                IsAll = True
        q = q[:idx]

        for w in words:
            # 길이가 다른경우 일치할 수 없음
            if len_q != len(w):
                continue
            # 길이가 같고 시작과 끝이 ? 인 경우 무조건 일치한다.
            elif IsAll:
                cnt += 1
                continue

            if IsReverse:
                w = w[::-1]

            if checkStr(w, q):
                cnt += 1

        answer.append(cnt)
        Checked[back_q] = cnt

    return answer

Solution2

Trie & DFS

  1. Trie 자료구조에 모든 words와 뒤집은 words를 담습니다.
  2. Solution1 처럼 query를 slicing 합니다.
    1. ? 가 접미사인 경우 words에서 starts_with 메서드로 해당되는 prefix를 가진 단어가 있는지 탐색합니다.
    2. ? 가 접두사인 경우 2-1 에서 뒤집은 words에서 탐색합니다.
    3. ? 로만 이루어진 경우 words에서 길이가 일치하는 단어 갯수를 완전탐색합니다.

결과

효율성 테스트 2,4번을 통과하지 못해서 starts_with 메서드에 매개변수를 추가해서

query의 길이와 동일할 때만 words에 추가하도록 변경했습니다.

그랬는데 4번을 통과하지 못했습니다.

class Node(object):
    def __init__(self, key, data=None):
        self.key = key              # 값으로 입력될 문자
        self.data = data            # 문자열의 종료를 알리는 Flag (전체 문자열을 저장해서 구분)
        self.children = {}          # 자식노드를 저장

class Trie:
    def __init__(self):
        self.head = Node(None)

    def insert(self, string):
        current_node = self.head
        for char in string:
            if char not in current_node.children:
                current_node.children[char] = Node(char)
            current_node = current_node.children[char]
        current_node.data = string

    def search(self, string):
        current_node = self.head
        for char in string:
            if char in current_node.children:
                current_node = current_node.children[char]
            else:
                return False

        if current_node.data:
            return True
        else:
            return False

    def starts_with(self, prefix, l):
        current_node = self.head
        words = []

        for p in prefix:
            if p in current_node.children:
                current_node = current_node.children[p]
            else:
                return words

        # DFS
        current_node = [current_node]
        next_node = []

        while True:
            for node in current_node:
                if node.data and len(node.data) == l:
                    words.append(node.data)
                next_node.extend(list(node.children.values()))
            if len(next_node) != 0:
                current_node = next_node
                next_node = []
            else:
                break

        return words

def solution(words, queries):
    answer = []
    Checked = {}
    myTrie = Trie()
    myReverseTrie = Trie()

    for w in words:
        myTrie.insert(w)
        myReverseTrie.insert(w[::-1])    

    for q in queries:
        if q in Checked:
            answer.append(Checked[q])
            continue

        prefix_words = []

        if q[0] == '?' and q[-1] == '?':
            for w in words:
                if len(w) == len(q):
                    prefix_words.append(w)
        elif q[0] == '?':
            qRev = q[::-1]
            prefix_words = myReverseTrie.starts_with(qRev[:qRev.find('?')], len(q))
        else:
            prefix_words = myTrie.starts_with(q[:q.find('?')], len(q))

        answer.append(len(prefix_words))
        Checked[q] = len(prefix_words)

    return answer

Solution3

Trie & DFS

  • 두번째 풀이에서 단어 길이가 긴 경우에 문자열의 종료에서만 길이비교해서 words를 거르는 경우가 생기면 결국에는 효율이 떨어지게 됩니다.
  • 아예 그런 경우를 없애기 위해 words의 길이에 따라 따로 Trie를 만들어두고 query의 길이에 맞는 Trie를 탐색하도록 변경해서 효율성 테스트를 통과했습니다.
class Node(object):
    def __init__(self, key, data=None):
        self.key = key              # 값으로 입력될 문자
        self.data = data            # 문자열의 종료를 알리는 Flag (전체 문자열을 저장해서 구분)
        self.children = {}          # 자식노드를 저장

class Trie:
    def __init__(self):
        self.head = Node(None)

    def insert(self, string):
        current_node = self.head
        for char in string:
            if char not in current_node.children:
                current_node.children[char] = Node(char)
            current_node = current_node.children[char]
        current_node.data = string

    def search(self, string):
        current_node = self.head
        for char in string:
            if char in current_node.children:
                current_node = current_node.children[char]
            else:
                return False

        if current_node.data:
            return True
        else:
            return False

    def starts_with(self, prefix):
        current_node = self.head
        words = []

        for p in prefix:
            if p in current_node.children:
                current_node = current_node.children[p]
            else:
                return words

        # DFS
        current_node = [current_node]
        next_node = []

        while True:
            for node in current_node:
                if node.data:
                    words.append(node.data)
                next_node.extend(list(node.children.values()))
            if len(next_node) != 0:
                current_node = next_node
                next_node = []
            else:
                break

        return words

def solution(words, queries):
    answer = []
    Checked = {}
    myTrie = [[Trie(), Trie()] for _ in range(10050)]
    myReverseTrie = Trie()

    for w in words:
        myTrie[len(w)][0].insert(w)
        myTrie[len(w)][1].insert(w[::-1])

    for q in queries:
        if q in Checked:
            answer.append(Checked[q])
            continue

        prefix_words = []

        if q[0] == '?' and q[-1] == '?':
            for w in words:
                if len(w) == len(q):
                    prefix_words.append(w)
        else:
            if q[0] == '?':
                qRev = q[::-1]
                prefix_words = myTrie[len(q)][1].starts_with(qRev[:qRev.find('?')])
            else:
                prefix_words = myTrie[len(q)][0].starts_with(q[:q.find('?')])

        answer.append(len(prefix_words))
        Checked[q] = len(prefix_words)

    return answer

Solution4

Binary Search

  1. 각 길이별로 단어를 정렬해서 딕셔너리에 담습니다.
  2. {5: ['frame', 'frodo', 'front', 'frost', 'kakao'], 6: ['frozen']}
  3. 접두사도 판단하기 위해 뒤집어서 하나 더 담습니다.
  4. {5: ['emarf', 'oakak', 'odorf', 'tnorf', 'tsorf'], 6: ['nezorf']}
  5. query로 탐색하는데, 접두사인지 접미사인지에 따라 ?를 a와 z로 바꿔준후 그 사이에 있는 단어들을 이진탐색합니다.
    • bisect_left, bisect_right를 활용해서 탐색합니다.
    • 예를 들어, fro?? 로 탐색을 한다면, ??aa / zz 로 바꾼 문자열 두개 사이의 문자열인 경우를 탐색하는 것입니다.['frame', 'frodo', 'front', 'frost', 'kakao'] 가 됩니다.
    • froaa ~ frozz 의 문자열이 탐색되는 것이고, 결과는
from collections import defaultdict
from bisect import bisect_left, bisect_right

def solution(words, queries):
    answer = []
    my_words = defaultdict(list)
    my_reverse_words = defaultdict(list)

    # 길이별로 딕셔너리에 담습니다
    for word in words:
        my_words[len(word)].append(word)
        my_reverse_words[len(word)].append(word[::-1])

    # 딕셔너리를 정렬합니다. (그래야 이진탐색이 가능하기 때문입니다.)
    # O(NlogN)
    for my_word in my_words.values():
        my_word.sort()
    for my_reverse_word in my_reverse_words.values():
        my_reverse_word.sort()

    # 이미 확인한 쿼리
    Checked = defaultdict()

    # 이진탐색을 합니다
    # O(N * logM)
    for query in queries:
        # 중복 쿼리 Memoization
        if query in Checked:
            answer.append(Checked[query])
            continue
        # Binary Search
        if query[0] == '?': # 와일드카드 접두사인 경우
            target = my_reverse_words[len(query)]
            start, end = query[::-1].replace('?','a'), query[::-1].replace('?','z')
        else: # 와일드카드 접미사인 경우
            target = my_words[len(query)]
            start, end = query.replace('?','a'), query.replace('?','z')
        Checked[query] = bisect_right(target, end) - bisect_left(target, start)
        answer.append(Checked[query])

    return answer

'SW > 알고리즘 문제풀이' 카테고리의 다른 글

안테나  (0) 2021.05.10
국영수  (0) 2021.05.10
인구이동  (0) 2021.04.20
감시피하기  (0) 2021.04.14
연산자끼워넣기  (0) 2021.04.03