├── .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 |
16 |
17 |
18 |
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 |
28 |
29 | # 💬 코딩 테스트 소통 공간(저자 직접 운영)
30 | - 카카오톡 오픈 채팅 : https://open.kakao.com/o/gX0WnTCf
31 | - 디스코드 : https://discord.gg/4fpt3jkz
32 |
33 |
34 |
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 |
--------------------------------------------------------------------------------