+++ 업데이트
100점 받았다 ^___^ 굿굿 👍





2주차 문제는 스택과 해시테이블을 직접 구현에 활용해보는 실습 과제였다.
스택이랑 해시테이블 개념부터 머리에 때려박느라 혹시나 딴길로 샐까봐 지선생을 좀 (많이) 갈궜다.

절대 답도 힌트도 주지마..
근데! 내가 하는걸 지켜봐..
그리고! 이상한 방향으로 가면 알려줘

 
이런 땡깡을 막 부림..ㅋ 그래서 한참을 허덕인 끝에 어찌저찌 두 문제의 답을 냈다.
 

요즘 나랑 지선생 (고마워 지피띠니야 결혼하자)

 
암튼 1번 문제에서는 스택의 LIFO(Last In Frist Out) 구조를 어떻게 실전 문제에 적용하는지 체감할 수 있었다. 처음 겁먹은 것보다는 의외로 문제가 크게 어렵지 않아서, 개념만 제대로 익히면 금방 풀 수 있어서 좋았다. 나의 자존감을 지켜준 든든한 문제였다. 고마워 따봉 1번아!
2번 문제슬라이딩 윈도우해시테이블이라는 개념이 처음엔 좀 낯설어서 감이 잘 안 왔다. 그래서 어떻게 풀어나가면 좋을지 로직을 종이에 직접 그려가면서 함.. 그랬더니 슬라이딩 윈도우 개념이 자연스럽게 잡혔다! 앗싸~
 
그럼 장황한 서론은 뒤로 하고 코드부터 보러 고고
 

참고: 이번주 문제부터는 내가 생각한 논리 흐름을 한글 주석으로 먼저 적어놓고, 그 아래에 코드를 작성하는 식으로 진행했다.
그래서 주석이 좀 많으니 양해 부탁드립니닷

 


 

2-1. 후위 표기법 계산하기

문제 요약

후위 표기법(Postfix Notation)은 연산자가 피연산자 뒤에 오는 수식 표현 방식이다.
문자열로 주어진 수식을 계산해서 최종 정수값을 출력하는 프로그램을 만들어야 했다.
ex. 2 3 + 5 * → (2 + 3) * 5

내 코드 vs. 모범 답안

내 코드

s = input().strip().split()
stack = []

for token in s:
    if token.isdigit():
        stack.append(token)
    else:
        # 두 숫자를 꺼내서 더하기 (예시: token == '+')
        result = 0  
      
        # 두 숫자 꺼내기 (앞에서부터 꺼내기 위해 num2부터 뽑기)
        num2 = int(stack.pop())
        num1 = int(stack.pop())
        
        # 두 숫자 계산하기
        if token == '+':  
            result = num1 + num2
        elif token == '-':
            result = num1 - num2
        elif token == '/':
            result = num1 / num2
        elif token == '*':
            result = num1 * num2

        # result가 소수로 안떨어지게 int로 만들어주고, 원래 형태인 string으로 되돌리기
        stack.append(str(int(result))) 

print(stack[0])

 
모범 답안

def evaluate_postfix(expression):
    stack = []
    for char in expression.split():
        if char.isdigit():
            stack.append(int(char))
        else:
            b = stack.pop()
            a = stack.pop()
            if char == '+':
                stack.append(a + b)
            elif char == '-':
                stack.append(a - b)
            elif char == '*':
                stack.append(a * b)
            elif char == '/':
                stack.append(int(a / b))  # 정수 나눗셈으로 처리
    return stack.pop()

# 테스트
expression = "2 3 + 5 *"
result = evaluate_postfix(expression)
print(result)

 
 

❌ 내가 놓친 부분

  • 나는 stack.append(str(...))과 같이 문자열로 넣었지만, 모범 답안은 숫자로 계속 유지했다
  • 모범 답안을 함수화를 했다. 이러면 테스트하거나 재사용하기 좋다고 하니, 앞으로는 함수화를 해보도록 하자!
  • 나눗셈 결과는 실수로 떨어질 수도 있기 때문에 int(a / b)로 정수 처리하는 게 중요하다! 나는 결과값을 int 처리해줬는데 사실상 실수가 나올 수  있는 나눗셈 부분에서 정수로 변환해주는 게 더 명시적인 것 같다.

 

✅ 기억할 포인트

 

  • 스택은 LIFO 구조니까, 꺼내는 순서(b, a)에 주의!
  • 실수형이 나오지 않게 나눗셈은 바로 int() 처리해주는 것이 안전하다
  • 입력값을 직접 함수에 넘기면, 테스트 코드 작성이 더 쉬워진다
  • isdigit()으로 숫자 여부 판단하는 방법을 처음 알았는데, 깔끔하고 유용하다! 굿굿

 


 

2-2. 중복 문자 없는 가장 긴 부분 문자열 찾기

문제 요약

주어진 문자열에서 중복되지 않는 가장 긴 부분 문자열의 길이를 구하는 문제
ex. "abcabcbb" → "abc" → 결과는 3

슬라이딩 윈도우와 해시테이블을 사용해서 문자 위치를 추적하고,
중복이 발생할 경우 시작 위치를 조정하며 최댓값을 갱신해 나가는 문제였다.


내 코드 vs. 모범 답안

내 코드

# 0. 인풋을 받는다 (문자열)
s = input().strip()

# 1. result 및 start 초기값으로 선언
result = 0
start = 0

#  1-1. 인풋의 길이가 0이면 result를 0으로 넣는다
if len(s) == 0:
     result = 0

#  1-2. 인풋의 길이가 1이면 인풋의 길이를 result로 넣는다
if len(s) == 1:
     result = len(s)

# 2. 빈 딕셔너리 (해시테이블) 를 만든다.
char_index = {}

# 3. 입력받은 문자열을 한 글자씩 순회한다
for i in range(len(s)):
    # 3-1. 인풋의 i번째 글자가 딕셔너리에 있는 경우 (중복 문자인 경우)
    if s[i] in char_index:
        # start 값을 기존 위치보다 한 칸 뒤로 이동시킨다 (중복 제거)
        start = max(start, char_index[s[i]] + 1)
    
    # 현재 문자의 인덱스를 딕셔너리에 저장한다
    char_index[s[i]] = i
    
    # 현재 길이(i - start + 1)와 기존 최댓값 중 더 큰 값을 result에 저장한다
    result = max(result, i - start + 1)

print(result)

 
모범 답안

def length_of_longest_substring(s):
    char_map = {}
    start = maxLength = 0

    for i, char in enumerate(s):
        if char in char_map and start <= char_map[char]:
            start = char_map[char] + 1
        else:
            maxLength = max(maxLength, i - start + 1)

        char_map[char] = i

    return maxLength

# 테스트
print(length_of_longest_substring("abcabcbb"))  # 3
print(length_of_longest_substring("bbbbb"))     # 1

❌ 내가 놓친 부분

  • enumerate()라는 파이썬 함수:
    나는 처음에 range(len(s))로 인덱스를 직접 돌렸지만, 모범 답안은 enumerate 라는 내장함수를 써서 더 간결하고 실수 없이 구현했다..!
    • for i, char in enumerate(s):
  • 중복 문자 처리할 때의 조건:
    나는 그냥 if s[i] in char_index: 로 중복 여부만 확인했는데, 모범 답안은 start 범위 안에 있는 중복만 신경썼다
    • if char in char_map and start <= char_map[char]: 
  • 함수를 써보자... 2-1과 동일!

✅ 기억할 포인트

  • 중복 문자가 start보다 앞에 있었던 거면 그냥 무시해도 된다!
    • 나는 max()를 사용해서 비슷하게 동작하게 하긴 했는데, 결국 모범답안은 start <= char_map[char] 조건을 if 문 안에 명시해서 중복 검사 범위를 더 논리적으로 분리해줬다
    • 내 답보다는 모범답안처럼 조건을 명확하게 분리해서 처리하면 가독성도 높고 논리 흐름이 더 뚜렷해져서 더 좋은 것 같다 굿굿
  • 최대 길이는 항상 i - start + 1 로 계산한다
    • 지금 보고 있는 문자의 인덱스(i) - 현재 윈도우의 시작 위치(start) + 1
  • enumerate() 가 뭐지?
    • 리스트나 문자열을 순회할 때, 인덱스랑 값 둘 다 자동으로 꺼내주는 함수
    • 파이썬에서 처음 접했는데, 앞으로 자주 쓰게 될 것 같다!
    • ex.
for i, ch in enumerate("abc"):
    print(i, ch)  # 0 a, 1 b, 2 c 출력

정리

내 답을 모범 답안과 비교해보니 문제를 풀기만 한 것과 문제의 의도를 완전히 구현한 것 사이에는 확연한 차이가 있다는 걸 느꼈다.
특히 조건을 명확하게 분기 처리하고, 함수로 만들어 사용하고, enumerate() 같은 내장 함수를 활용하는 등의 방식은 코드의 정확도뿐 아니라 가독성과 유지보수성에서도 꽤 중요한 차이를 만든다는 걸 알게 됐다..!!
다음 과제에서는 단순히 답을 맞추는 데서 그치지 않고, 더 깔끔하고 명확하게 문제 요구사항을 구현하는 코드를 목표로 해보고 싶다.
 
알고리즘 공부가 처음이라 개념 습득부터 허덕이고 있긴 하지만.. (실제로 3주차에서 허덕이는 중) 이번처럼 꼼꼼히 살펴보면서 기록을 해나가려 한다. 햄내자! 빠샤!