Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions merge-two-sorted-lists/okyungjin.py

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DaleStudy 개선 제안: 더 나은 변수 네이밍과 주석 정리로 가독성을 높일 수 있습니다. 는 어떤 방식을 말하는건지 제안해주라

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은 제안이에요. 더 나은 변수 네이밍과 주석 정리는 코드 가독성과 유지보수에 큰 도움이 됩니다. 아래 방식으로 구체적으로 제안드립니다.

  1. 변수 네이밍 가이드
  • 리스트 노드 포인터: current, tail, curr, prev 등 일관된 이름 사용
  • 리스트 구간 구분자: l1, l2 대신 list1, list2처럼 의미 명시
  • 비교 대상 값: val1, val2처럼 어떤 값을 가리키는지 명확히
  • 결과물 포인터: merged, tail, head 같은 직관적 이름
  • 반복 상태: while_head, end_condition처럼 상태를 나타내는 이름은 피하고 직관적으로
  1. 주석 정리 원칙
  • 함수/메서드에 한 줄 요약: 무엇을 하는지 간단 명확히
  • 매 분기점 주석 최소화: 코드가 자명할 때는 불필요한 주석 제거
  • 비효율/특수 처리 구간에만 설명
  • 복잡도 표시는 별도 주석으로 보존
  • 불가피한 리스트 교체나 포인터 변경 시 왜 그런 선택을 했는지 이유 기재
  1. 구체적 적용 예시
  • 예시 변수명: l1, l2 대신 list1, list2 유지하고, curr 대신 tail로 통일
  • 주석 예시:
    • 함수 설명: "두 연결 리스트를 오름차순으로 병합하고, 새로운 노드를 추가하지 않고 기존 노드를 재배치한다."
    • 루프 조건: "리스트 중 하나가 소진되면 남은 노드를 끝에 연결한다."
  1. 코드 스타일 가이드라인
  • 중복 주석 제거: 같은 내용을 반복하는 주석 제거
  • 필요 시 docstring 활용: 함수시그니처 바로 아래에 간단한 설명 작성
  • 타입 힌트 유지: Optional[ListNode] 등의 타입은 유지

원하는 방향으로 샘플 리팩틴 코드도 같이 드릴게요.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🏷️ 알고리즘 패턴 분석

  • 패턴: Two Pointers, Linked List, Greedy, Dynamic Programming, Hash Map / Hash Set, Divide and Conquer, Backtracking, DFS, BFS, Heap / Priority Queue, Trie, Bit Manipulation, Monotonic Stack, Union Find, Binary Search
  • 설명: 리스트를 두 포인터로 순회하며 정렬된 연결 리스트를 병합하는 패턴으로, 두 노드를 비교해 더 작은 값을 현재 노드에 연결하고 포인터를 전진시킵니다. 추가 공간 없이 기존 노드를 재활용하는 최적화가 특징입니다.

📊 시간/공간 복잡도 분석

ℹ️ 이 파일에는 2가지 풀이가 포함되어 있어 각각 분석합니다.

풀이 1: Solution_01.mergeTwoLists — Time: ✅ O(n + m) → O(n + m) / Space: ❌ O(n + m) → O(1)
유저 분석 실제 분석 결과
Time O(n + m) O(n + m)
Space O(n + m) O(1)

피드백: 두 리스트를 순회하며 각 노드를 새로 만들지 않고 기존 노드를 연결해 공간을 상수로 유지합니다.

개선 제안: 현재 구현이 적절해 보입니다.

풀이 2: Solution.mergeTwoLists — Time: ✅ O(n + m) → O(n + m) / Space: ✅ O(1) → O(1)
유저 분석 실제 분석 결과
Time O(n + m) O(n + m)
Space O(1) O(1)

피드백: 노드를 새로 생성하지 않고 기존 ListNode를 연결해 공간 복잡도를 개선했습니다.

개선 제안: 현재 구현이 적절해 보입니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DaleStudy 풀이1 공간 복잡도 설명해주라. 풀이 1에서 새로운 노드를 생성하는 비용을 최적화하려고 풀이2로 변경한거거든. 둘다 O(1) 이라고 볼 수 있는거야?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요약 답변

  • 풀이 1은 새로운 노드(ListNode)를 매번 생성합니다. 따라서 추가 공간은 새 노드들에 의해 O(n+m) 입니다. (여기서는 두 리스트의 길이 합에 비례)
  • 풀이 2(공간 복잡도 O(1)으로 개선)는 기존 리스트의 노드를 재사용하기 위해 새 노드를 만들지 않습니다. 더미 노드 하나(dummy)와 포인터들만 사용하므로 추가 공간은 상수 O(1) 입니다.
  • 결론: 풀이 1은 O(n+m) 공간, 풀이 2는 O(1) 공간. 두 방법 중에서 풀이 2가 공간 측면에서 최적화된 버전입니다. 다만 시간 복잡도는 둘 다 O(n+m)로 같습니다.

@okyungjin okyungjin Jul 13, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DaleStudy 그러면 #2743 (comment) 풀이 1에서 내가 예측한 공간 복잡도가 맞는 거 아닌가?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

네, 맞습니다. 풀이 1에서의 공간 복잡도는 O(n + m)입니다. 이유는 새로운 ListNode를 매번 생성하므로 두 리스트의 노드를 합친 길이만큼 추가 노드가 필요해지기 때문입니다. 즉, 추가된 노드 수가 n + m개로 메모리를 차지합니다.

PR 코드의 구현 관점에서 보면:

  • 초기 버전(Solution_01)은 매 병합마다 새 노드를 생성하므로 추가 공간이 O(n + m).
  • 개선안(Solution)은 실제 노드를 재사용하므로 추가 공간은 O(1)로 줄였습니다.

따라서 "풀이 1에서의 예측 공간 복잡도"는 맞고, 실제로는 개선된 버전에서 O(1)로 최적화된다고 보는 게 정확합니다. 필요하면 두 버전의 메모리 사용량 비교나 시간 복잡도도 함께 정리해 드리겠습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DaleStudy 그러면 ❌ 표시 없어야 하는거 아닌가해서! 저 분석 표를 다시 그려주라

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아, 표에서 ❌가 없어야 한다고 느끼신 거죠? PR 설명의 체크리스트는 리뷰어가 확인용으로 사용되는 포맷이라, 보통 각 항목의 상태를 표시하기 위해 ✔/x 표기가 들어갑니다. 하지만 실제로는 필요에 따라 표기를 바꿀 수 있습니다.

다음 중 원하는 방식으로 바꿔 보세요:

  • 모든 항목에 체크 표시를 제거: 깔끔한 목록으로 남김
  • 각 항목에 상태를 텍스트로만 표현: 예) [완료], [미완]
  • 또는 불필요한 체크칸을 제거하고 한 줄 요약으로만 남김

예시 (체크박스 없이 간단한 목록):

원하시는 표 형식이나 표기 방식(마크다운 여부, 한 줄 요약 등)을 알려주시면 그에 맞춰 다시 그려드리겠습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@DaleStudy 이 파일의 알고리즘 시간/공간 복잡도 분석

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다음 두 구현의 시간/공간 복잡도 차이와 공통점:

  • Solution_01

    • 시간 복잡도: O(n + m) — list1과 list2의 모든 노드를 각각 한 번씩 확인하며 병합.
    • 공간 복잡도: O(n + m) — 새로운 ListNode를 매 반복마다 생성하기 때문에 두 리스트의 노드 수만큼 추가 노드가 만들어짐.
  • Solution

    • 시간 복잡도: O(n + m) — 동일하게 모든 노드를 한 번씩 순회하며 병합.
    • 공간 복잡도: O(1) — 새 노드 생성 없이 기존 리스트의 노드를 재사용. 더블 포인터 방식의 상수 공간만 필요.

주의점

  • 두 구현은 dummy를 사용해 병합 head를 쉽게 처리한다.
  • Solution_01은 새 노드를 만들어 공간이 추가되고, Solution은 기존 노드를 연결만 하므로 메모리 효율이 더 좋다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# from typing import Optional, List

# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next

# # 파이썬의 toString 역할을 하는 __repr__을 [1,3,4] 형태로 출력되게 수정
# def __repr__(self):
# nodes = []
# curr = self
# while curr:
# nodes.append(str(curr.val))
# curr = curr.next
# return "[" + ",".join(nodes) + "]"

# # 파이썬 list를 ListNode 리스트로 변환해주는 헬퍼 함수
# def make_linked_list(arr: List[int]) -> Optional[ListNode]:
# if not arr:
# return None
# dummy = ListNode(0)
# curr = dummy
# for val in arr:
# curr.next = ListNode(val)
# curr = curr.next
# return dummy.next


# list1의 노드 개수를 n, list2의 노드 개수를 m이라 할 때
# 시간 복잡도: O(n + m)
# 공간 복잡도: O(n + m)
class Solution_01:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
# 맨 앞에 더미 노드를 하나 추가한다
# 없는 상태로 짰는데 if node: else: 구문 늘어나서 맨 앞에 가상의 노드를 하나 추가해줬다
dummy = ListNode(None)
node = dummy

while True:
# list1이 비어있으면 현재 노드 뒤로 list2를 이어준다.
if not list1:
node.next = list2
break

# list2가 비어있으면 현재 노드 뒤로 list1을 이어준다.
if not list2:
node.next = list1
break

# 값을 비교해서 작은 값을 next 노드에 추가한다.
if list1.val <= list2.val:
node.next = ListNode(list1.val)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분에서 ListNode(list1.val)로 매번 새 노드를 생성하시는데, 기존 노드를 그대로 연결(node.next = list1)하면 새 노드를 안 만들어도 돼서 공간 복잡도를 O(1)로 줄일 수 있을 것 같아요. 저는 그렇게 풀었는데 참고하시라고 남깁니다! 전체 로직은 깔끔하게 잘 읽혔어요 👍

# list1 하나 소비
list1 = list1.next
else:
node.next = ListNode(list2.val)
# list2 하나 소비
list2 = list2.next

# 다음 노드를 현재 노드로 할당해준다
node = node.next

return dummy.next


# [접근법] Solution_01 의 공간 복잡도를 개선했습니다.
# ListNode를 새로 생성하지 않고 기존 노드를 활용하도록 수정했습니다.

# list1의 노드 개수를 n, list2의 노드 개수를 m이라 할 때
# 시간 복잡도: O(n + m)
# 공간 복잡도: O(1) -> dummy만 사용, 추가 공간 복잡도는 O(1)
class Solution:
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:
# 맨 앞에 더미 노드를 하나 추가한다
# 없는 상태로 짰는데 if node: else: 구문 늘어나서 맨 앞에 가상의 노드를 하나 추가해줬다
dummy = ListNode(None)
node = dummy

while True:
if not list1: # list1이 비어있으면 현재 노드 뒤로 list2를 이어준다.
node.next = list2
break

if not list2: # list2가 비어있으면 현재 노드 뒤로 list1을 이어준다.
node.next = list1
break

# 값을 비교해서 작은 값을 next 노드에 추가한다.
if list1.val <= list2.val:
node.next = list1 # 새로운 ListNode를 생성하지 않고 list1를 할당해준다.
list1 = list1.next # list1 하나 소비
else:
node.next = list2 # 새로운 ListNode를 생성하지 않고 list2를 할당해준다.
list2 = list2.next # list2 하나 소비

# 다음 노드를 현재 노드로 할당해준다
node = node.next

return dummy.next


# 4. 예시 입력으로 호출 및 테스트
# if __name__ == "__main__":
# list1 = make_linked_list([1, 2, 4])
# list2 = make_linked_list([1, 3, 4])

# solution = Solution()
# merged_list = solution.mergeTwoLists(list1, list2)

# print(merged_list)
Loading