컴퓨터공학 전공 핵심정리 하편: 알고리즘 코딩 & SQL 주관식 실전 대비
컴퓨터공학 전공 핵심정리 하편: 알고리즘 코딩 & SQL 주관식 실전 대비
주관식 3문항은 코드 출력 결과 작성 또는 직접 코드/SQL 작성 형태로 출제된다. 레퍼런스 활용이 가능하지만, 시간이 제한되므로 핵심 패턴을 완벽히 숙지해야 한다.
Part 1. 알고리즘 코딩 — 언어별 핵심 문법과 출력 트레이싱
1. C/C++ 핵심 패턴
1.1 포인터와 배열
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* ★ 포인터 기초 — 출력 결과를 정확히 추적하라 */
#include <stdio.h>
int main() {
int a = 10, b = 20;
int *p = &a;
int **pp = &p;
printf("%d\n", *p); // 10 (p가 가리키는 값)
printf("%d\n", **pp); // 10 (pp→p→a)
*p = 30;
printf("%d\n", a); // 30 (p를 통해 a 변경)
p = &b;
printf("%d\n", **pp); // 20 (pp→p→b, p가 b를 가리킴)
return 0;
}
1
2
3
4
★ 포인터 핵심 정리:
int *p = &a; → p는 a의 주소, *p는 a의 값
int **pp = &p; → pp는 p의 주소, *pp는 p(=a의 주소), **pp는 a의 값
p++ → 포인터가 int 크기(4바이트)만큼 이동
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* ★ 배열과 포인터 관계 */
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("%d\n", *p); // 10
printf("%d\n", *(p+2)); // 30
printf("%d\n", p[3]); // 40 (p[i] == *(p+i))
p += 2;
printf("%d\n", *p); // 30
printf("%d\n", p[-1]); // 20 (p[-1] == *(p-1))
// 2차원 배열
int mat[2][3] = {{1,2,3},{4,5,6}};
printf("%d\n", mat[1][2]); // 6
printf("%d\n", *(*(mat+1)+2)); // 6 (동일)
printf("%d\n", *(*mat+4)); // 5 (행 무시, 연속 메모리)
return 0;
}
1.2 문자열 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* ★ 문자열 함수 — 빈출 */
#include <stdio.h>
#include <string.h>
int main() {
char s1[] = "Hello";
char s2[] = "World";
char s3[20];
printf("%lu\n", strlen(s1)); // 5 (널문자 미포함)
printf("%lu\n", sizeof(s1)); // 6 (널문자 포함)
strcpy(s3, s1);
printf("%s\n", s3); // Hello
strcat(s3, s2);
printf("%s\n", s3); // HelloWorld
printf("%d\n", strcmp("abc","abd")); // 음수 ('c' < 'd')
printf("%d\n", strcmp("abd","abc")); // 양수
printf("%d\n", strcmp("abc","abc")); // 0
// 문자열 역순
char str[] = "ABCDE";
int len = strlen(str);
for (int i = 0; i < len / 2; i++) {
char tmp = str[i];
str[i] = str[len-1-i];
str[len-1-i] = tmp;
}
printf("%s\n", str); // EDCBA
return 0;
}
1.3 구조체와 공용체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* ★ 구조체 */
#include <stdio.h>
struct Student {
char name[20];
int score;
float avg;
};
int main() {
struct Student s = {"Kim", 85, 3.5};
struct Student *p = &s;
printf("%s %d\n", s.name, s.score); // Kim 85
printf("%s %.1f\n", p->name, p->avg); // Kim 3.5
return 0;
}
/* ★ 공용체 — 메모리 공유 (sizeof = 가장 큰 멤버) */
union Data {
int i;
float f;
char c;
};
// sizeof(union Data) = sizeof(float) = 4 (가장 큰 멤버)
// 마지막에 저장한 값만 유효!
1.4 재귀와 출력 트레이싱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/* ★★★ 재귀 출력 추적 — 가장 빈출 */
#include <stdio.h>
void func(int n) {
if (n <= 0) return;
printf("%d ", n);
func(n - 1);
printf("%d ", n);
}
int main() {
func(3);
// 출력: 3 2 1 1 2 3
return 0;
}
1
2
3
4
5
6
7
호출 추적:
func(3): print 3 → func(2) → print 3
func(2): print 2 → func(1) → print 2
func(1): print 1 → func(0) → print 1
func(0): return
실행 순서: 3 → 2 → 1 → (return) → 1 → 2 → 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/* ★ 피보나치 재귀 */
int fib(int n) {
if (n <= 1) return n;
return fib(n-1) + fib(n-2);
}
// fib(5) = 5, fib(6) = 8, fib(7) = 13
/* ★ 팩토리얼 재귀 */
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n-1);
}
// factorial(5) = 120
/* ★ 하노이탑 */
void hanoi(int n, char from, char to, char via) {
if (n == 1) {
printf("%c -> %c\n", from, to);
return;
}
hanoi(n-1, from, via, to);
printf("%c -> %c\n", from, to);
hanoi(n-1, via, to, from);
}
// n개 원반의 이동 횟수 = 2^n - 1
1.5 비트 연산
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* ★ 비트 연산 — 출력 문제 빈출 */
#include <stdio.h>
int main() {
int a = 12; // 1100
int b = 10; // 1010
printf("%d\n", a & b); // 8 (1000) AND
printf("%d\n", a | b); // 14 (1110) OR
printf("%d\n", a ^ b); // 6 (0110) XOR
printf("%d\n", ~a); // -13 (보수)
printf("%d\n", a << 2); // 48 (110000) 좌측시프트 = ×4
printf("%d\n", a >> 1); // 6 (110) 우측시프트 = ÷2
// ★ XOR 활용: 두 변수 값 교환 (임시변수 없이)
a = a ^ b; // a=0110
b = a ^ b; // b=1100 (=원래 a)
a = a ^ b; // a=1010 (=원래 b)
return 0;
}
2. Java 핵심 패턴
2.1 클래스와 상속
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/* ★ 상속과 다형성 — 출력 추적 */
class Animal {
String name = "Animal";
void sound() { System.out.println("..."); }
}
class Dog extends Animal {
String name = "Dog";
@Override
void sound() { System.out.println("Woof"); }
void fetch() { System.out.println("Fetch!"); }
}
public class Main {
public static void main(String[] args) {
Animal a = new Dog(); // 업캐스팅
System.out.println(a.name); // "Animal" ★ 필드는 참조타입 기준!
a.sound(); // "Woof" ★ 메서드는 실제객체 기준!
// a.fetch(); // 컴파일 에러! Animal에 fetch 없음
Dog d = (Dog) a; // 다운캐스팅
System.out.println(d.name); // "Dog"
d.fetch(); // "Fetch!"
}
}
1
2
3
4
★ 핵심: Java 다형성
- 필드(변수): 컴파일 타임(참조 타입) 기준 → Animal.name
- 메서드: 런타임(실제 객체) 기준 → Dog.sound() ← 동적 바인딩
- 이것이 시험에서 가장 자주 출제되는 Java 문제!
2.2 추상 클래스와 인터페이스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* ★ 추상 클래스 */
abstract class Shape {
abstract double area(); // 추상 메서드 (구현 없음)
void display() { // 일반 메서드 (구현 있음)
System.out.println("Area: " + area());
}
}
class Circle extends Shape {
double r;
Circle(double r) { this.r = r; }
@Override
double area() { return Math.PI * r * r; }
}
/* ★ 인터페이스 */
interface Flyable {
void fly(); // 암묵적 public abstract
default void land() { // Java 8+ 디폴트 메서드
System.out.println("Landing");
}
}
interface Swimmable {
void swim();
}
// 다중 구현 가능 (다중 상속 X, 다중 구현 O)
class Duck extends Animal implements Flyable, Swimmable {
@Override public void fly() { System.out.println("Fly"); }
@Override public void swim() { System.out.println("Swim"); }
}
2.3 컬렉션과 자주 나오는 패턴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/* ★ ArrayList, HashMap — 코드 작성 문제 */
import java.util.*;
public class Main {
public static void main(String[] args) {
// ArrayList
List<Integer> list = new ArrayList<>(Arrays.asList(5,3,1,4,2));
Collections.sort(list);
System.out.println(list); // [1, 2, 3, 4, 5]
// HashMap — 빈도수 세기 패턴
String str = "banana";
Map<Character, Integer> map = new HashMap<>();
for (char c : str.toCharArray()) {
map.put(c, map.getOrDefault(c, 0) + 1);
}
System.out.println(map); // {a=3, b=1, n=2}
// Stack
Stack<Integer> stack = new Stack<>();
stack.push(1); stack.push(2); stack.push(3);
System.out.println(stack.pop()); // 3 (LIFO)
System.out.println(stack.peek()); // 2 (제거 안 함)
// Queue
Queue<Integer> queue = new LinkedList<>();
queue.offer(1); queue.offer(2); queue.offer(3);
System.out.println(queue.poll()); // 1 (FIFO)
}
}
2.4 예외 처리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/* ★ try-catch-finally 실행 순서 */
public class Main {
static int test() {
try {
System.out.println("try");
int result = 10 / 0; // ArithmeticException
return 1;
} catch (ArithmeticException e) {
System.out.println("catch");
return 2;
} finally {
System.out.println("finally"); // 항상 실행!
// return 3; // ★ finally에 return 있으면 catch의 return 무시!
}
}
public static void main(String[] args) {
System.out.println(test());
}
}
// 출력:
// try
// catch
// finally
// 2
// ★ finally에 return 3; 주석 해제 시 → 출력: 3
3. Python 핵심 패턴
3.1 리스트와 슬라이싱
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ★ 리스트 슬라이싱 — 인덱스 정확히!
arr = [10, 20, 30, 40, 50]
print(arr[1:4]) # [20, 30, 40] (인덱스 1~3)
print(arr[:3]) # [10, 20, 30] (처음~2)
print(arr[2:]) # [30, 40, 50] (인덱스 2~끝)
print(arr[-2:]) # [40, 50] (뒤에서 2개)
print(arr[::2]) # [10, 30, 50] (2칸씩)
print(arr[::-1]) # [50, 40, 30, 20, 10] (역순)
# ★ 리스트 컴프리헨션
squares = [x**2 for x in range(1, 6)]
# [1, 4, 9, 16, 25]
evens = [x for x in range(10) if x % 2 == 0]
# [0, 2, 4, 6, 8]
matrix = [[i*3+j for j in range(3)] for i in range(3)]
# [[0,1,2], [3,4,5], [6,7,8]]
3.2 딕셔너리와 집합
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# ★ 딕셔너리 활용
d = {'a': 1, 'b': 2, 'c': 3}
print(d.get('x', 0)) # 0 (없으면 기본값)
print(list(d.keys())) # ['a', 'b', 'c']
print(list(d.values())) # [1, 2, 3]
print(list(d.items())) # [('a',1), ('b',2), ('c',3)]
# 빈도수 세기
from collections import Counter
text = "banana"
cnt = Counter(text)
print(cnt) # Counter({'a': 3, 'n': 2, 'b': 1})
print(cnt.most_common(2)) # [('a', 3), ('n', 2)]
# ★ 집합 연산
A = {1, 2, 3, 4}
B = {3, 4, 5, 6}
print(A | B) # {1,2,3,4,5,6} 합집합
print(A & B) # {3,4} 교집합
print(A - B) # {1,2} 차집합
print(A ^ B) # {1,2,5,6} 대칭차집합
3.3 함수와 람다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# ★ 가변 인자
def add(*args):
return sum(args)
print(add(1, 2, 3)) # 6
print(add(1, 2, 3, 4)) # 10
# ★ 키워드 인자
def info(**kwargs):
for k, v in kwargs.items():
print(f"{k}: {v}")
info(name="Kim", age=25)
# name: Kim
# age: 25
# ★ 람다와 고차 함수
nums = [3, 1, 4, 1, 5, 9]
print(sorted(nums)) # [1, 1, 3, 4, 5, 9]
print(sorted(nums, reverse=True)) # [9, 5, 4, 3, 1, 1]
pairs = [(1,'b'), (2,'a'), (3,'c')]
print(sorted(pairs, key=lambda x: x[1])) # [(2,'a'), (1,'b'), (3,'c')]
print(list(map(lambda x: x**2, [1,2,3]))) # [1, 4, 9]
print(list(filter(lambda x: x>2, [1,2,3,4])))# [3, 4]
3.4 클래스
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# ★ Python 상속과 오버라이딩
class Animal:
def __init__(self, name):
self.name = name
def speak(self):
return "..."
class Dog(Animal):
def speak(self):
return f"{self.name}: Woof!"
class Cat(Animal):
def speak(self):
return f"{self.name}: Meow!"
# 다형성
animals = [Dog("Rex"), Cat("Kitty"), Dog("Buddy")]
for a in animals:
print(a.speak())
# Rex: Woof!
# Kitty: Meow!
# Buddy: Woof!
# ★ 특수 메서드 (매직 메서드)
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __str__(self):
return f"({self.x}, {self.y})"
p1 = Point(1, 2)
p2 = Point(3, 4)
print(p1 + p2) # (4, 6)
4. JavaScript 핵심 패턴
4.1 변수 스코프와 호이스팅
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/* ★ var vs let vs const */
console.log(a); // undefined (var 호이스팅: 선언만 끌어올림)
// console.log(b); // ReferenceError (let은 TDZ)
var a = 1;
let b = 2;
const c = 3;
// c = 4; // TypeError (const 재할당 불가)
/* ★ 클로저 — 빈출 */
function outer() {
let count = 0;
return function() {
return ++count;
};
}
const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3
// count는 outer 함수가 끝나도 사라지지 않음 → 클로저
/* ★ var와 클로저 함정 */
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 100);
}
// 출력: 3 3 3 (var는 함수 스코프 → 루프 종료 후 i=3)
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log(j);
}, 100);
}
// 출력: 0 1 2 (let은 블록 스코프 → 각 반복마다 새 j)
4.2 this와 화살표 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* ★ this 바인딩 */
const obj = {
name: "Kim",
greet: function() {
console.log(this.name); // "Kim" (obj가 호출)
},
greetArrow: () => {
console.log(this.name); // undefined (화살표 함수는 상위 this)
}
};
obj.greet(); // "Kim"
obj.greetArrow(); // undefined
/* ★ 배열 고차 함수 */
const nums = [1, 2, 3, 4, 5];
const doubled = nums.map(x => x * 2); // [2,4,6,8,10]
const evens = nums.filter(x => x % 2 === 0); // [2,4]
const sum = nums.reduce((acc, x) => acc + x, 0); // 15
const found = nums.find(x => x > 3); // 4
const hasEven = nums.some(x => x % 2 === 0); // true
const allPos = nums.every(x => x > 0); // true
4.3 프로미스와 async/await
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* ★ Promise 체이닝 */
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
delay(1000)
.then(() => { console.log("1초"); return delay(1000); })
.then(() => { console.log("2초"); })
.catch(err => console.error(err));
/* ★ async/await */
async function process() {
try {
await delay(1000);
console.log("완료");
return "result";
} catch (err) {
console.error(err);
}
}
Part 2. 주요 알고리즘 구현
5. 정렬 알고리즘
5.1 버블 정렬
1
2
3
4
5
6
7
8
9
10
11
12
13
# ★ 버블 정렬 — O(n²), 안정 정렬
def bubble_sort(arr):
n = len(arr)
for i in range(n - 1):
for j in range(n - 1 - i):
if arr[j] > arr[j + 1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
return arr
# [5,3,1,4,2] → 과정 추적:
# i=0: [3,1,4,2,5] ← 5가 맨 뒤로
# i=1: [1,3,2,4,5] ← 4 확정
# i=2: [1,2,3,4,5] ← 3 확정
5.2 선택 정렬
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# ★ 선택 정렬 — O(n²), 불안정 정렬
def selection_sort(arr):
n = len(arr)
for i in range(n - 1):
min_idx = i
for j in range(i + 1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
return arr
# [5,3,1,4,2] → 과정:
# i=0: min=1 → [1,3,5,4,2]
# i=1: min=2 → [1,2,5,4,3]
# i=2: min=3 → [1,2,3,4,5]
5.3 삽입 정렬
1
2
3
4
5
6
7
8
9
10
# ★ 삽입 정렬 — O(n²), 안정, 거의 정렬된 데이터에 효율적
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and arr[j] > key:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
return arr
5.4 퀵 정렬
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# ★ 퀵 정렬 — 평균 O(n log n), 최악 O(n²), 불안정
def quick_sort(arr):
if len(arr) <= 1:
return arr
pivot = arr[len(arr) // 2]
left = [x for x in arr if x < pivot]
middle = [x for x in arr if x == pivot]
right = [x for x in arr if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
# ★ 제자리(in-place) 퀵 정렬 — C 스타일 출제 빈출
def quick_sort_inplace(arr, low, high):
if low < high:
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i+1], arr[high] = arr[high], arr[i+1]
pi = i + 1
quick_sort_inplace(arr, low, pi - 1)
quick_sort_inplace(arr, pi + 1, high)
5.5 병합 정렬
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ★ 병합 정렬 — O(n log n), 안정, 추가 메모리 O(n)
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
result = []
i = j = 0
while i < len(left) and j < len(right):
if left[i] <= right[j]:
result.append(left[i])
i += 1
else:
result.append(right[j])
j += 1
result.extend(left[i:])
result.extend(right[j:])
return result
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
★ 정렬 알고리즘 비교표
┌──────────┬──────────┬──────────┬──────────┬────────┬────────┐
│ 알고리즘 │ 최선 │ 평균 │ 최악 │ 공간 │ 안정 │
├──────────┼──────────┼──────────┼──────────┼────────┼────────┤
│ 버블 │ O(n) │ O(n²) │ O(n²) │ O(1) │ O │
│ 선택 │ O(n²) │ O(n²) │ O(n²) │ O(1) │ X │
│ 삽입 │ O(n) │ O(n²) │ O(n²) │ O(1) │ O │
│ 퀵 │ O(nlogn) │ O(nlogn) │ O(n²) │ O(logn)│ X │
│ 병합 │ O(nlogn) │ O(nlogn) │ O(nlogn) │ O(n) │ O │
│ 힙 │ O(nlogn) │ O(nlogn) │ O(nlogn) │ O(1) │ X │
│ 기수 │ O(d·n) │ O(d·n) │ O(d·n) │ O(n) │ O │
│ 계수 │ O(n+k) │ O(n+k) │ O(n+k) │ O(k) │ O │
└──────────┴──────────┴──────────┴──────────┴────────┴────────┘
★ 퀵정렬 최악: 이미 정렬된 데이터 + 첫/마지막 원소를 피봇으로
★ 안정 정렬: 동일 키의 상대적 순서 유지 (버블, 삽입, 병합, 기수, 계수)
6. 탐색과 그래프
6.1 이진 탐색
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# ★ 이진 탐색 — O(log n), 정렬된 배열 필수
def binary_search(arr, target):
left, right = 0, len(arr) - 1
while left <= right:
mid = (left + right) // 2
if arr[mid] == target:
return mid
elif arr[mid] < target:
left = mid + 1
else:
right = mid - 1
return -1
# 예) arr=[1,3,5,7,9,11], target=7
# left=0, right=5, mid=2 → arr[2]=5<7 → left=3
# left=3, right=5, mid=4 → arr[4]=9>7 → right=3
# left=3, right=3, mid=3 → arr[3]=7 → return 3
# ★ lower_bound: target 이상인 첫 위치
def lower_bound(arr, target):
left, right = 0, len(arr)
while left < right:
mid = (left + right) // 2
if arr[mid] < target:
left = mid + 1
else:
right = mid
return left
6.2 BFS / DFS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# ★ BFS — 큐 사용, 최단 경로
from collections import deque
def bfs(graph, start):
visited = set([start])
queue = deque([start])
result = []
while queue:
node = queue.popleft()
result.append(node)
for neighbor in sorted(graph[node]):
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
return result
# ★ DFS — 스택/재귀 사용
def dfs(graph, node, visited=None):
if visited is None:
visited = set()
visited.add(node)
result = [node]
for neighbor in sorted(graph[node]):
if neighbor not in visited:
result.extend(dfs(graph, neighbor, visited))
return result
# 예제 그래프
graph = {
1: [2, 3],
2: [1, 4, 5],
3: [1, 6],
4: [2],
5: [2, 6],
6: [3, 5]
}
print(bfs(graph, 1)) # [1, 2, 3, 4, 5, 6]
print(dfs(graph, 1)) # [1, 2, 4, 5, 6, 3]
6.3 다이나믹 프로그래밍 (DP)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# ★ DP 핵심 패턴 — 피보나치 (탑다운 vs 바텀업)
# 탑다운 (메모이제이션)
def fib_top_down(n, memo={}):
if n <= 1:
return n
if n not in memo:
memo[n] = fib_top_down(n-1) + fib_top_down(n-2)
return memo[n]
# 바텀업 (타뷸레이션)
def fib_bottom_up(n):
if n <= 1:
return n
dp = [0] * (n + 1)
dp[1] = 1
for i in range(2, n + 1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
# ★ 0-1 배낭 문제
def knapsack(W, weights, values, n):
dp = [[0] * (W + 1) for _ in range(n + 1)]
for i in range(1, n + 1):
for w in range(1, W + 1):
if weights[i-1] <= w:
dp[i][w] = max(
dp[i-1][w],
dp[i-1][w - weights[i-1]] + values[i-1]
)
else:
dp[i][w] = dp[i-1][w]
return dp[n][W]
# ★ 최장 공통 부분수열 (LCS)
def lcs(s1, s2):
m, n = len(s1), len(s2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(1, m + 1):
for j in range(1, n + 1):
if s1[i-1] == s2[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[m][n]
# 예) lcs("ABCBDAB", "BDCAB") = 4 ("BCAB")
6.4 그리디 알고리즘
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# ★ 활동 선택 문제 (Activity Selection)
def activity_selection(activities):
# activities = [(시작, 종료), ...]
activities.sort(key=lambda x: x[1]) # 종료 시간순 정렬
result = [activities[0]]
for i in range(1, len(activities)):
if activities[i][0] >= result[-1][1]:
result.append(activities[i])
return result
# ★ 거스름돈 문제
def coin_change_greedy(amount, coins=[500, 100, 50, 10]):
count = 0
for coin in coins:
count += amount // coin
amount %= coin
return count
# coin_change_greedy(1260) → 500×2 + 100×2 + 50×1 + 10×1 = 6개
Part 3. SQL 주관식 실전
7. SQL 기본 — SELECT 심화
7.1 GROUP BY와 HAVING
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
-- ★ 부서별 평균 급여가 300만원 이상인 부서
SELECT dept_id, AVG(salary) AS avg_sal, COUNT(*) AS cnt
FROM employees
GROUP BY dept_id
HAVING AVG(salary) >= 3000000
ORDER BY avg_sal DESC;
-- ★ WHERE vs HAVING
-- WHERE: 그룹화 전 필터 (개별 행)
-- HAVING: 그룹화 후 필터 (집계 결과)
-- 예) 입사년도 2020년 이후 직원 중, 부서별 인원이 3명 이상인 부서
SELECT dept_id, COUNT(*) AS cnt
FROM employees
WHERE hire_date >= '2020-01-01' -- 개별 행 필터
GROUP BY dept_id
HAVING COUNT(*) >= 3 -- 그룹 필터
ORDER BY cnt DESC;
7.2 JOIN 총정리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
-- ★ INNER JOIN: 양쪽 모두 매칭되는 행만
SELECT e.name, d.dept_name
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id;
-- ★ LEFT JOIN: 왼쪽 테이블 전체 + 오른쪽 매칭 (없으면 NULL)
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id;
-- ★ RIGHT JOIN: 오른쪽 테이블 전체 + 왼쪽 매칭
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- ★ FULL OUTER JOIN (MySQL은 UNION으로 구현)
SELECT e.name, d.dept_name
FROM employees e
LEFT JOIN departments d ON e.dept_id = d.dept_id
UNION
SELECT e.name, d.dept_name
FROM employees e
RIGHT JOIN departments d ON e.dept_id = d.dept_id;
-- ★ SELF JOIN: 같은 테이블끼리 조인
-- 예) 직원과 그 직원의 관리자 이름
SELECT e.name AS employee, m.name AS manager
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.emp_id;
-- ★ CROSS JOIN: 카티션 곱 (M × N 행)
SELECT e.name, d.dept_name
FROM employees e
CROSS JOIN departments d;
7.3 서브쿼리
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
-- ★ 스칼라 서브쿼리 (SELECT 절)
SELECT name, salary,
(SELECT AVG(salary) FROM employees) AS avg_all
FROM employees;
-- ★ 인라인 뷰 (FROM 절)
SELECT dept_id, avg_sal
FROM (
SELECT dept_id, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id
) AS dept_avg
WHERE avg_sal > 3000000;
-- ★ 상관 서브쿼리 (WHERE 절) — 행마다 실행
-- 자기 부서 평균보다 급여가 높은 직원
SELECT e.name, e.salary, e.dept_id
FROM employees e
WHERE e.salary > (
SELECT AVG(e2.salary)
FROM employees e2
WHERE e2.dept_id = e.dept_id
);
-- ★ EXISTS / NOT EXISTS
-- 주문 이력이 있는 고객만
SELECT c.name
FROM customers c
WHERE EXISTS (
SELECT 1 FROM orders o WHERE o.customer_id = c.customer_id
);
-- ★ IN / NOT IN
-- 서울 지점 직원들
SELECT name FROM employees
WHERE dept_id IN (
SELECT dept_id FROM departments WHERE location = '서울'
);
8. SQL 고급 — 윈도우 함수
8.1 순위 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
-- ★ ROW_NUMBER, RANK, DENSE_RANK 차이
SELECT name, salary,
ROW_NUMBER() OVER (ORDER BY salary DESC) AS row_num,
RANK() OVER (ORDER BY salary DESC) AS rnk,
DENSE_RANK() OVER (ORDER BY salary DESC) AS dense_rnk
FROM employees;
-- 결과 예시:
-- name salary row_num rnk dense_rnk
-- Kim 5000 1 1 1
-- Lee 5000 2 1 1 ← 동점
-- Park 4000 3 3 2 ← RANK는 3, DENSE_RANK는 2
-- Choi 3000 4 4 3
-- ★ 부서별 급여 1등
SELECT *
FROM (
SELECT name, dept_id, salary,
ROW_NUMBER() OVER (PARTITION BY dept_id ORDER BY salary DESC) AS rn
FROM employees
) ranked
WHERE rn = 1;
8.2 집계 윈도우 함수
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- ★ 누적합
SELECT name, salary,
SUM(salary) OVER (ORDER BY hire_date) AS cumulative_sum
FROM employees;
-- ★ 이동 평균 (최근 3명)
SELECT name, salary,
AVG(salary) OVER (
ORDER BY hire_date
ROWS BETWEEN 2 PRECEDING AND CURRENT ROW
) AS moving_avg
FROM employees;
-- ★ 부서별 합계 대비 비율
SELECT name, dept_id, salary,
salary * 100.0 / SUM(salary) OVER (PARTITION BY dept_id) AS pct
FROM employees;
8.3 LAG / LEAD
1
2
3
4
5
6
7
8
9
10
-- ★ 전월 대비 매출 증감
SELECT month, sales,
LAG(sales, 1) OVER (ORDER BY month) AS prev_sales,
sales - LAG(sales, 1) OVER (ORDER BY month) AS diff
FROM monthly_sales;
-- ★ 다음 행과 비교
SELECT name, salary,
LEAD(salary, 1) OVER (ORDER BY salary DESC) AS next_lower
FROM employees;
9. SQL 실전 — DML과 DDL
9.1 데이터 조작 (DML)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
-- ★ INSERT
INSERT INTO employees (name, dept_id, salary, hire_date)
VALUES ('Park', 10, 3500000, '2026-04-01');
-- 다중 행 삽입
INSERT INTO employees (name, dept_id, salary)
VALUES ('A', 10, 3000000),
('B', 20, 3500000),
('C', 10, 4000000);
-- ★ UPDATE (WHERE 필수!)
UPDATE employees
SET salary = salary * 1.1
WHERE dept_id = 10;
-- ★ DELETE (WHERE 필수!)
DELETE FROM employees
WHERE hire_date < '2020-01-01';
-- ★ MERGE (Oracle) / INSERT ... ON DUPLICATE KEY UPDATE (MySQL)
-- Oracle MERGE:
MERGE INTO target t
USING source s ON (t.id = s.id)
WHEN MATCHED THEN
UPDATE SET t.val = s.val
WHEN NOT MATCHED THEN
INSERT (id, val) VALUES (s.id, s.val);
-- MySQL:
INSERT INTO employees (emp_id, name, salary)
VALUES (1, 'Kim', 5000000)
ON DUPLICATE KEY UPDATE salary = 5000000;
9.2 테이블 정의 (DDL)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
-- ★ CREATE TABLE
CREATE TABLE employees (
emp_id INT PRIMARY KEY AUTO_INCREMENT, -- MySQL
-- emp_id INT PRIMARY KEY GENERATED ALWAYS AS IDENTITY, -- Oracle
name VARCHAR(50) NOT NULL,
dept_id INT,
salary DECIMAL(12,2) DEFAULT 0,
hire_date DATE DEFAULT CURRENT_DATE,
FOREIGN KEY (dept_id) REFERENCES departments(dept_id)
ON DELETE SET NULL
ON UPDATE CASCADE
);
-- ★ ALTER TABLE
ALTER TABLE employees ADD COLUMN email VARCHAR(100);
ALTER TABLE employees MODIFY COLUMN name VARCHAR(100) NOT NULL;
ALTER TABLE employees DROP COLUMN email;
ALTER TABLE employees ADD CONSTRAINT uk_name UNIQUE (name);
-- ★ 인덱스
CREATE INDEX idx_dept ON employees(dept_id);
CREATE UNIQUE INDEX idx_email ON employees(email);
DROP INDEX idx_dept ON employees;
-- ★ 뷰
CREATE VIEW emp_summary AS
SELECT dept_id, COUNT(*) AS cnt, AVG(salary) AS avg_sal
FROM employees
GROUP BY dept_id;
SELECT * FROM emp_summary WHERE avg_sal > 3000000;
9.3 트랜잭션과 권한
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- ★ 트랜잭션 제어 (TCL)
START TRANSACTION; -- BEGIN (Oracle)
UPDATE accounts SET balance = balance - 10000 WHERE id = 1;
UPDATE accounts SET balance = balance + 10000 WHERE id = 2;
COMMIT;
-- ROLLBACK; -- 오류 시 되돌리기
-- ★ SAVEPOINT
START TRANSACTION;
INSERT INTO orders VALUES (1, 'A', 1000);
SAVEPOINT sp1;
INSERT INTO orders VALUES (2, 'B', 2000);
ROLLBACK TO sp1; -- 두 번째 INSERT만 취소
COMMIT; -- 첫 번째 INSERT만 반영
-- ★ 권한 (DCL)
GRANT SELECT, INSERT ON employees TO user1;
GRANT ALL PRIVILEGES ON *.* TO admin WITH GRANT OPTION;
REVOKE INSERT ON employees FROM user1;
10. 실전 모의 문제
문제 1: C 코드 출력 (포인터 + 반복)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
int main() {
int arr[] = {2, 4, 6, 8, 10};
int *p = arr + 1;
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += *(p + i);
}
printf("%d\n", sum);
p = arr;
while (*p < 8) {
*p += 1;
p++;
}
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
풀이:
1) p = arr+1 → p는 arr[1]=4를 가리킴
*(p+0)=4, *(p+1)=6, *(p+2)=8 → sum=18
출력: 18
2) p = arr, *p=2<8 → *p=3, p++
*p=4<8 → *p=5, p++
*p=6<8 → *p=7, p++
*p=8, 8<8 거짓 → 종료
arr = {3, 5, 7, 8, 10}
출력: 3 5 7 8 10
문제 2: Java 코드 출력 (상속 + 생성자)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Parent {
int x = 10;
Parent() { System.out.println("Parent: " + x); }
Parent(int x) { this.x = x; System.out.println("Parent(" + x + ")"); }
}
class Child extends Parent {
int y = 20;
Child() {
super(30);
System.out.println("Child: " + x + ", " + y);
}
}
public class Main {
public static void main(String[] args) {
Child c = new Child();
System.out.println(c.x + ", " + c.y);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
풀이:
1) new Child() → Child 생성자 호출
2) super(30) → Parent(int x) 호출 → this.x = 30
출력: Parent(30)
3) Child 생성자 본문 실행, y=20
출력: Child: 30, 20
4) main에서 출력
출력: 30, 20
전체 출력:
Parent(30)
Child: 30, 20
30, 20
문제 3: SQL 작성 문제
1
2
3
4
5
6
테이블 구조:
employees(emp_id, name, dept_id, salary, hire_date)
departments(dept_id, dept_name, location)
[문제] 각 부서에서 급여가 가장 높은 직원의 이름, 부서명, 급여를
급여 내림차순으로 조회하시오. 단, 직원이 없는 부서는 제외.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
-- 풀이 1: 서브쿼리 + JOIN
SELECT e.name, d.dept_name, e.salary
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id
WHERE e.salary = (
SELECT MAX(e2.salary)
FROM employees e2
WHERE e2.dept_id = e.dept_id
)
ORDER BY e.salary DESC;
-- 풀이 2: 윈도우 함수 (더 간결)
SELECT name, dept_name, salary
FROM (
SELECT e.name, d.dept_name, e.salary,
ROW_NUMBER() OVER (PARTITION BY e.dept_id ORDER BY e.salary DESC) AS rn
FROM employees e
INNER JOIN departments d ON e.dept_id = d.dept_id
) ranked
WHERE rn = 1
ORDER BY salary DESC;
문제 4: Python 코드 출력
1
2
3
4
5
6
7
8
9
def mystery(n):
if n <= 1:
return str(n)
if n % 2 == 0:
return mystery(n // 2) + "0"
else:
return mystery(n // 2) + "1"
print(mystery(13))
1
2
3
4
5
6
7
8
9
10
풀이: 이 함수는 10진수를 2진수로 변환한다.
mystery(13): 13%2==1 → mystery(6) + "1"
mystery(6): 6%2==0 → mystery(3) + "0"
mystery(3): 3%2==1 → mystery(1) + "1"
mystery(1): return "1"
→ "1" + "1" = "11"
→ "11" + "0" = "110"
→ "110" + "1" = "1101"
출력: 1101 (13의 이진수)
문제 5: SQL 실전 — 복합 쿼리
1
2
3
4
5
6
[문제] 2025년 주문 금액 상위 3개 고객의 이름, 총주문금액, 주문건수를 조회하시오.
동점 시 모두 포함하시오.
테이블:
customers(customer_id, name, email)
orders(order_id, customer_id, amount, order_date)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- RANK 사용 (동점 포함)
SELECT name, total_amount, order_count
FROM (
SELECT c.name,
SUM(o.amount) AS total_amount,
COUNT(*) AS order_count,
RANK() OVER (ORDER BY SUM(o.amount) DESC) AS rnk
FROM customers c
INNER JOIN orders o ON c.customer_id = o.customer_id
WHERE o.order_date >= '2025-01-01'
AND o.order_date < '2026-01-01'
GROUP BY c.customer_id, c.name
) ranked
WHERE rnk <= 3
ORDER BY total_amount DESC;
-- ★ 주의: ROW_NUMBER를 쓰면 동점이 잘리므로 RANK 사용!
최종 체크리스트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
┌──────────────────────────────────────────────────────────────────┐
│ ★★★ 코딩 주관식 핵심 체크리스트 ★★★ │
│ │
│ [코드 출력 문제] │
│ □ 포인터 역참조 (*p, **pp, *(p+i)) 정확히 추적 │
│ □ 재귀 호출 스택 그려가며 출력 순서 확인 │
│ □ Java 상속: 필드=참조타입, 메서드=실제객체 │
│ □ JavaScript var vs let 스코프 차이 │
│ □ 비트 연산 결과 2진수로 계산 │
│ □ 생성자 호출 순서: 부모 → 자식 │
│ │
│ [코드 작성 문제] │
│ □ 정렬: 버블/선택/삽입/퀵 직접 구현 가능 │
│ □ 탐색: 이진탐색 경계 조건 (left<=right vs left<right) │
│ □ BFS/DFS: 인접리스트/행렬 모두 구현 가능 │
│ □ DP: 점화식 세우기 → 탑다운/바텀업 선택 │
│ □ 그리디: 정렬 기준 결정 → 최적 선택 반복 │
│ │
│ [SQL 작성 문제] │
│ □ JOIN 유형 정확히 선택 (INNER/LEFT/RIGHT) │
│ □ GROUP BY + HAVING 조합 │
│ □ 서브쿼리: 상관/비상관 구분 │
│ □ 윈도우 함수: ROW_NUMBER vs RANK vs DENSE_RANK │
│ □ NULL 처리: IS NULL, COALESCE, NVL │
│ □ 날짜 범위: >= AND < (BETWEEN 주의) │
│ │
│ [MySQL vs Oracle 차이] │
│ ┌──────────────────┬──────────────────┬────────────────────────┐ │
│ │ 기능 │ MySQL │ Oracle │ │
│ ├──────────────────┼──────────────────┼────────────────────────┤ │
│ │ 자동증가 │ AUTO_INCREMENT │ SEQUENCE / IDENTITY │ │
│ │ 문자열 연결 │ CONCAT() │ || 연산자 │ │
│ │ NULL 대체 │ IFNULL() │ NVL() │ │
│ │ 현재 날짜 │ NOW() / CURDATE()│ SYSDATE │ │
│ │ 행 제한 │ LIMIT n │ ROWNUM / FETCH FIRST │ │
│ │ 조건문 │ IF() / CASE │ DECODE() / CASE │ │
│ │ 문자열 자르기 │ SUBSTRING() │ SUBSTR() │ │
│ │ FULL OUTER JOIN │ UNION으로 구현 │ FULL OUTER JOIN 지원 │ │
│ └──────────────────┴──────────────────┴────────────────────────┘ │
│ │
│ ★ 시험장에서 레퍼런스 활용 가능하므로 문법 외우기보다 │
│ "어떤 함수가 있는지"와 "접근법"을 아는 것이 핵심! │
└──────────────────────────────────────────────────────────────────┘
이전 글: 컴퓨터공학 전공 핵심정리 상편: 운영체제, 자료구조, 데이터베이스과 중편: 소프트웨어공학, 데이터통신, 네트워크에서 객관식 35문항을 대비한다.