제한시간 1초(n초) = 10^8번 연산 (100,000,000번)
따라서 입력값 n의 크기에 따라 시간복잡도를 고려하여 10^8번 연산 이내로 풀릴 수 있어야 한다.
O(n!), O(2^n)
- 어떤 알고리즘이든 가능
O(n^3)
- 3중 for문
- 플로이드 워셜 알고리즘
- 완전탐색
O(n^2)
- 2중 for문
- 벨만 포드 알고리즘
- 선택 정렬
- 삽입 정렬
O(n)
- 동적 프로그래밍(DP)
- 계수 정렬
- 유니언 파인드
- 위상 정렬
O(n log n)
- 이진 탐색
- 파라메트릭 서치
- 다익스트라 알고리즘
- 세그먼트 트리
- 투 포인터
- 퀵 정렬
- 크루스칼 알고리즘
O(log n)
- 수학적 기믹이 필요
- 유클리드 호제법
# 순열fromitertoolsimportpermutationforcaseinpermutations(array, len(array)):
...# 조합fromitertoolsimportcombinationsforcaseincombinations(array, len(array)):
...# reducefromfunctoolsimportreduceresult=reduce(lambdax, y: x*y, array)fromcollectionsimportdequequeue=deque() # queue 생성queue.append('a') # pushqueue[0] # peak 'a'queue.popleft() # pop 'a'queue.extend(['b', 'c', 'd']) # 배열 pushimportheapqpq= [] # priorityQueue 생성heapq.heappush(pq, (0, 1)) # pushpq[0] # peak (0, 1)heapq.heappop(pq) # pop (0, 1)array= [b, c, a]
heapq.heapify(array) # array를 priorityQueue로 생성heapq.heappop(array) # pop 'a'importmathvalue=math.gcd(3, 6)importcopyarray2=copy.deepcopy(array)importsysinput=sys.stdin.readline()stack= [] # stack 생성stack.append('a') # pushstack[0] # peakstack.pop() # pop 'a's=set() # set 생성s1=set(['a', 'b', 'c']) # array를 set으로 생성s2=set(['b', 'c', 'd'])
s1.add('c') # pushs1.remove('c') # removes1.update(['a', 'b', 'c']) # 배열 pushs1&s2# 교집합 {'b', 'c'}s1|s2# 합집합 {'a', 'b', 'c', 'd'}s1-s2# 교집합을 제거한 s1 {'a'}
{ xforxin'abcde' } # list comprehension {'a', 'b', 'c', 'd', 'e'}'a'ins1# Truelist(s1) # set to list ['a', 'b', 'c']d= {} # dictionary 생성d= {'a': 1, 'b': 2}
d=dict([('a', 1), ('b', 2)]) # touble array로 생성d_from_list= { x: ord(x) forxin'abc' } # dictionary comprehension {'a': 97, 'b': 98, 'c': 99}d['a'] # value 접근 1d['a'] =3# value updatedeld['a'] # value delete'a'ind# Truelist(d) # ['a', 'b']d.items(): # [(key, value)] 반환 [('b': 2)]d.keys() # ['b']d.values() # [2]forkey, valueinitems(): # key, value 순차접근print(key, value)array= [] # list 생성array.append('a') # pusharray.remove('a') # deletearray=list(range(0, 5)) # [0, 1, 2, 3, 4]array.index(3) # index 3array.count(3) # 1array[-1] # 4array[:-1] # return [0, 1, 2, 3]array[1:-1] # return [1, 2, 3]array[::2] # return [0, 2, 4]array[-1::-2] # return [4, 2, 0]array[1:-1][::-1] # return [3, 2, 1]temp=array+ [5, 6] # return [1, 2, 3, 4, 5, 6]array+= [5, 6] # update [1, 2, 3, 4, 5, 6]# 2차원배열n, m=2, 3graph= [[0]*mfor_inrange(n)]
# [[0, 0, 0],# [0, 0, 0]]graph[n-1][m-1] =1# [[0, 0, 0],# [0, 0, 1]]tuple= ('a', 1) # tuple 생성tuple[0] # 'a'tuple[1] # 1str(1) # '1'str(1)+str(2) # '12'string='abc'string.swapcase() # return 'ABC'string.isupper() # return Falsestring.islower() # return Truestring.upper() # return 'ABC'string.lower() # return 'abc'string='123'string.isdecimal() # return Truestring=' abcd 'string.strip() # return 'abcd'list(range(0, 5)) # [0, 1, 2, 3, 4]foriinrange(0, 5):
print(i, end=", ") # 0, 1, 2, 3, 4,# list.sort()array= [1, 4, 5, 2, 3]
array.sort() # update [1, 2, 3, 4, 5]array.sort(key=lambdax: -x) # update [5, 4, 3, 2, 1]array.sort(key=lambdax: abs(x-3)) # update [3, 4, 2, 5, 1]array.sort(reverse=True) # update [5, 4, 3, 2, 1]# sorted(array)array= [1, 4, 5, 2, 3]
sorted(array) # return [1, 2, 3, 4, 5]sorted(array, key=lambdax: -x) # return [5, 4, 3, 2, 1]sorted(array, reverse=True) # return [5, 4, 3, 2, 1]sorted({1: 'a', 2: 'b'}) # dictionary: return [1, 2]sorted([('a', 3, 10), ('b', 1, 9), ('c', 2, 9)], key=lambdax: (x[2], x[1])) # tuple: return [('b', 1, 9), ('c', 2, 9), ('a', 3, 10)]array= [1, 2, 3, 4, 5]
list(filter(lambdax: x>2, array)) # [3, 4, 5]array [1, 2, 3]
foridx, valueinenumerate(array):
print(idx, value, end=",") # 0 1,1 2,2 3,# 총합total=sum([1, 2, 3]) # 6# 1차원 list화array=sum([[1, 2], [3, 4]], []) # [1, 2, 3, 4]# string.join()''.join(['a', 'b', 'c']) # return 'abc'# string.replace()'abcde'.replace('a', 'A') # return 'Abcde'counts= [0]*26forcharin'abcde':
counts[ord(char)-ord('a')] +=1abs(-4) # 4a, b=map(int, input().split())INF=int(1e9)num=5odd=Trueifnum%2==1elseFalse# True가장 큰 순서대로, 가장 작은 순서대로 와 같은 기준 (정렬 알고리즘과 한쌍)
큰 단위가 작은 단위의 배수형태여야 한다. 아닌 경우 다이나믹 프로그래밍으로 접근
- while문 대신 몫을 계산하는 식으로 접근
완전탐색 or 시뮬레이션
move= [(0, 1), (1, 0), (0, -1), (-1, 0)]
ny, nx=y+move[direction][0], x+move[direction][1]
ifny<0orny>=nornx<0ornx>=n:
direction= (direction+1)%4y, x=y+move[direction][0], x+move[direction][1]dict= {'left': (-1, 0), 'right': (1, 0), 'up': (0, 1), 'down': (0, -1)}
nx, ny=x+dict[key][0], y+dict[key][1]
ifnx<-nornx>n:노드와 노드 사이에 연결된 간선 정보를 가지고 있는 자료구조
인접행렬
n, m=map(int, input().split())
graph= []
forrowinrange(n):
line= [int(x) forxinlist(input())]
graph.append(line)인접 리스트
n, m=map(int, input().split())
graph= [[] for_inrange(n)]
graph[0].append((1, 7)) #노드 1, cost 7graph[1].append((0, 7)) #노드 0, cost 7스택, 큐(deque: from collections) 자료구조 필요
재귀: 스택구조, 종료 조건이 필요
DFS: 스택(재귀)
- 방문하지 않은 경우 재귀를 통해 새로운 노드 방문
defdfs(graph, v, visited):
visited[v] =Trueforiingraph[v]:
ifnotvisited[i]:
dfs(graph, i, visited)BFS: 큐(deque)
- 방문하지 않은 경우 queue 에 추가, queue 순서에 맞게 노드 방문
defbfs(graph, start, visited):
queue=deque([start])
visited[start] =Truewhilequeue:
v=queue.popleft()
foriingraph[v]:
ifnotvisited[i]:
queue.append(i)
visited[i] =TrueO(n^2)
defsort(array):
foriinrange(len(array)):
min_index=i;
forjinrange(i+1, len(array)):
ifarray[min_index] >array[j]:
min_index=j;
array[i], array[min_index] =array[min_index], array[i]
returnarrayO(n^2) but 데이터가 거의 정렬되어 있을 때 효율적
정렬된 상태 + 적절한 위치 찾아 삽입하여 정렬
defsort(array):
foriinrange(1, len(array)):
forjinrange(i, 0, -1): # i to 1 까지 -1씩 감소ifarray[j] <array[j-1]:
array[j], array[j-1] =array[j-1], array[j]
else:
breakreturnarrayO(n log n) but 데이터가 거의 정렬되어 있을 때 비효율적pivot 을 기준으로 작은, 큰 데이터 좌우 분리, 분리된 좌, 우 역시 동일 방식으로 정렬
좌 → 우: pivot 보다 큰 데이터의 위치(left), 좌 ← 우: pivot 보다 작은 데이터의 위치(right)를 찾아 위치를 서로 변경
작은 데이터 위치(left)가 큰 데이터 위치(right)보다 크거나 같은 경우: pivot, 작은 데이터 switch, 종료
재귀 종료조건: 리스트가 1개인 경우 정렬된 상태이므로 반환
pivot 선택기준: 첫번째 데이터 (호어 분할 방식)
defsort(array):
quick_sort(array, 0, len(array)-1)
defquick_sort(array, start, end):
ifstart>=end: # 원소가 1개인 경우 종료returnpivot=startleft=start+1right=endwhileleft<=right:
whileleft<=endandarray[left] <=array[pivot]:
left+=1# find 큰값 indexwhileright>startandarray[right] >=aray[pivot]:
right-=1# find 작은값 indexifleft>right: # 엇갈린 경우: pivot, 작은값 switcharray[right], array[pivot] =array[pivot], array[right]
pivot=rightelse: # 큰값, 작은값 switch, 이후 while 통해 다음 switch 진행array[left], array[right] =array[right], array[left]
# pivot 보다 작은 값들 | pivot | pivot 보다 큰 값들quick_sort(array, start, pivot-1)
quick_sort(array, pivot+1, end)defsort(array):
iflen(array) <=1:
returnarraypivot=array[0]
tail=array[1:]
left_side= [xforxintailifx<=pivot]
right_side= [xforxintailifx>pivot]
returnsort(left_side) + [pivot] +sort(right_side)O(n log n)
분할 정복(divide and conqure) 방법을 통해 크가 같은 두 리스트로 분할 후 각각 정렬한 결과를 합병
분할하는 횟수는 절반식 줄어들기에 O(logN) 시간이 소모된다.
병합할 때 모든 값들을 비교해야 하기 때문에 O(N) 시간이 소모된다.
퀵정렬보다는 느리지만 최악의 경우도 O(n log n)보장
defmerge_sort(arr):
iflen(arr) <2:
returnarrmid=len(arr) //2low_arr=merge_sort(arr[:mid])
high_arr=merge_sort(arr[mid:])
merged_arr= []
l=h=0whilel<len(low_arr) andh<len(high_arr):
# 좌측이 더 작은 경우 -> 좌측 merged에 추가iflow_arr[l] <high_arr[h]:
merged_arr.append(low_arr[l])
l+=1# 우측이 더 작은 경우 -> 우측 merged에 추가else:
merged_arr.append(high_arr[h])
h+=1merged_arr+=low_arr[l:]
merged_arr+=high_arr[h:]
returnmerged_arrO(n + k) (k: 가장큰 수)
범위가 정해진 정수, 중복이 많은 경우 효율적
위치변경 X, 리스트 정보를 통한 정렬
defsort(array):
counts= [0]*(max(array)+1)
foriinrange(len(array)):
counts[array[i]] +=1sorted= []
foriinrange(len(counts)):
sorted+= [i] *counts[i]
returnsortedO(n) 앞에서부터 차례로 확인
defsearch(array, target):
foriinrange(len(array)):
ifarray[i] ==target:
returnireturn-1O(log n) 정렬된 배열 내에서 중간점 위치 데이터와 비교하며 반복적으로 절반씩 좁혀가며 찾는다
재귀 형식과 반복문 형식이 있는데, stack에 쌓이지 않는 반복문 방식이 더 좋다.
defbinary_search(array, target, start, end):
whilestart<=end:
mid= (start+end) //2ifarray[mid] ==target:
returnmideliftarget<array[mid]:
end=mid-1else:
start=mid+1return-1주어진 범위 내에서 원하는 값 또는 원하는 조건에 가장 일치하는 값을 찾아내는 알고리즘
최적화 문제(최댓값 x = ?) -> 결정문제(x일 경우 최댓값?)로 바꾸어 풀 수 있다.
- 범위의 중간값과 조건을 비교하며 범위를 수정해나간다
- 이진 탐색과 차이점은 값 비교 -> 조건 비교, 반환값 (mid, -1 -> result (현재까지의 최적값))이 다르다는 점.
# 최적화문제: 24시간을 7시간마다 배고파지는 사람이 최소 몇끼니를 먹어야 배부르게 지낼수있는가?# 결정문제: 7시간마다 배고파지는 사람이 x끼니를 먹으면 24시간을 배부르게 지낼 수 있다.defparametricSearch(start, end) {
result=0while (start<=end):
mid= (start+end) /2if (24/mid) <7:
end=mid-0.1else:
start=mid+0.1result=midreturnresult
}이진 탐색이 동작할 수 있도록 고안된 트리 자료구조
child-left < parent < child-right 식
큰 문제를 작은 문제로 나눌 수 있는 경우 동일문제를 중복되지 않게 계산하는 방법
Bottom up(반복문), Top down(재귀) 방식
메모이제이션: 계산된 결과들을 메모하는 기법 (캐싱)O(n)
d= [0] *100defpibo(x):
d[1] =1d[2] =1foriinrange(3, x+1):
d[i] =d[i-1] +d[i-2]시작 노드로부터 각 노드까지의 최단거리 알고리즘 (음의 간선이 없는 경우)
반복적으로 최소 최단거리의 노드를 선택하며 테이블을 갱신 (heapq 사용)O(E log V)
V: 노드수,E: 간선수visited: [False] * (n+1)distance: [INF] * (n+1)
defdijkstra(graph, startNode):
distance= [INF]*(len(graph)+1)
distance[startNode] =0q= []
# q: (distance, node), distance 가 적은 node 순으로 정렬된다heapq.heappush(q, (0, startNode))
whileq:
currentDistance, currentNode=heapq.heappop(q)
# currentNode 의 distance 가 갱신된적이 있는 경우 무시ifdistance[currentNode] <currentDistance:
continue# currentNode 의 인접노드들 확인fornodeingraph[currentNode]:
nodeNum=node[0]
nodeCost=node[1]
newDistance=currentDistance+nodeCostifnewDistance<distance[nodeNum]:
distance[nodeNum] =newDistanceheapq.heappush(q, (newDistance, nodeNum))
returndistanceimportsysimportheapqinput=sys.stdin.readlineINF=int(1e9)
n, m=map(int, input().split())
startNode=int(input())
graph= [[] foriinrange(n+1)]
for_inrange(m):
from, to, cost=map(int, input().split())
graph[from].append((to, cost))
distance=dijkstra(graph, startNode)
foriinrange(1, n+1):
ifdistance[i] ==INF:
print("INF")
else:
print(distance[i])여러개의 from, to 노드간 최단거리 구하기 알고리즘
점화식: AtoB = min(AtoB, AtoK + KtoB)
k, a, b 순차적으로 3중 for 구조O(n^3)
deffloydWarshall(graph):
forkinrange(1, n+1):
forainrange(1, n+1):
forbinrange(1, n+1):
graph[a][b] =min(graph[a][b], graph[a][k] +graph[k][b])
graph[b][a] =graph[a][b]importsysinput=sys.stdin.readline()
INF=int(1e9)
n, m=map(int, input().split())
graph= [[]]*(n+1) for_inrange(n+1)]
forainrange(1, n+1):
forbinrange(1, n+1):
ifa==b:
graph[a][b] =0graph[b][a] =0for_inrange(m):
a, b, cost=map(int, input().split())
graph[a][b] =costgraph[b][a] =costfloydWarshall(graph)
forainrange(1, n+1):
forbinrange(1, n+1):
val=graph[a][b] ifgraph[a][b] !=INFelse"INF"print(val, end=" ")
print()공통 원소가 없는 집합이 필요한 경우
하나의 집합으로 합치는 union 과 원소가 속한 집합을 알려주는 find 연산이 필요union 의 경우 작은 노드값이 집합의 값이 된다.
# 노드값과 집합값이 같을때까지 재귀적으로 호출deffindParent(parents, x):
ifparents[x] !=x:
# 경로 압축parents[x] =findParent(parents, parants[x])
returnparents[x]
defunionParent(parents, a, b):
setA=findParent(parents, a)
setB=findParent(parents, b)
ifsetA<setB:
parents[b] =setAelse:
parents[a] =setBv, e=map(int, input().split())
parents= [iforiinrange(v+1)]
for_inrange(e):
a, b=map(int, input().split())
unionParent(parents, a, b)
foriinrange(1, v+1):
print(findParent(parents, i), end=" ")
print()유향 그래프: DFS 알고리즘무향 그래프: 유니언 파인드 집합 알고리즘
# 무향그래프에서 사이클 판별v, e=map(int, input().split())
parents= [iforiinrange(v+1)]
cycle=Falseforiinrange(e):
a, b=map(int, input().split())
iffindParent(parents, a) ==findParent(parents, b):
cycle=Truebreakelse:
unionParent(parents, a, b)
ifcycle:
print("사이클 발생")
else:
print("사이클 비발생")모든 노드를 포함하면서 사이클이 존재하지 않는 부분 그래프
모든 도시를 연결할 때 최소 비용과 관련된 크루스칼 알고리즘의 경우 최소 신장 트리를 활용한 알고리즘
최소 신장 트리를 구하기 위한 알고리즘
신장 트리(Spanning tree): 모든 정점을 포함, 정점간 서로 연결, 싸이클이 존재하지 않는 그래프최소 신장 트리(Minimum Spanning Tree): 신장 트리 중 가중치 합이 최소인 그래프- 싸이클 여부:
서로소 집합(Disjoint Set)의Union & Find활용 크루스칼 알고리즘 블로그
경로를 최소한의 비용으로 연결하는 알고리즘
그래프 내 모든 정점들을 가장 적은 비용으로 연결하기 위해 사용 (사이클이 없는, 가중치의 합 최소가 되는 상태)
모든 간선에 대해 비용값 기준 정렬, 사이클이 발생하지 않는 간선부터 집합에 포함
무향 그래프이므로 find, union 을 통해 사이클 여부 판별 후 간선비용 총합O(E log E)
v, e=map(int, input().split())
parents= [iforiinrange(v+1)]
edges= [(a, b, cost) for_inrange(e) a, b, cost=map(int, iniput().split())]
result=0edges.sort()
foredgeinedges:
cost, a, b=edgeiffindParent(parents, a) !=findParent(parents, b):
unionParent(parents, a, b)
result+=costprint(result)우선순위(선후관계)에 따라 방향성에 거스르지 않도록 순서대로 나열
진입차수 테이블과 queue(deque)가 필요
- 진입차수가 0인 노드를 큐에 넣는다
- 큐가 빌때까지 꺼내며 해당 노드부터 출발하는 간선을 제거하며 진입차수가 0인 노드를 큐에 넣는다
O(V+E)
deftopologySort(graph, indegree):
result= []
q=deque()
# 처음 시작시 indegree 값이 0인 노드를 넣고 시작foriinrange(1, v+1):
ifindegree[i] ==0:
q.append(i)
whileq:
node=q.popleft()
result.append(node)
foriingraph[node]:
indegree[i] -=1ifindegree[i] ==0:
q.append(i)
returnresultfromcollectionsimportdequev, e=map(int, input().split())
indegree= [0] * (v+1)
graph= [[] foriinrange(v+1)]
for_inrange(e):
a, b=map(int, input().split())
graph[a].append(b)
indegree[b] +=1sorted=topologySort(graph, indegree)
forninresult:
print(n, end=" ")2부터 n-1까지 나누어 떨어지는 수가 없는 경우 소수 판별
defisPrime(n):
ifn<2:
returnFalseforiinrange(2, n):
ifn%i==0:
returnFalsereturnTrue정해진 범위 내 소수를 구하는 경우 사용되는 알고리즘
범위 내 숫자들에서 합성수를 반복적으로 지우는 방식으로 소수를 찾는다.
- 지워지지 않은수 중 작은수를 소수로 채택, 해당 수의 배수를 모두 제거
defprime_list(n):
prime= [True]*(n+1)
foriinrange(2, int(n**0.5)+1):
ifprime[i] ==True:
forjinrange(2*i, n+1, i):
prime[j] =Falsereturn [iforiinrange(2, n+1) ifprime[i] ==True]에라토스테네스의 체 블로그1에라토스테네스의 체 블로그2
1부터 루트n까지 n을 나누는 수들을 확인
defdivisors(n):
result= []
foriinrange(1, int(n**0.5)+1):
ifn%i==0:
result.append(i)
result.append(n/i)
returnset(result)