배열 문제를 풀다 보면
값을 정렬하는 것이 아니라, 기존 순서를 유지한 채 위치만 바꿔야 하는 문제가 자주 나옵니다.

이번 글에서는
배열에서 0이 아닌 숫자는 앞쪽으로 모으고, 0은 뒤로 보내는 문제를 정리해보겠습니다.

겉으로 보면 단순해 보이지만,
0이 아닌 값들의 상대적인 순서가 유지되어야 한다는 조건 때문에
생각 없이 정렬해서는 안 되는 문제입니다.


문제

정수 배열 nums가 주어집니다.

배열의 순서는 최대한 유지하면서,
모든 0을 배열의 뒤쪽으로 보내는 함수를 작성하세요.

단, 0이 아닌 숫자들의 상대적인 순서는 바뀌면 안 됩니다.

가능하면 추가 배열을 만들지 않고 처리해보겠습니다.

함수 형식은 아래와 같습니다.

move_zeros(nums)
  • nums: 정수 배열
  • 반환값: 문제 조건에 맞게 정리된 배열 또는 배열 자체를 직접 수정해도 됩니다

예시 1

입력: [0, 3, 0, 5, 8]
출력: [3, 5, 8, 0, 0]

예시 2

입력: [4, 0, 1, 0, 0, 2]
출력: [4, 1, 2, 0, 0, 0]

예시 3

입력: [1, 2, 3]
출력: [1, 2, 3]

처음 떠오르는 생각

이 문제에서 중요한 건 두 가지입니다.

  1. 0이 아닌 값들의 순서는 유지해야 합니다
  2. 0만 뒤로 보내면 됩니다

즉, 배열을 보면서
0이 아닌 값만 앞에서부터 차례대로 채워 넣고,
마지막에 남은 부분을 0으로 채우면 됩니다.

이 흐름에 잘 맞는 방식이 투 포인터입니다.

처음 보면
"0을 뒤로 보내려면 정렬하면 되는 거 아닌가?"
라는 생각이 들 수 있습니다.

그런데 정렬을 해버리면 0이 아닌 값들의 원래 순서가 깨질 수 있습니다.

예를 들어:

[4, 0, 1, 0, 2]

이 배열에서 중요한 건
4 → 1 → 2의 순서가 유지되어야 한다는 점입니다.

정렬은 이 조건에 맞지 않습니다.


핵심 아이디어

배열을 왼쪽부터 보면서:

  • 0이 아닌 값을 만나면 앞쪽에 채웁니다
  • 0은 일단 건너뜁니다
  • 나중에 남은 칸을 전부 0으로 채웁니다

즉, 하나는 읽는 위치,
하나는 채워 넣을 위치로 사용하면 됩니다.

이 문제는 swap으로 계속 바꾸는 방식보다,
앞쪽에 차례대로 덮어쓰기(write) 하는 방식이 더 깔끔합니다.


코드

def move_zeros(nums):
    write = 0

    for read in range(len(nums)):
        if nums[read] != 0:
            nums[write] = nums[read]
            write += 1

    for i in range(write, len(nums)):
        nums[i] = 0

    return nums

코드 해설

1) write 포인터 만들기

write = 0

write는 다음 0이 아닌 값을 어디에 써야 하는지를 가리키는 위치입니다.

2) 배열을 처음부터 끝까지 확인하기

for read in range(len(nums)):

read는 현재 읽고 있는 위치입니다.

즉:

  • read → 현재 보는 위치
  • write → 값을 써 넣을 위치

이렇게 역할이 나뉩니다.

3) 0이 아닌 값이면 앞으로 보내기

if nums[read] != 0:
    nums[write] = nums[read]
    write += 1

현재 값이 0이 아니면 앞쪽의 write 위치에 넣습니다.

이렇게 하면 0이 아닌 값들이
원래 등장한 순서대로 앞에 모이게 됩니다.

4) 남은 부분을 0으로 채우기

for i in range(write, len(nums)):
    nums[i] = 0

앞쪽에 0이 아닌 값들을 다 모은 뒤에는
남은 칸이 전부 뒤쪽입니다.

그 부분을 0으로 채워 주면 끝입니다.


실행 예시

입력:

[0, 3, 0, 5, 8]

초기 상태:

write = 0

read = 0

  • 값은 0
  • 건너뜁니다

read = 1

  • 값은 3
  • nums[0] = 3
  • write = 1

배열 상태:

[3, 3, 0, 5, 8]

read = 2

  • 값은 0
  • 건너뜁니다

read = 3

  • 값은 5
  • nums[1] = 5
  • write = 2

배열 상태:

[3, 5, 0, 5, 8]

read = 4

  • 값은 8
  • nums[2] = 8
  • write = 3

배열 상태:

[3, 5, 8, 5, 8]

이제 write = 3부터 끝까지 0으로 채웁니다.

최종 결과:

[3, 5, 8, 0, 0]

복잡도

  • 시간 복잡도: O(N)
  • 공간 복잡도: O(1)

배열을 두 번 순회하지만,
둘 다 길이에 비례하므로 전체 시간은 O(N)입니다.

추가 배열을 만들지 않으므로 공간 복잡도는 O(1)입니다.


정리

이 문제는 단순히 0을 뒤로 보내는 문제가 아닙니다.

핵심은:

  • 순서를 유지해야 합니다
  • 제자리에서 바꿀 수 있습니다
  • 투 포인터 방식이 잘 맞습니다

즉, 배열 문제에서 자주 나오는
읽는 포인터 / 쓰는 포인터 감각을 익히기 좋은 문제입니다.

이 문제의 핵심을 한 줄로 정리하면 아래와 같습니다.

0이 아닌 값만 앞에서부터 차례대로 채우고, 남은 자리를 0으로 채운다.

즉:

  • 배열을 순회하고
  • 필요한 값만 앞에 모으고
  • 뒤쪽은 0으로 채웁니다

이 흐름만 이해하면 어렵지 않게 풀 수 있습니다.

※ 이 글의 문제 상황과 예시는 학습용으로 직접 재구성한 내용입니다.