Move Zeroes
/LeetCode문제 해석
요약
주어진 배열에서 나머지는 순서는 그대로 유지하면서 모든 0을 배열의 끝으로 보내여라. in-place로 구현해야한다.
전문
🇰🇷 번역
배열인 nums
가 주어지면, 모든 0을 배열의 끝으로 보내는 함수를 작성하여라. 단, 0이 아닌 요소들은 모두 원래 순서를 유지해야 한다.
주의:
- 배열을 복사하지 않고, in-place(추가 메모리 없이)로 구현해야한다.
-
총 작동(단계) 수를 최소화 하여라.(시간복잡도를 최소로 해라)
🇺🇸 원문
Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.
Note:
- You must do this in-place without making a copy of the array.
-
Minimize the total number of operations.
🤔 문제 해결 방법 구상
- 모든 배열을 순회
- 0인 요소를 만나면 splice, 0을 push
- splice하면 인덱스가 꼬일 위험이 있으므로 처리해야함(
i--
) - 또한 push를 했으므로 0을 계속 만나게 되므로 처리해야함(
count
)
코드 구현
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
const moveZeroes = function(nums) {
if (nums.length < 2) return nums;
let count = 0;
for (let i = 0; i < nums.length; i++) {
if (count < nums.length && nums[i] === 0) {
nums.splice(i, 1);
i--;
nums.push(0);
}
count++;
}
return nums;
};
결과 분석
Runtime: 88 ms
Memory Usage: 37.2 MB
nums
배열을 추가로 복사하지 않고 테스트를 통과하였다. 표면적으론 for문을 한 번으로 문제를 풀어서 시간 복잡도가 O(n)이라고 생각했는데, 리트코드 런타임 랭크는 낮은 편이었다. 내 생각엔 splice
메소드를 사용하면서 인덱스 변화가 생기고, i를 감소시키는 등의 과정이 실제 런타임을 증가시키는 것 같다. splice
메소드의 시간복잡도가 O(n)이고, 사실상 이 코드의 시간복잡도는 O(n^2)이다.
🧐 개선
Related Topics 항목을 찾아보니, Two Pointers 태그가 있었다.
Two Pointers는 말그대로 두개의 포인터를 이용한 알고리즘이다.
하나의 포인터를 기준으로 잡고, 나머지 포인터는 나아가며 루프를 돌아야 할때 사용하는 알고리즘이라고 생각하면 될 것 같다. 기억하는 포인터의 시점은, 그 앞을 더이상 살펴볼 필요가 없는 시점일 것이다.
Two Pointers를 활용하여 splice
와 push
메소드 없이 코드를 작성해 보려한다.
개선 코드
- 이 문제에서 포인터들은
nums
의 인덱스를 가리킬것이다. - 첫번째 포인터(
pointer
)는 0인 인덱스를 기록하는 것이다. - 두번째 포인터는 for문을 도는
i
이다. - 0이 아닌 요소를 만나면
pointer
를 증가시킨다. 그리고 그 요소를 그대로 가져가면 된다. 만약 0을 만난다면,pointer
는 변화 없이i
만 증가하게될 것이다. 다시 0이 아닌 요소를 만난다면,pointer
와 같은 인덱스에 그 요소를 배치하고pointer
를 증가시게 되는 것이다. - 배열을 모두 순회하고 나면,
pointer
는 0이 아닌 수들을 지나고 처음으로 0인 인덱스 값이 될 것이다. 그러므로pointer
값부터 0을 배치하면 된다.
const moveZeroes = function(nums) {
if (nums.length < 2) return nums;
let pointer = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== 0) {
nums[pointer] = nums[i];
pointer++;
}
}
for (let i = pointer; i < nums.length; i++) {
nums[i] = 0;
}
return nums;
}
개선 결과
Runtime: 64 ms
Memory Usage: 36.8 MB
추가 메모리 없이 작업하므로 공간복잡도는 O(1), nums
의 크기에 따라 달라지므로 시간복잡도는 O(n)이다.
마무리
사용하는 메소드가 어떤 방식으로 작동하는지, 어떤 복잡도를 가지고 있는지도 생각해봐야 한다. 편리하다고 메소드를 마구 사용하다보면 코드는 조금 짧아지더라도 복잡도의 결과가 나쁠 수 있다.