diff --git "a/_algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206.md" "b/_algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206.md" new file mode 100644 index 0000000..cadb99d --- /dev/null +++ "b/_algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206.md" @@ -0,0 +1,162 @@ +--- +title: "[리트코드] Reverse Linked List 자바스크립트 풀이" +excerpt: "알고리즘 재활훈련 - 연결리스트" +coverImage: "/assets/algorithms/JS-리트코드/cover.png" +date: "2026-05-27T10:53:00" +ogImage: + url: "/assets/algorithms/JS-리트코드/cover.png" +--- + +## 문제 + +단일 연결 리스트(singly linked list)의 head가 주어질 때, 리스트를 뒤집은 후 그 head를 반환하는 문제입니다. +[문제출처](https://leetcode.com/problems/reverse-linked-list/) + +**예시 1:** + +입력: head = [1,2,3,4,5] +출력: [5,4,3,2,1] + +![reverse-linked-list-1](/assets/algorithms/JS-리트코드/leet-206/1.png) + +  +**예시 2:** + +입력: head = [1,2] +출력: [2,1] + +![reverse-linked-list-2](/assets/algorithms/JS-리트코드/leet-206/2.png) +  +**예시 3:** + +입력: head = [] +출력: [] + +## 내풀이 1차 + +### 접근: 일단 단순무식하게 구현 + +### 코드 + +```javascript +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var reverseList = function (head) { + // input값 없을때 처리 + if (!head) { + return head; + } + + // 배열로 평탄화 후 다시 연결리스트 생성 + let current = head; + let arr = []; + while (current) { + arr.push(current.val); + current = current.next; + } + + let newHead = null; + + for (let i = 0; i < arr.length; i++) { + newHead = new ListNode(arr[i], newHead); + } + + return newHead; +}; +``` + +### 복잡도 + +- 시간 복잡도: O(n) — 노드 n개를 한 번 순회해 배열에 넣고, 배열을 한 번 더 순회해 리스트를 만듦 (총 2n이지만 계수는 상수) +- 공간 복잡도: O(n) — 값을 담는 배열 arr가 O(n), 새로 만드는 ListNode도 n개라 O(n) + +## 내풀이 2차 + +### 접근: 같은 O(n)인데 n계수줄일수있음 + +### 코드 + +```javascript +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var reverseList = function (head) { + // input값 없을때 처리 + if (!head) { + return head; + } + + // 최적화 : 조회하자마자 바로넣으면 뒤집어짐 + let current = head; + let newHead = null; + + while (current) { + newHead = new ListNode(current.val, newHead); + current = current.next; + } + + return newHead; +}; +``` + +### 복잡도 + +- 시간 복잡도: O(n) — current로 리스트를 한 번만 순회하며 prepend (1차 대비 순회 횟수·상수 계수 감소) +- 공간 복잡도: O(n) — 보조 배열은 없지만 새 ListNode를 n개 생성 (포인터 변수 current, newHead만 보면 O(1), 노드 할당까지 포함하면 O(n)) + +## 인터넷풀이 : 재귀 + +### 접근: 끝까지 내려가서 돌아오며 포인터 뒤집기 + +- 베이스 케이스: head가 null이거나, head.next가 null(마지막 노드)이면 그대로 반환 +- 재귀 호출: head.next부터 뒤집어서, 뒤집힌 리스트의 새로운 head를 받음 +- 포인터 재연결: + - head.next.next = head (다음 노드가 현재 노드를 가리키도록 뒤집기) + - head.next = null (현재 노드가 앞으로 새지 않게 끊기) +- 최종 반환: 재귀에서 받은 newHead(뒤집힌 리스트의 head) 이걸 그대로 출력하면됨 + +### 코드 + +```javascript +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} head + * @return {ListNode} + */ +var reverseList = function (head) { + if (!head || !head.next) return head; + + const newHead = reverseList(head.next); + head.next.next = head; + head.next = null; + return newHead; +}; +``` + +### 복잡도 + +- 시간 복잡도: O(n) — 각 노드를 한 번씩 방문 +- 공간 복잡도: O(n) — 재귀 호출 스택이 최대 n까지 쌓임 (추가 노드를 생성하지는 않음) diff --git "a/_algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-98.md" "b/_algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-98.md" index 67ccb48..5556730 100644 --- "a/_algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-98.md" +++ "b/_algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-98.md" @@ -18,18 +18,14 @@ ogImage: **예시 1** -``` -Input: root = [2,1,3] -Output: true -``` +입력: root = [2,1,3] +출력: true **예시 2** -``` -Input: root = [5,1,4,null,null,3,6] -Output: false +입력: root = [5,1,4,null,null,3,6] +출력: false 설명: 루트의 오른쪽 자식 4가 루트보다 작으므로 BST 조건 위반 -``` **제약 조건** diff --git "a/_algorithms/JS-\354\236\220\353\243\214\352\265\254\354\241\260/problem-solving-patterns.md" "b/_algorithms/JS-\354\236\220\353\243\214\352\265\254\354\241\260/problem-solving-patterns.md" new file mode 100644 index 0000000..d688560 --- /dev/null +++ "b/_algorithms/JS-\354\236\220\353\243\214\352\265\254\354\241\260/problem-solving-patterns.md" @@ -0,0 +1,158 @@ +--- +title: "Problem Solving Patterns - 문제 해결 패턴 요약" +excerpt: "문제해결 패턴 정리" +coverImage: "/assets/algorithms/JS-자료구조/cover.png" +date: "2026-05-28T11:22:00" +ogImage: + url: "/assets/algorithms/JS-자료구조/cover.png" +--- + +> Colt Steele의 JavaScript Algorithms and Data Structures Masterclass 강의를 보고 “문제 해결 패턴”부분을 정리한 글입니다. + +  + +## 문제 해결 기본 루틴 + +- **문제 이해**: 입력/출력, 예외 케이스, 제약(시간/공간) 이해하기 +- **예시로 검증**: 작은 입력으로 손으로 따라가며 기대 결과 확인 +- **단순화**: 큰 문제를 더 작은 문제로 쪼개서 먼저 해결 (가장 쉬운 버전부터) +- **핵심 포인트 찾기**: 반복되는 연산, 중복 계산, “한 번만 보면 되는 정보”가 있는지 확인 +- **리팩터링**: 불필요한 루프/중첩 제거, 변수명/가독성 정리, 엣지 케이스 재확인 + +  + +## 1) 빈도수 세기 (Frequency Counter) + +- **언제**: 순서보다 “구성/개수”가 중요한 문제(애너그램, 두 배열의 관계, 중복 검증 등) +- **핵심**: Map/Object에 빈도 누적 후 비교 → 보통 **중첩 루프를 선형으로 줄임** +- **복잡도**: 시간 O(n), 공간 O(n) (알파벳 고정이면 사실상 O(1)) + +  + +## 2) 다중 포인터 (Multiple Pointers) + +- **언제**: 정렬된 배열/리스트에서 “쌍/구간/조건 만족”을 찾을 때(투포인터) +- **핵심**: left/right 포인터를 움직이며 탐색 범위를 줄임 +- **복잡도**: 보통 시간 O(n), 공간 O(1) + +  + +## 3) 슬라이딩 윈도우 (Sliding Window) + +- **언제**: “연속된 구간”의 합/최대/최소(고정 길이 또는 가변 길이) +- **핵심**: 윈도우를 한 칸씩 이동하며 이전 계산을 재사용 (누적합/카운트 갱신) +- **복잡도**: 보통 시간 O(n), 공간 O(1)~O(n) (윈도우 상태 저장 방식에 따라) + +  + +## 4) 분할 정복 (Divide and Conquer) + +- **언제**: 정렬된 데이터에서 탐색 범위를 반씩 줄일 수 있을 때(이진 탐색류) +- **핵심**: 매 단계마다 문제 크기를 절반으로 줄임 +- **복잡도**: 시간 O(log n) 또는 O(n log n) (문제에 따라) + +  + +## 5) 동적 계획법 (Dynamic Programming) + +- **언제**: 큰 문제를 작은 문제로 쪼갤 수 있고, 같은 하위 문제가 반복될 때(피보나치, 계단, 최적화 문제 등) +- **핵심**: + - **메모이제이션(Memoization)**: 탑다운(재귀) + 캐시로 이미 구한 값을 저장 + - **타뷸레이션(Tabulation)**: 보텀업(반복)으로 “테이블(배열/객체)”을 작은 값부터 채워가며 계산 +- **복잡도**: 보통 시간 O(n)~O(nm), 공간 O(n) (상태 정의에 따라) + +  + +## 6) 그리디 (Greedy) + +- **언제**: “지금 최선” 선택이 전체 최적해로 이어질 때(증명/근거가 있는 문제) +- **핵심**: 매 단계 로컬 최적 선택 +- **복잡도**: 문제에 따라 O(n), O(n log n) (정렬이 들어가면 로그 항이 붙음) + +  + +## 7) 스택/큐/덱 (Stack / Queue / Deque) + +- **언제**: + - 스택: 괄호 검사, 되돌리기/취소(undo), “이전 상태”를 기억해야 할 때 + - 큐: 순서대로 처리(BFS), 작업 대기열 + - 덱: 앞/뒤 양쪽에서 넣고 빼야 할 때 (슬라이딩 윈도우 최적화에도 등장) +- **핵심**: “LIFO/FIFO/양방향”이라는 자료구조 규칙으로 흐름을 강제해서 문제를 단순화 +- **복잡도**: 보통 시간 O(n), 공간 O(n) (저장하는 원소 수에 비례) + +  + +## 8) 그래프 탐색 (Traversal: BFS / DFS) + +- **언제**: 연결 관계가 있는 구조(그래프/격자/트리)에서 “도달 가능/최단 거리(무가중치)/영역 개수” 등을 구할 때 +- **핵심**: + - DFS: 깊게 들어갔다가 되돌아오며 탐색 (재귀/스택) + - BFS: 레벨 순서로 넓게 탐색 (큐) → 무가중치 최단거리에서 자주 사용 + - 방문 처리(visited)가 핵심 (중복 방문/무한 루프 방지) +- **복잡도**: 시간 O(V + E), 공간 O(V) (visited/queue/stack) + +  + +## 9) 이분 탐색 (Binary Search / Parametric Search) + +- **언제**: + - 정렬된 배열에서 특정 값/경계를 빠르게 찾을 때 + - “조건을 만족하는 최소/최대값”을 찾는 결정 문제(파라메트릭 서치) +- **핵심**: 정답(또는 탐색 구간)을 반으로 줄여가며 범위를 좁힘 +- **복잡도**: 시간 O(log n) (보통), 공간 O(1) + +  + +## 10) 백트래킹 / 완전탐색 (Backtracking / Brute Force) + +- **언제**: 경우의 수를 직접 탐색해야 하는데, 가지치기(조건)로 줄일 수 있을 때 (순열/조합, 선택/비선택) +- **핵심**: “선택 → 재귀 → 복원(undo)” 패턴 + 불가능한 분기는 빨리 중단(pruning) +- **복잡도**: 보통 매우 큼(지수/팩토리얼). 다만 pruning이 성능을 좌우 + +  + +## 11) 문자열 패턴 (String Patterns) + +- **언제**: 문자열 비교/변환/검증/파싱이 핵심인 문제 (애너그램, 회문, 토큰화 등) +- **핵심**: 투포인터, 빈도수 세기, 슬라이딩 윈도우, 파싱(상태 머신처럼) 조합이 자주 등장 +- **복잡도**: 보통 시간 O(n), 공간 O(1)~O(n) (추가 버퍼/맵 사용 여부) + +  + +## 12) 유니온 파인드 (Disjoint Set Union, DSU) + +- **언제**: “서로소 집합”을 관리하며 연결/그룹 여부를 빠르게 판단해야 할 때 (네트워크, 그룹 묶기) +- **핵심**: find(대표 찾기) + union(합치기), 경로 압축/랭크 최적화 +- **복잡도**: 거의 O(1)에 가까움(아커만 역함수 수준). 실전에서는 보통 O(α(n))로 표기 + +  + +## 13) 최단거리(가중치) (Dijkstra 기본) + +- **언제**: 가중치가 0 이상인 그래프에서 최단 거리를 구할 때 +- **핵심**: 우선순위 큐로 “가장 짧은 거리 후보”부터 확정해 나감 +- **복잡도**: 보통 O((V + E) log V) (우선순위 큐 사용) + +  + +## 14) 정렬 활용 (Sorting as a Strategy) + +- **언제**: “정렬하면 규칙이 생겨서” 투포인터/그리디/이분탐색이 가능해지는 문제 +- **핵심**: 정렬 자체가 목적이 아니라, 정렬 후에 문제 구조가 단순해지는지를 먼저 판단 +- **복잡도**: 보통 O(n log n) (정렬이 지배) + +  + +## 요약 + +- **구성/개수/중복**이 핵심이면 → 빈도수 세기(해시) +- **정렬되어 있고 양끝/쌍/조건**을 찾으면 → 다중 포인터 +- **연속 구간(부분 배열/substring) 최적화**면 → 슬라이딩 윈도우 +- **정답이 “최솟값/최댓값/경계값”이고 단조성**이 보이면 → 이분 탐색 / 파라메트릭 서치 +- **연결 관계/격자에서 도달/영역 개수**면 → DFS/BFS +- **무가중치 최단거리**면 → BFS +- **가중치(0 이상) 최단거리**면 → 다익스트라 +- **선택을 되돌리며 경우의 수를 탐색**해야 하면 → 백트래킹 +- **중복되는 하위 문제 + 최적화/경우의 수**면 → DP +- **그때그때 최선 선택이 맞는 문제**면 → 그리디 (정렬이 같이 붙는 경우 많음) +- **서로 연결/그룹 여부를 빠르게 판단**해야 하면 → 유니온 파인드(DSU) diff --git "a/public/assets/algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206/1.png" "b/public/assets/algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206/1.png" new file mode 100644 index 0000000..ca4afd4 Binary files /dev/null and "b/public/assets/algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206/1.png" differ diff --git "a/public/assets/algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206/2.png" "b/public/assets/algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206/2.png" new file mode 100644 index 0000000..28c3f78 Binary files /dev/null and "b/public/assets/algorithms/JS-\353\246\254\355\212\270\354\275\224\353\223\234/leet-206/2.png" differ