본문 바로가기

Algorithm with Python

[동빈나] 이코테 - 1. 코딩 테스트 출제 경향 분석 및 파이썬 문법 부수기

IT 교육 유튜브 채널 [동빈나](www.youtube.com/c/dongbinna/playlists)의

"이것이 취업을 위한 코딩 테스트다" 강의를 듣고 학습한 내용을 정리하는 글입니다.
중요한 내용 혹은 몰랐던 내용 위주로만 정리한 것이며,
코드 블럭 내 (>>>)는 실행결과를 표현하기 위한 것이니 이 점 참고해주시면 되겠습니다.


복잡도(Complexity)

  • 복잡도는 알고리즘의 성능을 나타내는 척도

    • 시간 복잡도: 특정한 크기의 입력에 대하여 알고리즘의 수행 시간 분석
    • 공간 복잡도: 특정한 크기의 입력에 대하여 알고리즘의 메모리 사용량 분석
  • 동일한 기능을 수행하는 알고리즘이 있다면, 일반적으로 복잡도가 낮을수록 좋은 알고리즘.

빅오 표기법(Big-O Notation)

  • 가장 빠르게 증가하는 항만을 고려하는 표기법
  • 예) 연산 횟수가 3N^3 + 5N^2 + 1,000,000인 알고리즘
    • 빅오 표기법에서는 차수가 가장 큰 항만 남기므로 O(N^3)으로 표현됨 (계수 무시)

알고리즘 설계 Tip

  • 일반적으로 CPU 기반의 개인 컴퓨터나 채점용 컴퓨터에서 연산 횟수가 5억을 넘어가는 경우
    • Python을 기준으로 5 ~ 15초 가량의 시간이 소요됨
  • 코딩 테스트 문제에서 시간 제한은 통상 1 ~ 5초가량. 문제에 명시되어 있지 않은 경우 대략 5초 정도.

요구사항에 따라 적절한 알고리즘 설계하기

  • 문제에서 시간제한(수행시간 요구사항) 가장 먼저 확인
  • 시간제한 1초인 문제의 경우, 일반적 기준
    • N의 범위 500인 경우: O(N^3)
    • N의 범위 2,000인 경우: O(N^2)
    • N의 범위 100,000인 경우: O(NlogN)
    • N의 범위 10,000,000인 경우: O(N) 

import time
start_time = time.time() # 측정 시작

# 프로그램 소스코드
end_time = time.time() # 측정 종료
print("time:", end_time - start_time) # 수행 시간 출력

실수형

# 소수부가 0일 때 0을 생략
a = 5.
print(a)
>>> 5.0
# 정수부가 0일 때 0을 생략
b = -.7
print(b)
>>> -0.7

지수 표현 방식

  • e나 E 다음에 오는 수는 10의 지수부를 의미
  • Ex) 1e9 = 10의 9제곱(1,000,000,000)
  • 유효숫자e^지수 = 유효숫자x10^지수
  • 임의의 큰 수 표현을 위해 자주 사용
  • 최단 경로 알고리즘에서는 도달할 수 없는 노드에 대하여 최단 거리를 무한(INF)로 설정하곤 함.
  • 이때 가능한 최댓값이 10억 미만이라면 무한(INF)의 값으로 1e9 이용할 수 있음
a = 1e9
print(a)
>>> 1,000,000,000.0

b = 75.25e1
print(b)
>>> 752.5

c = 3954e-3
print(c)
>>> 3.954

실수형

  • IEEE754 표준에서는 실수형을 저장하기 위해 4바이트 혹은 8바이트의 고정된 크기의 메모리를 할당하므로, 컴퓨터 시스템은 실수 정보를 표현하는 정확도에 한계를 가짐
  • 10진수 체계에서는 0.3 + 0.6 = 0.9로 정확히 떨어지지만, 2진수에서는 0.9를 정확히 표현할 수 없음.
  • 컴퓨터는 최대한 0.9와 가깝게 표현하지만 미세한 오차가 발생
a = 0.3 + 0.6
print(a)
>>> 0.899999...
  • 개발 과정에서 실수 값을 제대로 비교하지 못해서 원하는 결과를 얻지 못할 수 있음
  • 이럴 때는 round() 함수 사용이 권장
  • 123.456을 소수 셋째 자리에서 반올림하려면 round(123.456, 2)라고 작성 -> 결과는 123.46
a = 0.3 + 0.6
print(round(a, 4))

if round(a, 4) == 0.9:
    print(True)
else:
    print(False)

>>> 0.9
>>> True

리스트 컴프리헨션

  • 리스트 초기화하는 방법 중 하나
    • 대괄호 안에 조건문, 반복문을 적용하여 리스트 초기화
# 0부터 9까지 수를 포함하는 리스트
array = [i for i in range(10)]

print(array)
>>> [0,1,2,3,4,5,6,7,8,9]

# 0부터 9까지 수 중에서 홀수만 포함하는 리스트
array = [i for i in range(10) if i % 2 == 1]

# 1부터 9까지의 수들의 제곱 값을 포함하는 리스트
array = [i*i for i in range(1, 10)]
  • 2차원 리스트 초기화할 때 효과적
  • N X M 크기 2차원 리스트 한번에 초기화 해야 할 때
    • 좋은 예시: array = [[0] * m for _ in range(n)]
    • 잘못된 예시: array = [[0] * m ] * n
    • 위 코드는 전체 리스트 안에 포함된 각 리스트가 모든 같은 객체로 인식됨.
# N X M 크기의 2차원 리스트 초기화(잘못된 방법)
n = 4
m = 3
array = [[0] * m ] * n
print(array)
>>> [[0,0,0],[0,0,0],[0,0,0],[0,0,0]]

array[1][1] = 5
print(array)
>>> [[0,5,0],[0,5,0],[0,5,0],[0,5,0]]

언더바

  • 파이썬에서는 반복을 수행하되 반복을 위한 변수의 값을 무시하고자 할 때 언더바(_) 자주 사용
for _ in range(5):
  print('Hello World')

리스트 관련 기타 메서드

a = [1, 4, 3]

# 리스트에 원소 삽입
a.append(2)
print(a)
>>> [1, 4, 3, 2]

# 오름차순 정렬
a.sort()
print(a)
>>> [1, 2, 3, 4]

# 내림차순 정렬
a.sort(reverse = True)
print(a)
>>> [4, 3, 2, 1]

# 리스트 원소 뒤집기
a.reverse()
print(a)
>>> [1, 2, 3, 4]

# 특정 인덱스에 데이터 추가
a.insert(2, 3)
print(a)
>>> [1, 2, 3, 3, 4]

# 특정 값인 데이터 개수 세기
print(a.count(3))
>>> 2

# 특정 값 데이터 삭제
a.remove(1)
print(a)
>>> [2, 3, 3, 4]

리스트에서 특정 값을 가지는 원소를 모두 제거하기

a = [1, 2, 3, 4, 5, 5, 5]
remove_set = {3, 5}

# remove_set에 포함되지 않은 값만을 저장
result = [i for i in a if i not in remove_set]
print(result)

튜플 자료형(tuple)

  • 튜플은 한 번 선언된 값을 변경할 수 없음
  • 리스트는 []를 이용, 튜플은 ()을 이용
  • 튜플은 리스트에 비해 상대적으로 공간 효율적(더 적은 양의 메모리 사용)
  • 튜플을 사용하면 좋은 경우
    • 서로 다른 성질의 데이터를 묶어서 관리해야 할 때
      • 최단 경로 알고리즘에서는 (비용, 노드 번호)의 형태로 튜플 자료형 자주 사용
    • 데이터의 나열을 해싱(Hashing)의 키 값으로 사용해야 할 때
      • 튜플은 변경이 불가능하므로 리스트와 다르게 키 값으로 사용가능
    • 리스트보다 메모리 효율적으로 사용해야 할 때

사전 자료형(dictionary)

  • 키(Key)와 값(Value)의 쌍을 데이터로 가짐
  • 원하는 '변경 불가능한(Immutable) 자료형'을 키로 사용가능
  • 파이썬의 사전 자료형은 해시 테이블(Hash Table)을 이용하므로 데이터의 조회 및 수정에 있어서 O(1)의 시간에 처리 가능
data = dict()
data['사과'] = 'Apple'
data['바나나'] = 'Banana'
data['코코넛'] = 'Coconut'

print(data)

if '사과' in data:
  print("'사과'를 키로 가지는 데이터가 존재")

>>> {'사과': 'Apple', '바나나':'Banana', '코코넛':'Coconut'}
'사과'를 키로 가지는 데이터가 존재
  • 키 데이터만 뽑아서 리스트로 이용할 때 keys() 함수
  • 값 데이터만 뽑아서 리스트로 이용할 때 values() 함수
# 키 데이터만 담은 리스트
key_list = data.keys()
# 값 데이터만 담은 리스트
value_list = data.values()
print(key_list)
print(value_list)

# 각 키에 따른 값을 하나씩 출력
for key in key_list:
  print(data[key])

집합 자료형(set)

  • 중복 허용 하지 않음
  • 순서 없음
  • 집합은 리스트 혹은 문자열을 이용해서 초기화할 수 있음
    • 이때 set() 함수 이용
  • 혹은 중괄호 ({}) 안에 각 원소를 콤마(,)를 기준으로 구분하여 삽입함으로써 초기화할 수 있음.
  • 데이터의 조회 및 수정에 있어서 O(1)의 시간에 처리 가능
# 집합 자료형 초기화 방법 1
data = set([1, 1, 2, 3, 4, 4, 5])
print(data)
>>> {1, 2, 3, 4, 5}

# 집합 자료형 초기화 방법 2
data = {1, 1, 2, 3, 4, 4, 5}
print(data)
>>> {1, 2, 3, 4, 5}

집합 자료형의 연산

  • 합집합, 교집합, 차집합
a = set([1, 2, 3, 4, 5])
b = set([3, 4, 5, 6, 7])

print(a | b) # 합집합
print(a & b) # 교집합
print(a - b) # 차집합

>>> {1, 2, 3, 4, 5, 6, 7}
{3, 4, 5}
{1, 2}

집합 자료형 관련 함수

data = set([1, 2, 3])

# 새로운 원소 추가
data.add(4)
print(data)
>>> {1, 2, 3, 4}

# 새로운 원소 여러 개 추가
data.update([5, 6])
print(data)
>>> {1, 2, 3, 4, 5, 6}

# 특정한 값을 갖는 원소 삭제
data.remove(3)
print(data)
>>> {1, 2, 4, 5, 6}

사전 자료형과 집합 자료형의 특징

  • 리스트 / 튜플은 순서가 있기 때문에 인덱싱을 통해 자료형의 값을 얻을 수 있음
  • 사전 자료형과 집합 자료형은 순서가 없기 때문에 인덱싱으로 값을 얻을 수 없음
    • 사전의 키(key) 혹은 집합의 원소(element)를 이용해 O(1)의 시간 복잡도로 조회

기본 입출력

자주 사용되는 표준 입력 방법

  • map() 함수는 리스트의 모든 원소에 각각 특정한 함수를 적용할 때 사용
    • 예시) 공백을 기준으로 구분된 데이터 입력 받을 때
      • list(map(int, input().split()))

빠르게 입력 받기

  • 사용자로부터 입력을 최대한 빠르게 받아야 하는 경우
  • 파이썬의 경우 sys 라이브러리에 정의되어 있는 sys.stdin.readline() 메서드를 이용한다.
    • 단, 입력 후 엔터(Enter)가 줄 바꿈 기호로 입력되므로 rstrip() 메서드를 함께 사용한다.
import sys

# 문자열 입력 받기
data = sys.stdin.readline().rstrip()
print(data)

자주 사용되는 표준 출력 방법

  • print() 줄 바꿈을 원치 않는 경우 'end' 속성 이용할 수 있음.

f-string 예제

  • 파이썬 3.6부터 사용 가능, 문자열 앞에 접두사 'f'를 붙여 사용
  • 중괄호 안에 변수명을 기입하여 간단히 문자열과 정수를 함께 넣을 수 있음
answer = 7

print(f"정답은 {answer}입니다.")
>>> 정답은 7입니다.

조건문 간소화

  • 조건부 표현식(Conditional Expression)은 if ~ else문을 한 줄에 작성할 수 있도록 해줌
score = 85
result = "Success" if score >= 80 else "Fail"

print(result)
>>> Success

함수

  • 내장 함수 : 파이썬이 기본적으로 제공하는 함수
  • 사용자 정의 함수 : 개발자가 직접 정의하여 사용할 수 있는 함수

파라미터 지정하기

  • 파라미터의 변수를 직접 지정할 수 있음
    • 이 경우 매개변수의 순서가 달라도 상관없음
def add(a, b):
  print('함수의 결과:', a + b)

add(b = 7, a = 3)
>>> 함수의 결과: 10

global 키워드

  • global 키워드로 변수를 지정하면 해당 함수에서는 지역 변수를 만들지 않고, 함수 바깥에 선언된 변수를 바로 참조하게 됨.
a = 0

def func():
  global a
  a += 1

for i in range(10):
  func()

print(a)
array = [1, 2, 3, 4, 5]

def func():
  array = [3, 4, 5]
  array.append(6)
  print(array) # 함수 내의 지역변수를 참조

func()
print(array) # 전역변수를 참조

>>> [3, 4, 5, 6]
[1, 2, 3, 4, 5]

람다 표현식

  • 특정한 기능을 수행하는 함수를 한 줄에 작성할 수 있음
print((lambda a, b: a + b)(3, 7))

>>> 10
array = [('홍길동', 50), ('이순신', 32), ('아무개', 74)]

def my_key(x):
  return x[1]

print(sorted(array, key = my_key))
print(sorted(array, key = lambda x: x[1]))

>>> [('이순신', 32), ('홍길동', 50), ('아무개', 74)]
[('이순신', 32), ('홍길동', 50), ('아무개', 74)]
list1 = [1, 2, 3, 4, 5]
list2 = [6, 7, 8, 9, 10]

result = map(lambda a, b: a + b, list1, list2)

print(list(result))

실전에서 유용한 표준 라이브러리

  • 내장 함수: 기본 입출력 함수부터 정렬 함수까지 기본적인 함수들을 제공
  • itertools: 파이썬에서 반복되는 형태의 데이터를 처리하기 위한 유용한 기능들을 제공
    • 특히 순열과 조합 라이브러리는 코딩 테스트에서 자주 사용
  • heaps: 힙(Heap) 자료구조 제공
    • 일반적으로 우선순위 큐 기능을 구현하기 위해 사용
  • bisect: 이진 탐색(Binary Search) 기능을 제공
  • collections: 덱(deque), 카운터(Counter) 등의 유용한 자료구조 포함
  • math: 필수적인 수학적 기능 제공
    • 팩토리얼, 제곱근, 최대공약수(GCD), 삼각함수 관련 함수부터 파이(pi)와 같은 상수 포함

자주 사용되는 내장 함수

# eval()
result = eval("(3+5)*7")
print(result)
>>> 56

# sorted() with key
array = [('홍길동', 35), ('이순신', 75), ('아무개', 50)]
result = sorted(array, key = lambda x: x[1], reverse = True)
print(result)
>>> [('이순신', 75), ('아무개', 50), ('홍길동', 35)]

순열과 조합

  • 모든 경우의 수를 고려해야 할 때 어떤 라이브러리를 효과적으로 사용할 수 있을까?

  • 순열: 서로 다른 n개에서 서로 다른 r개를 선택하여 일렬로 나열

    • {'A', 'B', 'C'}에서 세 개를 선택하여 나열하는 경우: 'ABC', 'ACB', 'BAC', 'BCA', 'CAB', 'CBA'
  • 조합: 서로 다른 n개에서 순서에 상관없이 서로 다른 r개를 선택

    • {'A', 'B', 'C'}에서 순서를 고려하지 않고 두 개를 뽑는 경우: 'AB', 'AC', 'BC'
  • 순열의 수: nPr = n * (n-1) * ... * (n-r+1)

  • 조합의 수: nCr = n * (n-1) * ... * (n-r+1) / r!

# 순열
from itertools import permutations

data = ['A', 'B', 'C'] # 데이터 준비

result = list(permutations(data, 3)) # 모든 순열 구하기
print(result)

>>> [('A','B','C'), ('A','C','B'), ... ]


# 조합
from itertools import combinations

data = ['A', 'B', 'C']

result = list(combinations(data, 2)) # 2개를 뽑는 모든 조합 구하기
print(result)

>>> [('A','B'), ('A','C'), ('B','C')]


# 중복 순열
from itertools import product

data = ['A', 'B', 'C'] # 데이터 준비

result = list(product(data, repeat = 2)) # 2개를 뽑는 모든 순열 구하기 (중복 허용)
print(result)


# 중복 조합
from itertools import combinations_with_replacement

data = ['A', 'B', 'C'] # 데이터 준비

result = list(combinations_with_replacement(data, 2)) # 2개를 뽑는 모든 조합 구하기 (중복 허용)
print(result)

Counter

  • 파이썬 collections 라이브러리의 Counter는 등장 횟수를 세는 기능을 제공
  • 리스트와 같은 반복 가능한(iterable) 객체가 주어졌을 때 내부의 원소가 몇 번씩 등장했는지를 알려줌.
from collections import Counter

counter = Counter(['red', 'blue', 'red', 'green', 'blue', 'blue'])

print(counter['blue']) # 'blue'가 등장한 횟수 출력
print(counter['green']) # 'green'이 등장한 횟수 출력
print(dict(counter)) # 사전 자료형으로 변환

>>> 3
1
{'red': 2, 'blue': 3, 'green': 1}

최대 공약수와 최소 공배수

  • math 라이브러리의 gcd() 함수를 이용
import math

# 최소 공배수(LCM)를 구하는 함수
def lcm(a, b):
  return a * b // math.gcd(a, b)

a = 21
b = 14

print(math.gcd(12, 14)) # 최대 공약수(GCD) 계산
print(lcd(12, 14)) # 최소 공배수(LCM) 계산

>>> 7
42

'Algorithm with Python' 카테고리의 다른 글

[동빈나] 이코테 - 2. 구현  (0) 2021.02.05
[동빈나] 이코테 - 2. 그리디  (0) 2021.02.05