├── .gitignore ├── .prettierignore ├── README.md ├── algorithm-with-data-structure ├── binary-search-tree.js ├── combinations-with-repetition.js ├── combinations.js ├── max-heap.js ├── min-heap.js ├── permutations.js ├── queue-with-array.js ├── queue-with-linked-list.js └── stack.js └── solution ├── 01.js ├── 02.js ├── 03.js ├── 04.js ├── 05.js ├── 06.js ├── 07.js ├── 08.js ├── 09.js ├── 10.js ├── 100.js ├── 11.js ├── 12.js ├── 13.js ├── 14.js ├── 15.js ├── 16.js ├── 17.js ├── 18.js ├── 19.js ├── 20.js ├── 21.js ├── 22.js ├── 23.js ├── 24.js ├── 25.js ├── 26.js ├── 27.js ├── 28.js ├── 29.js ├── 30.js ├── 31.js ├── 32.js ├── 33.js ├── 34.js ├── 35.js ├── 36.js ├── 37.js ├── 38.js ├── 39.js ├── 40.js ├── 41.js ├── 42.js ├── 43.js ├── 44.js ├── 45.js ├── 46.js ├── 47.js ├── 48.js ├── 49.js ├── 50.js ├── 51.js ├── 52.js ├── 53.js ├── 54.js ├── 55.js ├── 56.js ├── 57.js ├── 58.js ├── 59.js ├── 60.js ├── 61.js ├── 62.js ├── 63.js ├── 64.js ├── 65.js ├── 66.js ├── 67.js ├── 68.js ├── 69.js ├── 70.js ├── 71.js ├── 72.js ├── 73.js ├── 74.js ├── 75.js ├── 76.js ├── 77.js ├── 78.js ├── 79.js ├── 80.js ├── 81.js ├── 82.js ├── 83.js ├── 84.js ├── 85.js ├── 86.js ├── 87.js ├── 88.js ├── 89.js ├── 90.js ├── 91.js ├── 92.js ├── 93.js ├── 94.js ├── 95.js ├── 96.js ├── 97.js ├── 98.js └── 99.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | solution/ 2 | algorithm-with-data-strucutre/ 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 안녕하세요? 독자 여러분! 이선협 저자입니다. 2 | 이 도서는 코딩 테스트를 위한 자바스크립트 문법 리마인드와 함께 코딩 테스트 필수 자료구조 알고리즘 지식을 코드 + 그림과 함께 풀어낸 책입니다.
3 | 이 책은 4 | - [〈코딩 테스트 합격자 되기〉(파이썬 편)](https://github.com/dremdeveloper/codingtest_python) 5 | - [〈코딩 테스트 합격자 되기〉(자바 편)](https://github.com/retrogemHK/codingtest_java) 6 | - [〈코딩 테스트 합격자 되기〉(C++ 편)](https://github.com/dremdeveloper/codingtest_cpp) 7 | 버전으로도 있습니다. 8 | 9 | 이 책의 구성은 다음과 같습니다.
10 | 11 | - 프로그래머스에서 엄선한 기출 문제에 대한 상세한 풀이 제공 12 | - 들고 다닐 수 있는 요약 노트 제공(40 페이지) 13 | - 실전 문제 위주로 출제된 기출 문제 5회분 제공(회당 3 문제) 14 | 15 | image 16 | image 17 | image 18 | image 19 | 20 | ## 📖 도서 구매 링크 21 | - Yes24 : https://m.yes24.com/Goods/Detail/128182419 22 | - 교보문고 : https://product.kyobobook.co.kr/detail/S000213641007 23 | - 알라딘 : http://aladin.kr/p/EqbOm 24 | - 인터파크: https://book.interpark.com/product/BookDisplay.do?_method=Detail&sc.prdNo=356949391 25 | - 리디북스(Ebook) : 26 | 27 | image 28 | 29 | # 💬 코딩 테스트 소통 공간(저자 직접 운영) 30 | - 카카오톡 오픈 채팅 : https://open.kakao.com/o/gX0WnTCf 31 | - 디스코드 : https://discord.gg/4fpt3jkz 32 | image 33 | image 34 | image 35 | 36 | # 오탈자 37 | ## p160 38 | ```diff 39 | -참고로 괄호 쌍을 확인할 때 append() 메서드와 pop() 메서드의 시간 복잡도는 O(1)입니다. 40 | +참고로 괄호 쌍을 확인할 때 push() 메서드와 pop() 메서드의 시간 복잡도는 O(1)입니다. 41 | ``` 42 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/binary-search-tree.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(key) { 3 | this.left = null; 4 | this.right = null; 5 | this.val = key; 6 | } 7 | } 8 | 9 | class BST { 10 | constructor() { 11 | this.root = null; 12 | } 13 | 14 | insert(key) { 15 | if (!this.root) { 16 | this.root = new Node(key); 17 | } else { 18 | let curr = this.root; 19 | while (true) { 20 | if (key < curr.val) { 21 | if (curr.left) { 22 | curr = curr.left; 23 | } else { 24 | curr.left = new Node(key); 25 | break; 26 | } 27 | } else { 28 | if (curr.right) { 29 | curr = curr.right; 30 | } else { 31 | curr.right = new Node(key); 32 | break; 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | search(key) { 40 | let curr = this.root; 41 | 42 | while (curr && curr.val !== key) { 43 | if (key < curr.val) { 44 | curr = curr.left; 45 | } else { 46 | curr = curr.right; 47 | } 48 | } 49 | return curr; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/combinations-with-repetition.js: -------------------------------------------------------------------------------- 1 | function combinationsWithRepetition(arr, n) { 2 | // 1개만 뽑는다면 그대로 조합을 반환하며 탈출 조건으로도 사용됩니다. 3 | if (n === 1) return arr.map((v) => [v]); 4 | const result = []; 5 | 6 | // 요소를 순환 7 | arr.forEach((fixed, idx, arr) => { 8 | // 현재 index를 포함한 요소를 추출 9 | // index번째는 선택된 요소 10 | const rest = arr.slice(idx); 11 | // 선택된 요소 이전 요소들을 제외하고 재귀 호출 12 | const combis = combinationsWithRepetition(rest, n - 1); 13 | // 선택된 요소와 재귀 호출을 통해 구한 조합을 합침 14 | const combine = combis.map((v) => [fixed, ...v]); 15 | // 결과 값을 추가 16 | result.push(...combine); 17 | }); 18 | 19 | // 결과 반환 20 | return result; 21 | } 22 | 23 | console.log(combinationsWithRepetition([1, 2], 2)); // [[1, 1], [1, 2], [2, 2]] 24 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/combinations.js: -------------------------------------------------------------------------------- 1 | function combinations(arr, n) { 2 | // 1개만 뽑는다면 그대로 조합을 반환하며 탈출 조건으로도 사용됩니다. 3 | if (n === 1) return arr.map((v) => [v]); 4 | const result = []; 5 | 6 | // 요소를 순환 7 | arr.forEach((fixed, idx, arr) => { 8 | // 현재 index 이후 요소를 추출 9 | // index번째는 선택된 요소 10 | const rest = arr.slice(idx + 1); 11 | // 선택된 요소 이전 요소들을 제외하고 재귀 호출 12 | const combis = combinations(rest, n - 1); 13 | // 선택된 요소와 재귀 호출을 통해 구한 조합을 합침 14 | const combine = combis.map((v) => [fixed, ...v]); 15 | // 결과 값을 추가 16 | result.push(...combine); 17 | }); 18 | 19 | // 결과 반환 20 | return result; 21 | } 22 | 23 | console.log(combinations([1, 2, 3, 4], 2)); // [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4], [3, 4]] 24 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/max-heap.js: -------------------------------------------------------------------------------- 1 | class MaxHeap { 2 | constructor() { 3 | this.items = []; 4 | } 5 | 6 | size() { 7 | return this.items.length; 8 | } 9 | 10 | push(item) { 11 | this.items.push(item); 12 | this.bubbleUp(); 13 | } 14 | 15 | pop() { 16 | if (this.size() === 0) { 17 | return null; 18 | } 19 | 20 | const min = this.items[0]; 21 | this.items[0] = this.items[this.size() - 1]; 22 | this.items.pop(); 23 | this.bubbleDown(); 24 | return min; 25 | } 26 | 27 | swap(a, b) { 28 | [this.items[a], this.items[b]] = [this.items[b], this.items[a]]; 29 | } 30 | 31 | bubbleUp() { 32 | let index = this.size() - 1; 33 | while (index > 0) { 34 | const parentIndex = Math.floor((index - 1) / 2); 35 | if (this.items[parentIndex] >= this.items[index]) { 36 | break; 37 | } 38 | this.swap(index, parentIndex); 39 | index = parentIndex; 40 | } 41 | } 42 | 43 | bubbleDown() { 44 | let index = 0; 45 | while (index * 2 + 1 < this.size()) { 46 | let leftChild = index * 2 + 1; 47 | let rightChild = index * 2 + 2; 48 | let smallerChild = 49 | rightChild < this.size() && 50 | this.items[rightChild] > this.items[leftChild] 51 | ? rightChild 52 | : leftChild; 53 | 54 | if (this.items[index] >= this.items[smallerChild]) { 55 | break; 56 | } 57 | 58 | this.swap(index, smallerChild); 59 | index = smallerChild; 60 | } 61 | } 62 | } 63 | 64 | const heap = new MaxHeap(); 65 | heap.push(5); 66 | heap.push(3); 67 | heap.push(10); 68 | heap.push(1); 69 | 70 | console.log(heap.pop()); // 10 71 | console.log(heap.pop()); // 5 72 | console.log(heap.pop()); // 3 73 | 74 | heap.push(2); 75 | heap.push(4); 76 | 77 | console.log(heap.pop()); // 4 78 | console.log(heap.pop()); // 2 79 | console.log(heap.pop()); // 1 80 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/min-heap.js: -------------------------------------------------------------------------------- 1 | class MinHeap { 2 | constructor() { 3 | this.items = []; 4 | } 5 | 6 | size() { 7 | return this.items.length; 8 | } 9 | 10 | push(item) { 11 | this.items.push(item); 12 | this.bubbleUp(); 13 | } 14 | 15 | pop() { 16 | if (this.size() === 0) { 17 | return null; 18 | } 19 | 20 | const min = this.items[0]; 21 | this.items[0] = this.items[this.size() - 1]; 22 | this.items.pop(); 23 | this.bubbleDown(); 24 | return min; 25 | } 26 | 27 | swap(a, b) { 28 | [this.items[a], this.items[b]] = [this.items[b], this.items[a]]; 29 | } 30 | 31 | bubbleUp() { 32 | let index = this.size() - 1; 33 | while (index > 0) { 34 | const parentIndex = Math.floor((index - 1) / 2); 35 | if (this.items[parentIndex] <= this.items[index]) { 36 | break; 37 | } 38 | this.swap(index, parentIndex); 39 | index = parentIndex; 40 | } 41 | } 42 | 43 | bubbleDown() { 44 | let index = 0; 45 | while (index * 2 + 1 < this.size()) { 46 | let leftChild = index * 2 + 1; 47 | let rightChild = index * 2 + 2; 48 | let smallerChild = 49 | rightChild < this.size() && 50 | this.items[rightChild] < this.items[leftChild] 51 | ? rightChild 52 | : leftChild; 53 | 54 | if (this.items[index] <= this.items[smallerChild]) { 55 | break; 56 | } 57 | 58 | this.swap(index, smallerChild); 59 | index = smallerChild; 60 | } 61 | } 62 | } 63 | 64 | const heap = new MinHeap(); 65 | heap.push(5); 66 | heap.push(3); 67 | heap.push(10); 68 | heap.push(1); 69 | 70 | console.log(heap.pop()); // 1 71 | console.log(heap.pop()); // 3 72 | console.log(heap.pop()); // 5 73 | 74 | heap.push(2); 75 | heap.push(4); 76 | 77 | console.log(heap.pop()); // 2 78 | console.log(heap.pop()); // 4 79 | console.log(heap.pop()); // 10 80 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/permutations.js: -------------------------------------------------------------------------------- 1 | function permutations(arr, n) { 2 | // 1개만 뽑는다면 그대로 순열을 반환하며 탈출 조건으로도 사용됩니다. 3 | if (n === 0) return [[]]; 4 | const result = []; 5 | 6 | // 요소를 순환 7 | arr.forEach((fixed, idx) => { 8 | // 현재 요소를 제외한 나머지 요소들을 복사합니다. 9 | const rest = [...arr]; 10 | rest.splice(idx, 1); 11 | 12 | // 나머지 요소들로 순열을 구합니다. 13 | const perms = permutations(rest, n - 1); 14 | 15 | // 나머지 요소들로 구한 순열에 현재 요소를 추가합니다. 16 | const combine = perms.map((p) => [fixed, ...p]); 17 | 18 | // 결과에 추가합니다. 19 | result.push(...combine); 20 | }); 21 | 22 | return result; 23 | } 24 | 25 | console.log(permutations([1, 2, 3], 2)); // [[1, 2], [1, 3], [2, 1], [2, 3], [3, 1], [3, 2]] 26 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/queue-with-array.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | push(item) { 7 | this.items.push(item); 8 | this.rear++; 9 | } 10 | 11 | first() { 12 | return this.items[this.front]; 13 | } 14 | 15 | last() { 16 | return this.items[this.rear - 1]; 17 | } 18 | 19 | pop() { 20 | return this.items[this.front++]; 21 | } 22 | 23 | isEmpty() { 24 | return this.front === this.rear; 25 | } 26 | } 27 | 28 | const queue = new Queue(); 29 | 30 | queue.push(1); 31 | queue.push(2); 32 | queue.push(3); 33 | 34 | console.log(queue.pop()); // 1 35 | console.log(queue.pop()); // 2 36 | 37 | queue.push(4); 38 | 39 | console.log(queue.first()); // 3 40 | console.log(queue.last()); // 4 41 | console.log(queue.isEmpty()); // false 42 | 43 | console.log(queue.pop()); // 3 44 | console.log(queue.pop()); // 4 45 | 46 | console.log(queue.isEmpty()); // true 47 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/queue-with-linked-list.js: -------------------------------------------------------------------------------- 1 | class Node { 2 | constructor(data) { 3 | this.data = data; // 요소의 값 4 | this.next = null; // 다음 요소를 참조 5 | } 6 | } 7 | 8 | class Queue { 9 | constructor() { 10 | this.head = null; // 첫 번째 요소 참조 11 | this.tail = null; // 마지막 요소 참조 12 | this.size = 0; // 큐의 길이 13 | } 14 | 15 | push(data) { 16 | // 새로운 요소를 생성 17 | const newNode = new Node(data); 18 | 19 | if (!this.head) { 20 | // 큐가 비어있다면 head와 tail을 모두 새로 생성한 요소로 설정 21 | this.head = newNode; 22 | this.tail = newNode; 23 | } else { 24 | // 아니면 현재 tail의 next 속성을 새로운 요소로 설정 후 tail이 새로운 요소를 참조하도록 변경 25 | this.tail.next = newNode; 26 | this.tail = newNode; 27 | } 28 | 29 | this.size++; // 큐 길이 증가 30 | } 31 | 32 | pop() { 33 | // head가 null이라면 비어있다는 뜻 34 | if (!this.head) { 35 | return null; 36 | } 37 | 38 | // 두 번째 요소를 head의 참조로 변경하면 39 | // 자연스럽게 첫 번째 요소를 사라짐 40 | const removeNode = this.head; 41 | this.head = this.head.next; 42 | 43 | // 만약 두 번째 요소가 없었다면 44 | // 큐가 비어있다는 뜻이니 tail도 null로 설정 45 | if (!this.head) { 46 | this.tail = null; 47 | } 48 | 49 | this.size--; // 큐 길이 감서 50 | 51 | // 삭제된 요소의 값을 반환 52 | return removeNode.data; 53 | } 54 | 55 | isEmpty() { 56 | return this.size === 0; 57 | } 58 | } 59 | 60 | const queue = new Queue(); 61 | 62 | queue.push(1); 63 | queue.push(2); 64 | queue.push(3); 65 | 66 | console.log(queue.pop()); // 1 67 | console.log(queue.pop()); // 2 68 | 69 | queue.push(4); 70 | 71 | console.log(queue.head.data); // 3 72 | console.log(queue.tail.data); // 4 73 | console.log(queue.isEmpty()); // false 74 | 75 | console.log(queue.pop()); // 3 76 | console.log(queue.pop()); // 4 77 | 78 | console.log(queue.isEmpty()); // true 79 | -------------------------------------------------------------------------------- /algorithm-with-data-structure/stack.js: -------------------------------------------------------------------------------- 1 | // 1. 자료구조 개념: 2 | // 스택(Stack)은 선입후출(FILO, First In Last Out) 원칙에 따라 작동하는 자료구조입니다. 3 | // 새로운 요소는 스택의 상단에 추가되고, 상단의 요소만이 삭제될 수 있습니다. 4 | 5 | // 2. 예시 입력 / 출력: 6 | // 입력: [1, 2, 3, 4] 7 | // 출력: [1, 2, 3] (4를 pop한 결과) 8 | 9 | // 3. 자료구조의 시간 복잡도: 10 | // - Push: O(1) 11 | // - Pop: O(1) 12 | // - Top: O(1) 13 | 14 | // 4. 해당 자료구조로 풀 수 있는 문제 예시: 15 | // - 괄호 짝 맞추기 16 | // - 브라우저 뒤로 가기 기능 17 | // - 트리의 깊이 우선 탐색(DFS) 18 | 19 | // 5. 상세과정: 20 | // - 스택 생성: 리스트를 초기화하여 스택으로 사용 21 | // - Push: 리스트의 append 메소드를 사용하여 요소를 스택의 맨 위에 추가 22 | // - Pop: 리스트의 pop 메소드를 사용하여 스택의 맨 위 요소를 삭제하고 반환 23 | // - Top: 리스트의 마지막 요소를 참조하여 스택의 맨 위 요소를 확인 24 | 25 | // 스택 생성 26 | const stack = []; 27 | 28 | // Push 연산: 요소를 스택의 맨 위에 추가 29 | stack.push(1); 30 | stack.push(2); 31 | stack.push(3); 32 | stack.push(4); 33 | console.log(stack); // 출력: [1, 2, 3, 4] 34 | 35 | // Top 연산: 스택의 맨 위 요소 확인 36 | console.log(stack[stack.length - 1]); // 출력: 4 37 | 38 | // Pop 연산: 스택의 맨 위 요소 삭제 및 반환 39 | stack.pop(); 40 | console.log(stack); // 출력: [1, 2, 3] 41 | -------------------------------------------------------------------------------- /solution/01.js: -------------------------------------------------------------------------------- 1 | function solution(arr) { 2 | arr.sort((a, b) => a - b); 3 | return arr; 4 | } 5 | 6 | console.log(solution([1, -5, 2, 4, 3])); // [-5, 1, 2, 3, 4] 7 | console.log(solution([2, 1, 1, 3, 2, 5, 4])); // [1, 1, 2, 2, 3, 4, 5] 8 | console.log(solution([1, 6, 7])); // [1, 6, 7] 9 | -------------------------------------------------------------------------------- /solution/02.js: -------------------------------------------------------------------------------- 1 | function solution(arr) { 2 | const uniqueArr = [...new Set(arr)]; // ➊ 중복값 제거 3 | uniqueArr.sort((a, b) => b - a); // ➋ 내림차순 정렬 4 | return uniqueArr; 5 | } 6 | 7 | // TEST 코드 입니다. 주석을 풀고 실행시켜 보세요 8 | // console.log(solution([4, 2, 2, 1, 3, 4])); // 반환값 : [4, 3, 2, 1] 9 | // console.log(solution([2, 1, 1, 3, 2, 5, 4])); // 반환값 : [5, 4, 3, 2, 1] 10 | -------------------------------------------------------------------------------- /solution/03.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/68644 2 | 3 | function solution(numbers) { 4 | const ret = []; // ➊ 빈 배열 생성 5 | // ➋ 두 수를 선택하는 모든 경우의 수를 반복문으로 구함 6 | for (let i = 0; i < numbers.length; i++) { 7 | for (let j = 0; j < i; j++) { 8 | // ➌ 두 수를 더한 결과를 새로운 배열에 추가 9 | ret.push(numbers[i] + numbers[j]); 10 | } 11 | } 12 | // ➍ 중복된 값을 제거하고, 오름차순으로 정렬 후 반환 13 | return [...new Set(ret)].sort((a, b) => a - b); 14 | } 15 | 16 | // TEST 코드입니다. 주석을 풀어서 실행시켜 보세요 17 | // console.log(solution([2, 1, 3, 4, 1])); // 반환값 : [2, 3, 4, 5, 6, 7] 18 | // console.log(solution([5, 0, 2, 7])); // 반환값 : [2, 5, 7, 9, 12] 19 | -------------------------------------------------------------------------------- /solution/04.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/42840 2 | 3 | function solution(answers) { 4 | // ➊ 수포자들의 패턴 5 | const patterns = [ 6 | [1, 2, 3, 4, 5], // 1번 수포자의 찍기 패턴 7 | [2, 1, 2, 3, 2, 4, 2, 5], // 2번 수포자의 찍기 패턴 8 | [3, 3, 1, 1, 2, 2, 4, 4, 5, 5], // 3번 수포자의 찍기 패턴 9 | ]; 10 | 11 | // ➋ 수포자들의 점수를 저장할 배열 12 | const scores = [0, 0, 0]; 13 | 14 | // ➌ 각 수포자의 패턴과 정답이 얼마나 일치하는지 확인 15 | for (const [i, answer] of answers.entries()) { 16 | for (const [j, pattern] of patterns.entries()) { 17 | if (answer === pattern[i % pattern.length]) { 18 | scores[j] += 1; 19 | } 20 | } 21 | } 22 | 23 | // ➍ 가장 높은 점수 저장 24 | const maxScore = Math.max(...scores); 25 | 26 | // ➎ 가장 높은 점수를 받은 수포자들의 번호를 찾아서 배열에 담음 27 | const highestScores = []; 28 | for (let i = 0; i < scores.length; i++) { 29 | if (scores[i] === maxScore) { 30 | highestScores.push(i + 1); 31 | } 32 | } 33 | 34 | return highestScores; 35 | } 36 | -------------------------------------------------------------------------------- /solution/05.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/12949 2 | 3 | function solution(arr1, arr2) { 4 | // ➊ 행렬 arr1과 arr2의 행과 열의 수 5 | const r1 = arr1.length; 6 | const c1 = arr1[0].length; 7 | 8 | const r2 = arr2.length; 9 | const c2 = arr2[0].length; 10 | 11 | // ➋ 결과를 저장할 2차원 배열 초기화 12 | const ret = []; 13 | for (let i = 0; i < r1; i++) { 14 | ret.push(new Array(c2).fill(0)); 15 | } 16 | 17 | // ➌ 첫 번째 행렬 arr1의 각 행과 두 번째 행렬 arr2의 각 열에 대해 18 | for (let i = 0; i < r1; i++) { 19 | for (let j = 0; j < c2; j++) { 20 | // ➍ 두 행렬의 데이터를 곱해 결과 배열에 더해줌 21 | for (let k = 0; k < c1; k++) { 22 | ret[i][j] += arr1[i][k] * arr2[k][j]; 23 | } 24 | } 25 | } 26 | 27 | return ret; 28 | } 29 | -------------------------------------------------------------------------------- /solution/06.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/42889 2 | 3 | function solution(N, stages) { 4 | // ➊ 스테이지별 도전자 수를 구함 5 | const challenger = new Array(N + 2).fill(0); 6 | for (const stage of stages) { 7 | challenger[stage] += 1; 8 | } 9 | 10 | // ➋ 스테이지별 실패한 사용자 수 계산 11 | const fails = {}; 12 | let total = stages.length; 13 | 14 | // ➌ 각 스테이지를 순회하며, 실패율 계산 15 | for (let i = 1; i <= N; i++) { 16 | if (challenger[i] === 0) { 17 | // ➍ 도전한 사람이 없는 경우, 실패율은 0 18 | fails[i] = 0; 19 | continue; 20 | } 21 | 22 | // ➎ 실패율 계산 23 | fails[i] = challenger[i] / total; 24 | 25 | // ➏ 다음 스테이지 실패율을 구하기 위해 현재 스테이지의 인원을 뺌 26 | total -= challenger[i]; 27 | } 28 | 29 | // ➐ 실패율이 높은 스테이지부터 내림차순으로 정렬 30 | const result = Object.entries(fails).sort((a, b) => b[1] - a[1]); 31 | 32 | // ➑ 스테이지 번호만 반환 33 | return result.map((v) => Number(v[0])); 34 | } 35 | -------------------------------------------------------------------------------- /solution/07.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/49994 2 | 3 | function isValidMove(nx, ny) { 4 | // ➊ 좌표평면을 벗어나는지 체크하는 함수 5 | return nx >= -5 && nx <= 5 && ny >= -5 && ny <= 5; 6 | } 7 | 8 | function updateLocation(x, y, dir) { 9 | // ➋ 명령어를 통해 다음 좌표 결정 10 | switch (dir) { 11 | case "U": 12 | return [x, y + 1]; 13 | case "D": 14 | return [x, y - 1]; 15 | case "R": 16 | return [x + 1, y]; 17 | case "L": 18 | return [x - 1, y]; 19 | } 20 | } 21 | 22 | function solution(dirs) { 23 | let x = 0; 24 | let y = 0; 25 | 26 | const visited = new Set(); // ➌ 겹치는 좌표는 1개로 처리하기 위함 27 | for (const dir of dirs) { 28 | // ➍ 주어진 명령어로 움직이면서 좌표 저장 29 | const [nx, ny] = updateLocation(x, y, dir); 30 | 31 | if (!isValidMove(nx, ny)) { 32 | // ➎ 벗어난 좌표는 인정하지 않음 33 | continue; 34 | } 35 | 36 | // ➏ A에서 B로 간 경우 B에서 A도 추가해야 함(총 경로의 개수는 방향성이 없음) 37 | visited.add(`${x}${y}${nx}${ny}`); 38 | visited.add(`${nx}${ny}${x}${y}`); 39 | 40 | [x, y] = [nx, ny]; // ➐ 좌표를 이동했으므로 업데이트 41 | } 42 | 43 | return visited.size / 2; 44 | } 45 | -------------------------------------------------------------------------------- /solution/08.js: -------------------------------------------------------------------------------- 1 | function solution(s) { 2 | const stack = []; 3 | for (const c of s) { 4 | if (c == "(") { 5 | stack.push(c); 6 | } else if (c == ")") { 7 | if (stack.length === 0) { 8 | return false; 9 | } else { 10 | stack.pop(); 11 | } 12 | } 13 | } 14 | 15 | return stack.length === 0; 16 | } 17 | 18 | // TEST 코드 입니다. 주석을 풀고 실행시켜보세요 19 | // console.log(solution("(())()")); // 반환값 : true 20 | // console.log(solution("((())()")); // 반환값 : false 21 | -------------------------------------------------------------------------------- /solution/09.js: -------------------------------------------------------------------------------- 1 | function solution(decimal) { 2 | const stack = []; 3 | 4 | while (decimal > 0) { 5 | const remainder = decimal % 2; 6 | stack.push(remainder); 7 | decimal = Math.floor(decimal / 2); 8 | } 9 | 10 | let binary = ""; 11 | while (stack.length > 0) { 12 | binary += stack.pop(); 13 | } 14 | 15 | return binary; 16 | } 17 | 18 | // TEST 코드 입니다. 주석을 풀고 실행시켜보세요 19 | // console.log(solution(10)); // 반환값 : 1010 20 | // console.log(solution(27)); // 반환값 : 11011 21 | // console.log(solution(12345)); // 반환값 : 11000000111001 22 | -------------------------------------------------------------------------------- /solution/10.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/76502 2 | 3 | function solution(s) { 4 | const n = s.length; 5 | let answer = 0; 6 | 7 | for (let i = 0; i < s.length; i++) { 8 | const stack = []; 9 | 10 | let isCorrect = true; 11 | for (let j = 0; j < n; j++) { 12 | // ➊ 괄호 문자열을 회전시키면서 참조 13 | const c = s[(i + j) % n]; 14 | 15 | if (c === "[" || c === "(" || c === "{") { 16 | // ➋ 열린 괄호는 푸시 17 | stack.push(c); 18 | } else { 19 | if (stack.length === 0) { 20 | // ➌ 여는 괄호가 없는 경우 21 | isCorrect = false; 22 | break; 23 | } 24 | 25 | // ➍ 닫힌 괄호는 스택의 top과 짝이 맞는지 비교 26 | const top = stack[stack.length - 1]; 27 | if (c === "]" && top === "[") { 28 | stack.pop(); 29 | } else if (c === ")" && top === "(") { 30 | stack.pop(); 31 | } else if (c === "}" && top === "{") { 32 | stack.pop(); 33 | } else { 34 | isCorrect = false; 35 | break; 36 | } 37 | } 38 | } 39 | 40 | // ➎ 모든 괄호의 짝이 맞는 경우 41 | if (isCorrect && stack.length === 0) { 42 | answer += 1; 43 | } 44 | } 45 | 46 | return answer; 47 | } 48 | -------------------------------------------------------------------------------- /solution/100.js: -------------------------------------------------------------------------------- 1 | // 로또의 최고 순위와 최저 순위 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/77484 3 | 4 | function solution(lottos, win_nums) { 5 | const rank = [6, 6, 5, 4, 3, 2, 1]; // ➊ 일치하는 번호의 개수에 따른 순위 6 | 7 | let zeroCount = lottos.filter(num => num === 0).length; // ➋ 로또 번호 중 0의 개수 8 | let matchCount = lottos.filter(num => win_nums.includes(num)).length; // ➌ 당첨 번호와 일치하는 번호의 개수 9 | 10 | // ➍ 최고 순위와 최저 순위를 계산 11 | const maxRank = rank[matchCount + zeroCount]; 12 | const minRank = rank[matchCount]; 13 | 14 | return [maxRank, minRank]; // ➎ 결과 반환 15 | } 16 | -------------------------------------------------------------------------------- /solution/11.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/12973 2 | 3 | function solution(s) { 4 | const stack = []; // 스택 초기화 5 | 6 | for (const c of s) { 7 | // ➊ 스택이 비어 있지 않고, 현재 문자와 스택의 맨 위 문자가 같으면 8 | if (stack.length > 0 && stack[stack.length - 1] === c) { 9 | stack.pop(); // ➋ 스택의 맨 위 문자 제거 10 | } else { 11 | stack.push(c); // ➌ 스택에 현재 문자 추가 12 | } 13 | } 14 | 15 | // ➍ 스택이 비어 있으면 1, 그렇지 않으면 0 반환 16 | return stack.length === 0 ? 1 : 0; 17 | } 18 | -------------------------------------------------------------------------------- /solution/12.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/42584 2 | 3 | function solution(prices) { 4 | const n = prices.length; 5 | const answer = new Array(n).fill(0); // ➊ 가격이 떨어지지 않은 기간을 저장할 배열 6 | 7 | // 스택(stack)을 사용해 이전 가격과 현재 가격을 비교 8 | const stack = [0]; // ➋ 스택 초기화 9 | for (let i = 1; i < n; i++) { 10 | while (stack.length > 0 && prices[i] < prices[stack[stack.length - 1]]) { 11 | // ➌ 가격이 떨어졌으므로 이전 가격의 기간을 계산 12 | const j = stack.pop(); 13 | answer[j] = i - j; 14 | } 15 | stack.push(i); 16 | } 17 | 18 | // ➍ 스택에 남아 있는 가격들은 가격이 떨어지지 않은 경우 19 | while (stack.length > 0) { 20 | const j = stack.pop(); 21 | answer[j] = n - 1 - j; 22 | } 23 | 24 | return answer; 25 | } 26 | -------------------------------------------------------------------------------- /solution/13.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/64061 2 | 3 | function solution(board, moves) { 4 | // ➊ 각 열에 대한 스택을 생성합니다. 5 | const lanes = [...new Array(board[0].length)].map(() => []); 6 | 7 | // ➋ board를 역순으로 탐색하며, 각 열의 인형을 lanes에 추가합니다. 8 | for (let i = board.length - 1; i >= 0; i--) { 9 | for (let j = 0; j < board[0].length; j++) { 10 | if (board[i][j]) { 11 | lanes[j].push(board[i][j]); 12 | } 13 | } 14 | } 15 | 16 | // ➌ 인형을 담을 bucket을 생성합니다. 17 | const bucket = []; 18 | 19 | // ➍ 사라진 인형의 총 개수를 저장할 변수를 초기화합니다. 20 | let answer = 0; 21 | 22 | // ➎ moves를 순회하며, 각 열에서 인형을 뽑아 bucket에 추가합니다. 23 | for (const m of moves) { 24 | if (lanes[m - 1].length > 0) { 25 | // 해당 열에 인형이 있는 경우 26 | const doll = lanes[m - 1].pop(); 27 | 28 | if (bucket.length > 0 && bucket[bucket.length - 1] === doll) { 29 | // ➏ 바구니에 인형이 있고, 가장 위에 있는 인형과 같은 경우 30 | bucket.pop(); 31 | answer += 2; 32 | } else { 33 | // ➐ 바구니에 인형이 없거나, 가장 위에 있는 인형과 다른 경우 34 | bucket.push(doll); 35 | } 36 | } 37 | } 38 | 39 | return answer; 40 | } 41 | -------------------------------------------------------------------------------- /solution/14.js: -------------------------------------------------------------------------------- 1 | // https://school.programmers.co.kr/learn/courses/30/lessons/81303 2 | 3 | function solution(n, k, cmd) { 4 | // ➊ 삭제된 행의 인덱스를 저장하는 리스트 5 | const deleted = []; 6 | 7 | // ➋ 링크드 리스트에서 각 행 위아래의 행의 인덱스를 저장하는 리스트 8 | const up = [...new Array(n + 2)].map((_, i) => i - 1); 9 | const down = [...new Array(n + 1)].map((_, i) => i + 1); 10 | 11 | // ➌ 현재 위치를 나타내는 인덱스 12 | k += 1; 13 | 14 | // ➍ 주어진 명령어(cmd) 리스트를 하나씩 처리 15 | for (const item of cmd) { 16 | // ➎ 현재 위치를 삭제하고 그다음 위치로 이동 17 | if (item.startsWith("C")) { 18 | deleted.push(k); 19 | up[down[k]] = up[k]; 20 | down[up[k]] = down[k]; 21 | k = n < down[k] ? up[k] : down[k]; 22 | } 23 | 24 | // ➏ 가장 최근에 삭제된 행을 복원 25 | else if (item.startsWith("Z")) { 26 | const restore = deleted.pop(); 27 | down[up[restore]] = restore; 28 | up[down[restore]] = restore; 29 | } 30 | 31 | // ➐ U 또는 D를 사용해 현재 위치를 위아래로 이동 32 | else { 33 | const [action, num] = item.split(" "); 34 | if (action === "U") { 35 | for (let i = 0; i < num; i++) { 36 | k = up[k]; 37 | } 38 | } else { 39 | for (let i = 0; i < num; i++) { 40 | k = down[k]; 41 | } 42 | } 43 | } 44 | } 45 | 46 | // ➑ 삭제된 행의 위치에 'X'를, 그렇지 않은 행의 위치에 'O'를 포함하는 문자열 반환 47 | const answer = new Array(n).fill("O"); 48 | for (const i of deleted) { 49 | answer[i - 1] = "X"; 50 | } 51 | return answer.join(""); 52 | } 53 | -------------------------------------------------------------------------------- /solution/15.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | push(item) { 7 | this.items.push(item); 8 | this.rear++; 9 | } 10 | 11 | size() { 12 | return this.rear - this.front; 13 | } 14 | 15 | pop() { 16 | return this.items[this.front++]; 17 | } 18 | } 19 | 20 | function solution(N, K) { 21 | const queue = new Queue(); 22 | 23 | // ❶ 1부터 N까지의 번호를 deque에 추가 24 | for (let i = 1; i <= N; i++) { 25 | queue.push(i); 26 | } 27 | 28 | while (queue.size() > 1) { 29 | // ❷ deque에 하나의 요소가 남을 때까지 30 | for (let i = 0; i < K - 1; i++) { 31 | queue.push(queue.pop()); // ❸ K번째 요소를 찾기 위해 32 | // 앞에서부터 제거하고 뒤에 추가 33 | } 34 | queue.pop(); // ❹ K번째 요소 제거 35 | } 36 | 37 | return queue.pop(); // ❺ 마지막으로 남은 요소 반환 38 | } 39 | 40 | console.log(solution(5, 2)); // 3 41 | -------------------------------------------------------------------------------- /solution/16.js: -------------------------------------------------------------------------------- 1 | function solution(progresses, speeds) { 2 | const answer = []; 3 | const n = progresses.length; 4 | // ➊ 각 작업의 배포 가능일 계산 5 | const daysLeft = progresses.map((progress, index) => Math.ceil((100 - progress) / speeds[index])); 6 | 7 | let count = 0; // ➋ 배포될 작업의 수 카운트 8 | let maxDay = daysLeft[0]; // ➌ 현재 배포될 작업 중 가장 늦게 배포될 작업의 가능일 9 | 10 | for (let i = 0; i < n; i++) { 11 | if (daysLeft[i] <= maxDay) { // ➍ 배포 가능일이 가장 늦은 배포일보다 빠르면 12 | count++; 13 | } else { // ➎ 배포 예정일이 기준 배포일보다 느리면 14 | answer.push(count); 15 | count = 1; 16 | maxDay = daysLeft[i]; 17 | } 18 | } 19 | 20 | answer.push(count); // ➏ 마지막으로 카운트된 작업들을 함께 배포 21 | return answer; 22 | } 23 | -------------------------------------------------------------------------------- /solution/17.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | // 생성자를 이용해 편하게 초기화 7 | constructor(array) { 8 | this.items = array; 9 | this.rear = array.length; 10 | } 11 | 12 | push(item) { 13 | this.items.push(item); 14 | this.rear++; 15 | } 16 | 17 | pop() { 18 | return this.items[this.front++]; 19 | } 20 | 21 | first() { 22 | return this.items[this.front]; 23 | } 24 | 25 | isEmpty() { 26 | return this.front === this.rear; 27 | } 28 | } 29 | 30 | 31 | function solution(cards1, cards2, goal) { 32 | // cards와 goal을 Queue로 변환 33 | cards1 = new Queue(cards1); 34 | cards2 = new Queue(cards2); 35 | goal = new Queue(goal); 36 | 37 | // ➊ goal의 문자열을 순차적으로 순회 38 | while (!goal.isEmpty()) { 39 | if (!cards1.isEmpty() && cards1.first() === goal.first()) { // ➋ card1의 front와 일치하는 경우 40 | cards1.pop(); 41 | goal.pop(); 42 | } else if (!cards2.isEmpty() && cards2.first() === goal.first()) { // ➌ card2의 front와 일치하는 경우 43 | cards2.pop(); 44 | goal.pop(); 45 | } else { 46 | break; 47 | } 48 | } 49 | 50 | return goal.isEmpty() ? "Yes" : "No"; // ➍ goal이 비었으면 “Yes” 아니면 “No”를 반환 51 | } 52 | -------------------------------------------------------------------------------- /solution/18.js: -------------------------------------------------------------------------------- 1 | function countSort(arr, k) { 2 | // ➊ 해시 테이블 생성 및 초기화 3 | const hashtable = new Array(k + 1).fill(0); 4 | for (const num of arr) { 5 | // 현재 원소의 값이 k 이하인 때에만 처리 6 | if (num <= k) { 7 | // 현재 원소의 값을 인덱스로 해 해당 인덱스의 해시 테이블 값을 1로 설정 8 | hashtable[num] = 1; 9 | } 10 | } 11 | 12 | return hashtable; 13 | } 14 | 15 | function solution(arr, target) { 16 | const hashtable = countSort(arr, target); 17 | for (const num of arr) { 18 | const complement = target - num; 19 | // ➋ target에서 현재 원소를 뺀 값이 해시 테이블에 있는지 확인 20 | if ( 21 | complement !== num && 22 | complement >= 0 && 23 | complement <= target && 24 | hashtable[complement] === 1 25 | ) { 26 | return true; 27 | } 28 | } 29 | 30 | return false; 31 | } 32 | 33 | console.log(solution([1, 2, 3, 4, 8], 6)); // true 34 | console.log(solution([2, 3, 5, 9], 10)); // false 35 | -------------------------------------------------------------------------------- /solution/19.js: -------------------------------------------------------------------------------- 1 | // ➊ polynomial hash를 구현한 부분 2 | function polynomialHash(str) { 3 | const p = 31; // 소수 4 | const m = 1_000_000_007; // 버킷 크기 5 | let hashValue = 0; 6 | for (let i = 0; i < str.length; i++) { 7 | hashValue = (hashValue * p + str.charCodeAt(i)) % m; 8 | } 9 | return hashValue; 10 | } 11 | 12 | function solution(stringList, queryList) { 13 | // ➋ stringList의 각 문자열에 대해 다항 해시값을 계산 14 | const hashList = stringList.map((str) => polynomialHash(str)); 15 | 16 | // ➌ queryList의 각 문자열이 stringList에 있는지 확인 17 | const result = []; 18 | for (const query of queryList) { 19 | const queryHash = polynomialHash(query); 20 | if (hashList.includes(queryHash)) { 21 | result.push(true); 22 | } else { 23 | result.push(false); 24 | } 25 | } 26 | 27 | return result; 28 | } 29 | 30 | 31 | console.log(solution(['apple', 'banana', 'cherry'], ['banana', 'kiwi', 'melon', 'apple'])); -------------------------------------------------------------------------------- /solution/20.js: -------------------------------------------------------------------------------- 1 | function solution(participant, completion) { 2 | // ➊ 해시 테이블 생성 3 | const obj = {}; 4 | 5 | // ➋ 참가자들의 이름을 해시 테이블에 추가 6 | for (const p of participant) { 7 | if (obj[p]) { 8 | obj[p] += 1; 9 | } else { 10 | obj[p] = 1; 11 | } 12 | } 13 | 14 | // ➌ 완주한 선수들의 이름을 키로 하는 값을 1씩 감소 15 | for (const c of completion) { 16 | obj[c] -= 1; 17 | } 18 | 19 | // ➍ 해시 테이블에 남아 있는 선수가 완주하지 못한 선수 20 | for (const key in obj) { 21 | if (obj[key] > 0) { 22 | return key; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /solution/21.js: -------------------------------------------------------------------------------- 1 | function isShallowEqual(object1, object2) { 2 | const objKeys1 = Object.keys(object1); 3 | const objKeys2 = Object.keys(object2); 4 | 5 | if (objKeys1.length !== objKeys2.length) return false; 6 | 7 | for (const key of objKeys1) { 8 | const value1 = object1[key]; 9 | const value2 = object2[key]; 10 | 11 | if (value1 !== value2) { 12 | return false; 13 | } 14 | } 15 | return true; 16 | }; 17 | 18 | function solution(want, number, discount) { 19 | // ➊ want 리스트를 오브젝트로 변환 20 | const wantObj = {} 21 | for (let i = 0; i < want.length; i++) { 22 | wantObj[want[i]] = number[i]; 23 | } 24 | 25 | let answer = 0; // ➋ 총 일수를 계산할 변수 초기화 26 | 27 | // ➌ 특정일 i에 회원가입 시 할인받을 수 있는 품목 체크 28 | for (let i = 0; i < discount.length - 9; i++) { 29 | const discount10d = {}; // ➍ i일 회원가입 시 할인받는 제품 및 개수를 담을 오브젝트 30 | 31 | // ➎ i일 회원가입 시 할인받는 제품 및 개수로 오브젝트 구성 32 | for (let j = i; j < i + 10; j++) { 33 | if (wantObj[discount[j]]) { 34 | // discount10d[discount[j]]가 비어있다면 0으로 기본값 설정 35 | discount10d[discount[j]] = (discount10d[discount[j]] || 0) + 1; 36 | } 37 | } 38 | 39 | // ➏ 할인하는 상품의 개수가 원하는 수량과 일치하면 정답 변수에 1 추가 40 | if (isShallowEqual(discount10d, wantObj)) { 41 | answer += 1; 42 | } 43 | } 44 | 45 | return answer; 46 | } -------------------------------------------------------------------------------- /solution/22.js: -------------------------------------------------------------------------------- 1 | function solution(record) { 2 | answer = []; 3 | uid = {}; 4 | 5 | for (line in record) { // ➊ record의 각 줄을 하나씩 처리 6 | cmd = record[line].split(" "); 7 | if (cmd[0] != "Leave") { // ➋ Enter 또는 Change인 경우 8 | uid[cmd[1]] = cmd[2]; 9 | } 10 | } 11 | 12 | for (line in record) { // ➌ record의 각 줄을 하나씩 처리 13 | cmd = record[line].split(" "); 14 | // ➍ 각 상태에 맞는 메시지를 answer에 저장 15 | if (cmd[0] == "Enter") { 16 | answer.push(uid[cmd[1]] + "님이 들어왔습니다."); 17 | } else if (cmd[0] == "Leave") { 18 | answer.push(uid[cmd[1]] + "님이 나갔습니다."); 19 | } 20 | } 21 | 22 | return answer; 23 | } 24 | -------------------------------------------------------------------------------- /solution/23.js: -------------------------------------------------------------------------------- 1 | function solution(genres, plays) { 2 | let answer = []; 3 | const genresObj = {}; 4 | const playObj = {}; 5 | 6 | // ➊ 장르별 총 재생 횟수와 각 곡의 재생 횟수 저장 7 | for (let i = 0; i < genres.length; i++) { 8 | genre = genres[i]; 9 | play = plays[i]; 10 | 11 | if (!(genre in genresObj)) { 12 | genresObj[genre] = []; 13 | playObj[genre] = 0; 14 | } 15 | 16 | genresObj[genre].push([i, play]); 17 | playObj[genre] += play; 18 | } 19 | 20 | // ➋ 총 재생 횟수가 많은 장르순으로 정렬 21 | sortedGenres = Object.keys(playObj).sort((a, b) => { 22 | return playObj[b] - playObj[a]; 23 | }); 24 | 25 | // ➌ 각 장르 내에서 노래를 재생 횟수 순으로 정렬해 최대 2곡까지 선택 26 | for (const genre of sortedGenres) { 27 | sortedSongs = genresObj[genre].sort((a, b) => { 28 | return a[1] === b[1] ? a[0] - b[0] : b[1] - a[1]; 29 | }); 30 | 31 | answer.push(...sortedSongs.slice(0, 2).map((song) => song[0])); 32 | } 33 | 34 | return answer; 35 | } 36 | -------------------------------------------------------------------------------- /solution/24.js: -------------------------------------------------------------------------------- 1 | function solution(id_list, report, k) { 2 | const reportedUser = {} // 신고당한 유저 - 신고 유저 집합을 저장할 오브젝트 3 | const count = {} // 처리 결과 메일을 받은 유저 - 받은 횟수를 저장할 오브젝트 4 | 5 | // ➊ 신고 기록 순회 6 | for (const r of report) { 7 | const [userId, reportedId] = r.split(' '); 8 | if (reportedUser[reportedId] === undefined) { // ➋ 신고당한 기록이 없다면 9 | reportedUser[reportedId] = new Set(); 10 | } 11 | reportedUser[reportedId].add(userId); // ➌ 신고한 사람의 아이디를 집합에 담아 12 | // 오브젝트에 연결 13 | } 14 | 15 | for (const reportedId of Object.keys(reportedUser)) { // ➍ 신고당한 유저별로 신고당한 횟수를 확인 16 | if (reportedUser[reportedId].size >= k) { // ➎ 정지 기준에 만족하는지 확인 17 | for (const uid of reportedUser[reportedId]) { 18 | count[uid] = (count[uid] || 0) + 1 19 | } 20 | } 21 | } 22 | 23 | const answer = []; 24 | for (let i = 0; i < id_list.length; i++) { // ➏ 각 아이디별 메일을 받은 횟수를 순서대로 정리 25 | answer.push(count[id_list[i]] || 0); 26 | } 27 | 28 | return answer; 29 | } 30 | -------------------------------------------------------------------------------- /solution/25.js: -------------------------------------------------------------------------------- 1 | function combinations(arr, n) { 2 | if (n === 1) return arr.map((v) => [v]); 3 | const result = []; 4 | 5 | arr.forEach((fixed, idx, arr) => { 6 | const rest = arr.slice(idx + 1); 7 | const combis = combinations(rest, n - 1); 8 | const combine = combis.map((v) => [fixed, ...v]); 9 | result.push(...combine); 10 | }); 11 | 12 | return result; 13 | } 14 | 15 | function solution(orders, course) { 16 | const answer = []; 17 | 18 | for (const c of course) { // ➊ 각 코스 요리 길이에 대해 19 | const menu = []; 20 | for (const order of orders) { // 모든 주문에 대해 21 | const orderArr = order.split("").sort(); // 주문을 배열로 만든 후 정렬 22 | const comb = combinations(orderArr, c); // ➋ 조합(combination)을 이용해 가능한 메뉴 구성을 모두 구함 23 | menu.push(...comb); 24 | } 25 | 26 | // ➌ 각 메뉴 구성이 몇 번 주문되었는지 세어줌 27 | const counter = {}; 28 | for (const m of menu) { 29 | const key = m.join(''); // 배열을 문자열로 변환 30 | counter[key] = (counter[key] || 0) + 1; 31 | } 32 | 33 | const max = Math.max(...Object.values(counter)); 34 | if (max > 1) { // ➍ 가장 많이 주문된 구성이 2번 이상 주문된 경우 35 | for (const [key, value] of Object.entries(counter)) { 36 | if (value === max) { // ➎ 가장 많이 주문된 구성을 찾아서 37 | answer.push(key); // ➏ 정답 리스트에 추가 38 | } 39 | } 40 | } 41 | } 42 | 43 | // ➐ 오름차순 정렬 후 반환 44 | return answer.sort(); 45 | } 46 | 47 | console.log(solution(['ABCFG', 'AC', 'CDE', 'ACDE', 'BCFG', 'ACDEH'], [2, 3, 4])); // ["AC", "ACDE", "BCFG", "CDE"] -------------------------------------------------------------------------------- /solution/26.js: -------------------------------------------------------------------------------- 1 | function preorder(nodes, idx) { 2 | // idx가 노드 리스트의 길이보다 작을 때 3 | if (idx < nodes.length) { 4 | // 루트 노드를 출력한 다음, 왼쪽 서브 트리와 오른쪽 서브 트리를 재귀 호출하여 출력 순서대로 이어붙임 5 | let ret = `${nodes[idx]} `; 6 | ret += preorder(nodes, idx * 2 + 1); 7 | ret += preorder(nodes, idx * 2 + 2); 8 | return ret; 9 | } 10 | 11 | // idx >= len(nodes)일 때는 빈 문자열 반환 12 | return ""; 13 | } 14 | 15 | function inorder(nodes, idx) { 16 | // idx가 노드 리스트의 길이보다 작을 때 17 | if (idx < nodes.length) { 18 | // 왼쪽 서브 트리를 먼저 재귀 호출하여 출력 순서대로 이어붙임 19 | let ret = inorder(nodes, idx * 2 + 1); 20 | // 루트 노드를 출력한 다음, 오른쪽 서브 트리를 재귀 호출하여 출력 순서대로 이어붙임 21 | ret += `${nodes[idx]} `; 22 | ret += inorder(nodes, idx * 2 + 2); 23 | return ret; 24 | } 25 | 26 | // idx >= len(nodes)일 때는 빈 문자열 반환 27 | return ""; 28 | } 29 | 30 | function postorder(nodes, idx) { 31 | // idx가 노드 리스트의 길이보다 작을 때 32 | if (idx < nodes.length) { 33 | // 왼쪽 서브 트리와 오른쪽 서브 트리를 재귀 호출하여 출력 순서대로 이어붙임 34 | let ret = postorder(nodes, idx * 2 + 1); 35 | ret += postorder(nodes, idx * 2 + 2); 36 | // 루트 노드를 출력함 37 | ret += `${nodes[idx]} `; 38 | return ret; 39 | } 40 | 41 | // idx >= len(nodes)일 때는 빈 문자열 반환 42 | return ""; 43 | } 44 | 45 | function solution(nodes) { 46 | // 전위 순회, 중위 순회, 후위 순회 결과 계산 47 | // 노드 리스트와 루트 노드의 인덱스를 매개변수로 각각 호출 48 | return [ 49 | preorder(nodes, 0).slice(0, -1), // 마지막 공백 제거 50 | inorder(nodes, 0).slice(0, -1), // 마지막 공백 제거 51 | postorder(nodes, 0).slice(0, -1), // 마지막 공백 제거 52 | ]; 53 | } 54 | 55 | console.log(solution([1, 2, 3, 4, 5, 6, 7])); // ['1 2 4 5 3 6 7', '4 2 5 1 6 3 7', '4 5 2 6 7 3 1'] 56 | -------------------------------------------------------------------------------- /solution/27.js: -------------------------------------------------------------------------------- 1 | // ➊ 노드 클래스 정의 2 | class Node { 3 | // ➋ 노드 클래스 생성자 4 | constructor(key) { 5 | this.left = null; 6 | this.right = null; 7 | this.val = key; 8 | } 9 | } 10 | 11 | // ➌ 이진 탐색 트리 클래스 12 | class BST { 13 | // ➍ 초기에 아무 노드도 없는 상태 14 | constructor() { 15 | this.root = null; 16 | } 17 | 18 | // ➎ 루트 노드부터 시작해서 이진 탐색 트리 규칙에 맞는 위치에 새 노드 삽입 19 | insert(key) { 20 | // 루트 노드가 없는 경우 새로운 노드를 루트 노드로 추가 21 | if (!this.root) { 22 | this.root = new Node(key); 23 | } else { 24 | let curr = this.root; 25 | while (true) { 26 | // 삽입하려는 값이 현재 노드의 값보다 작은 경우 왼쪽 자식 노드로 이동 27 | if (key < curr.val) { 28 | if (curr.left) { 29 | curr = curr.left; 30 | } else { 31 | // 현재 노드의 왼쪽 자식 노드가 없는 경우 새로운 노드 추가 32 | curr.left = new Node(key); 33 | break; 34 | } 35 | } else { 36 | // 삽입하려는 값이 현재 노드의 값보다 큰 경우 오른쪽 자식 노드로 이동 37 | if (curr.right) { 38 | curr = curr.right; 39 | } else { 40 | // 현재 노드의 오른쪽 자식 노드가 없는 경우 새로운 노드 추가 41 | curr.right = new Node(key); 42 | break; 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | // ➏ 이진 탐색 규칙에 따라 특정값이 있는지 확인(루트 노드부터 시작) 50 | search(key) { 51 | let curr = this.root; 52 | // 현재 노드가 존재하고, 찾으려는 값과 현재 노드의 값이 같지 않은 경우 반복 53 | while (curr && curr.val !== key) { 54 | // 찾으려는 값이 현재 노드의 값보다 작은 경우 왼쪽 자식 노드로 이동 55 | if (key < curr.val) { 56 | curr = curr.left; 57 | } else { 58 | // 찾으려는 값이 현재 노드의 값보다 큰 경우 오른쪽 자식 노드로 이동 59 | curr = curr.right; 60 | } 61 | } 62 | return curr; 63 | } 64 | } 65 | 66 | // ➐ list에 있는 데이터를 활용해서 이진 탐색 트리 생성, searchList 원소 탐색 67 | function solution(list, searchList) { 68 | const bst = new BST(); 69 | // 리스트의 각 요소를 이용하여 이진 탐색 트리 생성 70 | for (const key of list) { 71 | bst.insert(key); 72 | } 73 | const result = []; 74 | // 검색 리스트의 각 요소를 이진 탐색 트리에서 검색하고, 검색 결과를 리스트에 추가 75 | for (const searchVal of searchList) { 76 | if (bst.search(searchVal)) { 77 | result.push(true); 78 | } else { 79 | result.push(false); 80 | } 81 | } 82 | return result; 83 | } 84 | 85 | console.log(solution([5, 3, 8, 4, 2, 1, 7, 10], [1, 2, 5, 6])) // [true, true, true, false] 86 | -------------------------------------------------------------------------------- /solution/28.js: -------------------------------------------------------------------------------- 1 | function solution(n, a, b) { 2 | let answer = 0; 3 | while (a != b) { 4 | a = Math.ceil(a / 2); 5 | b = Math.ceil(b / 2); 6 | answer += 1; 7 | } 8 | return answer; 9 | } 10 | 11 | console.log(solution(8, 4, 7)); // 3 -------------------------------------------------------------------------------- /solution/29.js: -------------------------------------------------------------------------------- 1 | function solution(enroll, referral, seller, amount) { 2 | // ➊ parent 오브젝트 key는 enroll의 노드, value는 referral의 노드로 구성됨 3 | let parent = {}; 4 | for(let i = 0; i < enroll.length; i++) { 5 | parent[enroll[i]] = referral[i]; 6 | } 7 | 8 | // ➋ total 오브젝트 생성 및 초기화 9 | let total = {}; 10 | for(let name of enroll) { 11 | total[name] = 0; 12 | } 13 | 14 | // ➌ seller 배열과 amount 배열을 이용하여 이익 분배 15 | for(let i = 0; i < seller.length; i++) { 16 | // ➍ 판매자가 판매한 총 금액 계산 17 | let money = amount[i] * 100; 18 | let curName = seller[i]; 19 | 20 | // ➎ 판매자부터 차례대로 상위 노드로 이동하며 이익 분배 21 | while(money > 0 && curName != "-") { 22 | // ➏ 현재 판매자가 받을 금액 계산(10%를 제외한 금액) 23 | total[curName] += money - Math.floor(money / 10); 24 | curName = parent[curName]; 25 | 26 | // ➐ 10%를 제외한 금액 계산 27 | money = Math.floor(money / 10); 28 | } 29 | } 30 | 31 | // ➑ enroll 배열의 모든 노드에 대해 해당하는 이익을 배열로 반환 32 | return enroll.map(name => total[name]); 33 | } 34 | -------------------------------------------------------------------------------- /solution/30.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | push(item) { 7 | this.items.push(item); 8 | this.rear++; 9 | } 10 | 11 | pop() { 12 | return this.items[this.front++]; 13 | } 14 | 15 | isEmpty() { 16 | return this.front === this.rear; 17 | } 18 | } 19 | 20 | // ➊ 이동 가능한 좌표인지 판단하는 함수 21 | function isValidMove(ny, nx, n, m, maps) { 22 | return 0 <= ny && ny < n && 0 <= nx && nx < m && maps[ny][nx] !== 'X'; 23 | } 24 | 25 | // ➋ 방문한 적이 없으면 큐에 넣고 방문 여부 표시 26 | function appendToQueue(ny, nx, k, time, visited, q) { 27 | if (!visited[ny][nx][k]) { 28 | visited[ny][nx][k] = true; 29 | q.push([ny, nx, k, time + 1]); 30 | } 31 | } 32 | 33 | function solution(maps) { 34 | const n = maps.length; 35 | const m = maps[0].length; 36 | const visited = Array.from(Array(n), () => Array(m).fill(false).map(() => Array(2).fill(false))); 37 | 38 | // ➌ 위, 아래, 왼쪽, 오른쪽 이동 방향 39 | const dy = [-1, 1, 0, 0]; 40 | const dx = [0, 0, -1, 1]; 41 | const q = new Queue(); 42 | let endY = -1; 43 | let endX = -1; 44 | 45 | // ➍ 시작점과 도착점을 찾아 큐에 넣고 방문 여부 표시 46 | for (let i = 0; i < n; i++) { 47 | for (let j = 0; j < m; j++) { 48 | if (maps[i][j] === 'S') { // 시작점 49 | q.push([i, j, 0, 0]); 50 | visited[i][j][0] = true; 51 | } 52 | if (maps[i][j] === 'E') { // 도착점 53 | endY = i; 54 | endX = j; 55 | } 56 | } 57 | } 58 | 59 | while (!q.isEmpty()) { 60 | const [y, x, k, time] = q.pop(); // ➎ 큐에서 좌표와 이동 횟수를 꺼냄 61 | 62 | // ➏ 도착점에 도달하면 결과 반환 63 | if (y === endY && x === endX && k === 1) { 64 | return time; 65 | } 66 | 67 | // ➐ 네 방향으로 이동 68 | for (let i = 0; i < 4; i++) { 69 | const ny = y + dy[i]; 70 | const nx = x + dx[i]; 71 | 72 | // ➑ 이동 가능한 좌표인 때에만 큐에 넣음 73 | if (!isValidMove(ny, nx, n, m, maps)) { 74 | continue; 75 | } 76 | 77 | // ➒ 다음 이동 지점이 물인 경우 78 | if (maps[ny][nx] === 'L') { 79 | appendToQueue(ny, nx, 1, time, visited, q); 80 | } else { // ➓ 다음 이동 지점이 물이 아닌 경우 81 | appendToQueue(ny, nx, k, time, visited, q); 82 | } 83 | } 84 | } 85 | 86 | // ⓫ 도착점에 도달하지 못한 경우 87 | return -1; 88 | } 89 | -------------------------------------------------------------------------------- /solution/31.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | push(item) { 7 | this.items.push(item); 8 | this.rear++; 9 | } 10 | 11 | pop() { 12 | return this.items[this.front++]; 13 | } 14 | 15 | isEmpty() { 16 | return this.front === this.rear; 17 | } 18 | } 19 | 20 | // ➊ 트리 구축 함수 21 | function buildTree(info, edges) { 22 | const tree = Array.from({ length: info.length }, () => []); 23 | for (const [from, to] of edges) { 24 | tree[from].push(to); 25 | } 26 | return tree; 27 | } 28 | 29 | function solution(info, edges) { 30 | const tree = buildTree(info, edges); // ➋ 트리 생성 31 | let maxSheep = 0; // ➌ 최대 양의 수를 저장할 변수 초기화 32 | 33 | // ➍ BFS를 위한 큐 생성 및 초기 상태 설정 34 | const q = new Queue(); 35 | q.push([0, 1, 0, new Set()]); // (현재 위치, 양의 수, 늑대의 수, 방문한 노드 집합) 36 | 37 | // BFS 시작 38 | while (!q.isEmpty()) { 39 | // ➎ 큐에서 상태 가져오기 40 | const [current, sheepCount, wolfCount, visited] = q.pop(); 41 | // ➏ 최대 양의 수 업데이트 42 | maxSheep = Math.max(maxSheep, sheepCount); 43 | // ➐ 방문한 노드 집합에 현재 노드의 이웃 노드 추가 44 | for (const next of tree[current]) { 45 | visited.add(next); 46 | } 47 | // ➑ 인접한 노드들에 대해 탐색 48 | for (const next of visited) { 49 | if (info[next]) { // ➒ 늑대일 경우 50 | if (sheepCount !== wolfCount + 1) { 51 | const newVisited = new Set(visited) 52 | newVisited.delete(next) 53 | q.push([next, sheepCount, wolfCount + 1, newVisited]); 54 | } 55 | } else { // ➓ 양일 경우 56 | const newVisited = new Set(visited) 57 | newVisited.delete(next) 58 | q.push([next, sheepCount + 1, wolfCount, newVisited]); 59 | } 60 | } 61 | } 62 | 63 | return maxSheep; 64 | } 65 | -------------------------------------------------------------------------------- /solution/32.js: -------------------------------------------------------------------------------- 1 | // ➊ Node 클래스 정의 2 | class Node { 3 | constructor(info, num, left = null, right = null) { 4 | this.info = info; // 노드의 좌표 정보 저장 5 | this.left = left; // 노드의 왼쪽 자식 노드 6 | this.right = right; // 노드의 오른쪽 자식 노드 7 | this.num = num; // 노드의 번호 8 | } 9 | 10 | // ➋ 왼쪽 자식 노드가 있는지 확인하는 함수 11 | hasLeft() { 12 | return this.left !== null; 13 | } 14 | 15 | // ➌ 오른쪽 자식 노드가 있는지 확인하는 함수 16 | hasRight() { 17 | return this.right !== null; 18 | } 19 | } 20 | 21 | // ➍ 이진 트리 생성 함수 22 | function makeBT(nodeinfo) { 23 | const nodes = Array.from({ length: nodeinfo.length }, (_, i) => i + 1); // ➎ 노드의 번호 리스트 생성 24 | nodes.sort((a, b) => { 25 | const [ax, ay] = nodeinfo[a - 1]; 26 | const [bx, by] = nodeinfo[b - 1]; 27 | return ay === by ? ax - bx : by - ay; 28 | }); 29 | 30 | let root = null; 31 | for (const node of nodes) { 32 | if (!root) { 33 | root = new Node(nodeinfo[node - 1], node); 34 | } else { 35 | let parent = root; 36 | const newNode = new Node(nodeinfo[node - 1], node); 37 | while (true) { 38 | // ➏ 부모 노드의 x좌표가 더 크면 왼쪽으로 39 | if (newNode.info[0] < parent.info[0]) { 40 | if (parent.hasLeft()) { 41 | parent = parent.left; 42 | continue; 43 | } 44 | parent.left = newNode; 45 | break; 46 | } else { 47 | // ➐ 부모 노드의 x좌표가 더 작거나 같으면 오른쪽으로 48 | if (parent.hasRight()) { 49 | parent = parent.right; 50 | continue; 51 | } 52 | parent.right = newNode; 53 | break; 54 | } 55 | } 56 | } 57 | } 58 | return root; 59 | } 60 | 61 | // ➑ 전위 순회 함수 62 | function preOrder(root, answer) { 63 | const stack = [root]; 64 | while (stack.length) { 65 | const node = stack.pop(); 66 | if (!node) { 67 | continue; 68 | } 69 | answer[0].push(node.num); 70 | stack.push(node.right); 71 | stack.push(node.left); 72 | } 73 | } 74 | 75 | // ➒ 후위 순회 함수 76 | function postOrder(root, answer) { 77 | const stack = [[root, false]]; 78 | while (stack.length) { 79 | const [node, visited] = stack.pop(); 80 | if (!node) { 81 | continue; 82 | } 83 | if (visited) { 84 | answer[1].push(node.num); 85 | } else { 86 | stack.push([node, true]); 87 | stack.push([node.right, false]); 88 | stack.push([node.left, false]); 89 | } 90 | } 91 | } 92 | 93 | // ➓ 주어진 좌표 정보를 이용하여 이진 트리를 생성하고, 전위 순회와 후위 순회한 결과를 반환하는 함수 94 | function solution(nodeinfo) { 95 | const answer = [[], []]; // 결과를 저장할 리스트 초기화 96 | const root = makeBT(nodeinfo); // 이진 트리 생성 97 | preOrder(root, answer); // 전위 순회 98 | postOrder(root, answer); // 후위 순회 99 | return answer; // 결과 반환 100 | } 101 | -------------------------------------------------------------------------------- /solution/33.js: -------------------------------------------------------------------------------- 1 | // 루트 노드 찾는 함수 2 | function find(parents, x) { 3 | // 만약 x의 부모가 자기 자신이면, 즉 x가 루트 노드라면 4 | if (parents[x] === x) { 5 | return x; 6 | } 7 | 8 | // 그렇지 않다면 x의 부모를 찾아서 parents[x]에 저장하고, 9 | // 그 부모 노드의 루트 노드를 찾아서 parents[x]에 저장합니다. 10 | parents[x] = find(parents, parents[x]); 11 | return parents[x]; // parents[x]를 반환합니다. 12 | } 13 | 14 | // 두 개의 집합을 합치는 함수 15 | function union(parents, x, y) { 16 | const root1 = find(parents, x); // x가 속한 집합의 루트 노드 찾기 17 | const root2 = find(parents, y); // y가 속한 집합의 루트 노드 찾기 18 | 19 | parents[root2] = root1; // y가 속한 집합을 x가 속한 집합에 합침 20 | } 21 | 22 | function solution(k, operations) { 23 | const parents = Array.from({ length: k }, (_, i) => i); // 처음에는 각 노드가 자기 자신을 부모로 가지도록 초기화 24 | let n = k; // 집합의 개수를 저장할 변수, 처음에는 모든 노드가 서로 다른 집합에 있으므로 k 25 | 26 | for (const op of operations) { // operations 리스트에 있는 연산들을 하나씩 처리 27 | if (op[0] === 'u') { // 'u' 연산이면 28 | union(parents, op[1], op[2]); // op[1]과 op[2]가 속한 집합을 합칩니다. 29 | } else if (op[0] === 'f') { // 'f' 연산이면 30 | find(parents, op[1]); // op[1]이 속한 집합의 루트 노드를 찾습니다. 31 | } 32 | 33 | // 모든 노드의 루트 노드를 찾아서 집합의 개수를 계산 34 | n = new Set(Array.from({ length: k }, (_, i) => find(parents, i))).size; 35 | } 36 | 37 | return n; // 집합의 개수를 반환 38 | } 39 | 40 | console.log(solution(3,[['u', 0, 1], ['u', 1, 2], ['f', 2]])) // 반환값 : 1 41 | console.log(solution(4,[['u', 0, 1], ['u', 2, 3], ['f', 0]])) // 반환값 : 2 -------------------------------------------------------------------------------- /solution/34.js: -------------------------------------------------------------------------------- 1 | function solution(nums) { 2 | const numSet = new Set(nums); // ➊ nums 배열에서 중복을 제거한 집합(set)을 구함 3 | const n = nums.length; // ➋ 폰켓몬의 총 수 4 | const k = n / 2; // ➌ 선택할 폰켓몬의 수 5 | return Math.min(k, numSet.size); // ➍ 중복을 제거한 폰켓몬의 종류 수와 선택할 폰켓몬의 수 중 작은 값 반환 6 | } 7 | -------------------------------------------------------------------------------- /solution/35.js: -------------------------------------------------------------------------------- 1 | function solution(n, words) { 2 | usedWords = new Set(); // ➊ 이미 사용한 단어를 저장하는 set 3 | prevWord = words[0][0]; // ➋ 이전 단어의 마지막 글자 4 | for (i = 0; i < words.length; i++) { 5 | word = words[i]; 6 | // ➌ 이미 사용한 단어거나 첫 글자가 이전 단어와 일치하지 않으면 7 | if (usedWords.has(word) || word[0] != prevWord) { 8 | // ➍ 탈락하는 사람의 번호와 차례를 반환 9 | return [i % n + 1, Math.floor(i / n) + 1]; 10 | } 11 | usedWords.add(word); // ➎ 사용한 단어로 추가 12 | prevWord = word.slice(-1); // ➏ 이전 단어의 마지막 글자 업데이트 13 | } 14 | return [0, 0]; // ➐ 모두 통과했을 경우 반환값 15 | } 16 | -------------------------------------------------------------------------------- /solution/36.js: -------------------------------------------------------------------------------- 1 | function solution(phone_book) { 2 | phone_book.sort(); // ➊ 전화번호부 정렬 3 | // ➋ 전화번호부에서 연속된 두 개의 전화번호 비교 4 | for (let i = 0; i < phone_book.length - 1; i++) { 5 | if (phone_book[i + 1].startsWith(phone_book[i])) { 6 | return false; 7 | } 8 | } 9 | // ➌ 모든 전화번호를 비교한 후에도 반환되지 않았다면, 접두어가 없는 경우이므로 true 반환 10 | return true; 11 | } 12 | -------------------------------------------------------------------------------- /solution/37.js: -------------------------------------------------------------------------------- 1 | function find(parent, i) { 2 | // ➊ 'i'가 속한 집합의 루트 노드 찾기 3 | if (parent[i] == i) { 4 | return i; 5 | } 6 | 7 | // ➋ 경로 압축: 'i'의 부모를 직접 루트로 설정 8 | parent[i] = find(parent, parent[i]); 9 | return parent[i]; 10 | } 11 | 12 | function union(parent, rank, x, y) { 13 | // ➌ 랭크를 기준으로 두 집합을 합치기 14 | const xroot = find(parent, x); 15 | const yroot = find(parent, y); 16 | if (rank[xroot] < rank[yroot]) { 17 | // ➍ 작은 랭크의 트리를 큰 랭크의 트리 아래에 연결 18 | parent[xroot] = yroot; 19 | } else if (rank[xroot] > rank[yroot]) { 20 | parent[yroot] = xroot; 21 | } else { 22 | // ➎ 랭크가 같은 경우, 한 트리를 다른 트리에 붙이고 랭크 증가 23 | parent[yroot] = xroot; 24 | rank[xroot] += 1; 25 | } 26 | } 27 | 28 | function solution(n, costs) { 29 | // ➏ 비용을 기준으로 간선을 오름차순 정렬 30 | costs.sort((a, b) => a[2] - b[2]); 31 | 32 | // ➐ 각 노드의 부모를 추적하는 parent 배열 생성 33 | const parent = Array.from({ length: n }, (_, i) => i); 34 | 35 | // ➑ 각 노드의 트리의 랭크를 추적하는 rank 배열 생성 36 | const rank = Array(n).fill(0); 37 | 38 | let minCost = 0; // 최소 신장 트리의 총 비용 39 | let edges = 0; // 최소 신장 트리에 포함된 간선의 개수 40 | 41 | for (const edge of costs) { 42 | if (edges === n - 1) { 43 | // ➒ n - 1개의 간선이 포함된 경우 중단(최소 신장 트리의 속성) 44 | break; 45 | } 46 | 47 | // ➓ 현재 간선의 두 노드가 속한 집합의 루트 찾기 48 | const x = find(parent, edge[0]); 49 | const y = find(parent, edge[1]); 50 | 51 | if (x !== y) { 52 | // ⓫ 두 노드가 서로 다른 집합에 속하는 경우, 집합 합치기 53 | union(parent, rank, x, y); 54 | // 현재 간선의 비용을 최소 비용에 추가 55 | minCost += edge[2]; 56 | // ⓬ 포함된 간선의 개수 증가 57 | edges += 1; 58 | } 59 | } 60 | 61 | return minCost; 62 | } 63 | 64 | -------------------------------------------------------------------------------- /solution/38.js: -------------------------------------------------------------------------------- 1 | function solution(graph, start) { 2 | // ❶ 그래프를 인접 리스트로 변환 3 | const adjList = {}; 4 | graph.forEach(([u, v]) => { 5 | if (!adjList[u]) adjList[u] = []; 6 | adjList[u].push(v); 7 | }); 8 | 9 | // ❷ DFS 탐색 함수 10 | function dfs(node, visited, result) { 11 | visited.add(node); // ❸ 현재 노드를 방문한 노드들의 집합에 추가 12 | result.push(node); // ❹ 현재 노드를 결과 리스트에 추가 13 | (adjList[node] || []).forEach((neighbor) => { // ❺ 현재 노드와 인접한 노드 순회 14 | if (!visited.has(neighbor)) { // ❻ 아직 방문하지 않은 노드라면 15 | dfs(neighbor, visited, result); 16 | } 17 | }); 18 | } 19 | 20 | // DFS를 순회한 결과를 반환 21 | const visited = new Set(); 22 | const result = []; 23 | dfs(start, visited, result); // ❼ 시작 노드에서 깊이 우선 탐색 시작 24 | 25 | return result; // ❽ DFS 탐색 결과 반환 26 | } 27 | 28 | console.log(solution([['A', 'B'], ['B', 'C'], ['C', 'D'], ['D', 'E']], 'A')) // 반환값 : ['A', 'B', 'C', 'D', 'E'] 29 | console.log(solution([['A', 'B'], ['A', 'C'], ['B', 'D'], ['B', 'E'], ['C', 'F'], ['E', 'F']], 'A')) // 반환값 : ['A', 'B', 'D', 'E', 'F', 'C'] -------------------------------------------------------------------------------- /solution/39.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | push(item) { 7 | this.items.push(item); 8 | this.rear++; 9 | } 10 | 11 | pop() { 12 | return this.items[this.front++]; 13 | } 14 | 15 | isEmpty() { 16 | return this.front === this.rear; 17 | } 18 | } 19 | 20 | function solution(graph, start) { 21 | // 그래프를 인접 리스트로 변환 22 | const adjList = {}; 23 | for (let [u, v] of graph) { 24 | if (!adjList[u]) adjList[u] = []; 25 | adjList[u].push(v); 26 | } 27 | 28 | const visited = new Set(); // ❶ 방문한 노드를 저장할 셋 29 | 30 | // ❷ 탐색시 맨 처음 방문할 노드 푸시 하고 방문처리 31 | const queue = new Queue() 32 | queue.push(start); 33 | visited.add(start); 34 | const result = [start]; 35 | 36 | // ❸ 큐가 비어있지 않은 동안 반복 37 | while (!queue.isEmpty()) { 38 | const node = queue.pop(); // ❹ 큐에 있는 원소 중 가장 먼저 푸시된 원소 팝 39 | for (let neighbor of adjList[node] || []) { // ❺ 인접한 이웃 노드들에 대해서 40 | if (!visited.has(neighbor)) { // ❻ 방문되지 않은 이웃 노드인 경우 41 | // ❼ 이웃노드를 방문 처리함 42 | queue.push(neighbor); 43 | visited.add(neighbor); 44 | result.push(neighbor); 45 | } 46 | } 47 | } 48 | 49 | return result; 50 | } 51 | 52 | console.log(solution([[1, 2], [1, 3], [2, 4], [2, 5], [3, 6], [3, 7], [4, 8], [5, 8], [6, 9], [7, 9]], 1)) // 반환값 :[1, 2, 3, 4, 5, 6, 7, 8, 9] 53 | console.log(solution([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0]], 1)) // 반환값 : [1, 2, 3, 4, 5, 0] 54 | -------------------------------------------------------------------------------- /solution/40.js: -------------------------------------------------------------------------------- 1 | class MinHeap { 2 | constructor() { 3 | this.items = []; 4 | } 5 | 6 | size() { 7 | return this.items.length; 8 | } 9 | 10 | push(item) { 11 | this.items.push(item); 12 | this.bubbleUp(); 13 | } 14 | 15 | pop() { 16 | if (this.size() === 0) { 17 | return null; 18 | } 19 | 20 | const min = this.items[0]; 21 | this.items[0] = this.items[this.size() - 1]; 22 | this.items.pop(); 23 | this.bubbleDown(); 24 | return min; 25 | } 26 | 27 | swap(a, b) { 28 | [this.items[a], this.items[b]] = [this.items[b], this.items[a]]; 29 | } 30 | 31 | bubbleUp() { 32 | let index = this.size() - 1; 33 | while (index > 0) { 34 | const parentIndex = Math.floor((index - 1) / 2); 35 | if (this.items[parentIndex][0] <= this.items[index][0]) { 36 | break; 37 | } 38 | this.swap(index, parentIndex); 39 | index = parentIndex; 40 | } 41 | } 42 | 43 | bubbleDown() { 44 | let index = 0; 45 | while (index * 2 + 1 < this.size()) { 46 | let leftChild = index * 2 + 1; 47 | let rightChild = index * 2 + 2; 48 | let smallerChild = 49 | rightChild < this.size() && 50 | this.items[rightChild][0] < this.items[leftChild][0] 51 | ? rightChild 52 | : leftChild; 53 | 54 | if (this.items[index][0] <= this.items[smallerChild][0]) { 55 | break; 56 | } 57 | 58 | this.swap(index, smallerChild); 59 | index = smallerChild; 60 | } 61 | } 62 | } 63 | 64 | 65 | function solution(graph, start) { 66 | // ❶ 모든 노드의 거리 값을 무한대로 초기화 67 | const distances = {}; 68 | for (const node in graph) { 69 | distances[node] = Infinity; 70 | } 71 | 72 | // ❷ 시작 노드의 거리 값은 0으로 초기화 73 | distances[start] = 0; 74 | 75 | // 힙 생성 76 | const queue = new MinHeap(); 77 | queue.push([distances[start], start]); // ❸ 시작 노드를 큐에 삽입 78 | 79 | // ❹ 시작 노드의 경로를 초기화 80 | const paths = { [start]: [start] }; 81 | 82 | while (queue.size() > 0) { 83 | // ❺ 현재 가장 거리 값이 작은 노드를 가져옴 84 | const [currentDistance, currentNode] = queue.pop(); 85 | 86 | // ❻ 만약 현재 노드의 거리 값이 큐에서 가져온 거리 값보다 크면, 해당 노드는 이미 처리한 것이므로 무시 87 | if (distances[currentNode] < currentDistance) { 88 | continue; 89 | } 90 | 91 | // ❼ 현재 노드와 인접한 노드들의 거리 값을 계산하여 업데이트 92 | for (const adjacentNode in graph[currentNode]) { 93 | const weight = graph[currentNode][adjacentNode]; 94 | const distance = currentDistance + weight; 95 | 96 | // ❽ 현재 계산한 거리 값이 기존 거리 값보다 작으면 최소 비용 및 최단 경로 업데이트 97 | if (distance < distances[adjacentNode]) { 98 | distances[adjacentNode] = distance; // 최소 비용 업데이트 99 | paths[adjacentNode] = [...paths[currentNode], adjacentNode]; // 최단 경로 업데이트 100 | 101 | // ➒ 최소 경로가 갱신된 노드를 비용과 함께 큐에 푸시 102 | queue.push([distance, adjacentNode]); 103 | } 104 | } 105 | } 106 | 107 | // ➓ paths 배열을 노드 번호에 따라 오름차순 정렬하여 반환 108 | const sortedPaths = {}; 109 | Object.keys(paths) 110 | .sort() 111 | .forEach((node) => { 112 | sortedPaths[node] = paths[node]; 113 | }); 114 | 115 | return [distances, sortedPaths]; 116 | } 117 | 118 | console.log(solution({ A: { B: 9, C: 3 }, B: { A: 5 }, C: { B: 1 } }, 'A')); 119 | // [{'A': 0, 'B': 4, 'C': 3}, {'A': ['A'], 'B': ['A', 'C', 'B'], 'C': ['A', 'C']}] 120 | 121 | console.log(solution({ A: { B: 1 },B: { C: 5 },C: { D: 1 }, D: {} }, 'A')); 122 | // [{'A': 0, 'B': 1, 'C': 6, 'D': 7}, {'A': ['A'], 'B': ['A', 'B'], 'C': ['A', 'B', 'C'], 'D': ['A', 'B', 'C', 'D']}] 123 | -------------------------------------------------------------------------------- /solution/41.js: -------------------------------------------------------------------------------- 1 | function solution(graph, source) { 2 | // ➊ 그래프의 노드 수 3 | const numVertices = graph.length; 4 | 5 | // ➋ 거리 배열 초기화 6 | const distance = Array(numVertices).fill(Infinity); 7 | distance[source] = 0; 8 | 9 | // ➌ 직전 경로 배열 초기화 10 | const predecessor = Array(numVertices).fill(null); 11 | 12 | // ➍ 간선 수 만큼 반복하여 최단 경로 갱신 13 | for (let temp = 0; temp < numVertices - 1; temp++) { 14 | for (let u = 0; u < numVertices; u++) { 15 | for (const [v, weight] of graph[u]) { 16 | // ➎ 현재 노드 u를 거쳐서 노드 v로 가는 경로의 거리가 기존에 저장된 노드 v까지의 거리보다 짧은 경우 17 | if (distance[u] + weight < distance[v]) { 18 | // ➏ 최단 거리를 갱신해줍니다. 19 | distance[v] = distance[u] + weight; 20 | // ➐ 직전 경로를 업데이트합니다. 21 | predecessor[v] = u; 22 | } 23 | } 24 | } 25 | } 26 | 27 | // ➑ 음의 가중치 순회 체크 28 | for (let u = 0; u < numVertices; u++) { 29 | for (const [v, weight] of graph[u]) { 30 | // ➒ 현재 노드 u를 거쳐서 노드 v로 가는 경로의 거리가 기존에 저장된 노드 v까지의 거리보다 짧은 경우 31 | if (distance[u] + weight < distance[v]) { 32 | // ❿ 음의 가중치 순회가 발견되었으므로 [-1]을 반환합니다. 33 | return [-1]; 34 | } 35 | } 36 | } 37 | 38 | return [distance, predecessor]; 39 | } 40 | 41 | console.log(solution([[[1, 4], [2, 3], [4, -6 ]], [[3, 5]], [[1, 2]], [[0, 7], [2, 4]], [[2, 2]]], 0)) // [[0, -2, -4, 3, -6], [null, 2, 4, 1, 0]] 42 | console.log(solution([[[1, 5], [2, -1]], [[2, 2]], [[3, -2]], [[0, 2], [1, 6]]], 0)) // [-1] -------------------------------------------------------------------------------- /solution/42.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | push(item) { 7 | this.items.push(item); 8 | this.rear++; 9 | } 10 | 11 | first() { 12 | return this.items[this.front]; 13 | } 14 | 15 | last() { 16 | return this.items[this.rear - 1]; 17 | } 18 | 19 | pop() { 20 | return this.items[this.front++]; 21 | } 22 | 23 | isEmpty() { 24 | return this.front === this.rear; 25 | } 26 | } 27 | 28 | function solution(maps) { 29 | // ➊ 이동할 수 있는 방향을 나타내는 배열 move 선언 30 | const move = [[-1, 0], [0, -1], [0, 1], [1, 0]]; 31 | 32 | // ➋ 맵의 크기를 저장하는 변수 선언 33 | const n = maps.length; 34 | const m = maps[0].length; 35 | 36 | // ➌ 거리를 저장하는 배열 dist를 -1로 초기화 37 | const dist = Array.from({ length: n }, () => Array(m).fill(-1)); 38 | 39 | // ➍ bfs 함수를 선언 40 | function bfs(start) { 41 | // ➎ queue를 선언하고 시작 위치를 queue에 추가 42 | const q = new Queue() 43 | q.push(start); 44 | dist[start[0]][start[1]] = 1; 45 | 46 | // ➏ queue가 빌 때까지 반복 47 | while (!q.isEmpty()) { 48 | const here = q.pop(); 49 | 50 | // ➐ 현재 위치에서 이동할 수 있는 모든 방향 51 | for (const [dx, dy] of move) { 52 | const row = here[0] + dx; 53 | const column = here[1] + dy; 54 | 55 | // ➑ 이동한 위치가 범위를 벗어난 경우 다음 방향으로 넘어감 56 | if (row < 0 || row >= n || column < 0 || column >= m) { 57 | continue; 58 | } 59 | 60 | // ➒ 이동한 위치에 벽이 있는 경우 다음 방향으로 넘어 61 | if (maps[row][column] === 0) { 62 | continue; 63 | } 64 | 65 | // ➓ 이동한 위치가 처음 방문하는 경우, queue에 추가하고 거리 갱신 66 | if (dist[row][column] === -1) { 67 | q.push([row, column]); 68 | dist[row][column] = dist[here[0]][here[1]] + 1; 69 | } 70 | } 71 | } 72 | 73 | // 거리를 저장하는 배열 dist를 반환 74 | return dist; 75 | } 76 | 77 | // 시작 위치에서 bfs() 함수를 호출하여 거리 계산 78 | bfs([0, 0]); 79 | 80 | // 목적지까지의 거리 반환, 목적지에 도달하지 못한 경우 -1을 반환 81 | return dist[n - 1][m - 1]; 82 | } 83 | -------------------------------------------------------------------------------- /solution/43.js: -------------------------------------------------------------------------------- 1 | function dfs(computers, visited, node) { 2 | visited[node] = true; // ➊ 현재 노드 방문 처리 3 | for (let idx = 0; idx < computers[node].length; idx++) { 4 | if (computers[node][idx] && !visited[idx]) { // ➋ 연결되어 있으며 방문하지 않은 노드라면 5 | dfs(computers, visited, idx); // ➌ 해당 노드를 방문하러 이동 6 | } 7 | } 8 | } 9 | 10 | function solution(n, computers) { 11 | let answer = 0; 12 | const visited = Array(n).fill(false); // ➍ 방문 여부를 저장하는 리스트 13 | for (let i = 0; i < n; i++) { 14 | if (!visited[i]) { // ➎ 아직 방문하지 않은 노드라면 15 | dfs(computers, visited, i); // ➏ DFS로 연결된 노드들을 모두 방문하면서 네트워크 개수 증가 16 | answer++; 17 | } 18 | } 19 | return answer; 20 | } 21 | -------------------------------------------------------------------------------- /solution/44.js: -------------------------------------------------------------------------------- 1 | class MinHeap { 2 | constructor() { 3 | this.items = []; 4 | } 5 | 6 | size() { 7 | return this.items.length; 8 | } 9 | 10 | insert(item) { 11 | this.items.push(item); 12 | this.bubbleUp(); 13 | } 14 | 15 | pop() { 16 | if (this.size() === 0) { 17 | return null; 18 | } 19 | 20 | const min = this.items[0]; 21 | this.items[0] = this.items[this.size() - 1]; 22 | this.items.pop(); 23 | this.bubbleDown(); 24 | return min; 25 | } 26 | 27 | swap(a, b) { 28 | [this.items[a], this.items[b]] = [this.items[b], this.items[a]]; 29 | } 30 | 31 | bubbleUp() { 32 | let index = this.size() - 1; 33 | while (index > 0) { 34 | const parentIndex = Math.floor((index - 1) / 2); 35 | if (this.items[parentIndex][0] <= this.items[index][0]) { 36 | break; 37 | } 38 | this.swap(index, parentIndex); 39 | index = parentIndex; 40 | } 41 | } 42 | 43 | bubbleDown() { 44 | let index = 0; 45 | while (index * 2 + 1 < this.size()) { 46 | let leftChild = index * 2 + 1; 47 | let rightChild = index * 2 + 2; 48 | let smallerChild = 49 | rightChild < this.size() && 50 | this.items[rightChild][0] < this.items[leftChild][0] 51 | ? rightChild 52 | : leftChild; 53 | 54 | if (this.items[index][0] <= this.items[smallerChild][0]) { 55 | break; 56 | } 57 | 58 | this.swap(index, smallerChild); 59 | index = smallerChild; 60 | } 61 | } 62 | } 63 | 64 | function solution(N, road, K) { 65 | // ➊ 각 노드에 연결된 간선들을 저장할 리스트 66 | const graph = Array.from({ length: N + 1 }, () => []); 67 | // ➋ 출발점에서 각 노드까지의 최단 거리를 저장할 리스트 68 | const distances = Array(N + 1).fill(Infinity); 69 | distances[1] = 0; // 출발점은 0으로 초기화 70 | 71 | // ➌ 그래프 구성 72 | for (const [a, b, cost] of road) { 73 | graph[a].push([b, cost]); 74 | graph[b].push([a, cost]); 75 | } 76 | 77 | // ➍ 다익스트라 알고리즘 시작 78 | const heap = new MinHeap(); 79 | heap.insert([0, 1]); // ➎ 출발점을 heap에 추가 80 | while (heap.size() > 0) { 81 | const [dist, node] = heap.pop(); 82 | 83 | // ➏ 인접한 노드들의 최단 거리를 갱신하고 heap에 추가 84 | for (const [nextNode, nextDist] of graph[node]) { 85 | const cost = dist + nextDist; 86 | if (cost < distances[nextNode]) { 87 | distances[nextNode] = cost; 88 | heap.insert([cost, nextNode]); 89 | } 90 | } 91 | } 92 | 93 | // ➐ distances 리스트에서 K 이하인 값의 개수를 구하여 반환 94 | return distances.filter((dist) => dist <= K).length; 95 | } 96 | -------------------------------------------------------------------------------- /solution/45.js: -------------------------------------------------------------------------------- 1 | class Queue { 2 | items = []; 3 | front = 0; 4 | rear = 0; 5 | 6 | push(item) { 7 | this.items.push(item); 8 | this.rear++; 9 | } 10 | 11 | first() { 12 | return this.items[this.front]; 13 | } 14 | 15 | last() { 16 | return this.items[this.rear - 1]; 17 | } 18 | 19 | pop() { 20 | return this.items[this.front++]; 21 | } 22 | 23 | isEmpty() { 24 | return this.front === this.rear; 25 | } 26 | } 27 | 28 | function solution(board) { 29 | // ➊ 주어진 좌표가 보드의 범위 내에 있는지 확인 30 | function isValid(x, y) { 31 | return 0 <= x && x < N && 0 <= y && y < N; 32 | } 33 | 34 | // ➋ 주어진 좌표가 차단되었거나 이동할 수 없는지 확인 35 | function isBlocked(x, y) { 36 | return (x === 0 && y === 0) || !isValid(x, y) || board[x][y] === 1; 37 | } 38 | 39 | // ➌ 이전 방향과 현재 방향에 따라 비용을 계산 40 | function calculateCost(direction, prevDirection, cost) { 41 | if (prevDirection === -1 || (prevDirection - direction) % 2 === 0) { 42 | return cost + 100; 43 | } else { 44 | return cost + 600; 45 | } 46 | } 47 | 48 | // ➍ 주어진 좌표와 방향이 아직 방문하지 않았거나 새 비용이 더 작은 경우 49 | function isShouldUpdate(x, y, direction, new_cost) { 50 | return visited[x][y][direction] === 0 || visited[x][y][direction] > new_cost; 51 | } 52 | 53 | const queue = new Queue(); 54 | queue.push([0, 0, -1, 0]); 55 | const N = board.length; 56 | const directions = [ 57 | [0, -1], 58 | [-1, 0], 59 | [0, 1], 60 | [1, 0], 61 | ]; 62 | const visited = Array.from({ length: N }, () => 63 | Array.from({ length: N }, () => Array(4).fill(0)) 64 | ); 65 | let answer = Infinity; 66 | 67 | // ➎ 큐가 빌 때까지 반복 68 | while (!queue.isEmpty()) { 69 | const [x, y, prevDirection, cost] = queue.pop(); 70 | 71 | // ➏ 가능한 모든 방향에 대해 반복 72 | for (let direction = 0; direction < 4; direction++) { 73 | const [dx, dy] = directions[direction]; 74 | const newX = x + dx; 75 | const newY = y + dy; 76 | 77 | // ➐ 이동할 수 없는 좌표는 건너뛰기 78 | if (isBlocked(newX, newY)) { 79 | continue; 80 | } 81 | 82 | const new_cost = calculateCost(direction, prevDirection, cost); 83 | 84 | // ➑ 도착지에 도달한 경우 최소 비용 업데이트 85 | if (newX === N - 1 && newY === N - 1) { 86 | answer = Math.min(answer, new_cost); 87 | } 88 | // ➒ 좌표와 방향이 아직 방문하지 않았거나 새 비용이 더 작은 경우 큐에 추가 89 | else if (isShouldUpdate(newX, newY, direction, new_cost)) { 90 | queue.push([newX, newY, direction, new_cost]); 91 | visited[newX][newY][direction] = new_cost; 92 | } 93 | } 94 | } 95 | 96 | return answer; 97 | } 98 | -------------------------------------------------------------------------------- /solution/46.js: -------------------------------------------------------------------------------- 1 | function solution(n, wires) { 2 | // ➊ 그래프 생성 3 | const graph = Array.from({ length: n + 1 }, () => []); 4 | for (const [a, b] of wires) { 5 | graph[a].push(b); 6 | graph[b].push(a); 7 | } 8 | 9 | // ➋ 깊이 우선 탐색 함수 10 | function dfs(node, parent) { 11 | let cnt = 1; 12 | for (const child of graph[node]) { // ➌ 현재 노드의 자식 노드들에 방문 13 | if (child !== parent) { // ➍ 부모 노드가 아닌 경우에만 탐색 14 | cnt += dfs(child, node); 15 | } 16 | } 17 | return cnt; 18 | } 19 | 20 | let minDiff = Infinity; 21 | for (const [a, b] of wires) { 22 | // ➎ 간선 제거 23 | graph[a].splice(graph[a].indexOf(b), 1); 24 | graph[b].splice(graph[b].indexOf(a), 1); 25 | 26 | // ➏ 각 전력망 송전탑 개수 계산 27 | const cntA = dfs(a, b); 28 | const cntB = n - cntA; 29 | 30 | // ➐ 최소값 갱신 31 | minDiff = Math.min(minDiff, Math.abs(cntA - cntB)); 32 | 33 | // ➑ 간선 복원 34 | graph[a].push(b); 35 | graph[b].push(a); 36 | } 37 | 38 | return minDiff; 39 | } 40 | -------------------------------------------------------------------------------- /solution/47.js: -------------------------------------------------------------------------------- 1 | function solution(N) { 2 | const results = []; // ➊ 조합 결과를 담을 리스트 3 | 4 | function backtrack(sum, selectedNums, start) { 5 | if (sum === 10) { // ❷ 합이 10이 되면 결과 리스트에 추가 6 | results.push(selectedNums); 7 | return; 8 | } 9 | 10 | for (let i = start; i <= N; i++) { // ❸ 다음에 선택할 수 있는 숫자들을 하나씩 선택하면서 11 | if (sum + i <= 10) { // ❹ 선택한 숫자의 합이 10보다 작거나 같으면 12 | backtrack( 13 | sum + i, selectedNums.concat(i), i + 1 14 | ); // ❺ 백트래킹 함수를 재귀적으로 호출합니다. 15 | } 16 | } 17 | } 18 | 19 | backtrack(0, [], 1); // ❻ 백트래킹 함수 호출 20 | return results; // ❼ 조합 결과 반환 21 | } 22 | 23 | console.log(solution(5)) // [[1, 2, 3, 4], [1, 4, 5], [2, 3, 5]] 24 | console.log(solution(2)) // [] 25 | console.log(solution(7)) // [[1, 2, 3, 4], [1, 2, 7], [1, 3, 6], [1, 4, 5], [2, 3, 5], [3, 7], [4, 6]] 26 | -------------------------------------------------------------------------------- /solution/48.js: -------------------------------------------------------------------------------- 1 | function solution(board) { 2 | function isValid(num, row, col) { 3 | // ❶ 현재 위치에 num이 들어갈 수 있는지 검사 4 | return !( 5 | inRow(num, row) || 6 | inCol(num, col) || 7 | inBox(num, row, col) 8 | ); 9 | } 10 | 11 | function inRow(num, row) { 12 | // ❷ 해당 행에 num이 있는지 확인 13 | return board[row].includes(num); 14 | } 15 | 16 | function inCol(num, col) { 17 | // ❸ 해당 열에 num이 있는지 확인하는 함수 18 | return board.some(row => row[col] === num); 19 | } 20 | 21 | function inBox(num, row, col) { 22 | // ❹ 현재 위치의 3x3 박스에 num이 있는지 확인 23 | const boxRow = Math.floor(row / 3) * 3; 24 | const boxCol = Math.floor(col / 3) * 3; 25 | for (let i = boxRow; i < boxRow + 3; i++) { 26 | for (let j = boxCol; j < boxCol + 3; j++) { 27 | if (board[i][j] === num) { 28 | return true; 29 | } 30 | } 31 | } 32 | return false; 33 | } 34 | 35 | function findEmptyPosition() { 36 | // ❺ 스도쿠 보드에서 비어 있는 위치 반환 37 | for (let i = 0; i < 9; i++) { 38 | for (let j = 0; j < 9; j++) { 39 | if (board[i][j] === 0) { 40 | return [i, j]; 41 | } 42 | } 43 | } 44 | return null; 45 | } 46 | 47 | function findSolution() { 48 | // ❻ 비어 있는 위치에 가능한 숫자를 넣어가며 스도쿠 해결 49 | const emptyPos = findEmptyPosition(); 50 | // ❼ 빈 칸이 없으면 스도쿠가 해결된 것으로 간주 51 | if (!emptyPos) { 52 | return true; 53 | } 54 | const [row, col] = emptyPos; 55 | for (let num = 1; num <= 9; num++) { 56 | if (isValid(num, row, col)) { 57 | board[row][col] = num; 58 | if (findSolution()) { 59 | return true; // ❽ 다음 빈 칸으로 재귀적으로 탐색 60 | } 61 | board[row][col] = 0; // ❾ 가능한 숫자가 없으면 원래의 0으로 되돌림 62 | } 63 | } 64 | return false; 65 | } 66 | 67 | findSolution(); 68 | return board; 69 | } 70 | 71 | console.log( 72 | solution( 73 | [ 74 | [5, 3, 0, 0, 7, 0, 0, 0, 0], 75 | [6, 0, 0, 1, 9, 5, 0, 0, 0], 76 | [0, 9, 8, 0, 0, 0, 0, 6, 0], 77 | [8, 0, 0, 0, 6, 0, 0, 0, 3], 78 | [4, 0, 0, 8, 0, 3, 0, 0, 1], 79 | [7, 0, 0, 0, 2, 0, 0, 0, 6], 80 | [0, 6, 0, 0, 0, 0, 2, 8, 0], 81 | [0, 0, 0, 4, 1, 9, 0, 0, 5], 82 | [0, 0, 0, 0, 8, 0, 0, 7, 9], 83 | ] 84 | ) 85 | ) 86 | 87 | // [ 88 | // [5, 3, 4, 6, 7, 8, 9, 1, 2], 89 | // [6, 7, 2, 1, 9, 5, 3, 4, 8], 90 | // [1, 9, 8, 3, 4, 2, 5, 6, 7], 91 | // [8, 5, 9, 7, 6, 1, 4, 2, 3], 92 | // [4, 2, 6, 8, 5, 3, 7, 9, 1], 93 | // [7, 1, 3, 9, 2, 4, 8, 5, 6], 94 | // [9, 6, 1, 5, 3, 7, 2, 8, 4], 95 | // [2, 8, 7, 4, 1, 9, 6, 3, 5], 96 | // [3, 4, 5, 2, 8, 6, 1, 7, 9], 97 | // ] 98 | 99 | 100 | console.log( 101 | solution( 102 | [ 103 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 104 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 105 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 106 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 107 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 108 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 109 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 110 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 111 | [0, 0, 0, 0, 0, 0, 0, 0, 0], 112 | ] 113 | ) 114 | ) 115 | 116 | // [ 117 | // [1, 2, 3, 4, 5, 6, 7, 8, 9], 118 | // [4, 5, 6, 7, 8, 9, 1, 2, 3], 119 | // [7, 8, 9, 1, 2, 3, 4, 5, 6], 120 | // [2, 3, 4, 5, 6, 7, 8, 9, 1], 121 | // [5, 6, 7, 8, 9, 1, 2, 3, 4], 122 | // [8, 9, 1, 2, 3, 4, 5, 6, 7], 123 | // [3, 4, 5, 6, 7, 8, 9, 1, 2], 124 | // [6, 7, 8, 9, 1, 2, 3, 4, 5], 125 | // [9, 1, 2, 3, 4, 5, 6, 7, 8], 126 | // ] 127 | -------------------------------------------------------------------------------- /solution/49.js: -------------------------------------------------------------------------------- 1 | // 백트래킹을 위한 DFS 2 | function dfs(curK, cnt, dungeons, visited) { 3 | let answerMax = cnt; 4 | for (let i = 0; i < dungeons.length; i++) { 5 | // ➊ 현재 피로도(curK)가 i번째 던전의 최소 필요 피로도보다 크거나 같고, 6 | // i번째 던전을 방문한 적이 없다면 7 | if (curK >= dungeons[i][0] && visited[i] === 0) { 8 | visited[i] = 1; 9 | // ➋ 현재까지의 최대 탐험 가능 던전 수와 10 | // i번째 던전에서 이동할 수 있는 최대 탐험 가능 던전 수 중 큰 값을 선택하여 업데이트 11 | answerMax = Math.max(answerMax, dfs(curK - dungeons[i][1], cnt + 1, dungeons, visited)); 12 | visited[i] = 0; 13 | } 14 | } 15 | 16 | return answerMax; 17 | } 18 | 19 | // 최대 탐험 가능 던전 수를 계산하는 함수 20 | function solution(k, dungeons) { 21 | const visited = Array(dungeons.length).fill(0); // ➌ 던전 방문 여부를 저장할 지역 배열 22 | const answerMax = dfs(k, 0, dungeons, visited); // ➍ DFS 함수 호출 23 | return answerMax; 24 | } 25 | -------------------------------------------------------------------------------- /solution/50.js: -------------------------------------------------------------------------------- 1 | // ➊ 퀸이 서로 공격할 수 없는 위치에 놓이는 경우의 수를 구하는 함수 2 | function search(n, y, width, diagonal1, diagonal2) { 3 | let answer = 0; 4 | // ➋ 모든 행에 대해서 퀸의 위치가 결정되었을 경우 5 | if (y === n) { 6 | // ➌ 해결 가능한 경우의 수를 1 증가시킴 7 | answer += 1; 8 | } else { 9 | // ➍ 현재 행에서 퀸이 놓일 수 있는 모든 위치를 시도 10 | for (let i = 0; i < n; i++) { 11 | // ➎ 해당 위치에 이미 퀸이 있는 경우, 대각선 상에 퀸이 있는 경우 스킵 12 | if (width[i] || diagonal1[i + y] || diagonal2[i - y + n]) { 13 | continue; 14 | } 15 | // ➏ 해당 위치에 퀸을 놓음 16 | width[i] = diagonal1[i + y] = diagonal2[i - y + n] = true; 17 | // ➐ 다음 행으로 이동하여 재귀적으로 해결 가능한 경우의 수 찾기 18 | answer += search(n, y + 1, width, diagonal1, diagonal2); 19 | // ➑ 해당 위치에 놓인 퀸을 제거함 20 | width[i] = diagonal1[i + y] = diagonal2[i - y + n] = false; 21 | } 22 | } 23 | return answer; 24 | } 25 | 26 | function solution(n) { 27 | // ➒ search 함수 호출하여 해결 가능한 경우의 수 찾기 28 | const answer = search(n, 0, Array(n).fill(false), Array(n * 2).fill(false), Array(n * 2).fill(false)); 29 | return answer; 30 | } 31 | -------------------------------------------------------------------------------- /solution/51.js: -------------------------------------------------------------------------------- 1 | function combinationsWithRepetition(arr, n) { 2 | if (n === 1) return arr.map((v) => [v]); 3 | const result = []; 4 | 5 | arr.forEach((fixed, idx, arr) => { 6 | const rest = arr.slice(idx); 7 | const combis = combinationsWithRepetition(rest, n - 1); 8 | const combine = combis.map((v) => [fixed, ...v]); 9 | result.push(...combine); 10 | }); 11 | 12 | return result; 13 | } 14 | 15 | 16 | function solution(n, info) { 17 | let maxdiff = 0; 18 | let maxComb = {}; 19 | 20 | // ➊ 주어진 조합에서 각각의 점수 계산 21 | function calculateScore(combi) { 22 | let score1 = 0; 23 | let score2 = 0; 24 | for (let i = 1; i <= 10; i++) { 25 | if (info[10 - i] < combi.filter((x) => x === i).length) { 26 | score1 += i; 27 | } else if (info[10 - i] > 0) { 28 | score2 += i; 29 | } 30 | } 31 | return [score1, score2]; 32 | } 33 | 34 | // ➋ 최대 차이와 조합 저장 35 | function calculateDiff(diff, cnt) { 36 | if (diff > maxdiff) { 37 | maxComb = { ...cnt }; 38 | maxdiff = diff; 39 | } 40 | } 41 | 42 | // ➌ 가능한 라이언의 과녁점수 조합의 모든 경우에 대해서 체크 43 | for (const combi of combinationsWithRepetition([...Array(11).keys()], n)) { 44 | const cnt = combi.reduce((acc, cur) => { 45 | acc[cur] = (acc[cur] || 0) + 1; 46 | return acc; 47 | }, {}); 48 | const [score1, score2] = calculateScore(combi); 49 | const diff = score1 - score2; 50 | calculateDiff(diff, cnt); 51 | } 52 | 53 | // ➍ 최대 차이가 0 이상인 경우, 조합 반환 54 | if (maxdiff > 0) { 55 | const answer = Array(11).fill(0); 56 | for (const n of Object.keys(maxComb)) { 57 | answer[10 - n] = maxComb[n]; 58 | } 59 | return answer; 60 | } else { 61 | // ➎ 최대 차이가 0인 경우, -1 반환 62 | return [-1]; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /solution/52.js: -------------------------------------------------------------------------------- 1 | function permutations(arr, n) { 2 | if (n === 0) return [[]]; 3 | const result = []; 4 | 5 | arr.forEach((fixed, idx) => { 6 | const rest = [...arr]; 7 | rest.splice(idx, 1); 8 | const perms = permutations(rest, n - 1); 9 | const combine = perms.map((p) => [fixed, ...p]); 10 | result.push(...combine); 11 | }); 12 | 13 | return result; 14 | } 15 | 16 | 17 | function solution(n, weak, dist) { 18 | // ➊ 주어진 weak 지점들을 선형으로 만들기 위해 weak 리스트에 마지막 지점 + n을 추가 19 | const length = weak.length; 20 | for (let i = 0; i < length; i++) { 21 | weak.push(weak[i] + n); 22 | } 23 | 24 | // ➋ 투입할 수 있는 친구들의 수에 1을 더한 값을 초기값으로 설정 25 | let answer = dist.length + 1; 26 | 27 | // ➌ 모든 weak 지점을 시작점으로 설정하며 경우의 수를 탐색 28 | for (let i = 0; i < length; i++) { 29 | for (const friends of permutations(dist, dist.length)) { 30 | // ➍ friends에 들어있는 친구들을 순서대로 배치하며 투입된 친구 수(cnt) 측정 31 | let cnt = 1; 32 | let position = weak[i] + friends[cnt - 1]; 33 | // ➎ 현재 투입된 친구가 다음 weak 지점까지 갈 수 있는지 검사 34 | for (let j = i; j < i + length; j++) { 35 | if (position < weak[j]) { 36 | cnt += 1; 37 | // ➏ 투입 가능한 친구의 수를 초과한 경우 탐색 중단 38 | if (cnt > dist.length) { 39 | break; 40 | } 41 | position = weak[j] + friends[cnt - 1]; 42 | } 43 | } 44 | // ➐ 최소 친구 수를 구함 45 | answer = Math.min(answer, cnt); 46 | } 47 | } 48 | 49 | // ➑ 모든 경우의 수를 탐색한 결과가 투입 가능한 친구 수를 초과하는 경우, -1 반환 50 | return answer <= dist.length ? answer : -1; 51 | } 52 | -------------------------------------------------------------------------------- /solution/53.js: -------------------------------------------------------------------------------- 1 | function solution(board, aloc, bloc) { 2 | // ➊ 게임판의 행과 열의 개수를 저장합니다. 3 | const ROW = board.length; 4 | const COL = board[0].length; 5 | 6 | // ➋ 이동할 수 있는 방향을 저장합니다. 상, 우, 하, 좌 순서로 저장되어 있습니다. 7 | const DR = [-1, 0, 1, 0]; 8 | const DC = [0, 1, 0, -1]; 9 | 10 | // ➌ 주어진 위치가 유효한 위치인지 확인하는 함수입니다. 11 | function isValidPos(r, c) { 12 | return 0 <= r && r < ROW && 0 <= c && c < COL; 13 | } 14 | 15 | // ➍ 재귀적으로 호출되는 함수입니다. 16 | function recursiveFunc(alphaPos, betaPos, visited, step) { 17 | // ➎ 현재 플레이어의 위치와 이동 가능한지 여부, 18 | // 상대 플레이어가 이긴 경우를 저장하는 변수들입니다. 19 | const [r, c] = step % 2 === 0 ? alphaPos : betaPos; 20 | let canMove = false; 21 | let isOpponentWinner = true; 22 | 23 | // ➏ 이긴 경우와 지는 경우를 저장하는 리스트입니다. 24 | const winSteps = []; 25 | const loseSteps = []; 26 | 27 | // ➐ 현재 위치에서 이동할 수 있는 모든 방향으로 이동해봅니다. 28 | for (let i = 0; i < 4; i++) { 29 | const nr = r + DR[i]; 30 | const nc = c + DC[i]; 31 | 32 | // ➑ 이동할 수 있는 위치인 경우 33 | if (isValidPos(nr, nc) && !visited.has(`${nr},${nc}`) && board[nr][nc]) { 34 | canMove = true; 35 | // ➒ 두 플레이어의 위치가 같으면 A 플레이어가 이긴 것이므로 True와 step + 1을 반환합니다. 36 | if (alphaPos[0] === betaPos[0] && alphaPos[1] === betaPos[1]) { 37 | return [true, step + 1]; 38 | } 39 | 40 | // ➓ 재귀적으로 호출하여 이긴 여부와 남은 턴수를 가져옵니다. 41 | const [win, stepsLeft] = step % 2 === 0 42 | ? recursiveFunc([nr, nc], betaPos, new Set([...visited, `${r},${c}`]), step + 1) 43 | : recursiveFunc(alphaPos, [nr, nc], new Set([...visited, `${r},${c}`]), step + 1); 44 | 45 | // ⓫ 상대 플레이어가 이긴 경우만 true로 유지합니다. 46 | isOpponentWinner &= win; 47 | 48 | // ⓬ 이긴 경우와 지는 경우를 저장합니다. 49 | if (win) { 50 | winSteps.push(stepsLeft); 51 | } else { 52 | loseSteps.push(stepsLeft); 53 | } 54 | } 55 | } 56 | 57 | // ⓭ 이동할 수 있는 위치가 없는 경우 58 | if (!canMove) { 59 | return [false, step]; 60 | } 61 | 62 | // ⓮ 상대 플레이어가 이긴 경우 63 | if (isOpponentWinner) { 64 | return [false, Math.max(...winSteps)]; 65 | } 66 | 67 | // ⓯ 현재 플레이어가 이긴 경우 68 | return [true, Math.min(...loseSteps)]; 69 | } 70 | 71 | // ⓰ A 플레이어가 이길 때까지 걸리는 최소 턴 수를 반환합니다. 72 | const [_, steps] = recursiveFunc(aloc, bloc, new Set(), 0); 73 | 74 | return steps; 75 | } 76 | -------------------------------------------------------------------------------- /solution/54.js: -------------------------------------------------------------------------------- 1 | function solution(s) { 2 | const counts = Array(26).fill(0); // ❶ 알파벳 개수(26개)만큼 빈도수 배열 생성 3 | 4 | // ❷ 문자열의 각 문자에 대한 빈도수를 빈도수 배열에 저장 5 | for (const c of s) { 6 | counts[c.charCodeAt(0) - "a".charCodeAt(0)] += 1; 7 | } 8 | 9 | // ❸ 빈도수 배열을 순회하면서 정렬된 문자열을 생성 10 | let sortedStr = ""; 11 | for (let i = 0; i < 26; i++) { 12 | sortedStr += String.fromCharCode(i + "a".charCodeAt(0)).repeat(counts[i]); 13 | } 14 | 15 | return sortedStr; 16 | } 17 | 18 | console.log(solution('hello')) // 'ehllo' 19 | console.log(solution('algorithm')) // 'aghilmort' -------------------------------------------------------------------------------- /solution/55.js: -------------------------------------------------------------------------------- 1 | function solution(arr1, arr2) { 2 | const merged = []; // 정렬된 배열을 저장할 리스트 생성 3 | let i = 0, j = 0; // 두 배열의 인덱스 초기화 4 | 5 | // 두 배열을 순회하면서 정렬된 배열을 생성 6 | while (i < arr1.length && j < arr2.length) { 7 | if (arr1[i] <= arr2[j]) { 8 | merged.push(arr1[i]); 9 | i += 1; 10 | } else { 11 | merged.push(arr2[j]); 12 | j += 1; 13 | } 14 | } 15 | 16 | // arr1이나 arr2 중 남아있는 원소들을 정렬된 배열 뒤에 추가 17 | while (i < arr1.length) { 18 | merged.push(arr1[i]); 19 | i += 1; 20 | } 21 | while (j < arr2.length) { 22 | merged.push(arr2[j]); 23 | j += 1; 24 | } 25 | 26 | return merged; 27 | } 28 | 29 | console.log(solution([1, 3, 5], [2, 4, 6])) // [1, 2, 3, 4, 5, 6] 30 | console.log(solution([1, 2, 3], [4, 5, 6])) // [1, 2, 3, 4, 5, 6] -------------------------------------------------------------------------------- /solution/56.js: -------------------------------------------------------------------------------- 1 | function solution(strings, n) { 2 | return strings.sort(function (a, b) { 3 | if (a[n] === b[n]) { 4 | return a > b ? 1 : -1; 5 | } else { 6 | return a[n] > b[n] ? 1 : -1; 7 | } 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /solution/57.js: -------------------------------------------------------------------------------- 1 | function solution(n) { 2 | const digits = Array.from(String(n), Number); // ➊ 정수 n을 문자열로 변환하고 각 자릿수를 리스트로 저장합니다. 3 | digits.sort((a, b) => b - a); // ➋ 내림차순으로 정렬합니다. 4 | const answer = Number(digits.join('')); // ➌ 정렬된 자릿수를 다시 하나의 문자열로 합쳐 정수로 변환합니다. 5 | return answer; 6 | } 7 | -------------------------------------------------------------------------------- /solution/58.js: -------------------------------------------------------------------------------- 1 | function solution(array, commands) { 2 | const answer = []; 3 | for (const command of commands) { 4 | const [i, j, k] = command; 5 | const sliced = array.slice(i - 1, j); // ➊ i부터 j까지 자르기 6 | sliced.sort((a, b) => a - b); // ➋ 자른 배열을 정렬하기 7 | answer.push(sliced[k - 1]); // ➌ k번째 원소 구하기 8 | } 9 | return answer; 10 | } 11 | -------------------------------------------------------------------------------- /solution/59.js: -------------------------------------------------------------------------------- 1 | function compare(a, b) { 2 | // ➊ 각 수를 문자열로 바꾼 뒤, 조합하여 비교합니다 3 | // 예. a=3, b=30 -> t1='330', t2='303' t1 > t2 4 | const t1 = a.toString() + b.toString(); 5 | const t2 = b.toString() + a.toString(); 6 | // t1이 크다면 1, t2가 크다면 -1, 같으면 1 7 | return t1 > t2 ? -1 : 1; 8 | } 9 | 10 | function solution(numbers) { 11 | // ➋ reverse = True를 이용해 내림차순으로 정렬합니다. 12 | const sortedNums = numbers.sort(compare); 13 | // ➌ 각 정렬된 수를 문자열로 이어붙인 뒤, int로 변환한 값을 문자열로 다시 변환하여 반환합니다. 14 | const answer = sortedNums.join(""); 15 | return Number(answer) === 0 ? "0" : answer; 16 | } 17 | -------------------------------------------------------------------------------- /solution/60.js: -------------------------------------------------------------------------------- 1 | function solution(s) { 2 | // ➊ 문자열 s를 파싱하여 리스트로 변환합니다. 3 | const numbers = s.slice(2, -2).split("},{"); 4 | const sorted = numbers.sort((a, b) => a.length - b.length); 5 | const answer = []; 6 | 7 | // ➋ 각 원소를 순회하면서 이전 원소와 차이가 나는 부분을 구합니다. 8 | for (const element of sorted) { 9 | const nums = element.split(","); 10 | for (const num of nums) { 11 | if (!answer.includes(Number(num))) { 12 | answer.push(Number(num)); 13 | } 14 | } 15 | } 16 | 17 | return answer; 18 | } -------------------------------------------------------------------------------- /solution/61.js: -------------------------------------------------------------------------------- 1 | class MinHeap { 2 | constructor() { 3 | this.items = []; 4 | } 5 | 6 | size() { 7 | return this.items.length; 8 | } 9 | 10 | push(item) { 11 | this.items.push(item); 12 | this.bubbleUp(); 13 | } 14 | 15 | pop() { 16 | if (this.size() === 0) { 17 | return null; 18 | } 19 | 20 | const min = this.items[0]; 21 | this.items[0] = this.items[this.size() - 1]; 22 | this.items.pop(); 23 | this.bubbleDown(); 24 | return min; 25 | } 26 | 27 | swap(a, b) { 28 | [this.items[a], this.items[b]] = [this.items[b], this.items[a]]; 29 | } 30 | 31 | bubbleUp() { 32 | let index = this.size() - 1; 33 | while (index > 0) { 34 | const parentIndex = Math.floor((index - 1) / 2); 35 | if (this.items[parentIndex][0] <= this.items[index][0]) { 36 | break; 37 | } 38 | this.swap(index, parentIndex); 39 | index = parentIndex; 40 | } 41 | } 42 | 43 | bubbleDown() { 44 | let index = 0; 45 | while (index * 2 + 1 < this.size()) { 46 | let leftChild = index * 2 + 1; 47 | let rightChild = index * 2 + 2; 48 | let smallerChild = 49 | rightChild < this.size() && 50 | this.items[rightChild][0] < this.items[leftChild][0] 51 | ? rightChild 52 | : leftChild; 53 | 54 | if (this.items[index][0] <= this.items[smallerChild][0]) { 55 | break; 56 | } 57 | 58 | this.swap(index, smallerChild); 59 | index = smallerChild; 60 | } 61 | } 62 | } 63 | 64 | 65 | function solution(land, height) { 66 | let answer = 0 67 | const n = land.length; 68 | 69 | // ➊ 주변 노드 탐색을 위한 di, dj 70 | const di = [-1, 0, 1, 0]; 71 | const dj = [0, 1, 0, -1]; 72 | const visited = Array.from(Array(n), () => Array(n).fill(false)); 73 | 74 | // ➋ 시작 노드 추가 75 | const q = new MinHeap(); 76 | 77 | q.push([0, 0, 0]); // [비용, i, j] 78 | 79 | // ➌ BFS + 우선 순위 큐로 다음 노드 관리 80 | while (q.size() > 0) { 81 | const [cost, i, j] = q.pop(); 82 | // ➍ 아직 방문하지 않은 경로만 탐색 83 | if (!visited[i][j]) { 84 | visited[i][j] = true; 85 | // ➎ 현재까지 비용을 합산 86 | answer += cost; 87 | for (let d = 0; d < 4; d++) { 88 | const ni = i + di[d]; 89 | const nj = j + dj[d]; 90 | // ➏ 유효한 인덱스일 경우 91 | if (0 <= ni && ni < n && 0 <= nj && nj < n) { 92 | const tempCost = Math.abs(land[i][j] - land[ni][nj]); 93 | // ➐ 입력으로 주어진 height보다 높이차가 큰 경우 94 | const newCost = tempCost > height ? tempCost : 0; 95 | // ➑ 다음 경로를 푸시 96 | q.push([newCost, ni, nj]); 97 | } 98 | } 99 | } 100 | } 101 | 102 | return answer; 103 | } 104 | -------------------------------------------------------------------------------- /solution/62.js: -------------------------------------------------------------------------------- 1 | function solution(arr, n) { 2 | // ❶ 2차원 배열을 인자로 받고, 90도 회전시키는 함수 3 | function rotate90(arr) { 4 | // ❷ 배열의 크기를 저장 5 | const n = arr.length; 6 | 7 | // ❸ 배열의 크기와 동일한 2차원 배열 생성(초기값은 0) 8 | const rotated = Array.from({ length: n }, () => Array(n).fill(0)); 9 | 10 | // ❹ 배열을 90도 회전 11 | for (let i = 0; i < n; i++) { 12 | for (let j = 0; j < n; j++) { 13 | rotated[j][n - i - 1] = arr[i][j]; 14 | } 15 | } 16 | 17 | // ❺ 90도로 회전한 배열 반환 18 | return rotated; 19 | } 20 | 21 | // ❻ 원본 배열 arr을 복사 22 | let rotated = arr.map((row) => [...row]); 23 | 24 | // ❼ 90도 회전 함수 호출 25 | for (let i = 0; i < n; i++) { 26 | rotated = rotate90(rotated); 27 | } 28 | 29 | return rotated; 30 | } 31 | 32 | 33 | console.log( 34 | solution( 35 | [ 36 | [1, 2, 3, 4], 37 | [5, 6, 7, 8], 38 | [9, 10, 11, 12], 39 | [13, 14, 15, 16] 40 | ], 41 | 1 42 | ) 43 | ) 44 | // [ 45 | // [13, 9, 5, 1], 46 | // [14, 10, 6, 2], 47 | // [15, 11, 7, 3], 48 | // [16, 12, 8, 4] 49 | // ] 50 | 51 | console.log( 52 | solution( 53 | [ 54 | [1, 2, 3, 4], 55 | [5, 6, 7, 8], 56 | [9, 10, 11, 12], 57 | [13, 14,15,16] 58 | ], 59 | 2 60 | ) 61 | ) 62 | // [ 63 | // [16, 15, 14, 13], 64 | // [12, 11, 10, 9], 65 | // [8, 7, 6, 5], 66 | // [4, 3, 2, 1] 67 | // ] -------------------------------------------------------------------------------- /solution/63.js: -------------------------------------------------------------------------------- 1 | function multiplyMatrices(matrix1, matrix2) { 2 | // ❶ 결과 행렬을 0으로 초기화합니다. 3 | const result = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]; 4 | 5 | // ❷ 행렬 곱셈을 수행합니다. 6 | for (let i = 0; i < 3; i++) { 7 | for (let j = 0; j < 3; j++) { 8 | for (let k = 0; k < 3; k++) { 9 | result[i][j] += matrix1[i][k] * matrix2[k][j]; 10 | } 11 | } 12 | } 13 | 14 | return result; 15 | } 16 | 17 | function transposeMatrix(matrix) { 18 | // ❸ 결과 행렬을 0으로 초기화합니다. 19 | const result = [[0, 0, 0], [0, 0, 0], [0, 0, 0]]; 20 | 21 | // 전치 행렬을 계산합니다. 22 | for (let i = 0; i < 3; i++) { 23 | for (let j = 0; j < 3; j++) { 24 | result[j][i] = matrix[i][j]; 25 | } 26 | } 27 | 28 | return result; 29 | } 30 | 31 | function solution(matrix1, matrix2) { 32 | // 주어진 두 행렬을 곱합니다. 33 | const multiplied = multiplyMatrices(matrix1, matrix2); 34 | // 곱셈 결과의 전치 행렬을 계산합니다. 35 | const transposed = transposeMatrix(multiplied); 36 | return transposed; 37 | } 38 | 39 | 40 | console.log( 41 | solution( 42 | [ 43 | [1, 2, 3], 44 | [4, 5, 6], 45 | [7, 8, 9] 46 | ], 47 | [ 48 | [9, 8, 7], 49 | [6, 5, 4], 50 | [3, 2, 1] 51 | ] 52 | ) 53 | ); 54 | // [ 55 | // [30, 84, 138], 56 | // [24, 69, 114], 57 | // [18, 54, 90] 58 | // ] 59 | 60 | console.log( 61 | solution( 62 | [ 63 | [2, 4, 6], 64 | [1, 3, 5], 65 | [7, 8, 9] 66 | ], 67 | [ 68 | [9, 1, 2], 69 | [4, 5, 6], 70 | [7, 3, 8] 71 | ] 72 | ) 73 | ); 74 | // [ 75 | // [76, 56, 158], 76 | // [40, 31, 74], 77 | // [76, 60, 134] 78 | // ] 79 | -------------------------------------------------------------------------------- /solution/64.js: -------------------------------------------------------------------------------- 1 | function solution(n) { 2 | // ❶ n 크기의 2차원 배열 생성 3 | const snailArray = Array.from({ length: n }, () => Array(n).fill(0)); 4 | 5 | let num = 1; // ❷ 달팽이 수열의 시작 숫자 6 | 7 | // ❸ 행과 열의 시작과 끝 인덱스를 설정 8 | let startRow = 0, endRow = n - 1; 9 | let startCol = 0, endCol = n - 1; 10 | 11 | while (startRow <= endRow && startCol <= endCol) { 12 | // ❹ 첫 번째 행 채우기 13 | for (let i = startCol; i <= endCol; i++) { 14 | snailArray[startRow][i] = num; 15 | num += 1; 16 | } 17 | startRow += 1; 18 | 19 | // ❺ 마지막 열 채우기 20 | for (let i = startRow; i <= endRow; i++) { 21 | snailArray[i][endCol] = num; 22 | num += 1; 23 | } 24 | endCol -= 1; 25 | 26 | // ❻ 마지막 행 채우기 27 | if (startRow <= endRow) { 28 | for (let i = endCol; i >= startCol; i--) { 29 | snailArray[endRow][i] = num; 30 | num += 1; 31 | } 32 | endRow -= 1; 33 | } 34 | 35 | // ❼ 첫 번째 열 채우기 36 | if (startCol <= endCol) { 37 | for (let i = endRow; i >= startRow; i--) { 38 | snailArray[i][startCol] = num; 39 | num += 1; 40 | } 41 | startCol += 1; 42 | } 43 | } 44 | 45 | return snailArray; 46 | } 47 | 48 | console.log(solution(3)); 49 | // [ 50 | // [1, 2, 3], 51 | // [8, 9, 4], 52 | // [7, 6, 5] 53 | // ] 54 | 55 | console.log(solution(4)); 56 | // [ 57 | // [1, 2, 3, 4], 58 | // [12, 13, 14, 5], 59 | // [11, 16, 15, 6], 60 | // [10, 9, 8, 7] 61 | // ] 62 | -------------------------------------------------------------------------------- /solution/65.js: -------------------------------------------------------------------------------- 1 | function solution(s) { 2 | // ❶ 이진 변환 횟수를 저장하는 변수 3 | let countTransform = 0; 4 | // ❷ 제거된 모든 0의 개수를 저장하는 변수 5 | let countZero = 0; 6 | 7 | // ❸ s가 '1'이 아닌 동안 계속 반복 8 | while (s !== '1') { 9 | s = s.split(''); // 문자열을 배열로 변환 10 | // ❹ 이진 변환 횟수를 1 증가 11 | countTransform += 1; 12 | // ❺ s에서 '0'의 개수를 세어 countZero에 누적 13 | countZero += s.filter((c) => c === '0').length; 14 | // ❻ s에서 '1'의 개수를 세고, 이를 이진수로 변환 15 | s = s.filter((char) => char === '1').length.toString(2); 16 | } 17 | 18 | // ❼ 이진 변환 횟수와 제거된 모든 '0'의 개수를 리스트에 담아 반환 19 | return [countTransform, countZero]; 20 | } 21 | -------------------------------------------------------------------------------- /solution/66.js: -------------------------------------------------------------------------------- 1 | function solution(topping) { 2 | // ❶ 결과값을 저장할 변수 초기화 3 | let splitCount = 0; 4 | 5 | // ❷ 토핑의 개수를 세어서 Map에 저장 6 | const toppingCounter = new Map(); 7 | for (const t of topping) { 8 | toppingCounter.set(t, (toppingCounter.get(t) || 0) + 1); 9 | } 10 | 11 | // ❸ 절반에 속한 토핑의 종류를 저장할 Set 12 | const halfToppingSet = new Set(); 13 | 14 | // ❹ 롤케이크를 하나씩 절반에 넣으면서 확인 15 | for (const t of topping) { 16 | // ❺ 절반에 토핑을 추가하고, 해당 토핑의 전체 개수를 줄임 17 | halfToppingSet.add(t); 18 | toppingCounter.set(t, toppingCounter.get(t) - 1); 19 | 20 | // ❻ 토핑의 전체 개수가 0이면 오브젝트에서 제거 21 | if (toppingCounter.get(t) === 0) { 22 | toppingCounter.delete(t); 23 | } 24 | 25 | // ❼ 토핑의 종류의 수가 같다면 26 | if (halfToppingSet.size === toppingCounter.size) { 27 | splitCount += 1; 28 | } 29 | } 30 | 31 | // ❽ 공평하게 나눌 수 있는 방법의 수 반환 32 | return splitCount; 33 | } 34 | -------------------------------------------------------------------------------- /solution/67.js: -------------------------------------------------------------------------------- 1 | function solution(brown, yellow) { 2 | // ❶ 격자의 총 개수 (갈색 격자 + 노란 격자) 3 | const totalSize = brown + yellow; 4 | 5 | // ❷ 세로 길이의 범위는 3부터 (갈색 격자 + 노란 격자)의 제곱근 6 | for (let vertical = 3; vertical <= Math.sqrt(totalSize); vertical++) { 7 | // ❸ 사각형 구성이 되는지 확인 8 | if (totalSize % vertical === 0) { 9 | const horizontal = totalSize / vertical; // ❹ 사각형의 가로 길이 10 | // ❺ 카펫 형태로 만들 수 있는지 확인 11 | if (brown === (horizontal + vertical - 2) * 2) { 12 | return [horizontal, vertical]; // ❻ [가로 길이, 세로 길이] 13 | } 14 | } 15 | } 16 | 17 | return []; // ❼ 만약 답을 찾지 못했다면 빈 리스트를 반환 18 | } 19 | -------------------------------------------------------------------------------- /solution/68.js: -------------------------------------------------------------------------------- 1 | function solution(n) { 2 | return n 3 | .toString(2) // 2진수로 변환 4 | .split('') // 문자열에서 배열로 변환 5 | .filter((c) => c === '1') // 1만 필터링 6 | .length; // 1의 개수를 반환 7 | } 8 | -------------------------------------------------------------------------------- /solution/69.js: -------------------------------------------------------------------------------- 1 | function solution(keyinput, board) { 2 | // ❶ 캐릭터의 초기 위치 3 | let x = 0; 4 | let y = 0; 5 | 6 | // ❷ 각 방향에 대한 움직임 7 | const moves = { 8 | up: [0, 1], 9 | down: [0, -1], 10 | left: [-1, 0], 11 | right: [1, 0], 12 | }; 13 | 14 | // ❸ 게임 경계좌표 15 | const width = board[0] / 2; 16 | const height = board[1] / 2; 17 | 18 | // ❹ 보드의 경계좌표를 벗어나는지 확인하는 함수 19 | function isInBounds(x, y, dx, dy) { 20 | return -width <= x + dx && x + dx <= width && -height <= y + dy && y + dy <= height; 21 | } 22 | 23 | for (const key of keyinput) { 24 | // ❺ 방향키에 따른 오프셋 25 | const [dx, dy] = moves[key]; 26 | 27 | // ❻ 게임 맵의 크기를 벗어나지 않는지 확인 28 | if (isInBounds(x, y, dx, dy)) { 29 | x += dx; 30 | y += dy; 31 | } 32 | } 33 | 34 | // ❼ 캐릭터의 위치를 반환합니다. 35 | return [x, y]; 36 | } 37 | -------------------------------------------------------------------------------- /solution/70.js: -------------------------------------------------------------------------------- 1 | function solution(str1, str2) { 2 | // ❶ 두 문자열의 길이를 저장 3 | const m = str1.length; 4 | const n = str2.length; 5 | 6 | // ❷ LCS를 저장할 테이블 초기화 7 | const dp = Array.from(Array(m + 1), () => Array(n + 1).fill(0)); 8 | 9 | // ❸ 동적 프로그래밍을 통해 LCS 길이 계산 10 | for (let i = 1; i <= m; i++) { 11 | for (let j = 1; j <= n; j++) { 12 | // ❹ 현재 비교하는 문자가 같으면 13 | if (str1[i - 1] === str2[j - 1]) { 14 | dp[i][j] = dp[i - 1][j - 1] + 1; 15 | } else { 16 | // ❺ 현재 비교하는 문자가 같지 않으면 17 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); 18 | } 19 | } 20 | } 21 | 22 | // ❻ LCS 길이 반환 23 | return dp[m][n]; 24 | } 25 | 26 | 27 | console.log(solution("ABCBDAB","BDCAB")) // 4 28 | console.log(solution("AGGTAB","GXTXAYB")) // 4 29 | -------------------------------------------------------------------------------- /solution/71.js: -------------------------------------------------------------------------------- 1 | function solution(nums) { 2 | const n = nums.length; 3 | 4 | // ❶ dp[i]는 nums[i]를 마지막으로 하는 LIS의 길이를 저장하는 배열입니다. 5 | const dp = Array(n).fill(1); 6 | 7 | for (let i = 1; i < n; i++) { 8 | for (let j = 0; j < i; j++) { 9 | // ❷ nums[i]와 nums[j]를 비교하여, nums[i]가 더 큰 경우에만 처리합니다. 10 | if (nums[i] > nums[j]) { 11 | // ❸ nums[i]를 이용하여 만든 부분 수열의 길이와 12 | // nums[j]를 이용하여 만든 부분 수열의 길이 + 1 중 최댓값을 저장합니다. 13 | dp[i] = Math.max(dp[i], dp[j] + 1); 14 | } 15 | } 16 | } 17 | 18 | // ❹ dp 배열에서 최댓값을 찾아 최장 증가 부분 수열의 길이를 반환합니다. 19 | return Math.max(...dp); 20 | } 21 | 22 | 23 | console.log(solution([1, 4, 2, 3, 1, 5, 7, 3])) // 5 24 | console.log(solution([3, 2, 1])) // 1 25 | -------------------------------------------------------------------------------- /solution/72.js: -------------------------------------------------------------------------------- 1 | function solution(arr) { 2 | const n = arr[0].length; // ❶ 입력 배열의 열의 개수를 저장합니다. 3 | const dp = Array.from(Array(4), () => Array(n).fill(0)); // ❷ dp 배열을 초기화합니다. 4행 n열의 2차원 배열입니다. 4 | 5 | // 각 열에서 선택 가능한 4가지 조약돌 배치 패턴에 대해 첫 번째 열의 가중치를 초기화합니다. 6 | // ❸ 0: 상단, 1: 중앙, 2: 하단, 3: 상단과 하단 7 | dp[0][0] = arr[0][0]; 8 | dp[1][0] = arr[1][0]; 9 | dp[2][0] = arr[2][0]; 10 | dp[3][0] = arr[0][0] + arr[2][0]; 11 | 12 | // ❹ 두 번째 열부터 마지막 열까지 각 열에서 선택 가능한 4가지 조약돌 배치 패턴에 대해 최대 가중치를 계산합니다. 13 | for (let i = 1; i < n; i++) { 14 | // 패턴 0이 선택된 경우, 이전은 패턴 {1, 2} 가능 15 | dp[0][i] = arr[0][i] + Math.max(dp[1][i - 1], dp[2][i - 1]); 16 | // 패턴 1이 선택된 경우, 이전은 패턴 {0, 2, 3|가능 17 | dp[1][i] = arr[1][i] + Math.max(dp[0][i - 1], dp[2][i - 1], dp[3][i - 1]); 18 | // 패턴 2가 선택된 경우, 이전은 패턴 {0, 1}이 가능 19 | dp[2][i] = arr[2][i] + Math.max(dp[0][i - 1], dp[1][i - 1]); 20 | // 패턴 3이 선택된 경우, 이전은 패턴{1}이 가능 21 | dp[3][i] = arr[0][i] + arr[2][i] + dp[1][i - 1]; 22 | } 23 | 24 | // ❺ 마지막 열에서 선택 가능한 4가지 조약돌 배치 패턴 중 최대 가중치를 반환합니다. 25 | return Math.max(...dp.map((row) => row[n - 1])); 26 | } 27 | 28 | 29 | console.log(solution([[1, 3, 3, 2], [2, 1, 4, 1], [1, 5, 2, 3]])) // 19 30 | console.log(solution([[1, 7, 13, 2, 6], [2, -4, 2, 5, 4], [5, 3, 5, -3, 1]])) // 32 31 | -------------------------------------------------------------------------------- /solution/73.js: -------------------------------------------------------------------------------- 1 | function solution(n) { 2 | const fib = [0, 1]; // F(0) = 0, F(1) = 1 3 | for (let i = 2; i <= n; i++) { 4 | fib.push((fib[i - 1] + fib[i - 2]) % 1234567); 5 | } 6 | return fib[n]; 7 | } 8 | 9 | // function solution(n) { 10 | // const fib = [0, 1]; // F(0) = 0, F(1) = 1 11 | // for (let i = 2; i <= n; i++) { 12 | // fib.push((fib[i - 1] + fib[i - 2])); 13 | // } 14 | // return fib[n] % 1234567; 15 | // } 16 | -------------------------------------------------------------------------------- /solution/74.js: -------------------------------------------------------------------------------- 1 | function solution(n) { 2 | // ➊ 바닥의 가로 길이가 1인 경우, 바닥을 채우는 방법의 수는 1 3 | if (n === 1) { 4 | return 1; 5 | } 6 | 7 | // ➋ 바닥의 가로 길이가 2인 경우, 바닥을 채우는 방법의 수는 2 8 | if (n === 2) { 9 | return 2; 10 | } 11 | 12 | // ➌ 동적 계획법을 위한 리스트 초기화 13 | // dp[i]는 가로 길이가 i일 때 바닥을 채우는 방법의 수 14 | const dp = Array(n + 1).fill(0); 15 | dp[1] = 1; 16 | dp[2] = 2; 17 | 18 | // ➍ 가로 길이가 3부터 n까지의 각각의 경우에 대해 바닥을 채우는 방법의 수를 구함 19 | for (let i = 3; i <= n; i++) { 20 | // ➎ dp[i]는 dp[i-1]과 dp[i-2]를 더한 값 21 | dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007; 22 | } 23 | 24 | // ➏ 바닥의 가로 길이가 n일 때 바닥을 채우는 방법의 수인 dp[n]을 반환 25 | return dp[n]; 26 | } 27 | 28 | 29 | // function solution(n) { 30 | // let a = 1; 31 | // let b = 2; 32 | 33 | // for (let i = 2; i < n; i += 1) { 34 | // const temp = b; 35 | // b = (a + b) % 1000000007; 36 | // a = temp; 37 | // } 38 | 39 | // return b; 40 | // } 41 | -------------------------------------------------------------------------------- /solution/75.js: -------------------------------------------------------------------------------- 1 | function solution(triangle) { 2 | const n = triangle.length; 3 | const dp = Array.from(Array(n), () => Array(n).fill(0)); // ➊ dp 테이블 초기화 4 | 5 | // ➋ dp 테이블의 맨 아래쪽 라인 초기화 6 | for (let i = 0; i < n; i++) { 7 | dp[n - 1][i] = triangle[n - 1][i]; 8 | } 9 | 10 | // ➌ 아래쪽 라인부터 올라가면서 dp 테이블 채우기 11 | for (let i = n - 2; i >= 0; i--) { 12 | for (let j = 0; j <= i; j++) { 13 | dp[i][j] = Math.max(dp[i + 1][j], dp[i + 1][j + 1]) + triangle[i][j]; 14 | } 15 | } 16 | 17 | return dp[0][0]; // 꼭대기에서의 최댓값 반환 18 | } 19 | -------------------------------------------------------------------------------- /solution/76.js: -------------------------------------------------------------------------------- 1 | function solution(land) { 2 | // ➊ 각 행마다 이전 행에서의 최대 점수를 더해가며 최대 점수를 구합니다. 3 | for (let i = 1; i < land.length; i++) { 4 | for (let j = 0; j < 4; j++) { 5 | // ➋ 이전 행에서 현재 열의 값을 제외한 나머지 열들 중에서 가장 큰 값을 더해줍니다. 6 | land[i][j] += Math.max(...land[i - 1].filter((_, index) => index !== j)); 7 | } 8 | } 9 | 10 | // ➌ 마지막 행에서 얻을 수 있는 최대 점수를 반환합니다. 11 | return Math.max(...land[land.length - 1]); 12 | } 13 | -------------------------------------------------------------------------------- /solution/77.js: -------------------------------------------------------------------------------- 1 | function solution(money) { 2 | // ➊ 점화식에 필요한 변수를 초기화 3 | const n = money.length; 4 | const dp1 = Array(n).fill(0); 5 | const dp2 = Array(n).fill(0); 6 | 7 | // ➋ 첫 번째 집을 도둑질하는 경우 8 | dp1[0] = money[0]; 9 | dp1[1] = money[0]; 10 | for (let i = 2; i < n - 1; i++) { 11 | dp1[i] = Math.max(dp1[i - 1], dp1[i - 2] + money[i]); 12 | } 13 | 14 | // ➌ 첫 번째 집을 도둑질하지 않는 경우 15 | dp2[1] = money[1]; 16 | for (let i = 2; i < n; i++) { 17 | dp2[i] = Math.max(dp2[i - 1], dp2[i - 2] + money[i]); 18 | } 19 | 20 | // ➍ 두 경우 중 최댓값 찾기 21 | const answer = Math.max(dp1[n - 2], dp2[n - 1]); 22 | 23 | return answer; 24 | } 25 | -------------------------------------------------------------------------------- /solution/78.js: -------------------------------------------------------------------------------- 1 | function solution(board) { 2 | // ➊ 주어진 2차원 보드의 행과 열의 개수를 변수에 저장합니다. 3 | const ROW = board.length; 4 | const COL = board[0].length; 5 | 6 | // ➋ 각 행과 열을 순회하며 최적의 정사각형을 찾습니다. 7 | for (let i = 1; i < ROW; i++) { 8 | for (let j = 1; j < COL; j++) { 9 | // ➌ 현재 위치의 값이 1인 경우를 확인합니다. 10 | if (board[i][j] === 1) { 11 | // ➍ 현재 위치에서 위, 왼쪽, 대각선 왼쪽 위의 값들을 가져옵니다. 12 | const up = board[i - 1][j]; 13 | const left = board[i][j - 1]; 14 | const upLeft = board[i - 1][j - 1]; 15 | 16 | // ➎ 현재 위치의 값을 이전 위치들의 값들 중 가장 작은 값에 1을 더한 값으로 업데이트합니다. 17 | board[i][j] = Math.min(up, left, upLeft) + 1; 18 | } 19 | } 20 | } 21 | 22 | // ➏ 보드에서 가장 큰 값(최대 정사각형의 한 변의 길이)을 찾습니다. 23 | const maxVal = Math.max(...board.map((row) => Math.max(...row))); 24 | 25 | // ➐ 최대 정사각형의 넓이를 반환합니다. 26 | return maxVal * maxVal; 27 | } 28 | -------------------------------------------------------------------------------- /solution/79.js: -------------------------------------------------------------------------------- 1 | function solution(strs, t) { 2 | const n = t.length; // ➊ 타겟 문자열 t의 길이 3 | const dp = Array(n + 1).fill(Infinity); // ➋ 각 위치에서 필요한 최소 조각수를 저장할 배열(초기값은 INF로 함) 4 | dp[0] = 0; // ➌ 빈 문자열을 위해 필요한 최소 조각수는 0 5 | const sizes = new Set(strs.map((str) => str.length)); // ➍ strs 조각들의 길이를 저장한 집합 6 | 7 | for (let i = 1; i <= n; i++) { // ➎ dp[i]부터 dp[n]까지 채우기 위한 반복문 8 | for (const size of sizes) { // ➏ 각 str 조각의 문자열 길이에 대하여 9 | if (i - size >= 0 && strs.includes(t.slice(i - size, i))) { // ➐ 이미 구한 해와 strs 조각을 추가해서 문자열을 만들 수 있다면 10 | dp[i] = Math.min(dp[i], dp[i - size] + 1); // ➑ 해당 위치의 최소 조각수를 갱신 11 | } 12 | } 13 | } 14 | 15 | return dp[n] === Infinity ? -1 : dp[n]; // ➒ 최소 조각수를 반환 16 | } 17 | -------------------------------------------------------------------------------- /solution/80.js: -------------------------------------------------------------------------------- 1 | function solution(amount) { 2 | const denominations = [1, 10, 50, 100]; 3 | denominations.sort((a, b) => b - a); // ❶ 화폐 단위를 큰 순서대로 정렬 4 | 5 | const change = []; // ❷ 거스름돈을 담을 리스트 6 | 7 | for (const coin of denominations) { 8 | while (amount >= coin) { // ❸ 해당 화폐 단위로 거스름돈을 계속 나눠줌 9 | change.push(coin); // ❹ 거스름돈 리스트 업데이트 10 | amount -= coin; // ❺ 정산이 완료된 거스름돈 뺌 11 | } 12 | } 13 | 14 | return change; // ❻ 거스름돈 리스트 반환 15 | } 16 | 17 | console.log(solution(123)) // [100, 10, 10, 1, 1, 1] 18 | console.log(solution(350)) // [100, 100, 100, 50] 19 | -------------------------------------------------------------------------------- /solution/81.js: -------------------------------------------------------------------------------- 1 | // ❶ 각 물건의 단위 무게당 가치를 계산하여 items 리스트에 추가 2 | function calculateUnitValue(items) { 3 | for (let i = 0; i < items.length; i++) { 4 | items[i].push(items[i][1] / items[i][0]); 5 | } 6 | return items; 7 | } 8 | 9 | // ❷ 단위 무게당 가치가 높은 순으로 물건을 정렬 10 | function sortByUnitValue(items) { 11 | items.sort((a, b) => b[2] - a[2]); 12 | return items; 13 | } 14 | 15 | function knapsack(items, weightLimit) { 16 | let totalValue = 0; // ❸ 선택한 물건들의 총 가치를 저장하는 변수 17 | let remainingWeight = weightLimit; // ❹ 남은 무게 한도를 저장하는 변수 18 | 19 | // ❺ 물건을 선택합니다. 20 | for (let i = 0; i < items.length; i++) { 21 | if (items[i][0] <= remainingWeight) { 22 | // ❻ 남은 무게 한도 내에서 물건을 통째로 선택 23 | totalValue += items[i][1]; 24 | remainingWeight -= items[i][0]; 25 | } else { 26 | // ❼ 남은 무게 한도가 물건의 무게보다 작으면 쪼개서 일부분만 선택 27 | const fraction = remainingWeight / items[i][0]; 28 | totalValue += items[i][1] * fraction; 29 | break; // ❽ 이미 배낭의 무게 한도를 모두 사용한 경우, 30 | } 31 | } 32 | return totalValue; 33 | } 34 | 35 | function solution(items, weightLimit) { 36 | items = calculateUnitValue(items); 37 | items = sortByUnitValue(items); 38 | 39 | // ❾ 배낭의 무게 한도 내에서 담을 수 있는 물건들의 최대 가치를 반환 40 | return knapsack(items, weightLimit); 41 | } 42 | 43 | 44 | console.log(solution([[10, 19], [7, 10], [6, 10]], 15)) // 27.333.. 45 | console.log(solution([[10, 60], [20, 100], [30, 120]], 50)) // 240 -------------------------------------------------------------------------------- /solution/82.js: -------------------------------------------------------------------------------- 1 | function solution(d, budget) { 2 | d.sort((a, b) => a - b); // ➊ 배열 d를 오름차순으로 정렬 3 | let count = 0; // ➋ 지원할 수 있는 부서의 개수를 세는 변수 4 | 5 | for (const amount of d) { 6 | if (budget < amount) { 7 | break; // ➌ 남은 예산이 신청한 금액보다 작으면 더 이상 지원할 수 없으므로 종료 8 | } 9 | 10 | budget -= amount; // ➍ 예산에서 신청한 금액을 차감 11 | count++; 12 | } 13 | 14 | return budget >= 0 ? count : count - 1; 15 | } 16 | -------------------------------------------------------------------------------- /solution/83.js: -------------------------------------------------------------------------------- 1 | function solution(people, limit) { 2 | people.sort((a, b) => a - b); // ➊ 몸무게를 오름차순으로 정렬 3 | let count = 0; // ➋ 필요한 보트 개수 4 | let i = 0; // ➌ 가장 가벼운 사람을 가리키는 인덱스 5 | let j = people.length - 1; // ➍ 가장 무거운 사람을 가리키는 인덱스 6 | 7 | while (i <= j) { 8 | // ➎ 가장 무거운 사람과 가장 가벼운 사람을 같이 태울 수 있으면 두 사람 모두 보트에 태움 9 | if (people[j] + people[i] <= limit) { 10 | i += 1; 11 | } 12 | 13 | // ➏ 무거운 사람만 태울 수 있으면 무거운 사람만 보트에 태움 14 | j -= 1; 15 | count += 1; 16 | } 17 | 18 | return count; 19 | } 20 | -------------------------------------------------------------------------------- /solution/84.js: -------------------------------------------------------------------------------- 1 | function solution(k, tragerine) { 2 | // ➊ 귤의 개수를 세는 counter 오브젝트 생성 3 | const counter = {}; 4 | for (const t of tragerine) { 5 | counter[t] = (counter[t] || 0) + 1; 6 | } 7 | 8 | // ➋ 개수를 내림차순으로 정렬 9 | const sortedCounts = Object.values(counter).sort((a, b) => b - a); 10 | 11 | let numTypes = 0; // ➌ 현재까지의 종류 수 12 | let countSum = 0; // ➍ 현재까지의 귤 개수 합 13 | 14 | for (const count of sortedCounts) { 15 | countSum += count; 16 | numTypes += 1; 17 | 18 | // ➎ 귤 개수 합이 k 이상이 되는 순간 종료 19 | if (countSum >= k) { 20 | break; 21 | } 22 | } 23 | 24 | return numTypes; 25 | } 26 | -------------------------------------------------------------------------------- /solution/85.js: -------------------------------------------------------------------------------- 1 | function solution(N, stations, W) { 2 | let answer = 0; 3 | let location = 1; // ➊ 현재 탐색하는 아파트의 위치 4 | let idx = 0; // ➋ 설치된 기지국의 인덱스 5 | 6 | while (location <= N) { 7 | // ➌ 기지국이 설치된 위치에 도달한 경우 8 | if (idx < stations.length && location >= stations[idx] - W) { 9 | location = stations[idx] + W + 1; 10 | idx += 1; 11 | } else { 12 | location += 2 * W + 1; // ➍ 기지국을 설치하고 해당 범위를 넘어감 13 | answer += 1; 14 | } 15 | } 16 | 17 | return answer; 18 | } 19 | -------------------------------------------------------------------------------- /solution/86.js: -------------------------------------------------------------------------------- 1 | // 미로 탈출 명령어 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/150365 3 | 4 | function solution(n, m, x, y, r, c, k) { 5 | const directions = ['d', 'l', 'r', 'u']; 6 | const dx = [1, 0, 0, -1]; 7 | const dy = [0, -1, 1, 0]; 8 | 9 | // ➊ 남은 거리 체크 10 | const dist = Math.abs(r - x) + Math.abs(c - y); 11 | 12 | // ➋ 남은 거리가 k보다 크거나 남은 거리와 k의 차이가 홀수이면 불가능 13 | if (dist > k || (k - dist) % 2 !== 0) return "impossible"; 14 | 15 | let answer = ""; 16 | 17 | // ➌ 가능한 한 가장 사전순으로 앞서는 경로를 탐색 18 | while (k > 0) { 19 | for (let i = 0; i < 4; i++) { 20 | const nx = x + dx[i]; 21 | const ny = y + dy[i]; 22 | 23 | // ➍ nx, ny가 유효한 범위 내에 있는지 확인 24 | if (nx > 0 && nx <= n && ny > 0 && ny <= m) { 25 | const remainingDist = Math.abs(r - nx) + Math.abs(c - ny); 26 | 27 | // ➎ 남은 거리가 가능한지 확인 28 | if (remainingDist <= k - 1) { 29 | answer += directions[i]; 30 | x = nx; 31 | y = ny; 32 | k--; 33 | break; 34 | } 35 | } 36 | } 37 | } 38 | 39 | return answer; 40 | } 41 | -------------------------------------------------------------------------------- /solution/87.js: -------------------------------------------------------------------------------- 1 | // 택배 배달과 수거하기 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/150369 3 | 4 | function solution(cap, n, deliveries, pickups) { 5 | let answer = 0; // ➊ 총 이동 거리를 저장하는 변수 6 | 7 | let deliveryLoad = 0; // ➋ 현재 집에서 배달해야 할 물량을 저장 8 | let pickupLoad = 0; // ➋ 현재 집에서 수거해야 할 물량을 저장 9 | 10 | // ➌ 마지막 집부터 첫 번째 집까지 역순으로 탐색 11 | for (let i = n - 1; i >= 0; i--) { 12 | deliveryLoad += deliveries[i]; // ➍ 현재 집에서 배달해야 할 물량을 누적 13 | pickupLoad += pickups[i]; // ➍ 현재 집에서 수거해야 할 물량을 누적 14 | 15 | // ➎ 트럭이 용량 내에서 배달 및 수거를 처리 16 | while (deliveryLoad > 0 || pickupLoad > 0) { 17 | deliveryLoad -= cap; // ➏ 트럭 용량만큼 배달 물량 처리 18 | pickupLoad -= cap; // ➏ 트럭 용량만큼 수거 물량 처리 19 | answer += (i + 1) * 2; // ➐ 현재 집까지의 왕복 거리 계산 및 누적 20 | } 21 | } 22 | 23 | return answer; // ➑ 총 이동 거리 반환 24 | } 25 | -------------------------------------------------------------------------------- /solution/88.js: -------------------------------------------------------------------------------- 1 | // 개인정보 수집 유효기간 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/150370 3 | 4 | function solution(today, terms, privacies) { 5 | const answer = []; 6 | const todayDate = new Date(today); // ➊ 오늘 날짜를 Date 객체로 변환 7 | 8 | // ➋ 약관 유형별 유효기간을 객체로 저장 9 | const termMap = {}; 10 | terms.forEach(term => { 11 | const [type, period] = term.split(' '); 12 | termMap[type] = parseInt(period); 13 | }); 14 | 15 | // ➌ 각 개인정보에 대해 만료일을 계산하고, 만료 여부를 확인 16 | for ([index, privacy] of privacies.entries()) { 17 | const [date, type] = privacy.split(' '); 18 | const expireDate = new Date(date); // 편리한 날짜 계산을 위해 수집일을 Date 객체로 변환 19 | expireDate.setMonth(expireDate.getMonth() + termMap[type]); // ➍ 약관에 따른 유효기간을 더함 20 | 21 | // ➎ 만료일이 오늘보다 이전이거나 같은 경우, 만료된 것으로 간주 22 | if (expireDate <= todayDate) { 23 | answer.push(index + 1); // 인덱스는 1부터 시작 24 | } 25 | }; 26 | 27 | return answer; // ➏ 만료된 개인정보의 인덱스를 반환 28 | } 29 | -------------------------------------------------------------------------------- /solution/89.js: -------------------------------------------------------------------------------- 1 | // 110 옮기기 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/77886 3 | 4 | function solution(s) { 5 | const answer = []; // ➊ 최종 결과를 저장할 배열 6 | 7 | // ➋ 주어진 각 문자열에 대해 처리 8 | for (const str of s) { 9 | const stack = []; // ➌ 110 패턴을 찾기 위한 스택 10 | let count = 0; // ➍ '110' 패턴의 개수를 카운트 11 | 12 | // ➎ 문자열의 각 문자를 순회하며 '110' 패턴을 찾음 13 | for (const char of str) { 14 | stack.push(char); // ➏ 현재 문자를 스택에 추가 15 | // ➐ 스택의 마지막 3자리가 '110'인지 확인 16 | if (stack.length >= 3 && 17 | stack[stack.length - 3] === '1' && 18 | stack[stack.length - 2] === '1' && 19 | stack[stack.length - 1] === '0') { 20 | count += 1; // ➑ '110' 패턴 발견 시 카운트 증가 21 | stack.splice(-3); // ➒ 스택에서 '110' 패턴 제거 22 | } 23 | } 24 | 25 | // ➓ 스택에서 마지막으로 등장한 '0'의 위치를 찾음 26 | let idx = stack.lastIndexOf('0'); 27 | 28 | if (idx === -1) { 29 | // ⓫ 스택에 '0'이 없는 경우, '110' 패턴을 앞에 추가 30 | answer.push('110'.repeat(count) + stack.join('')); 31 | } else { 32 | // ⓬ 마지막 '0' 뒤에 '110' 패턴을 삽입 33 | stack.splice(idx + 1, 0, '110'.repeat(count)); 34 | answer.push(stack.join('')); // 최종 결과를 배열에 추가 35 | } 36 | } 37 | 38 | return answer; // ⓭ 모든 문자열에 대해 처리한 결과를 반환 39 | } 40 | -------------------------------------------------------------------------------- /solution/90.js: -------------------------------------------------------------------------------- 1 | // 쿼드압축 후 개수 세기 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/68936 3 | 4 | function solution(arr) { 5 | const answer = [0, 0]; // ➊ 최종 결과를 저장할 배열 6 | 7 | // ➋ 주어진 배열의 크기를 저장 8 | const n = arr.length; 9 | 10 | // ➌ 주어진 배열에 대해 재귀적으로 탐색 11 | const dfs = (r, c, size) => { 12 | // ➍ 현재 영역의 모든 값이 같은지 확인 13 | const first = arr[r][c]; 14 | let isUniform = true; 15 | for (let i = r; i < r + size; i++) { 16 | for (let j = c; j < c + size; j++) { 17 | if (arr[i][j] !== first) { 18 | isUniform = false; 19 | break; 20 | } 21 | } 22 | if (!isUniform) break; 23 | } 24 | 25 | // ➎ 만약 모든 값이 같다면 해당 값의 개수를 증가 26 | if (isUniform) { 27 | answer[first] += 1; 28 | return; 29 | } 30 | 31 | // ➏ 같지 않다면 4개의 영역으로 나누어 재귀적으로 탐색 32 | const half = size / 2; 33 | dfs(r, c, half); 34 | dfs(r, c + half, half); 35 | dfs(r + half, c, half); 36 | dfs(r + half, c + half, half); 37 | }; 38 | 39 | // ➐ 시작 위치와 배열의 크기를 전달하여 DFS 탐색 40 | dfs(0, 0, n); 41 | 42 | return answer; // ➑ 최종 결과 반환 43 | } 44 | -------------------------------------------------------------------------------- /solution/91.js: -------------------------------------------------------------------------------- 1 | // 없는 숫자 더하기 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/86051 3 | 4 | function solution(numbers) { 5 | const totalSum = 45; // ➊ 0부터 9까지의 숫자의 합: 0 + 1 + 2 + ... + 9 = 45 6 | const sumOfNumbers = numbers.reduce((acc, num) => acc + num, 0); // ➋ 배열에 주어진 숫자들의 합을 계산 7 | const answer = totalSum - sumOfNumbers; // ➌ 0부터 9까지의 숫자 합에서 주어진 숫자 합을 빼면 없는 숫자들의 합이 됨 8 | 9 | return answer; // ➍ 최종 결과 반환 10 | } -------------------------------------------------------------------------------- /solution/92.js: -------------------------------------------------------------------------------- 1 | // 불량 사용자 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/64064 3 | 4 | function solution(user_id, banned_id) { 5 | const possibleSets = new Set(); // ➊ 가능한 모든 경우의 수를 저장할 Set 6 | 7 | // ➋ banned_id 패턴과 일치하는 user_id를 찾는 함수 8 | function isMatch(user, ban) { 9 | if (user.length !== ban.length) return false; // 길이가 다르면 매칭 불가 10 | for (let i = 0; i < user.length; i++) { 11 | if (ban[i] !== '*' && user[i] !== ban[i]) return false; // 문자가 다르면 매칭 불가 12 | } 13 | return true; // 모든 조건을 만족하면 매칭 성공 14 | } 15 | 16 | // ➌ 모든 가능한 조합을 찾는 재귀 함수 17 | function findCombinations(currentIndex, selectedUsers) { 18 | if (currentIndex === banned_id.length) { 19 | const sortedSelected = [...selectedUsers].sort().join(','); // 선택된 아이디들을 정렬하여 문자열로 변환 20 | possibleSets.add(sortedSelected); // Set에 추가하여 중복을 방지 21 | return; 22 | } 23 | 24 | // ➍ 현재 banned_id와 매칭되는 user_id들을 순회 25 | user_id.forEach(user => { 26 | if (!selectedUsers.includes(user) && isMatch(user, banned_id[currentIndex])) { 27 | selectedUsers.push(user); 28 | findCombinations(currentIndex + 1, selectedUsers); // 다음 banned_id로 재귀 호출 29 | selectedUsers.pop(); // 백트래킹을 위해 pop 30 | } 31 | }); 32 | } 33 | 34 | // ➎ 조합 찾기 시작 35 | findCombinations(0, []); 36 | 37 | return possibleSets.size; // ➏ 가능한 경우의 수 반환 38 | } 39 | -------------------------------------------------------------------------------- /solution/93.js: -------------------------------------------------------------------------------- 1 | // k진수에서 소수 개수 구하기 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/92335 3 | 4 | function solution(n, k) { 5 | // ➊ n을 k진수로 변환 6 | const kBase = n.toString(k); 7 | 8 | // ➋ 0을 기준으로 문자열 분할하여 숫자 배열로 변환 9 | const candidates = kBase.split('0').filter(x => x !== '').map(Number); 10 | 11 | // ➌ 소수 판별 함수 12 | function isPrime(num) { 13 | if (num <= 1) return false; // 1 이하는 소수가 아님 14 | if (num === 2) return true; // 2는 소수 15 | if (num % 2 === 0) return false; // 2를 제외한 짝수는 소수가 아님 16 | 17 | for (let i = 3; i <= Math.sqrt(num); i += 2) { 18 | if (num % i === 0) return false; // 나누어떨어지면 소수가 아님 19 | } 20 | 21 | return true; // 위 조건을 통과하면 소수 22 | } 23 | 24 | // ➍ 소수인 숫자들의 개수를 셈 25 | const answer = candidates.filter(isPrime).length; 26 | 27 | return answer; // ➎ 최종 결과 반환 28 | } -------------------------------------------------------------------------------- /solution/94.js: -------------------------------------------------------------------------------- 1 | // 거리두기 확인하기 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/81302 3 | 4 | function solution(places) { 5 | const answer = []; 6 | 7 | const directions = [ 8 | [0, 1], [1, 0], [0, -1], [-1, 0], // 상하좌우 9 | [1, 1], [1, -1], [-1, 1], [-1, -1], // 대각선 10 | [2, 0], [0, 2], [-2, 0], [0, -2] // 거리 2 11 | ]; 12 | 13 | // ➊ 거리두기 규칙 확인 함수 14 | function check(place) { 15 | const n = place.length; 16 | 17 | for (let i = 0; i < n; i++) { 18 | for (let j = 0; j < n; j++) { 19 | if (place[i][j] === 'P') { 20 | // ➋ 현재 위치에서 모든 방향 탐색 21 | for (let [dx, dy] of directions) { 22 | const x = i + dx; 23 | const y = j + dy; 24 | 25 | // ➌ 유효한 좌표인지 확인 26 | if (x >= 0 && x < n && y >= 0 && y < n) { 27 | const distance = Math.abs(dx) + Math.abs(dy); 28 | 29 | if (place[x][y] === 'P') { 30 | if (distance === 1) { 31 | return 0; // ➍ 맨해튼 거리 1에서 바로 옆에 사람이 있으면 규칙 위반 32 | } else if (distance === 2) { 33 | // ➎ 맨해튼 거리 2인 경우, 사이에 파티션('X')이 있는지 확인 34 | if (dx === 2 && place[i + 1][j] !== 'X') return 0; 35 | if (dy === 2 && place[i][j + 1] !== 'X') return 0; 36 | if (dx === -2 && place[i - 1][j] !== 'X') return 0; 37 | if (dy === -2 && place[i][j - 1] !== 'X') return 0; 38 | if (Math.abs(dx) === 1 && Math.abs(dy) === 1) { 39 | if (place[i + dx][j] !== 'X' || place[i][j + dy] !== 'X') return 0; 40 | } 41 | } 42 | } 43 | } 44 | } 45 | } 46 | } 47 | } 48 | 49 | return 1; // ➏ 규칙을 모두 만족하면 1 반환 50 | } 51 | 52 | // ➐ 모든 대기실에 대해 규칙 확인 53 | for (place of places) { 54 | answer.push(check(place)); 55 | }; 56 | 57 | return answer; // ➑ 결과 반환 58 | } 59 | -------------------------------------------------------------------------------- /solution/95.js: -------------------------------------------------------------------------------- 1 | // 코딩 테스트 공부 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/118668 3 | 4 | function solution(alp, cop, problems) { 5 | // ➊ 목표 알고력과 코딩력 계산 6 | let maxAlp = 0; 7 | let maxCop = 0; 8 | for (const [alp_req, cop_req, alp_rwd, cop_rwd, cost] of problems) { 9 | maxAlp = Math.max(maxAlp, alp_req); 10 | maxCop = Math.max(maxCop, cop_req); 11 | } 12 | 13 | // ➋ 초기 값 조정 (현재 능력치가 목표 능력치보다 큰 경우) 14 | alp = Math.min(alp, maxAlp); 15 | cop = Math.min(cop, maxCop); 16 | 17 | // ➌ DP 배열 초기화 18 | const dp = Array.from({ length: maxAlp + 1 }, () => Array(maxCop + 1).fill(Infinity)); 19 | dp[alp][cop] = 0; 20 | 21 | // ➍ 가능한 모든 상태 탐색 22 | for (let i = alp; i <= maxAlp; i++) { 23 | for (let j = cop; j <= maxCop; j++) { 24 | // 현재 능력치가 목표 능력치보다 작을 때만 갱신 25 | if (i + 1 <= maxAlp) dp[i + 1][j] = Math.min(dp[i + 1][j], dp[i][j] + 1); 26 | if (j + 1 <= maxCop) dp[i][j + 1] = Math.min(dp[i][j + 1], dp[i][j] + 1); 27 | 28 | // ➎ 각 문제를 풀어보는 경우 29 | for (const [alp_req, cop_req, alp_rwd, cop_rwd, cost] of problems) { 30 | if (i >= alp_req && j >= cop_req) { 31 | const nextAlp = Math.min(maxAlp, i + alp_rwd); 32 | const nextCop = Math.min(maxCop, j + cop_rwd); 33 | dp[nextAlp][nextCop] = Math.min(dp[nextAlp][nextCop], dp[i][j] + cost); 34 | } 35 | } 36 | } 37 | } 38 | 39 | return dp[maxAlp][maxCop]; // ➏ 목표 알고력과 코딩력에 도달하는 데 필요한 최소 시간 반환 40 | } 41 | -------------------------------------------------------------------------------- /solution/96.js: -------------------------------------------------------------------------------- 1 | // 두 큐 합 같게 만들기 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/118667 3 | 4 | function solution(queue1, queue2) { 5 | const sum1 = queue1.reduce((acc, cur) => acc + cur, 0); // ➊ queue1의 합 6 | const sum2 = queue2.reduce((acc, cur) => acc + cur, 0); // ➊ queue2의 합 7 | const totalSum = sum1 + sum2; // 두 큐의 총합 8 | 9 | if (totalSum % 2 !== 0) return -1; // ➋ 총합이 홀수이면 불가능 10 | 11 | const targetSum = totalSum / 2; // 두 큐의 목표 합 12 | const combinedQueue = [...queue1, ...queue2]; // ➌ 두 큐를 결합 13 | 14 | let currentSum = sum1; 15 | let left = 0; 16 | let right = queue1.length; 17 | const maxMoves = queue1.length * 3; // ➍ 최대 이동 횟수 18 | 19 | for (let moves = 0; moves < maxMoves; moves++) { 20 | if (currentSum === targetSum) return moves; // ➎ 두 큐의 합이 같아지면 이동 횟수 반환 21 | 22 | if (currentSum > targetSum) { 23 | currentSum -= combinedQueue[left++]; // ➏ left 포인터 이동: 큐에서 원소 제거 24 | } else { 25 | currentSum += combinedQueue[right++ % combinedQueue.length]; // ➐ right 포인터 이동: 큐에 원소 추가 26 | } 27 | } 28 | 29 | return -1; // ➑ 모든 경우를 시도했으나 두 큐의 합이 같아지지 않으면 -1 반환 30 | } 31 | -------------------------------------------------------------------------------- /solution/97.js: -------------------------------------------------------------------------------- 1 | // 숫자 게임 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/12987 3 | 4 | function solution(A, B) { 5 | let answer = 0; // ➊ 승리 횟수를 저장하는 변수 6 | let bIdx = 0; // ➋ B팀의 인덱스를 저장하는 변수 7 | A.sort((a, b) => b - a); // ➌ A 팀의 숫자를 오름차순으로 정렬 8 | B.sort((a, b) => b - a); // ➍ B 팀의 숫자를 오름차순으로 정렬 9 | 10 | // ➎ A팀의 모든 카드에 대해 B팀의 카드를 비교 11 | for (let aIdx = 0; aIdx < A.length; aIdx++) { 12 | // ➏ B팀의 카드가 A팀의 카드보다 크다면 승리 13 | if (B[bIdx] > A[aIdx]) { 14 | answer += 1; // ➐ 승리 횟수 증가 15 | bIdx += 1; // B팀 인덱스 증가 16 | } 17 | // ➑ B팀 인덱스가 A팀 카드보다 큰 카드에서만 증가하므로, B팀이 더 많은 카드를 쓸 수 있음 18 | } 19 | 20 | return answer; // ➒ 승리 횟수 반환 21 | } 22 | -------------------------------------------------------------------------------- /solution/98.js: -------------------------------------------------------------------------------- 1 | // 보석 쇼핑 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/67258 3 | 4 | function solution(gems) { 5 | let answer = [0, gems.length]; // ➊ 초기 범위를 최대 범위로 설정 6 | const gemKinds = new Set(gems).size; // ➋ 보석의 종류 수 7 | 8 | let start = 0, end = 0; 9 | const gemsCount = new Map(); // ➌ 보석의 개수를 저장할 Map 10 | gemsCount.set(gems[0], 1); // ➍ 첫 번째 보석을 초기값으로 설정 11 | 12 | while (start < gems.length && end < gems.length) { 13 | if (gemsCount.size === gemKinds) { // ➎ 모든 보석 종류를 포함하는지 확인 14 | // ➏ 현재 구간이 더 짧으면 결과 갱신 15 | if (end - start < answer[1] - answer[0]) { 16 | answer = [start + 1, end + 1]; // 1-based index로 변환하여 저장 17 | } 18 | 19 | // ➐ start 포인터를 옮겨서 구간을 줄이기 20 | gemsCount.set(gems[start], gemsCount.get(gems[start]) - 1); 21 | 22 | // ➑ 보석의 개수가 0이 되면 Map에서 제거 23 | if (gemsCount.get(gems[start]) === 0) { 24 | gemsCount.delete(gems[start]); 25 | } 26 | start += 1; 27 | } else { 28 | // ➒ end 포인터를 옮겨서 구간을 늘리기 29 | end += 1; 30 | if (end >= gems.length) break; // end가 배열을 넘어서면 종료 31 | gemsCount.set(gems[end], (gemsCount.get(gems[end]) || 0) + 1); 32 | } 33 | } 34 | 35 | return answer; // ➓ 최종 구간 반환 36 | } 37 | -------------------------------------------------------------------------------- /solution/99.js: -------------------------------------------------------------------------------- 1 | // 파괴되지 않은 건물 2 | // https://school.programmers.co.kr/learn/courses/30/lessons/92344 3 | 4 | function solution(board, skill) { 5 | const N = board.length; // ➊ 보드의 행 길이 6 | const M = board[0].length; // ➋ 보드의 열 길이 7 | 8 | let answer = 0; // ➌ 살아남은 건물의 개수를 저장할 변수 9 | let dp = [...Array.from(Array(N), () => [...Array.from(Array(M), () => 0)])]; // ➍ 변화량을 기록할 2차원 배열 초기화 10 | 11 | // ➎ 스킬로 인한 변화량을 기록한다. 12 | for ([type, r1, c1, r2, c2, degree] of skill) { 13 | const d = type === 1 ? -degree : degree; // ➏ type이 1이면 공격이므로 음수, 2이면 회복이므로 양수로 설정 14 | dp[r1][c1] += d; // ➐ 스킬의 시작 지점에 변화량을 추가 15 | if (c2 + 1 < M) dp[r1][c2 + 1] -= d; // ➑ 행의 범위를 벗어나지 않으면 오른쪽 끝에 변화량을 빼줌 16 | if (r2 + 1 < N) dp[r2 + 1][c1] -= d; // ➒ 열의 범위를 벗어나지 않으면 아래쪽 끝에 변화량을 빼줌 17 | if (r2 + 1 < N && c2 + 1 < M) dp[r2 + 1][c2 + 1] += d; // ➓ 오른쪽 아래 모서리의 변화량을 추가하여 보정 18 | }; 19 | 20 | // ⓫ 변화량에 대한 누적합을 구한다. 21 | for (let i = 0; i < N; i += 1) { 22 | for (let j = 0; j < M; j += 1) { 23 | const top = i > 0 ? dp[i - 1][j] : 0; // ⓬ 위쪽 셀의 누적합 24 | const left = j > 0 ? dp[i][j - 1] : 0; // ⓭ 왼쪽 셀의 누적합 25 | const intersection = i > 0 && j > 0 ? dp[i - 1][j - 1] : 0; // ⓮ 왼쪽 위 대각선의 누적합 26 | 27 | dp[i][j] = top + left - intersection + dp[i][j]; // ⓯ 누적합 계산 (상하좌우 값을 고려하여 현재 셀에 추가) 28 | 29 | // ⓰ 변화량과 기존 보드 값의 합이 0보다 크다면 건물이 존재하는 것이므로 answer를 증가시킴 30 | answer += dp[i][j] + board[i][j] > 0; 31 | } 32 | } 33 | 34 | return answer; // ⓱ 최종적으로 살아남은 건물의 개수를 반환 35 | } 36 | --------------------------------------------------------------------------------