알고리즘 코딩테스트 핵심이론 강의 - 시간복잡도 (Java)
Post

알고리즘 코딩테스트 핵심이론 강의 - 시간복잡도 (Java)

안녕하세요~👋

오늘도 공부하는 무럭무럭 ✨성장몬✨ 입니다.


지난 Database 포스팅에 시간복잡도를 고려해서 문제를 푸는 분들이 있어

더 열심히 해야겠다고 다짐했었는데요!

시간복잡도를 고려하려면~

시간복잡도가 뭔지 알아야겠죠?! 🤭

마침 관련 강의를 발견해서 듣고서 포스팅해 봅니다^_^*


같이 성장하고 싶다면~?



[TOC]


01 어떤 알고리즘으로 풀어야 할까?

ㅡ 알고리즘 선택의 기준이 되는 시간 복잡도


코딩 테스트의 핵심 중 하나는 문제마다 주어진 시간 복잡도를 고려해

적절한 알고리즘을 선택하는 것입니다.

처음에 알고리즘을 잘못 선택하면 아무리 코드를 잘 짜려고 노력해도

좋은 결과를 거두기 어려우니까요.

문제를 본격적으로 풀어 보기 전에 시간 복잡도를 표기하는 방법과

활용하는 방법을 익혀 보겠습니다.



01-1 시간 복잡도 표기법 알아보기

알고리즘에서 시간 복잡도는 주어진 문제를 해결하기 위한 연산 횟수를 말합니다. 일반적으로 수행 시간은 1억 번의 연산을 1초의 시간으로 간주하여 예측합니다.


💙 시간 복잡도 정의하기

실제 시간 복잡도를 정의하는 3가지 유형은 다음과 같습니다.

시간 복잡도 유형

  • 빅-오메가(Ω(n)): 최선일 때(best case)의 연산 횟수를 나타낸 표기법
  • 빅-세타(Θ(n)): 보통일 때(average case)의 연산 횟수를 나타낸 표기법
  • 빅-오(O(n)): 최악일 때(worst case)의 연산 횟수를 나타낸 표기법


다음은 0~99 사이의 무작윗값을 찾아 출력하는 코드입니다. 빅-오메가 표기법(Ω(n))의 시간 복잡도는 1번, 빅-세타 표기법(Θ(n))의 시간 복잡도는 2/N번, 빅-오 표기법(O(n))의 시간 복잡도는 N번입니다.

시간 복잡도 예제 코드

1
2
3
4
5
6
7
8
9
10
11
12
public class timeComplexExample1 {
    public static void main(String[] args) {
        // 1~100 사이 값 랜덤 선택
        int findNumber = (int)(Math.random() * 100);
        for(int i= 0; i < 100; i++) {
            if(i == findNumber){
                System.out.print(i);
                break;
            }
        }
    }
}


💙 코딩 테스트에서는 어떤 시간 복잡도 유형을 사용해야 할까?

코딩 테스트에서는 빅-오 표기법(Ο(n))을 기준으로 수행 시간을 계산하는 것이 좋습니다. 실제 테스트에서는 1개의 테스트 케이스로 합격, 불합격을 결정하지 않습니다. 응시자가 작성한 프로그램으로 다양한 테스트 케이스를 수행해 모든 케이스를 통과해야만 합격으로 판단하므로 시간 복잡도를 판단할 때는 최악일 때 worst case를 염두에 둬야 합니다.

다음은 빅-오 표기법(Ο(n))으로 표현한 시간 복잡도 그래프입니다. 각각의 시간 복잡도는 데이터 크기(N)의 증가에 따라 성능(수행 시간)이 다르다는 것을 확인할 수 있습니다.

빅-오 표기법의 시간 복잡도


다음 절에서는 이러한 내용을 바탕으로 시간 복잡도의 개념을 코딩 테스트에 어떻게 활용해야 하는지 알아보겠습니다.



01-2 시간 복잡도 활용하기

💙 알고리즘 선택의 기준으로 사용하기

우리가 이 책에서 정렬 부분의 학습을 완료했고, 버블 정렬과 병합 정렬의 시간 복잡도를 각각 Ο(n²), Ο(nlogn)이라고 알고 있다고 가정하고 다음 문제를 예로 들어 설명하겠습니다.


문제 000 🎁 수 정렬하기

N개의 수가 주어졌을 때 이를 오름차순 정렬하는 프로그램을 작성하시오.

  • 시간 제한 2초
  • 백준 온라인 저지 2750번


📥 입력

1번째 줄에 수의 개수 N(1 ≤ N ≤ 1,000,000), 2번재 줄부터는 N개의 줄에 숫자가 주어진다. 이 수는 절댓값이 1,000,000보다 작거나 같은 정수다. 수는 중복되지 않는다.

📤 출력

1번째 줄부터 N개의 줄에 오름차순 정렬한 결과를 1줄에 1개씩 출력한다.

  • 예제 입력 1

    1
    2
    3
    4
    5
    6
    
    5
    5
    2
    3
    4
    1
    
  • 예제 출력 1

    1
    2
    3
    4
    5
    
    1
    2
    3
    4
    5
    

시간 제한이 2초이므로 이 조건을 만족하려면 2억 번 이하의 연산 횟수로 문제를 해결해야 합니다. 따라서 문제에서 주어진 시간 제한과 데이터 크기를 바탕으로 어떤 정렬 알고리즘을 사용해야 할 것인지를 판단할 수 있습니다.


✅ 연산 횟수는 1초에 1억 번 연산하는 것을 기준으로 생각합니다.

✅ 시간 복잡도는 항상 최악일 때, 즉 데이터의 크기가 가장 클 때를 기준으로 합니다.


연산 횟수 계산 방법

  • 연산 횟수 = 알고리즘 시간 복잡도 X 데이터의 크기


위 공식을 대입해 각 알고리즘이 이 문제에 적합한지 판단해 보겠습니다.

알고리즘 적합성 평가

  • 버블 정렬 = (1,000,000)² = 1,000,000,000,000 > 200,000,000 → 부적합 알고리즘

  • 병합 정렬 = 1,000,000log(1,000,000) = 약 20,000,000 < 200,000,000 → 적합 알고리즘


문제에 주어진 시간 제한이 2초이므로 연산 횟수 2억 번 안에 원하는 답을 구해야 합니다. 버블 정렬은 약 10억 번의 연산 횟수가 필요하므로 이 문제를 풀기에 적합한 알고리즘이 아니라고 판단할 수 있습니다. 병합 정렬을 약 2,000만 번의 연산 횟수로 답을 구할 수 있으므로 문제를 풀기에 적합한 알고리즘이라고 판단할 수 있습니다.

이와 같이 시간 복잡도 분석으로 문제에서 사용할 수 있는 알고리즘을 선택할 수 있고, 이 범위를 바탕으로 문제의 실마리를 찾을 수 있습니다. 즉, 데이터의 크기(N)를 단서로 사용해야하는 알고리즘을 추측해 볼 수 있습니다.


💙 시간 복잡도를 바탕으로 코드 로직 개선하기

시간 복잡도는 작성한 코드의 비효율적인 로직을 개선하는 바탕으로도 사용할 수 있습니다. 이 부분을 활용하려면 가장 먼저 코드의 시간 복잡도를 도출할 수 있어야 합니다. 시간 복잡도를 도출하려면 다음 2가지 기준을 고려해야 합니다.

시간 복잡도 도출 기준

① 상수는 시간 복잡도 계산에서 제외한다.

② 가장 많이 중첩된 반복문의 수행 횟수가 시간 복잡도의 기준이 된다.


코드를 예로 들어 설명하겠습니다.

연산 횟수가 N인 경우

1
2
3
4
5
6
7
8
9
public class 시간복잡도_판별원리1 {
	public static void main(String[] args) {
		int N = 100000;
		int cnt = 0;
		for (int i = 0; i < N; i++) {
			System.out.println("연산 횟수:" + cnt++);
		}
	}
}

연산 횟수가 3N인 경우

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class 시간복잡도_판별원리2 {
	public static void main(String[] args) {
		int N = 100000;
		int cnt = 0;
		for (int i = 0; i < N; i++) {
			System.out.println("연산 횟수:" + cnt++);
		}
		for (int i = 0; i < N; i++) {
			System.out.println("연산 횟수:" + cnt++);
		}
		for (int i = 0; i < N; i++) {
			System.out.println("연산 횟수:" + cnt++);
		}
	}
}

두 예제 코드의 연산 횟수는 3배의 차이가 납니다. 언뜻 생각하면 큰 차이인 것 같지만 코딩 테스트에서는 일반적으로 상수를 무시하므로 두 코드 모두 시간 복잡도는 Ο(n)으로 같습니다.


다음 예제 코드를 확인해 보겠습니다.

연산 횟수가 N²인 경우

1
2
3
4
5
6
7
8
9
10
11
public class 시간복잡도_판별원리3 {
	public static void main(String[] args) {
		int N = 100000;
		int cnt = 0;
		for(int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				System.out.println("연산 횟수:" + cnt++);
			}
		}
	}
}

시간 복잡도는 가장 많이 중첩된 반복문을 기준으로 도출하므로 이 코드에서는 이중 for 문이 전체 코드의 시간 복잡도 기준이 됩니다. 만약 일반 for 문이 10개 더 있다 하더라도 도출 원리의 기준 ②에 따라 시간 복잡도의 변화 없이 N²으로 유지됩니다.

이와 같이 자신이 작성한 코드의 시간 복잡도를 도출할 수 있다면 실제 코딩 테스트에서 시간 초과가 발생했을 때 이 원리를 바탕으로 문제가 되는 코드 부분을 도출할 수 있고, 이 부분을 연산에 더욱 효율적인 구조로 수정하는 작업으로 문제를 해결할 수 있습니다.

SUM, MAX, MIN > 가격이 제일 비싼 식품의 정보 출력하기

String, Date > 중성화 여부 파악하기