개발 이야기/Problem Solving
[묘공단] 코딩테스트 스터디 2주차
가마뫼
2023. 11. 30. 21:21
이 글은 골든래빗 코딩 테스트 합격자 되기 파이썬 편의 0장 ~ 4장 써머리입니다.
배열 개념
인덱스와 값을 일대일 대응해 관리하는 자료구조
인덱스만 알면 빠르게 탐색 가능 (Random access)
배열 선언
# 1차원 배열
vector = [0] * 6
# 2차원 배열 (e.g., 3 by 4 matrix)
matrix = [[0] * 3 for _ in range(4)]
배열 차원
중첩 리스트 형태로 다차원 배열을 표현 가능
그러나 실제 메모리는 1차원 구조로 저장된다
또한 2차원 배열을 1차원 배열로 표현해서 풀이하는 문제도 꽤 자주 출제됨
matrix1 = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matrix2 = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 2행 3열 원소
row_dim = 3
val1 = matrix1[1][2]
val2 = matrix2[1 * row_dim + 2]
배열의 효율성
임의 접근 특징을 가지므로 데이터 접근의 시간 복잡도는 상수 시간복잡도 O(1)
데이터 삽입은 마지막에 삽입 (append)는 상수 시간복잡도 그 외 처음 또는 중간 삽입은 선형 시간복잡도 O(N)
데이터에 자주 접근하고 읽어야한다면 배열 사용을 고려해볼 수 있음
- 배열로 표현하려는 데이터가 메모리에 할당 가능한지
- 중간에 데이터 삽입이 많은지
2가지를 고려해서 사용 여부를 판단하면 된다
자주 활용하는 리스트 기법
# 맨 뒤에 데이터 추가
my_list = [1, 2, 3]
my_list.append(4) # [1, 2, 3, 4]
# + 연산자로 데이터 추가
my_list = [1, 2, 3]
my_list = my_list + [4, 5] # [1, 2, 3, 4, 5]
# 또는 extend를 활용할 수 있음
my_list = my_list.extend([4, 5])
# pop 메서드
# 마지막 원소를 지우는 케이스 외에 인덱스를 받을 수 있으나 효율이 떨어짐
my_list = [1, 2, 3, 4, 5]
popped_element = my_list.pop() # [1, 2, 3, 4]
Insert와 Remove 메서드는 존재는 하나 코딩 테스트에서는 효율 문제로 사용할 일이 거의 없을 것 같다
my_list = [1, 2, 3, 4, 5]
my_list.insert(2, 9999) # [1, 2, 9999, 3, 4, 5]
# 제거하려는 값과 일치하는 처음 만나는 원소를 제거
my_list = [1, 2, 3, 2, 4, 5]
my_list.remove(2) # [1, 3, 2, 4, 5]
그 외 리스트 컴프리헨션, len, index, sort, count 같은 메서드들도 자주 사용되는 메서드
배열 정렬하기
from typing import List
def solution(arr: List[int]):
return sorted(arr)
배열 제어하기
from typing import List
def solution(arr: List[int]) -> List[int]:
unique_arr = list(set(arr))
sorted_arr = sorted(unique_arr, reverse=True)
return sorted(sorted_arr, reverse=True)
두 개 뽑아서 더하기
조건이 매우 널널하므로 2중 루프로 가능한 경우를 찾고, 조건에 맞춰서 중복 제거 및 정렬하면 쉽게 해결 가능
itertools의 조합 연산을 활용하는 코드도 있음
from typing import List
# 2 <= len(numbers) <= 100
# 0 <= numbers[i] <= 100
def solution(numbers: List[int]) -> List[int]:
num_of_elements = len(numbers)
answer = set()
for i in range(0, num_of_elements - 1):
for j in range(i + 1, num_of_elements):
answer.add(numbers[i] + numbers[j])
return sorted(list(answer))
모의고사
from collections import defaultdict
from typing import List
def solution(answers: List[int]) -> List[int]:
answer_rules = [
[1, 2, 3, 4, 5],
[2, 1, 2, 3, 2, 4, 2, 5],
[3, 3, 1, 1, 2, 2, 4, 4, 5, 5],
]
score_by_student_id = defaultdict(int)
max_score = 0
for student_id, rule in enumerate(answer_rules):
for problem_id, answer in enumerate(answers):
if answer == rule[problem_id % len(rule)]:
score_by_student_id[student_id + 1] += 1
max_score = max(max_score, score_by_student_id[student_id + 1])
student_ids = {
student_id: score
for student_id, score in score_by_student_id.items()
if score == max_score
}.keys()
return sorted(student_ids)
assert solution([1, 2, 3, 4, 5]) == [1]
assert solution([1, 3, 2, 4, 2]) == [1, 2, 3]
행렬의 곱셈
from typing import List
def solution(arr1: List[List[int]], arr2: List[List[int]]) -> List[List[int]]:
r1, c1 = len(arr1), len(arr1[0])
r2, c2 = len(arr2), len(arr2[0])
answer = [[0] * c2 for _ in range(r1)]
for i in range(r1):
for j in range(c2):
for k in range(c1):
answer[i][j] += arr1[i][k] * arr2[k][j]
return answer
assert solution([[1, 4], [3, 2], [4, 1]], [[3, 3], [3, 3]]) == [
[15, 15],
[15, 15],
[15, 15],
]
assert solution(
[[2, 3, 2], [4, 2, 4], [3, 1, 4]], [[5, 4, 3], [2, 4, 1], [3, 1, 1]]
) == [
[22, 22, 11],
[36, 28, 18],
[29, 20, 14],
]
실패율
from collections import defaultdict
from typing import List
def solution(N: int, stages: List[int]) -> List[int]:
sorted_stages = sorted(stages)
stage_info_dict = defaultdict(int)
for stage in sorted_stages:
stage_info_dict[stage] += 1
denominator = len(stages)
failure_ratio_list = []
for stage in range(1, N + 1):
denominator -= stage_info_dict[stage - 1]
if denominator == 0:
failure_ratio_list.append((stage, 0.0))
else:
failure_ratio_list.append((stage, stage_info_dict[stage] / denominator))
return [e[0] for e in sorted(failure_ratio_list, key=lambda x: (-x[1], x[0]))]
방문 길이
frozenset을 활용하거나 양방향 경로로 간주해서 2가지 케이스 모두 집어넣는 방식 둘 다 활용 가능
def solution(dirs: str) -> int:
delta_by_direction = {"U": (0, 1), "D": (0, -1), "L": (-1, 0), "R": (1, 0)}
visited_path = set()
current_coordinate = (0, 0)
for direction in dirs:
x, y = current_coordinate
dx, dy = delta_by_direction[direction]
next_coordinate = (x + dx, y + dy)
if -5 <= next_coordinate[0] <= 5 and -5 <= next_coordinate[1] <= 5:
path = frozenset([current_coordinate, next_coordinate])
if path not in visited_path:
visited_path.add(path)
current_coordinate = next_coordinate
return len(visited_path)
assert solution("UD") == 1
assert solution("ULURRDLLU") == 7
assert solution("LULLLLLLU") == 7
배열 뒤집기
Slicing을 활용하는 것은 생각하지 못했는데, 더 간결하고 의미도 명확해보임
from typing import List
def solution(num_list: List[int]) -> List[int]:
# return num_list[::-1]
return list(reversed(num_list))
assert solution([1, 2, 3, 4, 5]) == [5, 4, 3, 2, 1]
assert solution([1, 1, 1, 1, 1, 2]) == [2, 1, 1, 1, 1, 1]
assert solution([1, 0, 1, 1, 1, 3, 5]) == [5, 3, 1, 1, 1, 0, 1]
나누어 떨어지는 숫자 배열
요구사항대로 구현하면 되는 문제
from typing import List
def solution(arr: List[int], divisor: int) -> List[int]:
divisible_elements = [x for x in arr if x % divisor == 0]
answer = sorted(divisible_elements) if divisible_elements else [-1]
return answer
다만 반환 시 or를 활용해 더 간단하게 아래와 같이도 가능하다
from typing import List
def solution(arr: List[int], divisor: int) -> List[int]:
divisible_elements = [x for x in arr if x % divisor == 0]
return sorted(divisible_elements) or [-1]