괄호 회전하기 리벤지

기존의 풀었던 방식은 테스트 코드에선 성공적이었으나, 실제 체점에서 엣지케이스를 잡지 못하여 다시 도전한다.

문제 설명

다음 규칙을 지키는 문자열을 올바른 괄호 문자열이라고 정의합니다.

  • ()[]{} 는 모두 올바른 괄호 문자열입니다.
  • 만약 A가 올바른 괄호 문자열이라면, (A)[A]{A} 도 올바른 괄호 문자열입니다. 예를 들어, [] 가 올바른 괄호 문자열이므로, ([]) 도 올바른 괄호 문자열입니다.
  • 만약 AB가 올바른 괄호 문자열이라면, AB 도 올바른 괄호 문자열입니다. 예를 들어, {} 와 ([]) 가 올바른 괄호 문자열이므로, {}([]) 도 올바른 괄호 문자열입니다.

대괄호, 중괄호, 그리고 소괄호로 이루어진 문자열 s가 매개변수로 주어집니다. 이 s를 왼쪽으로 x (0 ≤ x < (s의 길이)) 칸만큼 회전시켰을 때 s가 올바른 괄호 문자열이 되게 하는 x의 개수를 return 하도록 solution 함수를 완성해주세요.


제한사항
  • s의 길이는 1 이상 1,000 이하입니다.

입출력 예
s result
"[](){}" 3
"}]()[{" 2
"[)(]" 0
"}}}" 0

입출력 예 설명

입출력 예 #1

  • 다음 표는 "[](){}" 를 회전시킨 모습을 나타낸 것입니다.
x s를 왼쪽으로 x칸만큼 회전 올바른 괄호 문자열?
0 "[](){}" O
1 "](){}[" X
2 "(){}[]" O
3 "){}[](" X
4 "{}[]()" O
5 "}[](){" X
  • 올바른 괄호 문자열이 되는 x가 3개이므로, 3을 return 해야 합니다.

입출력 예 #2

  • 다음 표는 "}]()[{" 를 회전시킨 모습을 나타낸 것입니다.
x s를 왼쪽으로 x칸만큼 회전 올바른 괄호 문자열?
0 "}]()[{" X
1 "]()[{}" X
2 "()[{}]" O
3 ")[{}](" X
4 "[{}]()" O
5 "{}]()[" X
  • 올바른 괄호 문자열이 되는 x가 2개이므로, 2를 return 해야 합니다.

입출력 예 #3

  • s를 어떻게 회전하더라도 올바른 괄호 문자열을 만들 수 없으므로, 0을 return 해야 합니다.

입출력 예 #4

  • s를 어떻게 회전하더라도 올바른 괄호 문자열을 만들 수 없으므로, 0을 return 해야 합니다.

문제 해석

최초 오답

def solution(s):
    # 특정 괄호 모양이 나오면, 해당하는 것의 반쪽이 반드시 존재해야한다. 
    # 그 상태에서 전체를 돌리면서, 조건에 부합하는 경우 개수를 샌다 
    # target 은 enumerate 되면서 복사 된듯 
    target = s 
    answer = 0    
    loop = 0
    # 1차 loop 로 감쌈. 
    # 2차 전체 검사 및 문제 없을시 answer += 1
    # while loop < len(s):
    #     answer += checker(target)
    #     target = target[1:] + target[0]
    #     loop += 1
    for i in range(len(s)):
        answer += checker(target)
        target = target[1:] + target[0]
    return answer

def checker(string):
    origin = "{[("
    opposite = "}])"
    for i, word in enumerate(string):
        if word in origin:
            if opposite[origin.index(word)] not in string[i+1:]:
                return 0
            else:
                for j in range(len(string)):
                    j += i
                    if string[j] == opposite[origin.index(word)]:
                        if (j - i - 1) % 2 != 0:
                            return 0
                        break
        else:
            if origin[opposite.index(word)] not in string[:i]:
                return 0
    return 1

해결 답안

# 해결 방법...! 
def solution(s):
    answer = 0
    
    for _ in range(len(s)): 
        if checkBracket(s):
            answer += 1
        s = s[1:] + s[0]
        
    return answer

def checkBracket(target):
    stack = []
    bracketMap = {'}': '{', ']': '[', ')': '('}
    
    for char in target:
        if char in bracketMap.values():
            stack.append(char)
        else :
            if stack:
                if stack[-1] == bracketMap[char]:
                    stack.pop()
                else:
                    return False
            else:
                return False
    return not stack
  • 가장 정공법으로 생각한 것은 역시나 짝 맞추기의 방식이다.
  • 그러나 기존에는 수학적으로 계산하고 조건을 구하려고 했으나 이는 시간 복잡도를 포함하여 효율적이지 못했으며, 엣지 케이스가 너무 많았다.
  • 이에 stack 자료구조를 활용하여 짝이 맞을 때 지우는 구조를 했을 때, 효과적으로이를 해결 할 수 있었다.
테스트 1 〉 통과 (12.86ms, 10.3MB)
테스트 2 〉 통과 (6.71ms, 10.2MB)
테스트 3 〉 통과 (7.98ms, 10.3MB)
테스트 4 〉 통과 (10.09ms, 10.2MB)
테스트 5 〉 통과 (37.26ms, 10.2MB)
테스트 6 〉 통과 (13.55ms, 10.2MB)
테스트 7 〉 통과 (18.85ms, 10.2MB)
테스트 8 〉 통과 (25.89ms, 10.3MB)
테스트 9 〉 통과 (52.62ms, 10.2MB)
테스트 10 〉 통과 (77.32ms, 10.2MB)
테스트 11 〉 통과 (119.04ms, 10.2MB)
테스트 12 〉 통과 (0.00ms, 10.3MB)
테스트 13 〉 통과 (0.01ms, 10.2MB)
테스트 14 〉 통과 (0.01ms, 10.4MB)

다른 사람 풀이 방식

from collections import deque

def check(s):
    while True:
        if "()" in s: s=s.replace("()","")
        elif "{}" in s: s=s.replace("{}","")
        elif "[]" in s: s=s.replace("[]","")
        else: return False if s else True       

def solution(s):
    ans = 0
    que = deque(s)

    for i in range(len(s)):
        if check(''.join(que)): ans+=1
        que.rotate(-1)
    return ans
  • 핵심은 온전한 괄호가 나타나면, 그 괄호는 탐색 되며 지울 수 있다는 것이다.
  • 엣지케이스가 될만한 중첩된 괄호가 나온다고 하더라도, 결국 안 쪽 괄호가 정확하게 괄호로 묶이지 않는다면 정상 케이스가 아닌 것이니 문제가 없다.
  • 그러나 이 경우의 문제점은 replace를 활용해서 제거해나가는 것이지만, 이때 문자열 탐색을 전체를 도는 조건이 두개나 들어있다는 점이다(최초 조건, 이후 replace를 하는 과정) 그렇기에 최종 결과를 보면 테스트 결과1만 봐도 stack을 활용한 방법이 13ms 정도인데 비해 540ms 로 상당히 오래 걸리는 것을 볼 수 있다.
테스트 1 〉 통과 (540.39ms, 10.3MB)
테스트 2 〉 통과 (198.98ms, 10.2MB)
테스트 3 〉 통과 (138.11ms, 10MB)
테스트 4 〉 통과 (70.93ms, 10MB)
테스트 5 〉 통과 (60.71ms, 10.2MB)
테스트 6 〉 통과 (182.23ms, 10.2MB)
테스트 7 〉 통과 (122.82ms, 10.3MB)
테스트 8 〉 통과 (74.52ms, 10.1MB)
테스트 9 〉 통과 (70.00ms, 10.2MB)
테스트 10 〉 통과 (57.02ms, 10MB)
테스트 11 〉 통과 (18.16ms, 10.3MB)
테스트 12 〉 통과 (0.01ms, 10.2MB)
테스트 13 〉 통과 (0.01ms, 10.1MB)
테스트 14 〉 통과 (0.01ms, 10.2MB)