문제 설명
배열 arr가 주어집니다. 배열 arr의 각 원소는 숫자 0부터 9까지로 이루어져 있습니다. 이때, 배열 arr에서 연속적으로 나타나는 숫자는 하나만 남기고 전부 제거하려고 합니다. 단, 제거된 후 남은 수들을 반환할 때는 배열 arr의 원소들의 순서를 유지해야 합니다. 예를 들면,
arr = [1, 1, 3, 3, 0, 1, 1] 이면 [1, 3, 0, 1] 을 return 합니다.
arr = [4, 4, 4, 3, 3] 이면 [4, 3] 을 return 합니다.
배열 arr에서 연속적으로 나타나는 숫자는 제거하고 남은 수들을 return 하는 solution 함수를 완성해 주세요.
제한사항
배열 arr의 크기 : 1,000,000 이하의 자연수
배열 arr의 원소의 크기 : 0보다 크거나 같고 9보다 작거나 같은 정수
입출력 예시
arr | answer |
[1,1,3,3,0,1,1] | [1,3,0,1] |
[4,4,4,3,3] | [4,3] |
내가 제출한 답
import java.util.*;
class Solution {
public List<Integer> solution(int[] arr) {
LinkedList<Integer> list = new LinkedList<>();
for (int num : arr) {
if (list.isEmpty() || !list.getLast().equals(num)) {
list.add(num);
}
}
return list;
}
}
최초 답을 정한 이유.
1. 임시로 만든 리스트 객체 안에 마지막 값을 비교해서 add 하자 (중복 허용이므로 Set 안됨)
2.내가 생각해낸게 마지막 배열 혹은 노드에 값을 꺼내 비교를 하자.
3. arraylist가 속도는 가장 빠르지만 , get(size() - 1) 이런식으로 마지막 값을 꺼내야해서 가독성이? 다소 마음에 들지 않아
선택한 것이 LinkedList였다.
4.list.getLast() 사용해 마지막 노드 값을 확인해 비교를 해 삽입을 하는 방법으로 통과 시킴.
내가 생각한 회고후 최선의 답?
import java.util.*;
public class Solution {
public Deque<Integer> solution(int[] arr) {
Deque<Integer> deque = new ArrayDeque<>();
for (int num : arr) {
if (deque.isEmpty() || deque.peekLast() != num) {
deque.addLast(num);
}
}
return deque;
}
}
LinkedList는 노드를 사용하여, next() 등으로 내부적으로 tail 포인터 접근 (약간의 오버헤드)가 발생하여 비효율적이라는 걸 배운적 있다. 내 블로그 참고.(컬렉션 비교 글)
그래서 찾아본게 바로 ArrayDeque 이다.
ArrayDeque 더 효율적인 이유
1. 배열 기반 원형 버퍼
2. 순회속도 배열 순회라 훨씬 빠름. (linkedlist는 노드르 따라가야해서 느림.)
3. 메모리 사용량도 적음 (linkedlist는 값 + next + prev 포인터 라 사용량 많음.)
+++번외+++
import java.util.*;
public class Solution {
public int[] solution(int []arr) {
ArrayList<Integer> tempList = new ArrayList<Integer>();
int preNum = 10;
for(int num : arr) {
if(preNum != num)
tempList.add(num);
preNum = num;
}
int[] answer = new int[tempList.size()];
for(int i=0; i<answer.length; i++) {
answer[i] = tempList.get(i).intValue();
}
return answer;
}
}
이건 다른 사람이 푼 답을 가져온 것이다.
코드를 보고 아 정말 속도와 메모리 효율에 극대화된 코드구나를 느꼈다.
처음엔 단순해 보였지만, 자세히 보면
속도와 메모리 효율 측면에서 굉장히 극대화된 방식이다.
- int 기본형 변수 하나로 이전 값을 기억하고
- 불필요한 자료구조 접근 없이 배열을 순회
- 결과도 바로 ArrayList에 담기기 때문에
→ 불필요한 메모리 접근이나 메서드 호출이 없다.
나 역시 Deque 구조로 풀었지만,
내 방식은 매번 peekLast() 같은 메서드로
자료구조 내부를 참조해야 해서, 미세하지만
메모리 접근이나 비교 연산이 누적된다.
그런 점에서 이 코드는
코딩테스트에 최적화된 풀이라고 생각했다.
하지만 고민이 들었다.
이 코드는 정말 빠르지만,
확장성이나 실무 상황에 바로 적용할 수 있을까? 라는 의문이 생겼다.
- 예를 들어, preNum의 초기값인 10은 "0~9" 조건을 가정한 값인데
만약 범위가 바뀌거나 null, 음수, 객체 등으로 확장된다면? - 또는 "이전 값과만 다르면 추가"가 아니라
"두 개 전 값과 다르면", "홀수일 때만 추가"처럼
조건이 바뀌는 경우는?
→ 조건이 바뀔수록 구조 자체를 손대야 한다.
이런 점에서 실무에서는 유지보수나 전략 분리가 중요하기 때문에
Deque 기반처럼 상태가 구조 안에 캡슐화된 방식이 더 유연할 수 있다.
💡 마무리 고민
코딩테스트는 효율 최우선.
실무는 확장성과 유지보수성이 최우선.
이 두 기준 사이에서 어떤 걸 더 우선순위에 둘지는
문제의 성격, 쓰임의 맥락, 그리고 팀의 개발 철학에 따라 달라질 수 있다.
나 역시 아직 딜레마가 있다.
하지만 중요한 건,
단순히 "더 빠른 코드"를 쓰는 게 아니라,
"문제에 맞는 적절한 수준의 설계"를 할 줄 아는 개발자가 되는 것이라고 느꼈다.
✨ 한줄 회고
좋은 코드는 빠른 코드일 수도 있지만,
진짜 좋은 코드는 변화에 잘 견디는 코드일지도 모른다.
참고하면 좋을 글
컬렉션- https://moon-97.tistory.com/39
Java Map 컬렉션 정리 – HashMap, LinkedHashMap, TreeMap
먼저 이 글을 쓰게 된 시작 점은 treeMap을 굳이 왜 써야할까? 어떤 상황에서 사용하면 이점이 클까? 그리고 어떤 구조로 관리가 될까? DB에서 원하는 정렬된 값을 받아와 hashmap에 담으면 되는데? 라
moon-97.tistory.com