SJ_Koding

Python3 코딩테스트 기초 TIP 모음 본문

Algorithm

Python3 코딩테스트 기초 TIP 모음

성지코딩 2023. 4. 26. 15:42

주로 사용하는 파이썬 코딩테스트의 문법들을 정리해보았습니다. 지속적으로 추가할 예정입니다.

자세한 이론적 설명은 생략하겠습니다! 더욱 효율적인 코드가 있거나 틀린 내용이 있으면 댓글로 공유해주세요 :)  즉각 반영하고 감사의 메세지를 남기겠습니다. 

필수로 알아둬야할 문법입니다.

알아두면 정말 편합니다

이렇게도 코딩할 수 있습니다.

1. 문자열 뒤집기

아래와 같이 [::-1] 문법을 사용할 수 있다.

string = 'Welcome SJKOding!'
print(string[::-1])

출력 결과: '!gnidOKJS emocleW'


2. 중복 제거하기

set자료형을 이용하자. 변환할 변수를 set()으로 묶으면 된다. set은 중복을 허용하지 않고 순서가 없다.

temp = [1, 1, 2, 2, 3, 4, 4, 5]
print(list(set(temp)))

출력 결과: [1, 2, 3, 4, 5]

 

3. 한 줄에 여러 정수(int) 혹은 실수(float) 입력받기

python의 input함수는 기본적으로 string형태의 데이터로 입력받게 된다. 따라서 숫자를 입력받는다면 반드시 형 변환을 해주어야한다. 백준 알고리즘 같은 사이트에서 한 줄에 여러 값을 입력받는 경우가 대다수이다. 이는 map()함수와 문자열의 split()함수를 이용하여 아래와 같이 입력받을 수 있다.

 

map()함수는 iterable한 자료형(리스트, 튜플과 같은 여러 값이 연속되어있는 자료형) 의 원소들을 각각 지정한 함수에 넣어 반환하는 함수이다.

temp_list = list(map(int, input().split()))
print(temp_list)

입력:  34 2 566 4 7 8 11

출력: [34, 2, 566, 4, 7, 8, 11]

 

4. 특정 값으로 2차원 맵 (BFS, DFS 등에서 주로 사용) 생성

numpy와 같은 라이브러리로 쉽게 만들 수 있지만 코딩테스트에서는 표준 라이브러리만 허용하기 때문에 numpy를 사용할 수 없다. 따라서 리스트 컴프리헨션으로 아래와 같이 2차원 맵을 생성할 수 있다.

visited = [[False for _ in range(m)] for _ in range(n)] # n: 행 개수 m: 열 개수

visited는 주로 사용되는 변수명이다. 방문여부를 체크할 때 쓰인다.

 

5. 파이썬에서는 if 0 < n < 10: 과 같은 문법을 허용한다.

일반적으로 java나 C언어 같은 경우 아래와 같이 분할해서 &&연산자로 엮어야한다.

if(0 < n && n < 10){ }

하지만 파이썬에서는 아래의 문법을 허용한다. 가독성이 더 좋아지더라.

if 0 < n < 10:

6. 두 변수의 값 바꾸기

다른 언어에서는 ^연산자를 사용하거나(숫자 swap의 경우) temp변수를 사용하여 두 변수의 값을 바꾸지만 파이썬은 아래와 같이 쉽게 바꿀 수 있다.

a, b = b, a

7.  리스트의 원소들을 차례로 순회할 때, 인덱스까지 동시에 가져오기

아래와 같이 enumerate() 함수를 사용하여 동시에 가져올 수 있다.

temp = ['k', 'o', 'r', 'e', 'a']

for idx, value in enumerate(temp):
	print(idx, value)

출력 결과:

0 'k'

1 'o'

(생략)

 

8. Queue를 이용할 시 temp.pop(0)대신 deque사용하기.

queue의 자료형을 구현할 경우 가장 먼저 들어온 데이터를 빼낼 때 .pop(0)으로 빼올 수 있지만 값을 빼온 후 나머지의 원소들을 한 칸씩 땡겨야 하기 때문에 시간복잡도 O(N)이 소요된다. 이를 해결하기위해 표준라이브러리 collections의 deque를 아래와 같이 사용한다. 이는 시간복잡도 O(1)이 소요되어 코딩테스트의 필수 라이브러리이며 BFS구현시 반드시 사용된다.

from collections import deque

queue = deque([1, 2, 3, 4, 5])
print(queue.popleft())
print(list(queue))

출력결과:
1
[2, 3, 4, 5]

 

popleft(), appendleft()를 모두 허용하며 물론 pop()과 append()도 당연히 사용 가능하다. 앞 뒤로 자유자재로 데이터를 처리할 수 있고 시간복잡도가 O(1)이므로 필수로 알아둬야하는 라이브러리이다.

 

9. 길이가 같은 두 개 이상의 iterable 객체를 동시에 for문 돌리기

이는 zip()함수로 아래와 같이 사용할 수 있다.

temp1 = [1, 3, 5]
temp2 = [2, 4, 6]

for n1, n2 in zip(temp1, temp2):
	print(n1, n2)

 

출력 결과:

1 2

3 4

5 6

 

10. 딕셔너리 정렬 (key기준, value 기준)

딕셔너리의 value값을 기준으로 정렬하고자 할때 sorted 함수의 key인자를 아래와같이 사용하면 된다.

dic = {'apple': 3, 'banana': 1, 'pear': 5}
sorted(dic.item(), key = lambda x: x[1])

딕셔너리의 key값을 기준으로 정렬하려면 그냥 sorted(dic)을 하면 된다.

 

만약 value값을 기준으로 먼저 정렬하고 그 상태에서 key값으로 정렬하려면 아래와 같이 수행하면 된다.

dic = {'apple': 3, 'banana': 1, 'pear': 5}
sorted(dic.item(), key = lambda x: (x[1], x[0]))

 

11. for-else문, while-else문

해당 문법은 반복문 내에서 break로써 빠져나오지 않고 조건을 만족하여 빠져나왔을 경우에만 수행할 수 있게 지원한다.

아래와 같이 사용하면 된다. 별도의 flag변수가 필요하지 않아 편하다.

for i in range(1, 10):
	if i == 11:
    	break
        
else:
	print('for-break 안걸림!')
    
j = 0
while j < 10:
	if j == 4:
    	break
        
   	j += 1
else:
	print('while-break 안걸림!')

출력결과:

for-break 안걸림!

 

12. 정렬된 리스트에서 이진 탐색으로 탐색 및 값 삽입하기 (bisect)

정렬된 리스트에서 값을 찾거나 삽입할 때 단순 순회탐색이 아닌 이진탐색을 자동으로 지원하는 라이브러리. 시간복잡도는 O(logN)이므로 매우 효율적이다.

 

import bisect

lst = [1, 3, 5, 6, 6, 8]

# 숫자 4가 어디에 위치해야하는지 index가져오기
# 이때 탐색할 숫자가 이미 존재할때, 
#
# bisect_left는 그 숫자의 가장 왼쪽 index 반환
# bisect_right는 그 숫자의 가장 오른쪽 index 반환

print(bisect.bisect_left(lst, 4) # 2

print(bisec.bisect_left(lst, 6) # 3
print(bisec.bisect_right(lst, 6) # 5

13. 2차원 리스트에서 열 추출하기

ndarray, DataFrame은 열 추출을 쉽게 할 수 있지만 리스트는 열 접근을 지원하지 않는다. 이는 zip연산을 통해 간단하게 구현할 수 있다.

a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
b = list(zip(*a))[0]
print(b)
# (1, 4, 7)

c = list(zip(*a))[1]
print(c)
# (2, 5, 8)

14. 백준에서 표준 입력으로 시간 단축하기

sys라이브러리의 readline표준입력을 사용하면 입력시간이 대폭 감소한다. 거의 필수적인 기능이다. 예를 들어 테스트케이스의 최대 길이가 1000인 경우(1000번 입력받아야함), 특정 조건이 나올때 까지 입력받는 경우, 맵이나 크기가 100이 넘거나 그래프의 노드수가 100개가 넘는 등 입력이 다수 진행될 경우에 사용할 수 있다.

*주의사항 sys.stdin.readline은 줄바꿈 문자를 포함하여 입력받기때문에 strip()을 포함해줘야한다. 단, 정수형으로 캐스팅할 경우 자동으로 줄바꿈 문자를 생략해준다. 

import sys

input = sys.stdin.readline # built-in input() 함수를 표준입력으로 대체

while True:
	num = int(input()) # 정수로 캐스팅했으므로 strip()필요없음.
  	if num == -1: break
    
    print(num)

 

15. defaultdict() 유사 딕셔너리

딕셔너리를 사용할 때 다음과 같은 로직을 자주 만난다.

if 1 in dic:
	dic[1].append('temp')
    
else:
	dic[1] = 'temp'

key가 존재하지 않을 때 어떠한 연산을 하려고하면 에러가 발생하기 때문이다. 이때, defaultdict()를 사용하면 편하다.

key가 존재하지 않다면 자동으로 키를 생성하여 연산을 수행해주기 때문이다.

# 백준의 어떠한 문제 해답 중 일부

from collections import defaultdict
dic = defaultdict(list)

N = int(input())

for i in range(N):
    deadline, cup = map(int, input().split())
    dic[deadline].append(cup)

16. 우선순위 큐 (heap)

그리디 문제인데 골드 이상이다. 혹은 정렬문제인데 골드 이상이다 하면 우선순위 큐를 먼저 떠올려보자.
우선순위 큐는 위의 예제 뿐 만 아니라 정말 많은 곳에서 사용되는 자료구조이다.

우선순위 큐란 가중치가 가장 큰 값을 우선으로 pop하는 자료구조이다. 힙 구조를 사용하는데 이때 push하거나 pop하는 과정에서 시간복잡도 O(logN)이 소요된다. 

standard queue에서의 append연산 --> O(1), 
standard queue에서의 우선순위 pop연산 --> O(n) # 반드시 n이 소요. 순회하여 최대 가중치인지 확인해야함

만약 정렬이 되어있다면?

standard Queue에서의 append 연산 --> O(NlogN) # 값을 넣을 때 마다 정렬 필요
standard Queue에서의 우선순위 pop 연산 --> O(1) # 정렬 되어있으므로

대부분의 상황에서는 push와 pop이 반복적으로 발생하므로, standard Queue는 절대 효율적인 자료구조가 아니다.

따라서 push와 pop이 모두 O(logN)이 소요되는 heap을 사용하자. 정말 효율적인 알고리즘이다.

사용방법

import heapq

priority_queue = []

for val, idx in enumerate([3, 1, 9, 24, 13, 71]):
	heapq.heappush(priority_queue, (i, f'작업{idx+1}')
    
# 우선순위가 가장 낮은 요소부터 우선순위 큐에서 제거하고 출력 (최소 힙)
while priority_queue:
    priority, task = heapq.heappop(priority_queue)
    print(f'우선순위: {priority}, 작업: {task}')
    
# 만약 우선순위가 가장 높은 요소부터 추출하려면? (최대 힙)
priority_queue = []

for val, idx in enumerate([3, 1, 9, 24, 13, 71]):
	heapq.heappush(-priority_queue, (i, f'작업{idx+1}') # 우선순위에 -를 곱해서 넣으세요!
    
while priority_queue:
    priority, task = heapq.heappop(priority_queue)
    print(f'우선순위: {priority}, 작업: {task}')

 

17. 2차원 리스트 깊은복사 했다니깐요??!?!?!?! 왜 틀리죠?

혹시 [:] 로 '서로 다른 메모리주소를 가지게된다'라고 배워서 이렇게 사용했는지 체크하자. 1차원 리스트면 문제가 없다만, 2차원 리스트에는 아주아주 큰 문제가 발생한다.

파이썬에서의 [:]연산은 첫 번째 차원은 깊은복사하지만, 내부 배열은 참조를 유지하게된다. 따라서 내부 리스트의 원소를 복사한 곳에서 변경하면 원본 리스트에도 영향을 미치게 된다.이때 사용하는 것이 deepcopy()이다. deepcopy()는 리스트의 모든 차원을 새로운 메모리 위치에 복사하여 독립적인 복사본을 만든다.

사용방법

import copy

# 2차원 배열 생성
original = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]

# 얕은 복사
shallow_copied = original[:]
shallow_copied[0][0] = 999
print("얕은 복사 후 원본 배열:", original)  # 변경됨!

# 깊은 복사
deep_copied = copy.deepcopy(original)
deep_copied[0][0] = 111
print("깊은 복사 후 원본 배열:", original)  # 변경되지 않음!
print("깊은 복사된 배열:", deep_copied)
얕은 복사 후 원본 배열: [[999, 2, 3], [4, 5, 6], [7, 8, 9]]
깊은 복사 후 원본 배열: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
깊은 복사된 배열: [[111, 2, 3], [4, 5, 6], [7, 8, 9]]