자꾸만 망각하게 되는 코딩테스트 유형들. 자료구조 기준으로 요약을 정리하고, 활용 사례 및 예제를 정리해 보자. 코딩테스트 하루 전에 숙지해야할 내용들을 정리해 본다.
Key-Value 쌍을 이용한 빠른 탐색
해시를 사용하는 대표적인 자료구조는 Dictionary 이다. 키-값 (Key-Value)를 사용하여 자료를 저장하는 해시(Hash) 중에서 Python에서는 Ditionary를 편하게 다루기 위해서 `Counter`, `defaultdict` 등의 라이브러리를 제공한다.
다음은 주어진 `단어배열` (Array of Words)에서 각 단어의 수를 세는 예제이다.
Counter()를 이용하면 아래와 같이 단순히 `Counter(array): --> dict`로 그 수를 세어 저장할 수 있다.
from collections import Counter
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
a = Counter(input_val)
print(a)
# Counter({'Code': 1, 'Hello': 2, 'Python': 2, 'Test': 1, 'World': 1, 'with': 1})
Counter() 는 Dictionary로써 뿐만 아니라 산술연산을 지원한다. 따라서 동일한 Key에 대한 산술이 가능하므로, 아래와 같이 Key값의 빈도수에 대한 차이를 `a-b`만으로 구할 수 있다.
from collections import Counter
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
a, b = Counter(input_val), Counter(input_val[:4])
print(a-b)
# Counter({'Python': 1, 'Code': 1, 'Test': 1, 'with': 1})
경우에 따라서는 `Counter()`가 아니락 직접 dictionary를 다루어야 할 때가 있다. 이 경우, dictionary를 생성하여 value를 계산하는데, 기본적으로 dictionary는 type이 지정되지 않기 때문에 `키-값 셋` (Key-Value 셋)의 초기화가 되어 있지 않다. 위처럼 단어수를 세는 예제의 경우 아래처럼 단순히 덧셈(Add)를 하여 처리 할 수 있다.
from collections import defaultdict
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
d = defaultdict(int)
for w in input_val:
d[w] = d[w]+1
print(d)
defaultdict를 사용하지 않고 Key값에 대한 초기화 없이 `dictionary[key]`를 사용하면 `Key Error`를 발생하게 될것이다. 이러한 경우 값이 없는 경우 초기화 해주는 코드를 추가해 주어야 하는데, defaultdict는 이 과정을 내부처리하도록 dictionary를 Class화 해둔 것이다. 편의성을 높이고, 에러발생율을 줄일 수 있다.
다음은 초기화 없이 dictionary에 수치 연산을 사용할 경우이다.
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
d = dict()
for w in input_val:
d[w] = d[w]+1
print(d)
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
<ipython-input-6-3c2a402366b9> in <module>()
2 d = dict()
3 for w in input_val:
----> 4 d[w] = d[w]+1
5 print(d)
KeyError: 'Hello'
만약 collection library들을 사용할 수 없고, 순수 Python만을 사용해야 한다면 다음과 같이 초기화 처리를 해 주어야 한다.
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
d = dict()
for w in input_val:
d[w] = d[w]+1 if w in d.keys() else 1
print(d)
# {'Hello': 2, 'World': 1, 'Python': 2, 'Code': 1, 'Test': 1, 'with': 1}
딕셔너리(Dictionary)는 Key들의 순서가 없기 때문에 Index로 접근할 수 없다. Key로만 접근 할 수 있기 때문에, dict.keys()를 사용해서 전체 Key값을 List화 할 수 있다. 그러나, 어느 문서에서는 `dict.keys()`를 사용하지 말고, [w for w in d] 형태를 사용할것을 권고하기도 한다. dict.keys() 대신 dict변수이름 자체를 사용하면 key값을 반환한다.
그러나 위 예제에서 `w in d.keys()`는 list로 변환한다. 이 이야기는 해시의 장점인 Key-Search를 사용하지 않고 Full Search를 사용했다는 이야기가 된다. 즉 복잡도 O(n)이 추가 되는 과정이므로 위 전체 연산은 O(n^2)이 되어 버린다. 해시를 사용한 의미가 없다. 따라서, 위와 같은 예제는 해시의 키 값을 바로 사용하도록 아래와 같이 수정되어야 한다.
# Hash의 Key 초기화 : w in d.keys() ==> List로 변경되기 때문에 Hash Key Search를 사용하지 못하는 예
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
d = dict()
for w in input_val:
d[w] = d[w]+1 if w in d else 1
print(d)
# {'Hello': 2, 'World': 1, 'Python': 2, 'Code': 1, 'Test': 1, 'with': 1}
line 5 : `d[w] = d[w]+1 if w in d else 1` 으로 Hash Key 사용. 전체 Full Search 보다는 복잡도 감소
해시 키를 검사하는 `w in d` 와 유사하게, dictionary의 함수 get()을 활용할 수도 있다.
# Hash의 Key 초기화 : dict.get() 사용
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
d = dict()
for w in input_val:
d[w] = d.get(w,0)+1
print(d)
# {'Hello': 2, 'World': 1, 'Python': 2, 'Code': 1, 'Test': 1, 'with': 1}
dict.get(key, default) : dictionary에 key값을 반환한다. key값이 없으면 default를 반환한다.
각 단어별로 위치의 Index들을 저장하는 Dictionary를 반환하라. : dictionary ==> {word : [ index 1st , index 2th, ... ] } ex) {"Hello": [0, 3] , "Python" : [2, 7] , ... }
from collections import defaultdict
input_val = ["Hello", "World", "Python", "Hello", "Code", "Test", "with", "Python"]
d = defaultdict(list)
for i,w in enumerate(input_val):
d[w].append(i)
print( d )
# {'Hello': [0, 3], 'World': [1], 'Python': [2, 7], 'Code': [4],
# 'Test': [5], 'with': [6]})
스택 (Push/Pop), LIFO/FIFO, 큐 (Queue)
DeQueue
from collections import deque
dq=deque() # 덱 생성
dq.append() # 덱의 가장 오른쪽에 원소 삽입
dq.popleft() # 가장 왼쪽 원소 반환
dq.appendleft() # 덱의 가장 왼쪽에 원소 삽입
dp.pop() # 가장 오른쪽 원소 반환
dp.clear() # 모든 원소 제거
dp.copy() # 덱 복사
dp.count(x) #x와 같은 원소의 개수를 계산
프로그래머스 스택/큐 - 기능개발 문제 풀이
def solution(progresses, speeds):
answer = []
dq, s = [x for x in progresses], speeds
while( len(dq)>0 ):
r = 100-dq[0]
div, remainder = r//s[0], r%s[0]
nTimes = div if remainder==0 else div+1
# update deploy of all functions
dq = [x + nTimes*s[ti] for ti, x in enumerate(dq) ]
# check if finish of each dev. and save the result into stack
stack = []
for ti,x in enumerate(dq):
if x >= 100:
stack.append(ti)
else:
break
c = len(stack)
answer.append( c )
dq, s = dq[c:], s[c:]
return answer
Heap은 이진트리 구조로써, 최소값 최대값을 쉽게 구하기 위하여 특정 조건이 적용된 트리이다.
Heap 을 이용한 우선순위 큐
힙(heap)은 최댓값 및 최솟값을 찾아내는 연산을 빠르게 하기 위해 고안된 완전이진트리(complete binary tree)를 기본으로 한 자료구조(tree-based structure)로서 다음과 같은 힙 속성(property)을 만족한다.
import heapq as hq
h=[]
hq.heappush(h, 30)
hq.heappush(h, 10)
hq.heappush(h, 20)
hq.heappush(h, 15)
print(h) # [10, 15, 20, 30]
print(hq.heappop(h)) # 10
print(h) # [15, 30, 20]
h2 = [10, 1,2,3,9,5]
hq.heapify(h2)
print(h2) # [1, 3, 2, 10, 9, 5]
print(h2[0]) # 1
Min Heap
import heapq as hq
h = [4,1,2,3,9,5]
hq.heapify(h)
print(h)
min_heap = [hq.heappop(h) for _ in range(len(h))]
print(min_heap)
Max Heap
# Max Heap --> 음수화
h2 = [4,1,2,3,9,5]
h2 = [ -1*x for x in h2]
hq.heapify(h2)
max_heap = [-1*hq.heappop(h2) for _ in range(len(h2))]
print(max_heap)
Max Heap : tuple을 사용하는 경우
import heapq as hq
heap_items = [1,3,5,7,9]
h=[]
for x in heap_items:
hq.heappush(h, (-x,x))
print(h) # [(-9, 9), (-7, 7), (-3, 3), (-1, 1), (-5, 5)]
print([ x[1] for x in h]) # [9, 7, 3, 1, 5]
Heap을 사용한 예제 : 프로그래머스 "더 맵게"
import heapq
def solution(scoville, K):
answer = 0
heapq.heapify(scoville)
for i in range(1000000000):
if len(scoville)<=1: return -1
heapq.heappush(scoville, heapq.heappop(scoville) + heapq.heappop(scoville)*2)
answer+=1
if scoville[0]>=K :
return answer
return -1
여러가지 기준으로 정렬, 문제를 풀어보자
a = [1, 5, 2, 6, 3, 7, 4]
d = sorted(a)
print(d) # [1, 2, 3, 4, 5, 6, 7]
a = [1, 5, 2, 6, 3, 7, 4]
d = sorted(a, reverse=True)
print(d) # [7, 6, 5, 4, 3, 2, 1]
내림차순 정렬을 할 때, 오름차순으로 정렬 후 전체 순서를 반대로 뒤집어서 처리할 수도 있다. `d = sorted(a)[::-1]`
a = [1, 5, 2, 6, 3, 7, 4]
b = [10, 2, 5, 7, 3, 1, -1]
c = [ (x,y) for x,y in zip(a,b)]
e = sorted(c, key=lambda x: x[1])
print(e) # [(4, -1), (7, 1), (5, 2), (3, 3), (2, 5), (6, 7), (1, 10)]
print([ x[0] for x in e]) # [4, 7, 5, 3, 2, 6, 1]
a = {'a': 10, 'f': 5, 'c':3, 'b': 15, 'e': 20, 'd':13}
b = sorted(a)
c = sorted(a.items())
d = sorted(a.items(), key=lambda x: x[1])
print( b ) # ['a', 'b', 'c', 'd', 'e', 'f']
print( c ) # [('a', 10), ('b', 15), ('c', 3), ('d', 13), ('e', 20), ('f', 5)]
print( d ) # [('c', 3), ('f', 5), ('a', 10), ('d', 13), ('b', 15), ('e', 20)]
dictionary 자체만 사용하면 기본적으로 Key를 반환한다. `dict.items()` 는 "Key-Value" 쌍을 튜플로 반환해준다.
python
시간만 충분하다면, 완전 탐색이 명확하지...
완전 탐색은 수많은 방법이 있기 때문에, for 문과 조건검색을 잘 사용하면 될것 같다.
Python의 경우는, List Compression, map, Lambda, zip 등이 유용하게 사용될 수 있다.
또한, 만약 Numpy 등을 사용할 수 있다면, Python은 최고의 해결법이 될 것 이다.
다음은 프로그래머스 "완전탐색" 영역의 "모의고사"문제 이다.
< 문제 정의>
다음 패턴 3개를 주어진 배열(Array)와 Full Matching하여 Matching되는 갯수를 세는 문제이다.
매칭된 갯수가 동일 (max치가 여러개)일 때 인덱스로 정렬하여 제출하는 것만 유의하면 크게 문제될것이 없다.
def solution(answers):
answer = []
n = len(answers)
s1 = [1, 2, 3, 4, 5] * (n//5+1)
s2 = [2, 1, 2, 3, 2, 4, 2, 5] * (n//8+1)
s3 = [3, 3, 1, 1, 2, 2, 4, 4, 5, 5] * (n//10+1)
s = [s1[:n], s2[:n], s3[:n] ]
c = [0,0,0]
for i,x in enumerate(answers):
c = [ c[j]+1 if x-s[j][i]==0 else c[j] for j in range(3)]
m = max(c)
d = [x for x in zip(c, [1,2,3])]
e = [x[1] for x in d if x[0]==m]
return sorted(e)
python
완전탐색은 너무 오래걸려. 효율적으로 값을 찾아보자.
python
탐색 방식 : 깊이 우선? 너비 우선?
로컬 최적이 전체 최적이 되는 경우
로컬에서 주어진 데이터에서 최적을 선택을 하고, 전체로 확대 한다.
이 때 주어진 list 등에서 추가(Insert)하거나, 제거 (remove)하면, 전체 List가 변하기 때문에, 반복문에 영향이 가지 않도록 유의 해야 한다. 대표적으로, 다음과 같은 명령어가 있다고 하면,
for x in y:
if x>5: y.remove(x)
for문이 y의 원소인데 중간에 y의 원소를 추가 삭제하면, Loop가 정상적으로 동작하지 않는다.
다음은 프로그래머스의 탐용법 (Greedy) 알고리즘의 체육복 문제이다.
문제 정의는 다음 접은글을 확인 하기 바란다.
체육복이 도난당함. 체육복을 보유한 학생만 수업에 참석 가능. 최대 수업참석 학생수를 구하는 문제
def solution(n, lost, reserve):
answer = 0
lost.sort()
reserve.sort()
lost_self =[]
rs = reserve.copy()
for r in rs:
if r in lost:
lost.remove(r)
reserve.remove(r)
for r in reserve:
if (r-1 in lost):
lost.remove(r-1)
continue
elif (r+1 in lost):
lost.remove(r+1)
continue
return n-len(lost)
반복되는 계산은 줄인다....
동적 계획법이 활용될 수 있는 경우는, 수열로 표현이 되는 경우이다.
즉, `X(t) = X(t-1) + X(t-2)` 등과 같이 반복적으로 표현 되는 경우라 할 수 있다.
대부분 Recursive 형태로 표현이 가능한 경우, 동적 계획법을 사용하면 효과적이라 볼 수 있다.
기본적으로 동적 계획법은 동일한 연산이 반복 되기 때문에, 1) 이러한 연산을 미리 저장 (Memoization) 해 놓고 이를 참조해서 사용하는 방법, 2) 혹은 Recursion을 사용하는 방법으로 나뉜다.
다음은 프로그래머스의 동적계획법 (Dynamic Programming) 문제중, "정수 삼각형" 문제의 풀이이다.
문제 정의는 다음 접은글 확인
이 문제는 다음과 같은 식으로 표현이 가능하기 때문에 동적계획법을 사용하기에 최적 문제이다.
따라서, 위 내용을 코드로 표현하면 다음과 같이 표현 할 수 있다. 차이점이 있다면, 삼각형의 밑변부터 위로 올라오며 계산한 부분만 차이라 할 수 있겠다.
def solution(triangle):
answer = 0
t = [ [0]*len(x) for x in triangle ]
nRows = len(triangle)
for r in range(nRows)[::-1]:
# print(f' - {r}th : {t}')
for c in range(r+1):
if r==nRows-1:
t[r][c] = triangle[r][c]
else:
t[r][c] = triangle[r][c] + max(t[r+1][c], t[r+1][c+1])
return t[0][0]
python
노드(Node)와 엣지(Edge)
[Python] 파이썬 모듈/패키지 파해치기 - 모듈 생성하기/불러오기/상대경로/절대경로 (0) | 2022.06.25 |
---|---|
[librosa 설치] 설치오류 - sndfile library not found (0) | 2022.06.02 |
[Python] 문자열 구조화 4종 : 문자열 포맷스트링 (0) | 2022.05.08 |
[음성인식 - 6라인] 가장 쉬운 음성인식 (STT) 해 보기 (0) | 2022.05.08 |
[Python] 통계 대표값 (Mean, Median, Mode) 구하기 - 패키지 사용 vs. 패키지 미사용 (0) | 2022.05.07 |
댓글 영역