Skip to content

Commit dfab7de

Browse files
authored
Merge pull request #1179 from yoouyeon/main
[yoouyeon] WEEK 01 solutions
2 parents aa7bd24 + 287b005 commit dfab7de

File tree

5 files changed

+229
-0
lines changed

5 files changed

+229
-0
lines changed

contains-duplicate/yoouyeon.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/**
2+
* Solution 1. Set을 활용하기 1
3+
*
4+
* [Idea]
5+
* nums에서 중복된 원소를 제거하기 위해서 nums를 이용해서 중복을 허용하지 않는 자료구조인 Set을 생성 (numSet)
6+
* 중복된 원소가 있었다면 set을 생성할 때 제거되어 nums의 길이와 numSet의 크기가 다를 것이므로 비교해서 결과를 반환
7+
*
8+
* [Time Complexity]
9+
* O(n) (n: nums의 원소 개수)
10+
* nums 배열을 이용해서 Set을 만드는 것에서 O(n)
11+
*
12+
* [Space Complexity]
13+
* O(n) (n: nums의 원소 개수)
14+
* numSet을 생성하기 위해 최대 n만큼의 공간이 추가로 필요함
15+
*/
16+
function containsDuplicate1(nums: number[]): boolean {
17+
const numsSet = new Set<number>(nums);
18+
return numsSet.size !== nums.length;
19+
}
20+
21+
/**
22+
* Solution 2. Set을 활용하기 2
23+
*
24+
* [Idea]
25+
* 최악의 경우가 아닐 때 약간 더 빠르게 결과를 반환할 수 있는 방법
26+
* 중복인 원소를 마주하자마자 바로 반환하기 때문에 아주 조금 더 빠름
27+
*
28+
* [Time Complexity]
29+
* O(n) (n: nums의 원소 개수)
30+
*
31+
* [Space Complexity]
32+
* O(n) (n: nums의 원소 개수)
33+
* 생성자로 추가하는 것과 add로 추가하는 차이 때문인지 실제로 사용된 메모리는 Solution 1보다 조금 많다.
34+
*/
35+
function containsDuplicate2(nums: number[]): boolean {
36+
const numSet = new Set<number>();
37+
38+
for (const num of nums) {
39+
if (numSet.has(num)) {
40+
return true;
41+
}
42+
numSet.add(num);
43+
}
44+
45+
return false;
46+
}

house-robber/yoouyeon.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* [Idea]
3+
* 어떤 집을 턴다고 했을 때 최대 금액을 구하는 식은 아래와 같이 세울 수 있다.:
4+
* Math.max(전 집을 털었을 때의 최대 금액, 전전 집을 털었을 때의 최대 금액 + 지금 집을 털었을 때 얻는 금액) => DP
5+
* 연산 횟수를 줄여주기 위해서 메모 배열을 이용했다.
6+
*
7+
* [Time Complexity]
8+
* O(n)
9+
* for loop 에서 nums 배열의 각 원소에 한번씩만 접근하므로 O(n)
10+
*
11+
* [Space Complexity]
12+
* O(n)
13+
* 메모 배열을 위한 추가 공간
14+
*/
15+
function rob(nums: number[]): number {
16+
const n = nums.length;
17+
if (n === 1) {
18+
return nums[0];
19+
}
20+
21+
// idx 집을 터는 경우 vs 안 터는 경우를 비교해서 큰 값을 저장하는 dp 배열
22+
const memo = new Array(n).fill(0);
23+
memo[0] = nums[0];
24+
memo[1] = Math.max(memo[0], nums[1]);
25+
26+
for (let idx = 2; idx < n; idx++) {
27+
// idx번째 집에서의 최대 금액 = idx번째 집을 터는 경우 vs 안 터는 경우 중 최댓값
28+
memo[idx] = Math.max(memo[idx - 2] + nums[idx], memo[idx - 1]);
29+
}
30+
31+
return memo[n - 1];
32+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* [Idea]
3+
* O(n)이기 때문에 정렬 방식은 불가능하다고 판단함. => 특별한 방법이 생각이 안나서 일일이 구간을 확인해 주는 방식을 시도했다.
4+
* 배열을 순회할 때 빠르게 원소를 찾아야 하기 때문에 Set을 이용하기로 함.
5+
*
6+
* [Time Complexity]
7+
* O(n + n) => O(n)
8+
* - Set 생성: O(n)
9+
* - for loop: O(n)
10+
* for loop 내부에 while loop가 있긴 하지만 "증가하는 구간의 시작점일 때만 실행되기 때문에" (이걸 놓쳐서 시간 초과 났었다..)
11+
* 각 원소에 접근하는 횟수는 결국 1번뿐.
12+
*
13+
* [Space Complexity]
14+
* O(n)
15+
* Set을 생성하기 위해 추가로 필요한 공간
16+
*/
17+
18+
function longestConsecutive(nums: number[]): number {
19+
const numSet = new Set<number>(nums);
20+
let longest = 0;
21+
22+
for (const startNum of numSet) {
23+
// 증가하는 구간의 시작점인 경우에만 검사한다. (같은 구간을 중복해서 탐색하는 것을 막기)
24+
// nums.length가 10000인 경우에 뜬 Time Limit Exceeded를 해결하기 위해 추가함...
25+
if (numSet.has(startNum - 1)) {
26+
continue;
27+
}
28+
let length = 1;
29+
let currNum = startNum + 1;
30+
while (numSet.has(currNum)) {
31+
length++;
32+
currNum++;
33+
}
34+
longest = Math.max(longest, length);
35+
}
36+
37+
return longest;
38+
}

top-k-frequent-elements/yoouyeon.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* [Idea]
3+
* 숫자와 등장 횟수를 세는 counter Map을 활용했다.
4+
* 1. counter Map을 만든 뒤
5+
* 2. 배열로 변환
6+
* 3. count 내림차순으로 정렬
7+
* 4. 정답 배열 만들기
8+
*
9+
* [Time Complexity]
10+
* O(n + m log m) => O(n log n) (n: nums의 길이, m: nums에서 unique elements의 개수)
11+
* - counter Map을 생성할 때 O(n)
12+
* - counter를 배열로 변환해서 정렬할 때 O(m log m)
13+
* - sortedCounter를 k 길이로 자르고 count만 담은 배열로 만들 때 O(k)
14+
*
15+
* [Space Complexity]
16+
* O(m + k) => O(n)
17+
* - counter Map의 O(m)
18+
* - 정답 배열을 만들 때 추가로 사용하는 O(k)
19+
*/
20+
function topKFrequent1(nums: number[], k: number): number[] {
21+
const counter = new Map<number, number>(); // key: 숫자, value: count
22+
for (const num of nums) {
23+
const count = counter.get(num);
24+
counter.set(num, count === undefined ? 1 : count + 1);
25+
}
26+
27+
const sortedCounter = [...counter].sort((a, b) => b[1] - a[1]);
28+
return sortedCounter.slice(0, k).map((count) => count[0]);
29+
}

two-sum/yoouyeon.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/**
2+
* Solution 1. 배열에서 짝 찾기
3+
*
4+
* [Idea]
5+
* 단순하게 nums를 순회하면서 현재 원소(num)과 합해서 target을 만들 수 있는 원소가 있는지 확인함
6+
*
7+
* [Time Complexity]
8+
* O(n^2) (n: nums의 원소 개수)
9+
* nums를 순회하면서 (O(n)) Array.indexOf 메소드로 짝이 되는 원소를 찾는 방식 (O(n))
10+
* O(n)이 중첩되어 있으므로 O(n^2)
11+
*
12+
* [Space Complexity]
13+
* O(1)
14+
*/
15+
function twoSum1(nums: number[], target: number): number[] {
16+
for (let idx = 0; idx < nums.length - 1; idx++) {
17+
const complementIdx = nums.indexOf(target - nums[idx], idx + 1);
18+
if (complementIdx !== -1) {
19+
return [idx, complementIdx];
20+
}
21+
}
22+
// 타입 에러를 제거하기 위해 추가한 코드.
23+
// 답이 무조건 존재한다는 조건이 있었으므로 이 코드에는 도달하지 않는다.
24+
return [];
25+
}
26+
27+
/**
28+
* Solution 2. Map을 이용하기 - 1
29+
*
30+
* [Idea]
31+
* O(n^2)보다 작게 만들 수 있는 방법은 인덱스를 찾는 데 걸리는 시간을 줄이는 것이라 생각해서 검색 시간이 짧은 Map을 활용함.
32+
*
33+
* [Time Complexity]
34+
* O(n) (n: nums의 원소 개수)
35+
* Map을 생성할 때 O(n)
36+
* Map에서 짝이 되는 원소를 찾는 것은 O(1)
37+
*
38+
* [Space Complexity]
39+
* O(n) (n: nums의 원소 개수)
40+
* Map을 생성할 때 필요한 공간 n
41+
*/
42+
function twoSum2(nums: number[], target: number): number[] {
43+
const numMap = nums.reduce((map, num, idx) => {
44+
map.set(num, idx);
45+
return map;
46+
}, new Map());
47+
48+
for (let idx = 0; idx < nums.length; idx++) {
49+
const complementIdx = numMap.get(target - nums[idx]);
50+
if (complementIdx !== undefined && complementIdx !== idx) {
51+
return [idx, complementIdx];
52+
}
53+
}
54+
55+
return [];
56+
}
57+
58+
/**
59+
* Solution 3. Map을 이용하기 - 2
60+
*
61+
* [Idea]
62+
* Map을 미리 생성하지 않고 짝을 찾는 for loop에서 Map을 생성한다.
63+
* [a, b]가 답일 때 지금까지는 a를 기준으로 b를 찾았지만 지금은 b를 기준으로 a를 찾게 되는 것!
64+
*
65+
* [Time Complexity]
66+
* O(n) (n: nums의 원소 개수)
67+
*
68+
* [Space Complexity]
69+
* O(n) (n: nums의 원소 개수)
70+
* Solution 1보다 조금 메모리를 덜 쓴다.
71+
*/
72+
function twoSum3(nums: number[], target: number): number[] {
73+
const numMap = new Map();
74+
75+
for (let idx = 0; idx < nums.length; idx++) {
76+
const complementIdx = numMap.get(target - nums[idx]);
77+
if (complementIdx !== undefined) {
78+
return [idx, complementIdx];
79+
}
80+
numMap.set(nums[idx], idx);
81+
}
82+
83+
return [];
84+
}

0 commit comments

Comments
 (0)