[Algorithm] 26년 08월

호두먹는 호람이·2026년 8월 20일

Algorithm

목록 보기
2/3

목차

  1. 배열 · 인덱스
  2. 문자열
  3. 딕셔너리 · collections
  4. 2차원 배열 · 리스트 컴프리헨션
  5. ASCII · 문자 처리

1. 배열 · 인덱스

1-1. 인덱스 vs 값 (i vs arr[i])

값을 비교해야 하는 조건문에는 i가 아니라 arr[i].

# ❌ 틀린 코드
for i in range(len(arr)):
    if i % 2 == 0 and i >= 50:   # i는 인덱스일 뿐
        ...

# ✅ 고친 코드
for i in range(len(arr)):
    if arr[i] % 2 == 0 and arr[i] >= 50:
        ...
  • i = 창고 선반 번호 / arr[i] = 그 선반 위의 물건
  • 헷갈리면 for x in arr:로 아예 값을 직접 꺼내 회피
  • % 홀짝 판별 / // 정수 나눗셈 vs / 실수 나눗셈

1-2. .index() 쓰기 전에 "없으면?"

.index()는 대상이 없으면 ValueError. 존재 확인이 먼저.

# ❌ 틀린 코드 — 2가 없으면 터짐
idx = arr.index(2)

# ✅ 고친 코드
if 2 in arr:
    idx = arr.index(2)
else:
    ...  # 없을 때 처리
  • 0을 "못 찾음" 신호로 쓰면 안 됨 → 0도 유효한 인덱스
  • 존재 확인을 코어 로직보다 먼저 짜는 습관

1-3. 슬라이싱 경계 (off-by-one)

[a:b]는 a 포함, b 제외. 오른쪽 슬라이싱에서 경계 원소 딸려 들어감 주의.

# ❌ 틀린 코드 — 기준 원소가 결과에 포함됨
return arr[idx:]

# ✅ 고친 코드 — 기준 다음부터
return arr[idx+1:]
  • s[:-0]은 전체가 아니라 빈 문자열 (-0 == 0이라 [:0]이 됨)
  • 슬라이싱 쓸 때마다 [a:b] 경계를 손으로 추적

2. 문자열

2-1. 문자열 불변성 (immutability)

문자열 메서드는 원본을 안 바꾼다. 반환값을 받아야 함.

# ❌ 틀린 코드 — 원본 그대로
s.lower()
s.replace("a", "b")

# ✅ 고친 코드 — 반환값 대입
s = s.lower()
s = s.replace("a", "b")
  • 비유: 복사기 — 원본은 그대로 두고 바뀐 사본을 뱉어줌
  • enumerate 루프에서 v += n도 복사본을 수정할 뿐 원본 리스트는 안 바뀜
# ❌ 원본 리스트 안 바뀜
for idx, v in enumerate(arr):
    v += n

# ✅ 인덱스로 직접 접근
for idx in range(len(arr)):
    arr[idx] += n

2-2. 문자열 분할: list() vs split()

문자 하나씩은 list(), 구분자로 덩어리 나누기는 split().

list("abc")        # ['a', 'b', 'c']   문자 단위
"a b c".split()    # ['a', 'b', 'c']   공백 기준 덩어리
"a,b,c".split(",") # ['a', 'b', 'c']   구분자 기준

"abc".split("")    # ❌ ValueError — 빈 구분자 불가
  • 문자열은 그 자체가 iterable → for ch in s도 가능
  • 자주 나오는 패턴: list() → 처리 → "".join()
# 문자열 뒤집기 예시
s = "hello"
result = "".join(list(s)[::-1])   # 'olleh'

3. 딕셔너리 · collections

3-1. defaultdict — 없는 키 자동 초기화

없는 키를 조회해도 기본값으로 자동 생성. 그룹핑에 강력.

from collections import defaultdict

# 길이별로 문자열 그룹핑
d = defaultdict(list)
for s in strArr:
    d[len(s)].append(s)   # 키가 없어도 빈 리스트 자동 생성
  • 비유: 빈 장부 자동 생성기 — 없는 페이지를 알아서 만들어줌
  • defaultdict(int)은 카운팅, defaultdict(list)은 그룹핑/인접리스트에 자주 씀
  • ⚠️ 길이별 그룹핑은 len(s)를 키로 (문자열 자체를 키로 쓰면 안 됨)

3-2. Counter — 빈도 계산 완제품

빈도 세기 전용. defaultdict보다 간결하지만 동작 차이 주의.

from collections import Counter

cnt = Counter(strArr)          # 각 원소 빈도
max_freq = max(cnt.values())   # 최대 빈도
cnt.most_common(1)             # [('a', 3)] — 리스트 반환!
cnt.most_common(1)[0][0]       # 'a'  (값)
cnt.most_common(1)[0][1]       # 3    (횟수)

Counter vs defaultdict 차이 (주의할 실수)

구분동작
없는 키 조회Counter는 0 반환하지만 키로 저장 안 함
.update()dict는 덮어쓰기 / Counter는 누적
뺄셈 -음수 버림
.subtract()음수 보존

코테 활용 패턴

# 애너그램 판별
Counter(s1) == Counter(s2)

# 부분집합 검사 (s2가 s1에 다 포함되나)
not (Counter(s2) - Counter(s1))

4. 2차원 배열 · 리스트 컴프리헨션

4-1. 2D 배열 초기화 함정

[[0]*n]*n은 참조 공유 버그. 컴프리헨션이 정답.

# ❌ 틀린 코드 — 모든 행이 같은 리스트를 가리킴
arr = [[0]*n]*n
arr[0][0] = 1   # 모든 행의 [0]이 같이 1로 바뀜!

# ✅ 고친 코드 — 행마다 독립된 리스트
arr = [[0]*n for _ in range(n)]
  • arr[i][j] = i행 j열
  • 순회 도구: zip, enumerate, len, 슬라이싱

4-2. 컴프리헨션 두 가지 if 구분

  • 필터형 if(뒤)와 삼항 if-else(앞)는 역할이 다르다. 섞으면 문법 에러.
# 필터형 if — for 뒤. 조건 안 맞으면 아예 제외
[x for x in arr if x > 0]

# 삼항 if-else — 표현식 자리. 모두에게 값 배정
[x if x > 0 else 0 for x in arr]
  • 필터형 = 출입문 (조건 안 맞으면 돌려보냄)
  • 삼항 = 매표소 (통과한 모두에게 다른 값 배정)

5. ASCII · 문자 처리

5-1. ord() / chr()와 오프셋 연산

문자↔숫자 변환 후, 기준 문자를 빼서 인덱스로 매핑.

ord('A')   # 65   문자 → 숫자
chr(65)    # 'A'  숫자 → 문자

# 대문자 A~Z를 0~25 인덱스로
idx = ord(ch) - ord('A')   # ord('A') = 65
  • 65는 주어진 값(ASCII 표의 'A'), 71은 유도된 값
    • 소문자를 인덱스 26부터 놓으려면: ord('a') - 26 = 97 - 26 = 71
  • 52칸 배열: 대문자 0~25, 소문자 26~51

5-2. 대소문자·문자 판별은 elif로

else로 뭉뚱그리면 비알파벳 입력이 조용히 오답을 만든다.

# ❌ 틀린 코드 — 숫자·기호가 else로 흘러 음수 인덱싱
if ch.isupper():
    count[ord(ch) - 65] += 1
else:
    count[ord(ch) - 71] += 1   # 비알파벳이면 엉뚱한 곳 +1

# ✅ 고친 코드 — 명시적으로 걸러냄
if ch.isupper():
    count[ord(ch) - 65] += 1
elif ch.islower():
    count[ord(ch) - 71] += 1
# 그 외는 무시
  • 판별 메서드: isupper(), islower(), isalpha(), isdigit()
  • 습관: 입력에 예외 문자가 섞일 수 있는지 먼저 생각

0개의 댓글