Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[pepper] Week 4 Solutions #434

Merged
merged 5 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
61 changes: 61 additions & 0 deletions longest-consecutive-sequence/whewchews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/*
* 조건
* 연속된 수가 가장 긴 경우, 몇 번인지 구하기. 같은 값은 이어서 연결할 수 있음.
* 시간복잡도는 O(n)이어야함 = nums 배열을 한번만 돌아서 구할 것, 정렬 메소드는 쓸 수 없음(O(nlogn))
*
* 아이디어
* 시작지점과 끝나는 지점, 그 길이가 가장 긴 것을 구해야함
* 배열 값들을 한번만 체크할 수 있도록 배열을 돌면서 값을 set 자료구조에 저장해둔다
* 이때 set 사용하는 이유는,
* 특정 값이 중복인 경우를 고려할 필요가 없기 때문
*
* 다시 nums 배열을 돌면서 현재 값 직전값이나 이후값이 있는지, 앞뒤로 연속되는 수가 몇개가 있는지 체크한다
* 앞뒤로 연속되는 숫자의 개수로 새로운 배열을 만든다
* 새로 만들어진 배열 중 가장 큰 값을 리턴한다

* 이렇게 했을때 leetcode 시간초과가 뜬다.
* 중복계산 하는 부분을 줄여보자.
* 왼쪽에 있는 값은 왼쪽값에서 체크를 할거라 미리 계산해줄 필요가 없다, 현재 값부터 오른쪽 값만 계산한다.
* nums 배열을 돌면서 왼쪽값이 없는 경우만 오른쪽 값에 대해 길이를 계산한다
* 값에 대한 오른쪽 길이를 이미 계산한 적 있는 경우, memo된 값을 사용한다
*
*/
function longestConsecutive(nums: number[]): number {
// TC: O(n)
// SC: O(n)
const numSet: Set<number> = new Set(nums);

// SC: O(n)
const numLengthMemo: Map<number, number> = new Map();
let maxLength = 0;

// TC: O(n)
for (let n of nums) {
if (!numSet.has(n - 1)) {
let length = 1;

if (numLengthMemo.has(n)) {
length = numLengthMemo.get(n);
maxLength = Math.max(maxLength, length);
continue;
}

// TC: O(n)
while (numSet.has(n + length)) {
length++;
}

numLengthMemo.set(n, length);
maxLength = Math.max(maxLength, length);
}
}

return maxLength;
}

function getNumCount(current: number, dict: Map<number, number>) {
return dict.get(current) ?? 0;
}

// TC: O(n)
// SC: O(n)
35 changes: 35 additions & 0 deletions maximum-product-subarray/whewchews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* 조건
* 가장 큰 배열 곱을 찾아서 return
* 32-bit integer
* -10<=num[i]<=10

* 아이디어
* 이전 최대값, 최소값을 구해둔다
* 최대곱이 나올 수 있는 경우
* 1) 최소값 곱한것(-*-)
* 2) 최대값 곱한것(+*+)
* 3) 자기자신인 경우(+)
* 배열을 돌면서 세 가지 중 최대최소값을 갱신해나간다
*
*/
function maxProduct(nums: number[]): number {
let max = nums[0];
let min = nums[0];
let result = nums[0];

for (let i = 1; i < nums.length; i++) {
const candidates = [nums[i] * max, nums[i], nums[i] * min];
let currMax = Math.max(...candidates);
let currMin = Math.min(...candidates);

max = currMax;
min = currMin;

result = Math.max(currMax, result);
}
return result;
}

// TC: O(n)
// SC: O(1)
39 changes: 39 additions & 0 deletions missing-number/whewchews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 조건
* [0, n] 중에 없는 값을 리턴, n == nums.length
* 모든 값은 unique

* 아이디어
* => nums.length는 실제 배열 index보다 1이 더 크기 때문에 항상 없는 값이 1개 존재함
* 0부터 n까지의 모든 값을 set에 저장해둔다.
* set 자료구조를 사용하는 이유는
* 1. nums에 들어오는 값이 중복값이 존재하지 않기 때문
* 2. 같은 동작을 배열에서 하게 되면
* 각 인덱스를 1로 두고 값이 있을때마다 0으로 변경, 1이 있는 index를 찾음으로 풀 수 있음
* => 이 경우, 남은 값을 찾을 때 배열을 한번 다 돌아야 해서 시간복잡도가 O(n) 더 소요됨.
* nums 배열을 돌며 값이 있을때 마다 set에서 값을 삭제한다.
* set에서 남은 값을 찾아 리턴해준다.
*
*/
function missingNumber(nums: number[]): number {
// TC: O(n)
// SC: O(n)
const numSet: Set<number> = new Set(
Array(nums.length + 1)
.fill(0)
.map((v, i) => i)
);

// TC: O(n)
nums.forEach((n) => {
numSet.delete(n);
});

// TC: O(1)
// SC: O(1)
const [lastNum] = numSet.values();
return lastNum;
}

// TC: O(n)
// SC: O(n)
34 changes: 34 additions & 0 deletions valid-palindrome/whewchews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 조건
* 모든 대문자를 소문자로 바꿔서 처리
* 영어 대소문자, 숫자가 아닌 문자는 제거 후 체크
*
* 아이디어
* 정규식으로 문자열 중에 숫자, 문자만 추출
* 짝수인 경우 123456
* 1-6, 2-5, 3-4를 비교: length/2번 비교
* 홀수인 경우 1234567
* 1-7, 2-6, 3-5를 비교: length/2번 비교
*/

function isPalindrome(s: string): boolean {
// TC: O(n)
// SC: O(n)
const str = s
.replace(/[^a-zA-Z0-9]/g, "")
.replace(/\s+/g, "")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공백도 바로 윗 줄에서 제거되지 않았을까요?

.toLocaleLowerCase();
const len = str.length;

// TC: O(n)
for (let i = 0; i <= str.length / 2 - 1; i++) {
if (str[i] !== str[len - i - 1]) {
return false;
}
}

return true;
}

// TC: O(n)
// SC: O(n)
64 changes: 64 additions & 0 deletions word-search/whewchews.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 조건
* 같은 단어 위치는 한번만 쓰인다
* 이웃해서 연속된 단어가 있는지 찾는다

* 백트래킹
* 모든 원소를 완전탐색하기 위한 목적으로 사용.
* 단순히 완전탐색하는 것이 아니라 조건에 따라서 유망한 노드로 이동.
*/
function exist(board: string[][], word: string): boolean {
const m = board.length;
const n = board[0].length;
const l = word.length;
const directions = [
[-1, 0], // 상
[1, 0], // 하
[0, -1], // 좌
[0, 1], // 우
];

const backtrack = (
col: number,
row: number,
idx: number,
visited: Set<string>
): boolean => {
if (idx === l) {
return true;
}
if (
col < 0 ||
col >= m ||
row < 0 ||
row >= n ||
board[col][row] !== word[idx] ||
visited.has(`${col},${row}`)
) {
return false;
}

visited.add(`${col},${row}`);

for (const [dCol, dRow] of directions) {
if (backtrack(col + dCol, row + dRow, idx + 1, visited)) {
return true;
}
}

visited.delete(`${col},${row}`);
return false;
};

for (let col = 0; col < m; col++) {
for (let row = 0; row < n; row++) {
if (backtrack(col, row, 0, new Set<string>())) {
return true;
}
}
}
return false;
}

// TC: O(m*n*4^l) <= m*n: 보드의 크기, l: 단어의 길이. 최대 4개 방향으로 이동 가능
// SC: O(l) <= 단어 길이만큼 방문 가능