Skip to content

Commit 0e31704

Browse files
authored
Merge pull request #434 from whewchews/main
[pepper] Week 4 Solutions
2 parents b37ae9b + 2286ca9 commit 0e31704

File tree

5 files changed

+233
-0
lines changed

5 files changed

+233
-0
lines changed
+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* 조건
3+
* 연속된 수가 가장 긴 경우, 몇 번인지 구하기. 같은 값은 이어서 연결할 수 있음.
4+
* 시간복잡도는 O(n)이어야함 = nums 배열을 한번만 돌아서 구할 것, 정렬 메소드는 쓸 수 없음(O(nlogn))
5+
*
6+
* 아이디어
7+
* 시작지점과 끝나는 지점, 그 길이가 가장 긴 것을 구해야함
8+
* 배열 값들을 한번만 체크할 수 있도록 배열을 돌면서 값을 set 자료구조에 저장해둔다
9+
* 이때 set 사용하는 이유는,
10+
* 특정 값이 중복인 경우를 고려할 필요가 없기 때문
11+
*
12+
* 다시 nums 배열을 돌면서 현재 값 직전값이나 이후값이 있는지, 앞뒤로 연속되는 수가 몇개가 있는지 체크한다
13+
* 앞뒤로 연속되는 숫자의 개수로 새로운 배열을 만든다
14+
* 새로 만들어진 배열 중 가장 큰 값을 리턴한다
15+
16+
* 이렇게 했을때 leetcode 시간초과가 뜬다.
17+
* 중복계산 하는 부분을 줄여보자.
18+
* 왼쪽에 있는 값은 왼쪽값에서 체크를 할거라 미리 계산해줄 필요가 없다, 현재 값부터 오른쪽 값만 계산한다.
19+
* nums 배열을 돌면서 왼쪽값이 없는 경우만 오른쪽 값에 대해 길이를 계산한다
20+
* 값에 대한 오른쪽 길이를 이미 계산한 적 있는 경우, memo된 값을 사용한다
21+
*
22+
*/
23+
function longestConsecutive(nums: number[]): number {
24+
// TC: O(n)
25+
// SC: O(n)
26+
const numSet: Set<number> = new Set(nums);
27+
28+
// SC: O(n)
29+
const numLengthMemo: Map<number, number> = new Map();
30+
let maxLength = 0;
31+
32+
// TC: O(n)
33+
for (let n of nums) {
34+
if (!numSet.has(n - 1)) {
35+
let length = 1;
36+
37+
if (numLengthMemo.has(n)) {
38+
length = numLengthMemo.get(n);
39+
maxLength = Math.max(maxLength, length);
40+
continue;
41+
}
42+
43+
// TC: O(n)
44+
while (numSet.has(n + length)) {
45+
length++;
46+
}
47+
48+
numLengthMemo.set(n, length);
49+
maxLength = Math.max(maxLength, length);
50+
}
51+
}
52+
53+
return maxLength;
54+
}
55+
56+
function getNumCount(current: number, dict: Map<number, number>) {
57+
return dict.get(current) ?? 0;
58+
}
59+
60+
// TC: O(n)
61+
// SC: O(n)

maximum-product-subarray/whewchews.ts

+35
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/*
2+
* 조건
3+
* 가장 큰 배열 곱을 찾아서 return
4+
* 32-bit integer
5+
* -10<=num[i]<=10
6+
7+
* 아이디어
8+
* 이전 최대값, 최소값을 구해둔다
9+
* 최대곱이 나올 수 있는 경우
10+
* 1) 최소값 곱한것(-*-)
11+
* 2) 최대값 곱한것(+*+)
12+
* 3) 자기자신인 경우(+)
13+
* 배열을 돌면서 세 가지 중 최대최소값을 갱신해나간다
14+
*
15+
*/
16+
function maxProduct(nums: number[]): number {
17+
let max = nums[0];
18+
let min = nums[0];
19+
let result = nums[0];
20+
21+
for (let i = 1; i < nums.length; i++) {
22+
const candidates = [nums[i] * max, nums[i], nums[i] * min];
23+
let currMax = Math.max(...candidates);
24+
let currMin = Math.min(...candidates);
25+
26+
max = currMax;
27+
min = currMin;
28+
29+
result = Math.max(currMax, result);
30+
}
31+
return result;
32+
}
33+
34+
// TC: O(n)
35+
// SC: O(1)

missing-number/whewchews.ts

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* 조건
3+
* [0, n] 중에 없는 값을 리턴, n == nums.length
4+
* 모든 값은 unique
5+
6+
* 아이디어
7+
* => nums.length는 실제 배열 index보다 1이 더 크기 때문에 항상 없는 값이 1개 존재함
8+
* 0부터 n까지의 모든 값을 set에 저장해둔다.
9+
* set 자료구조를 사용하는 이유는
10+
* 1. nums에 들어오는 값이 중복값이 존재하지 않기 때문
11+
* 2. 같은 동작을 배열에서 하게 되면
12+
* 각 인덱스를 1로 두고 값이 있을때마다 0으로 변경, 1이 있는 index를 찾음으로 풀 수 있음
13+
* => 이 경우, 남은 값을 찾을 때 배열을 한번 다 돌아야 해서 시간복잡도가 O(n) 더 소요됨.
14+
* nums 배열을 돌며 값이 있을때 마다 set에서 값을 삭제한다.
15+
* set에서 남은 값을 찾아 리턴해준다.
16+
*
17+
*/
18+
function missingNumber(nums: number[]): number {
19+
// TC: O(n)
20+
// SC: O(n)
21+
const numSet: Set<number> = new Set(
22+
Array(nums.length + 1)
23+
.fill(0)
24+
.map((v, i) => i)
25+
);
26+
27+
// TC: O(n)
28+
nums.forEach((n) => {
29+
numSet.delete(n);
30+
});
31+
32+
// TC: O(1)
33+
// SC: O(1)
34+
const [lastNum] = numSet.values();
35+
return lastNum;
36+
}
37+
38+
// TC: O(n)
39+
// SC: O(n)

valid-palindrome/whewchews.ts

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* 조건
3+
* 모든 대문자를 소문자로 바꿔서 처리
4+
* 영어 대소문자, 숫자가 아닌 문자는 제거 후 체크
5+
*
6+
* 아이디어
7+
* 정규식으로 문자열 중에 숫자, 문자만 추출
8+
* 짝수인 경우 123456
9+
* 1-6, 2-5, 3-4를 비교: length/2번 비교
10+
* 홀수인 경우 1234567
11+
* 1-7, 2-6, 3-5를 비교: length/2번 비교
12+
*/
13+
14+
function isPalindrome(s: string): boolean {
15+
// TC: O(n)
16+
// SC: O(n)
17+
const str = s
18+
.replace(/[^a-zA-Z0-9]/g, "")
19+
.replace(/\s+/g, "")
20+
.toLocaleLowerCase();
21+
const len = str.length;
22+
23+
// TC: O(n)
24+
for (let i = 0; i <= str.length / 2 - 1; i++) {
25+
if (str[i] !== str[len - i - 1]) {
26+
return false;
27+
}
28+
}
29+
30+
return true;
31+
}
32+
33+
// TC: O(n)
34+
// SC: O(n)

word-search/whewchews.ts

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* 조건
3+
* 같은 단어 위치는 한번만 쓰인다
4+
* 이웃해서 연속된 단어가 있는지 찾는다
5+
6+
* 백트래킹
7+
* 모든 원소를 완전탐색하기 위한 목적으로 사용.
8+
* 단순히 완전탐색하는 것이 아니라 조건에 따라서 유망한 노드로 이동.
9+
*/
10+
function exist(board: string[][], word: string): boolean {
11+
const m = board.length;
12+
const n = board[0].length;
13+
const l = word.length;
14+
const directions = [
15+
[-1, 0], // 상
16+
[1, 0], // 하
17+
[0, -1], // 좌
18+
[0, 1], // 우
19+
];
20+
21+
const backtrack = (
22+
col: number,
23+
row: number,
24+
idx: number,
25+
visited: Set<string>
26+
): boolean => {
27+
if (idx === l) {
28+
return true;
29+
}
30+
if (
31+
col < 0 ||
32+
col >= m ||
33+
row < 0 ||
34+
row >= n ||
35+
board[col][row] !== word[idx] ||
36+
visited.has(`${col},${row}`)
37+
) {
38+
return false;
39+
}
40+
41+
visited.add(`${col},${row}`);
42+
43+
for (const [dCol, dRow] of directions) {
44+
if (backtrack(col + dCol, row + dRow, idx + 1, visited)) {
45+
return true;
46+
}
47+
}
48+
49+
visited.delete(`${col},${row}`);
50+
return false;
51+
};
52+
53+
for (let col = 0; col < m; col++) {
54+
for (let row = 0; row < n; row++) {
55+
if (backtrack(col, row, 0, new Set<string>())) {
56+
return true;
57+
}
58+
}
59+
}
60+
return false;
61+
}
62+
63+
// TC: O(m*n*4^l) <= m*n: 보드의 크기, l: 단어의 길이. 최대 4개 방향으로 이동 가능
64+
// SC: O(l) <= 단어 길이만큼 방문 가능

0 commit comments

Comments
 (0)