본문 바로가기
AI/Coding_Test

Day19)programmers-문자열 분리, 반복 추가, 빈 배열 추가·삭제, 스택, K개 수 뽑기

by 거대웅 TitanBear 2026. 7. 30.

Programmers | Python study note

문자열 분리와 리스트·스택 조작 5문제 정리

문자열을 여러 기준으로 나누는 방법부터 리스트에 여러 값을 넣고 빼는 방법, 스택으로 인접한 같은 값을 처리하는 방법까지. 처음 작성한 코드에서 왜 오류가 났는지와 Python답게 다듬는 흐름을 함께 기록했다.

오늘의 문제와 핵심 개념

공식 문제 링크
문제핵심
세 개의 구분자replace, split, extend, 빈 리스트 처리
배열의 원소만큼 추가하기반복 횟수 초기화, 리스트 곱셈, 이중 컴프리헨션
빈 배열에 추가, 삭제하기zip, +=, extend, del, 슬라이싱
배열 만들기 6스택, pop, 빈 리스트 안전 확인
무작위로 K개의 수 뽑기중복 제거, break, 부족한 값 채우기

1. 세 개의 구분자: 여러 번 나누려다 데이터가 꼬일 때

문자열에서 a, b, c를 모두 구분자로 삼아 나누는 문제다. 처음에는 a로 나누고, 나온 조각을 다시 b와 c로 나누는 방식으로 접근할 수 있다. 방향은 맞지만 리스트를 다루는 방식이 중요하다.

처음 시도에서 생긴 문제

def solution(myStr):
    answer = []
    for char in myStr.split("a"):
        answer = "".join(char)
    ...

= 는 덮어쓰기다. 반복할 때마다 새 값을 대입하면 앞에서 얻은 조각은 사라지고 마지막 조각만 남는다.

split()의 결과는 리스트다. append(char.split("b"))는 리스트 안에 또 리스트를 넣는다. 다음 단계에서 꺼낸 값은 문자열이 아니라 리스트가 되어 split()을 쓸 수 없다.

순회 중인 리스트에 append하지 않는다. for item in answer 안에서 같은 answer에 계속 추가하면 반복 대상이 끝없이 늘어날 수 있다.

리스트와 문자열은 다르다. answer == ""이 아니라 not answer 또는 answer == []로 빈 리스트를 확인한다.

내 생각을 그대로 살린 단계별 풀이

def solution(myStr):
    step1 = myStr.split("a")

    step2 = []
    for text in step1:
        step2.extend(text.split("b"))

    step3 = []
    for text in step2:
        step3.extend(text.split("c"))

    answer = []
    for text in step3:
        if text != "":
            answer.append(text)

    return answer if answer else ["EMPTY"]

append()는 값을 하나의 원소로 넣고, extend()는 리스트의 원소를 풀어서 넣는다. 그래서 split() 결과를 이어 붙일 때는 extend가 알맞다.

더 간단한 기본 풀이: 구분자를 공백으로 통일하기

def solution(myStr):
    answer = myStr.replace("a", " ").replace("b", " ").replace("c", " ").split()
    return answer if answer else ["EMPTY"]

여기서 공백 " "과 빈 문자열 ""을 구분해야 한다. 빈 문자열로 바꾸면 구분자가 삭제되어 양옆 글자가 붙고, 공백으로 바꾸면 split이 나눌 경계가 남는다. 또한 인수 없이 쓰는 split()은 연속된 공백과 빈 조각을 자동으로 무시하므로 별도의 if x 필터가 필요 없다.

선택 풀이: 정규표현식

import re

def solution(myStr):
    answer = re.findall(r"[^abc]+", myStr)
    return answer if answer else ["EMPTY"]

정규표현식 [^abc]+는 a, b, c가 아닌 문자가 한 글자 이상 이어진 덩어리를 찾는다. 고정된 구분자 세 개만 처리하는 이 문제에서는 replace와 split이 더 쉽게 읽히며, 정규표현식은 구분 규칙이 복잡해질 때 유용하다.

2. 배열의 원소만큼 추가하기: 횟수 변수는 매번 새로 시작한다

def solution(arr):
    answer = []
    cnt = 0

    for char in arr:
        while char > cnt:
            answer.append(char)
            cnt += 1
    return answer

첫 번째 원소를 처리한 뒤에도 cnt 값이 남아 있는 것이 문제다. 예를 들어 [3, 1]에서 3을 처리하면 cnt는 3이 되고, 다음 1은 1 > 3이 거짓이라 추가되지 않는다. 원소 하나마다 횟수는 0부터 다시 세야 한다.

def solution(arr):
    answer = []
    for num in arr:
        cnt = 0
        while cnt < num:
            answer.append(num)
            cnt += 1
    return answer

같은 값을 여러 번 넣는 일은 리스트 곱셈으로 더 직접적으로 표현할 수 있다. [num] * num은 num이 들어 있는 리스트를 num번 반복한 새 리스트다.

def solution(arr):
    answer = []
    for num in arr:
        answer.extend([num] * num)
    return answer
def solution(arr):
    return [num for num in arr for _ in range(num)]

마지막 코드는 이중 for문을 한 줄로 쓴 것이다. 바깥쪽에서 num을 꺼내고, 안쪽에서 num번 반복해 맨 앞의 num을 결과에 넣는다. 반복 횟수만 필요하고 그 값은 쓰지 않을 때는 관례적으로 _를 쓴다.

3. 빈 배열에 추가, 삭제하기: pop은 개수가 아니라 위치를 받는다

def solution(arr, flag):
    answer = []
    for i, value in enumerate(flag):
        if value:
            answer.extend([arr[i]] * arr[i] * 2)
        else:
            answer.pop(arr[i])
    return answer

참일 때 여러 값을 넣는 부분은 맞다. 하지만 pop(n)은 “뒤에서 n개를 삭제”가 아니라 “인덱스 n의 원소 하나를 꺼내기”다. 마지막에서 여러 개를 지우려면 슬라이싱이나 del을 사용한다.

def solution(arr, flag):
    answer = []

    for num, is_true in zip(arr, flag):
        if is_true:
            answer += [num] * (num * 2)
        else:
            del answer[-num:]

    return answer

zip(arr, flag)은 두 배열에서 같은 위치의 값을 함께 꺼낸다. 그래서 인덱스를 얻고 다시 arr[i]를 찾을 필요가 없다. del answer[-num:]은 기존 리스트의 뒤 num개를 제자리에서 지운다.

리스트에 값을 더할 때의 차이
코드결과
[1, 2] + [3, 4][1, 2, 3, 4]
answer += [3, 4]리스트 원소를 뒤에 이어 붙임
answer.extend([3, 4])+=와 같은 모양으로 원소를 이어 붙임
answer.append([3, 4])[1, 2, [3, 4]]처럼 리스트 하나를 원소로 넣음

더하기 기호는 같은 종류끼리 사용한다. 리스트와 리스트, 문자열과 문자열, 숫자와 숫자는 가능하지만 리스트와 문자열 또는 문자열과 숫자를 더하면 TypeError가 난다. 따라서 answer += [num] * ...은 리스트에 리스트를 더하는 형태라 정상 동작한다.

4. 배열 만들기 6: 스택의 마지막 값과 현재 값을 비교하기

이 문제는 현재 값이 스택의 마지막 값과 같으면 마지막 값을 제거하고, 다르면 현재 값을 넣는다. 비어 있으면 당연히 넣는다.

for i in arr:
    if arr[i] != stk[-1]:
        ...

for i in arr에서 i는 인덱스가 아니라 실제 값이다. i를 그대로 사용하거나, 인덱스가 필요할 때만 enumerate()를 쓴다.

빈 리스트에서 stk[-1]을 읽으면 오류가 난다. 먼저 스택이 비었는지 확인해야 한다.

pop()은 괄호를 비우면 마지막 원소 하나를 제거한다. pop(value)는 값을 지우는 문법이 아니다.

for문은 다음 원소로 자동 이동하므로, 문제 지문의 “i에 1을 더한다”를 그대로 i += 1로 옮길 필요가 없다.

def solution(arr):
    stk = []

    for value in arr:
        if not stk:
            stk.append(value)
        elif stk[-1] != value:
            stk.append(value)
        else:
            stk.pop()

    return stk if stk else [-1]

첫 조건에서 값을 넣었다면 그 반복에서는 다른 조건을 실행하면 안 된다. 그래서 서로 하나만 실행할 조건은 if 다음에 elif, else로 묶는다.

def solution(arr):
    stk = []

    for value in arr:
        if stk and stk[-1] == value:
            stk.pop()
        else:
            stk.append(value)

    return stk or [-1]

if stk and ...는 왼쪽부터 확인한다. 스택이 비어 있으면 뒤의 stk[-1]을 읽지 않으므로 안전하다. 빈 리스트는 거짓으로 취급되므로 stk or [-1]은 스택이 비면 [-1], 아니면 stk를 반환한다.

5. 무작위로 K개의 수 뽑기: 채우는 작업은 반복문 뒤에서

def solution(arr, k):
    answer = []
    for value in arr:
        if value not in answer:
            answer.append(value)
        elif not answer[:k]:
            while len(answer) > k:
                answer.append(-1)
    return answer

-1은 중복을 만났을 때 채우는 값이 아니다. arr를 끝까지 확인하거나 k개를 이미 모은 뒤, 결과가 부족할 때만 채워야 한다. 또 원소를 추가하는 while문은 길이가 k보다 작을 때 돌아야 한다. 길이가 k보다 큰데 계속 추가하면 멈출 수 없다.

def solution(arr, k):
    answer = []

    for value in arr:
        if value not in answer:
            answer.append(value)

        if len(answer) == k:
            break

    while len(answer) < k:
        answer.append(-1)

    return answer
def solution(arr, k):
    answer = []

    for value in arr:
        if value not in answer:
            answer.append(value)
        if len(answer) == k:
            break

    answer.extend([-1] * (k - len(answer)))
    return answer

두 번째 풀이는 부족한 개수만큼 [-1]을 반복해 한 번에 이어 붙인다. break를 넣으면 필요한 k개를 다 모은 뒤에는 뒤의 입력을 더 볼 필요가 없다.

이번 기록에서 챙길 실수 방지 포인트

1

split() 결과는 리스트다. 리스트를 문자열과 비교하지 않는다.

2

리스트를 순회하면서 같은 리스트에 계속 추가하지 않는다. 필요하면 새 리스트를 만든다.

3

append는 원소 하나, extend와 +=는 다른 리스트의 원소를 이어 붙인다.

4

pop()은 마지막 하나를 뺀다. 여러 개 삭제에는 슬라이싱이나 del을 쓴다.

5

for value in arr에서 value는 원소다. arr[value]처럼 인덱스로 다시 쓰지 않는다.

문자열과 리스트 문제는 한 번에 짧게 쓰는 것보다, 자료형과 데이터 흐름이 바뀌는 지점을 분명히 보는 편이 먼저다. 기본 풀이가 이해되면 replace, split, extend, zip, del 같은 도구가 왜 코드를 줄여 주는지도 자연스럽게 보인다.