배열은 동일한 타입의 원소들을 연속된 메모리 공간에 저장하는 가장 기본적인 자료구조다. 인덱스로 O(1) 접근이 가능하지만, 삽입 위치에 따라 시간 복잡도가 크게 달라진다. 맨 뒤 삽입은 O(1)이지만 맨 앞 삽입은 O(N)이 된다. 이 차이를 이해하면 어떤 자료구조를 선택해야 할지 판단하는 기준이 생긴다.
int, 문자형 배열이면 모든 원소가 char다.arr[i]처럼 어떤 위치든 O(1) 상수 시간에 직접 접근할 수 있다.int arr[5] = {10, 20, 30, 40, 50} — 메모리 배치
int형 원소 하나는 4바이트이므로 주소가 4씩 증가하는 것을 확인할 수 있다.
선언 문법은 data_type array_name[size]다. 초기화 방식에 따라 동작이 달라지므로 주의해야 한다.
쓰레기값(Garbage Value)이 들어간다. 실행할 때마다 값이 달라지므로 반드시 초기화해야 한다.
int arr[5] = {1,2,3,4,5}
앞에서부터 순서대로 값이 채워진다.
int arr[5] = {1,2,3}
지정한 값 이후 나머지는 0으로 자동 초기화된다.
int arr[5] = {0} 또는 int arr[5] = {}
모든 원소를 0으로 빠르게 초기화하는 관용 표현이다.
주의: 초기화하지 않고 선언만 한 배열을 그대로 사용하면 쓰레기값이 출력된다. 코딩 테스트에서 원인 모를 오답의 주범이 될 수 있으므로, 선언과 동시에 초기화하는 습관을 들이자.
2차원 배열은 1차원 배열을 여러 개 묶어 놓은 형태로, int arr[2][3]처럼 행(row)과 열(col) 크기를 함께 명시한다.
int arr[2][3] = {{1,2,3},{4,5,6}} — 논리 구조
행 우선(Row-Major) 저장: C++에서 2차원 배열은 내부적으로 1행 전체 → 2행 전체 순서로 연속 메모리에 저장된다. 즉 arr[0][0], arr[0][1], arr[0][2], arr[1][0], arr[1][1], arr[1][2] 순으로 배치된다. 2차원처럼 보여도 메모리는 항상 1차원이다.
int arr[2][3] = { {1, 2, 3}, // 0행 {4, 5, 6} // 1행 }; // 메모리: [1][2][3][4][5][6] 연속 배치 // arr[1][0]의 주소 = arr[0][0]의 주소 + (3 * sizeof(int))
배열은 연속 메모리 구조이기 때문에 삽입 위치에 따라 성능이 크게 달라진다.
기존 원소를 전혀 건드리지 않고 다음 빈 자리에 바로 추가. 다른 원소 이동이 없으므로 상수 시간.
기존 원소 전체를 한 칸씩 뒤로 밀어야 한다. 원소가 N개면 N번의 이동이 필요.
맨 뒤 삽입 — arr[size]에 바로 대입
맨 앞 삽입 — 기존 원소 전체 이동
// 맨 뒤 삽입 — O(1) if (size < capacity) { arr[size] = newValue; // 그냥 넣기만 하면 끝 size++; } // 맨 앞 삽입 — O(N) for (int i = size - 1; i >= 0; i--) arr[i + 1] = arr[i]; // 전체를 한 칸씩 뒤로 arr[0] = newValue; size++;
① 배열 제어하기 — 중복 제거 + 내림차순 정렬
핵심 아이디어: sort()로 내림차순 정렬 후 unique()로 연속된 중복값을 뒤로 밀고, erase()로 제거한다. unique()는 정렬된 배열에서만 올바르게 동작하므로 순서가 중요하다.
bool compare(int a, int b) { return a > b; } // 내림차순 기준 vector<int> solution(vector<int> lst) { sort(lst.begin(), lst.end(), compare); // 내림차순 정렬 lst.erase(unique(lst.begin(), lst.end()), // 중복 제거 lst.end()); return lst; } // {4,2,2,1,1,3,4} → {4,3,2,1}
② 두 수를 뽑아서 더하기 — set으로 중복 없는 합 수집
핵심 아이디어: set<int>에 두 원소의 합을 삽입하면 중복이 자동 제거되고 오름차순 정렬도 자동으로 된다. 이중 루프로 모든 쌍을 순회하면 O(N²).
vector<int> solution(vector<int> numbers) { set<int> sum; for (int i = 0; i < numbers.size(); ++i) for (int j = i + 1; j < numbers.size(); ++j) sum.insert(numbers[i] + numbers[j]); // set이 중복+정렬 처리 return vector<int>(sum.begin(), sum.end()); } // {2,1,3,4,1} → {2,3,4,5,6,7}
③ 모의고사 — 패턴 배열 + 나머지 연산
핵심 아이디어: 각 수포자의 반복 패턴을 배열로 정의하고 i % pattern.size()로 패턴을 순환시킨다. 맞힌 횟수를 비교해 최댓값과 동률인 사람을 모두 반환한다.
vector<int> p1 = {1,2,3,4,5}; vector<int> p2 = {2,1,2,3,2,4,2,5}; vector<int> p3 = {3,3,1,1,2,2,4,4,5,5}; for (int i = 0; i < answers.size(); i++) { if (answers[i] == p1[i % p1.size()]) cnt[0]++; if (answers[i] == p2[i % p2.size()]) cnt[1]++; if (answers[i] == p3[i % p3.size()]) cnt[2]++; } // i % size() 로 패턴을 무한 반복시키는 것이 핵심
| 연산 | 시간 복잡도 | 이유 |
|---|---|---|
임의 접근 arr[i] |
O(1) | 시작 주소 + i × 타입 크기로 직접 계산 |
| 맨 뒤 삽입/삭제 | O(1) | 다른 원소 이동 없음 |
| 중간/맨 앞 삽입/삭제 | O(N) | 이후 원소들을 전부 밀거나 당겨야 함 |
| 선형 탐색 | O(N) | 최악의 경우 모든 원소를 순회 |
| 크기 변경 | 불가 | C++ 정적 배열은 선언 시 크기 고정 |
이번 챕터에서 가장 인상 깊었던 건 2차원 배열도 메모리상에서는 1차원으로 저장된다는 사실이었다. 행 우선 순서로 연속 배치된다는 것을 이해하니, 2차원 배열의 열(col) 방향 순회가 왜 행(row) 방향 순회보다 느린지 — 캐시 미스가 더 자주 발생하기 때문 — 까지 자연스럽게 이해됐다.
맨 앞 삽입이 O(N)이라는 사실을 단순히 외우는 것에서 끝내지 않고, 연속 메모리 구조상 앞의 빈 공간이 없기 때문에 전체를 밀어야 한다는 이유를 이해한 것이 중요했다. 앞쪽 삽입/삭제가 잦은 경우에는 배열 대신 deque를 선택해야 한다는 판단 기준도 이 이해에서 나온다.
'프로그래밍 > 코드카타' 카테고리의 다른 글
| [C++] 코딩테스트에서의 시뮬레이션 (0) | 2026.05.12 |
|---|---|
| [C++] 코딩테스트에서의 정렬 (0) | 2026.05.11 |
| [C++] 재귀 함수를 코드로 (0) | 2026.05.06 |
| [C++] 입출력 데이터 다루기 - 숫자 연산과 문자열 스트림 정리 (0) | 2026.04.28 |
| [코딩 테스트] 어떻게 준비하고 어떻게 풀어야 할까? (1) | 2026.04.27 |