Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
50da957
docs(그리디): 1주차 그리디 알고리즘 학습을 시작한다
bigsaigon333 Sep 25, 2021
8600e71
docs(그리디): 그리디 알고리즘 접근법을 정리한다
bigsaigon333 Sep 25, 2021
781151d
feat(그리디): 이코테 03장 그리디 예제/실전문제를 푼다
bigsaigon333 Sep 25, 2021
db8d964
fix: 디렉토리명의 공백을 하이픈으로 치환한다
bigsaigon333 Sep 25, 2021
db057f6
docs(그리디): 그리디 알고리즘 관련 링크를 README.md에 추가한다
bigsaigon333 Sep 25, 2021
3b6d562
docs: 스터디 학습 방법을 README.md에 작성한다
bigsaigon333 Sep 27, 2021
fd2d29f
chore: git pre-commit hook을 추가한다
bigsaigon333 Sep 28, 2021
edc1e22
docs(ds): JS에서 stack, queue, deque 구현법을 정리한다
bigsaigon333 Sep 30, 2021
52411a9
feat(lib): range, isInRange, Array2D 함수를 구현한다
bigsaigon333 Sep 30, 2021
0f989dd
Merge remote-tracking branch 'upstream/main' into greedy
bigsaigon333 Sep 30, 2021
2797a4d
feat(그리디): 11장 03번 문자열 뒤집기를 푼다
bigsaigon333 Sep 30, 2021
5cecea4
feat(그리디): 이코테 11장 그리디 문제 06번을 푼다
bigsaigon333 Oct 1, 2021
fec9084
docs: node.js 입출력 방법을 정리한다
bigsaigon333 Oct 1, 2021
7e410f9
docs: docs 목차를 추가한다
bigsaigon333 Oct 1, 2021
cb83cf5
docs: 11장 풀이한 문제의 링크를 README에 추가한다
bigsaigon333 Oct 1, 2021
0fbe507
fix(그리디): fs.readFileSync 메서드의 인코딩 옵션을 수정한다
bigsaigon333 Oct 1, 2021
ed0356d
refactor(그리디): 배열의 최소값을 전개 연산자로 구한다
bigsaigon333 Oct 1, 2021
07ec378
docs(입출력): 입력 데이터 처리시 String.prototype.trim 메서드를 사용한다
bigsaigon333 Oct 3, 2021
653b3e7
docs(자료형 변환): String -> Number 자료형 변환을 정리한다
bigsaigon333 Oct 3, 2021
1ce2f52
docs(그리디): 그리디 이론 설명 내용을 추가한다
bigsaigon333 Oct 3, 2021
dded4b6
lib(heapq): 최소힙을 이용하여 우선순위 큐를 구현한다
bigsaigon333 Oct 3, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .husky/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
_
4 changes: 4 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

npx lint-staged
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,18 @@
# js-algorithm-basecamp

## 1주차 그리디 알고리즘

- 2021-09-27 Mon ~ 2021-10-03 Sun

### 이코테 PART 02 3장 그리디

- [이론 정리](/docs/03-그리디.md)
- [예제 3-1 거스름돈 문제](/src/js-for-coding-test/PART-02/03-그리디/01.js)
- [실전 3-2 동빈이의 큰 수의 법칙](/src/js-for-coding-test/PART-02/03-그리디/02.js)
- [실전 3-3 숫자 카드게임](/src/js-for-coding-test/PART-02/03-그리디/03.js)
- [실전 3-4 1이 될 때까지](/src/js-for-coding-test/PART-02/03-그리디/04.js)

### 이코테 PART 03 11장 그리디 문제

- [11-3 문자열 뒤집기](/src/js-for-coding-test/PART-03/11-그리디-문제/03.js)
- [11-6 무지의 먹방 라이브](/src/js-for-coding-test/PART-03/11-그리디-문제/06.js)
5 changes: 5 additions & 0 deletions docs/00-목차.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# 목차

1. [입출력](./01-입출력.md)
2. [자료구조](./02-자료구조.md)
3. [그리디](./03-그리디.md)
138 changes: 138 additions & 0 deletions docs/01-입출력.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# PS 입출력 in Node.js

## 0. PS는 일반적으로 입력 - 계산 - 출력의 3단계로 구성됩니다

1. 입력: 주어진 입력을 받아들여 프로그램의 자료구조에 저장하기
2. 계산: 알고리즘을 활용하여 정답을 계산하기
3. 출력: 정답을 주어진 형식에 맞게끔 출력하기

Baekjoon Online Judge(이하 "BOJ")는 위와 같은 흐름이나, Programmers 같은 경우에는 `2. 계산`에 더욱 초점을 맞추고자 `1. 입력`과 `3. 출력`을 간소화하였습니다.

- `1. 입력`의 경우, 함수의 인자(`parameter`)로 주어집니다.
- `3. 출력`의 경우 정답을 함수의 반환값(`return value`)으로 하는 것으로 대체하였습니다.

따라서 Programmers 같은 경우에는, 입출력 처리에 골머리를 썩을 필요가 없으나, BOJ의 경우에는 입출력 처리로 인해 처음에 고전할 수 있습니다.

## 1. 입력

```js
const fs = require("fs");
const input = fs.readFileSync(0, "utf8").trim().split(" ");
const a = Number(input[0]);
const b = Number(input[1]);
console.log(a + b);
```

### 1-1. `fs.readFileSync` 메서드를 사용합니다

PS에서는 입력을 받은 뒤에 계산을 하고 출력을 합니다. 즉, 비동기적으로 계산 도중에 유저로부터 입력을 받는 일이 없습니다. 따라서, 이벤트 드리븐 방식의 복잡한 readline 모듈을 사용하거나, `fs.readFile` 메서드를 사용할 필요가 없습니다.

#### fs 모듈

`fs`는 `file system`을 의미합니다. POSIX 시스템에서는 모든 프로세스에 대해 커널이 현재 열려 있는 파일 및 리소스 테이블을 유지 관리합니다. 열려 있는 각 파일에는 파일 설명자(file descriptor)라는 간단한 숫자 식별자가 할당됩니다. 시스템 수준에서 모든 파일 시스템 작업은 이러한 파일 설명자를 사용하여 각 특정 파일을 식별하고 추적합니다. 윈도우즈 시스템은 리소스를 추적하기 위해 다르지만 개념적으로 유사한 메커니즘을 사용합니다. 사용자를 위한 작업을 단순화하기 위해 Node.js는 운영 체제 간의 특정 차이를 추상화하고 열려 있는 모든 파일에 숫자 파일 설명자를 할당합니다. 참고로, 표준입력(stdin: standard input)의 파일 설명자는 0 입니다.

`readFile`함수는 비동기적으로 파일의 전체를 읽으므로 파일을 읽어들이기가 완료된 후에 콜백 함수를 통하여 다음 스텝을 진행할 수 있는 반면, `readFileSync`는 동기적으로 파일을 읽어들입니다. 어차피 입력을 다 받아야만 계산을 진행할 수 있으므로, 동기적 처리 방식이 코드도 간결하고 이해하기 편합니다.

`fs.readFileSync(path[, options])`

- path: <string\> | <Buffer\> | <URL\> | <integer\> filename or file descriptor
- options: <Object\> | <string\>
- encoding <string\> | <null\> Default: null
- flag <string\> See support of file system flags. Default: 'r'.
- Returns: <string\> | <Buffer\>

```js
const fs = require("fs");

// 개행문자(\n)로 구분된 각 행을 요소로 가지는 배열을 반환.
// 인코딩을 명시적으로 넘기지 않은 경우에는 raw Buffer가 반환되므로
// toString()함수를 호출하여 string으로 변환하여야 한다.
const input1 = fs.readFileSync(0).toString().trim().split("\n");

// options으로 인코딩을 string 자료형으로 넘기는 경우, string을 반환한다.
const input2 = fs.readFileSync(0, "utf8").trim().split("\n");

// input2와 동일
const input3 = fs.readFileSync(0, { encoding: "utf8" }).trim().split("\n");
```

참고: [Node.js v14.15.4 Documentation - File system](https://nodejs.org/dist/latest-v14.x/docs/api/fs.html#fs_file_system)

### 1-2. String.prototype.trim

실제 코딩테스트에서 입력 데이터는 입력 예시 그대로 주어집니다. 즉, space, EOL 등 모든 문자가 보이는 그대로 동일하게 주어집니다.
하지만, 실제 구현 과정에서 입력 데이터를 하나씩 넣어 볼 때는 입력 데이터의 마지막에 추가적인 space, EOL 등의 불필요한 공백문자가 포함될 수 있습니다.
이를 제거하기 위해서 `String.prototype.trim` 을 항상 입력시에 붙여 주는 것을 권장합니다.

```js
const input = require("fs").readFileSync(0, "utf8").trim().split("\n");
```

## 2. 출력

### `console.log`를 이용해서 출력

`console.log`는 호출시 많은 시간이 소요되는 함수이므로, 백트래킹과 같이 알고리즘 중간중간에 출력을 하는 경우에는 배열이나 `string`에 출력값을 저장해두었다가 계산 종료후 한번에 출력을 해주는 것이 좋습니다. (개인적으로 배열에 값을 저장하는 것을 선호합니다 ㅎㅎ)

아래는 [BOJ 15649번 문제 N과 M (1)](https://www.acmicpc.net/problem/15649)의 채점결과입니다. 위에는 계산 종료후 코드의 마지막에 한번만 출력을 해준 경우이고, 아래는 계산 중간중간에 계속해서 출력을 해준 경우입니다. 보시다시피 실행 시간의 차이가 엄청 많이 나신다는 것을 알 수 있습니다.

#### 계산 종료후 마지막에 한번만 출력

```javascript
...
let print = "";

(function rec(lev) {
if (lev === M) {
print += `${arr.join(" ")}\n`; // depth가 M일 때마다 print에 출력 값 저장
return;
}

...
rec(lev + 1);
...
})(0);

console.log(print); // 계산 종료후 마지막에 출력
```

#### 계산 중간중간에 출력

```javascript

(function rec(lev) {
if (lev === M) {
console.log(`${arr.join(" ")}\n`); // depth가 M일 때마다 print에 출력 값 저장
return;
}

...
rec(lev + 1);
...
})(0);
```

![BOJ 15649번 문제 채점결과](https://images.velog.io/images/bigsaigon333/post/14c5f8aa-2f4a-4f4a-be0d-6399d5863abc/%ED%95%9C%EB%B2%88%EC%97%90_%EC%B6%9C%EB%A0%A5_vs_%EB%A7%A4%EB%B2%88_%EC%B6%9C%EB%A0%A5.png)

### `process.stdout.write` vs `console.log`

Writable 스트림인 `process.stdout`의 `write`함수를 이용하여 출력을 할 수 있습니다. `console.log`도 내부적으로 `process.stdout.write`를 이용하여 출력한다고 합니다. 하지만 경험적으로 `console.log`와 `process.stdout.write`의 실행속도 차이를 느끼지 못하였습니다. `console.log`, `process.stdout.write` 모두 무거운 함수 호출이므로, 이를 최소화하기 위하여 배열이나 `string`에 출력값을 저장해두는 테크닉이 더 실행속도 단축에 도움이 되는 것 같습니다.

참고URL: [Node.js v14.15.4 Documentation - Writable Stream: writable.write(chunk[, encoding][, callback])](https://nodejs.org/dist/latest-v14.x/docs/api/stream.html#stream_writable_write_chunk_encoding_callback)

### 출력 맨 마지막에 공백 혹은 줄바꿈이 추가로 있어도 상관이 없습니다

공백이랑 비슷하게 줄바꿈 또한 출력 맨 마지막에 추가로 있어도 정답 처리가 됩니다. 그래서 이 부분을 별도로 예외처리를 할 필요가 없습니다.

### 디버깅시 디버거 사용보다는 콘솔에 출력해보자

구현을 끝냈는데 예제에 대해 답이 올바르게 나오지 않을 때 코드의 어디가 잘못됐는지 알고 싶어서 디버거를 이용하는 경우가 간혹 있습니다.
사실 코딩테스트의 코드는 끽해야 100줄 전후 길이일 것입니다.

그럴 때 디버거를 켜면 뭔가 꼬이고 늪에 빠져드는 것과 같은 느낌을 받을 수 있습니다.
중간 변수를 보고 싶으면 console.log로 출력을 찍어서 확인하고 디버거는 굳이 사용을 안하는 것을 권장합니다.

## Reference

- [JavaScript로 코딩테스트 준비하기(1) - 입출력](https://velog.io/@bigsaigon333/Javascript%EB%A1%9C-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84%ED%95%98%EA%B8%B01-%EC%9E%85%EC%B6%9C%EB%A0%A5)
- [BaaaaaaaarkingDog - [실전 알고리즘] 0x02강 - 기초 코드 작성 요령 II](https://blog.encrypted.gg/923?category=773649)
3 changes: 3 additions & 0 deletions docs/02-자료구조.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 자료구조

1. [스택/큐/덱](./data-structure/stack_queue_deque.md)
25 changes: 25 additions & 0 deletions docs/03-그리디.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# 그리디

- 현재 상황에서 지금 당장 좋은 것만 고르는 방법

- 사전에 외우고 있지 않아도 풀 수 있을 가능성이 높은 문제 유형

- 다르게 말하면, 문제 출제의 폭이 매우 넓기 때문에, 다익스트라 알고리즘과 같은 특이 케이스를 제외하고는 단순 암기를 통해 모든 문제를 대처하기 어렵다

- 그리디 알고리즘은 기준에 따라 좋은 것을 선택하는 알고리즘이므로 문제에서 '가장 큰 순서대로', '가장 작은 순서대로'와 같은 기준을 알게 모르게 제시해준다. 대체로 이 기준은 정렬 알고리즘을 사용했을 때 만족시킬 수 있으므로 그리디 알고리즘 문제는 자주 정렬 알고리즘과 짝을 이뤄 출제된다

- 대다수의 경우 그리디 알고리즘은 올바른 답을 주지 않는다. 그러나 일부 문제에서는 그리디 알고리즘이 올바른 답을 주기 때문에 시간복잡도를 줄일 수 있다.

## 그리디 알고리즘의 정당성

- 그리디 알고리즘으로 문제의 해법을 찾았을 때는 그 해법이 정당한지 검토해야한다.

- 대부분의 그리디 알고리즘 문제에서는 문제 풀이를 위한 최소한의 아이디어를 떠올리고 이것이 정당한지 검토할 수 있어야 답을 도출할 수 있다.

- 그리디 알고리즘을 사용하려면 우선 해당 알고리즘이 성립함을 수학적으로 엄밀하게 증명해야 한다. 그렇지 않고 감으로 때려맞춰서 코딩을 하면 시간만 버리게 될 수 있다.

## 그리디 알고리즘 접근법

1. 어떤 코딩 테스트 문제를 만났을 때, 바로 문제 유형을 파악하기 어렵다면 그리디 알고리즘을 의심한다
2. 문제를 해결할 수 있는 탐욕적인 해결법이 존재하는지 고민해본다.
3. 오랜 시간을 고민해도 그리디 알고리즘으로 해결 방법을 찾을 수 없다면, 그때는 이후의 장에서 다루게 될 다이나믹 프로그래밍이나 그래프 알고리즘 등으로 문제를 해결할 수 있는지를 재차 고민해본다
70 changes: 70 additions & 0 deletions docs/21-자료형_변환.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# 자료형 변환

## string 타입을 number 타입으로 변환하는 방법: Number를 사용합시다

### Q1. Number vs new Number

`Number` 는 function으로서 작용하여 primitive type을 반환하지만, `new Number`는 constructor로서 작용하여 Object를 반환합니다.

사칙연산시에 Number 의 인스턴스는 number 타입으로 자동 형변환되므로, new Number나 Number나 같은거 아니냐고 생각할 수 있습니다.

#### Number 와 new Number의 차이점

1. `Object`는 boolean context에서 항상 `true`입니다.

```javascript
const a = Number("0");
const b = new Number("0");

if (a) { // false 이므로 조건문 내부는 실행되지 아니함
...
}

if (b) { // `Object([Number: 0])`는 boolean context에서 `true`이므로 조건문 내부가 실행됨
...
}
```

2. Object와 primitive type 의 === 비교시 false가 반환됩니다.

```javascript
const a = new Number("123"); // a === 123 is false
const b = Number("123"); // b === 123 is true
console.log(a === 123); // false
console.log(b === 123); // true

console.log(a instanceof Number); // true
console.log(b instanceof Number); // false
```

혼란을 피하기 위하여 **new Number는 사용하지 않습니다.**

### Q2. Number vs parseInt

`parseInt`는 브라우저별 내부 구현 방법에 따라 다르게 해석될 수 있기 때문에 명시적으로 radix를 기재하여야 합니다.
Node.js로 코딩테스트를 준비하기 때문에 크게 상관은 없지만, 알아두면 좋을 것 같습니다.

`parseInt(str, 10)`과 `Number(str)`의 차이는 아래와 같습니다.

```javascript
const str = "10x";
console.log(parseInt(str, 10)); // 10
console.log(Number(str)); // NaN
```

경험적으로 `parseInt`와 `Number`의 실행속도차이는 미미한 것 같습니다.
개인적으로 `Number`를 선호합니다. radix를 기재하는게 귀찮기도 하고, 그냥 익숙해서요.
코딩테스트에서 입력값의 정확성은 보장되므로 어떤 것을 쓰더라도 문제 없을 것 같습니다.

```javascript
const fs = require("fs");
const input = fs.readFileSync("/dev/stdin").toString().split(" ");
const a = Number(input[0]); // 또는 parseInt(input[0], 10);
const b = Number(input[1]); // 또는 parseInt(input[1], 10);
console.log(a + b);
```

## Reference

- [new Number() vs Number()](https://stackoverflow.com/questions/4719320/new-number-vs-number)
- [JavaScript로 코딩테스트 준비하기(1) - 입출력](https://velog.io/@bigsaigon333/Javascript%EB%A1%9C-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8-%EC%A4%80%EB%B9%84%ED%95%98%EA%B8%B01-%EC%9E%85%EC%B6%9C%EB%A0%A5)
46 changes: 46 additions & 0 deletions docs/data-structure/heap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 힙(Heap)

- 최댓값 혹은 최속값을 빠르게 찾아내기 위한 이진 트리
- 최소힙의 경우 부모는 자식보다 작아야 하고, 최대힙의 경우 부모는 자식보다 커야 한다. 이 때 루트가 최솟값 혹은 최댓값이 된다.
- 최소힙: 요소의 삽입, 최솟값 확인, 최솟값 삭제의 기능 제공
최대힙: 요소의 삽입, 최댓값 확인, 최댓값 삭제의 기능 제공
최소 혹은 최대값이 아닌 값의 존재 여부 확인/요소 제거는 불가능
- 힙에 요소를 삽입할 때 (-)를 곱하여 삽입하는 방식으로 최대힙을 구현할 수 있으므로, 이후의 힙은 모두 최소힙을 나타내기로 한다.

## 삽입

- 일단 요소를 트리 상의 다음 공간에 추가하고, 힙의 성질을 만족하게끔 서로 자리를 바꾸는 방식으로 구현되므로 균형 트리임이 보장된다. ⇒ `O(logN)` 보장
- '트리 상의 다음 공간에 추가한다'란 높이가 작은 곳부터, 높이가 같다면 왼쪽부터 채워나간다는 뜻이다.

## **최솟값 확인**

- 루트에 적힌 요소 ⇒ `O(1)`

## **최솟값 삭제**

- 트리 구조 상에서 가장 마지막 위치의 노드와 최솟값의 위치를 바꾸고 제거. 부모가 자식보다 작다는 조건을 만족할 때 까지 부모와 자식의 자리를 바꾼다. ⇒ `O(logN)`

## 구현

- 트리의 각 요소를 배열에 대응시키면 구현이 간편하다.
- 1-index
- x의 왼쪽 자식: 2x
- x의 오른쪽 자식: 2x+1
- x의 부모: x/2

### 최소힙을 이용한 우선순위큐: [heapq](../../lib/heapq.js)

- `heapify(iter)`: iterable 을 heap으로 변환하여 반환한다
- `heappush(heap, item)`: heap에 요소를 삽입
- `heaptop(heap)`: heap의 최솟값 확인
- `heappop(heap)`: heap의 최솟값을 제거하고 반환한다

## FAQ

### Q. 힙에서 할 수 있는건 어차피 균형 이진 트리에서도 할 수 있지 않나요? 그러면 균형 이진 트리가 더 제공해주는 기능이 많은데 힙을 쓸 이유가 있나요?

A. 힙에서 할 수 있는걸 균형 이진 트리에서 할 수 있는 것이 맞고, 자가 균형 트리라는 가정하에 시간복잡도가 O(log N)_O_(_logN_)으로 동일한 것도 맞습니다. 그러나 힙은 균형 이진 트리보다 수행 속도도 빠르고, 구현도 쉽고, 공간도 적게 차지하기에 최소 혹은 최댓값의 확인/삭제만 필요할 때에는 힙을 쓰는 것이 더 좋습니다.

## 정리

- 삽입, 최솟값 삭제는 `O(logN`), 최솟값 확인은 `O(1)`. 해쉬, 이진 검색 트리와 달리 구조적으로 시간복잡도가 보장된다.
Loading