Ch02 - 시간복잡도
Last updated -
2023년 04월 23일
Edit Source
패스트캠퍼스 핵심유형 20개로 한 번에 끝내는 알고리즘 코딩테스트 with Java 강의를 정리한 내용
# 시간 복잡도
- 입력 크기와 알고리즘 간의 관계
- 알고리즘의 복잡도를 나타내는 지표 중 하나
- 입력 크기에 대해 프로그램의 동작시간을 가늠해볼 수 있는 수단
- Big-O / Big-Omega / Big-Theta 표기법
- 주로 Big-O를 사용, 정의된 입력 데이터 중 가장 최악의 상황을 포함한 시간의 상한선
코딩테스트에서의 시간복잡도
- 보편적으로 문제마다 시간 제한이 주어짐
- 시간 제한이 1초라면, 최악의 테스트케이스에서도 해당 시간 내에 프로그램이 답을 구할 수 있어야 한다.
- 시간 제한을 넘어가면 시간 초과뜨고 프로그램 종료
- 편의상 1초에 약 1억 번 연산을 기준으로 소요시간을 가늠할 수 있다.
- 상수/최적화 등에 따라 시간 복잡도가 1천만 밖에 되지 않아도 1초를 넘기거나 시간 복잡도로 10억이 넘어도 1초 안에 실행될 수 있다.
- 절대적 기준이 아닌 상대적 지표
적합한 알고리즘 선택 기준
- 정답을 구하는 알고리즘이 여러 개인 경우
- 시간이 넉넉하다? => 구현이 쉬운 방법
- 제한이 있다? => 시간/메모리상으로 효율적인 방법
배열의 최댓값을 구하는 예시
- 반복문 이용 => O(n)
1
2
3
4
5
6
7
8
| public static int getMaxIntArray(int[] arr) {
int maxValue = arr[0];
for (int i = 1; i < arr.length; i++) {
if (arr[i] > maxValue)
maxValue = arr[i];
}
return maxValue;
}
|
- 정렬 메서드 이용 => O(n log n), O(n2)
1
2
3
4
| public static int getMaxIntArray(int[] arr) {
Arrays.sort(arr);
return arr[arr.length - 1];
}
|
예를 들어, 입력 n이 1000만이라고 하자. => 1번 방법 선택
만약, 시간 제한이 크지는 않다면 ? n이 작다면? => 편한 방법 고르기
- (번외) 스트림
1
2
3
| public static int getMaxIntArray(int[] arr) {
return Arrays.stream(arr).max.getAsInt();
}
|
# 시간복잡도를 통한 추론
O(1) < O(log2n) < O(n) < O(n log2n) < O(n2) < O(2n)
N의 범위 | 시간 복잡도 |
---|
N <= 10 ~ 11 | O(N!) |
N <= 24 ~ 25 | O(2N) |
N <= 300 ~ 500 | O(N3) |
==N <= 5,000 ~ 10,000== | O(N2) |
N <= 50,000 ~ 100,000 | O(N 루트 N) |
==N <= 100,000 ~ 1,000,000== | O(N log2N) |
==N <= 10,000,000== | O(N) |
N개의 데이터가 입력이 아닌 범위 등으로 주어질 때 | O(루트 N) , O(log N), O(1) |
# 10158번 문제
- 해설은 강의내용을 참고하도록 하자.
- 내가 생각한 포인트는 x축과 y축을 별개로 나눠서 따로 구하는 것까지는 생각했음
- 그러나, 시간제한 때문에 반복문을 아예 안쓰는걸로 생각해서 더 나아가지 못했다.
- 반복문을 사용하면 시간제한이 O(N)이 되버리니까 당연히 시간초과
여기에서 개미가 움직이는걸 보다보면 주기성이 있다는 것을 확인할 수 있음.
=> 주기성이 있다 = 반복된다 = 모듈러(%
) 연산으로 연산 횟수를 낮출 수 있다. = 반복문을 사용하더라도 시간복잡도가 O(N)이 아니게 할 수 있다.
- 기본적인 입력값은 받아왔음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int w = Integer.parseInt(st.nextToken());
int h = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
int t = Integer.parseInt(br.readLine());
}
}
|
- 기초로직
- w나 h에 이를 곱하는건 왔다갔다 하는거니까
- 1시간에 1만큼 이동하니까, 거리 1을 1시간으로 볼 수도 있겠네
- 따라서, 시작 위치가 (2, ?), w = 6 이었다고 하자.
- (2 ~ 6) 4만큼 + (6 ~ 0) 6만큼 + (0 ~ 2) 2만큼 => 12
- 12번마다 처음으로 돌아오네! 주기가 12
- 여기서 처음은 위치와 가는 방향까지 동일한거 의미함
- 그러면, 시간이 30이라고 하면
(시간 % 주기)
를 하면 나머지니까 마지막 위치 나오겠네.
1
2
3
4
5
6
7
8
9
10
11
12
| // 시간복잡도 O(2W)
int dx = 1;
int timeX = t % (2 * w);
while (timeX-- > 0) {
if (x == w)
dx = -1;
else if (x == 0)
dx = 1;
x += dx;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
| // 시간복잡도 O(2H)
int dy = 1;
int timeY = t % (2 * h);
while (timeY-- > 0) {
if (y == h)
dy = -1;
else if (y == 0)
dy = 1;
y += dy;
}
|
처음에 문제 범위가 2 <= W, H <= 40,000
, 1 <= T <= 200,000,000
이었다. 따라서 기초로직의 시간복잡도는 O(max(W, H))
이기 때문에 통과할 것이다.
하지만, W, H가 40,000보다 더 커지면 어떻게 할거냐? 시간 복잡도 더 줄여보자
- 시작을 시작위치가 아니라 0에서부터 시작
- 아까 위치 1만큼 1시간이라고 했으니까 그냥 시간으로 볼 수도 있다고 했다.
- 그러면 0부터 시작하고
(현재위치 + 걸린시간)
하면 되지않을까?
1
2
3
4
5
6
7
8
9
10
11
| int dx = 1;
int timeX = (x + t) % (2 * w);
while (timeX-- > 0) {
if (x == w)
dx = -1;
else if (x == 0)
dx = 1;
x += dx;
}
|
1
2
3
4
5
6
7
8
9
10
11
| int dy = 1;
int timeY = (y + t) % (2 * h);
while (timeY-- > 0) {
if (y == h)
dy = -1;
else if (y == 0)
dy = 1;
y += dy;
}
|
- 다음으로 더 나아가서 0부터 시작했고, 주기가 2W니까 가운데 W를 기점으로 값이 바뀌잖아? 이를 이용하면
x <= w
이면 값 그대로 x좌표이다.x > w
이면 w를 만나 방향이 전환되므로 식을 세울 수 있다. w로부터 멀어지는데 현재 위치인 x와 w 만큼의 차이만큼 멀어짐!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| import java.util.*;
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
int w = Integer.parseInt(st.nextToken());
int h = Integer.parseInt(st.nextToken());
st = new StringTokenizer(br.readLine());
int x = Integer.parseInt(st.nextToken());
int y = Integer.parseInt(st.nextToken());
int t = Integer.parseInt(br.readLine());
int currentX = (t + x) % (2 * w);
int currentY = (t + y) % (2 * h);
if (currentX > w)
currentX = 2 * w - currentX;
if (currentY > h)
currentY = 2 * h - currentY;
System.out.println(currentX + " " + currentY);
}
}
|
- 이렇게 최종적으로 반복문을 쓰지않고
O(1)
을 만들면서 문제를 끝낼 수 있다.