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

[재호] WEEK 04 Solutions #408

Merged
merged 10 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
43 changes: 43 additions & 0 deletions longest-consecutive-sequence/wogha95.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* TC: O(N)
* SC: O(N)
* nums의 숫자에 접근하는 횟수는 2번에서 N만큼, 4번에서 최대 N만큼 입니다.
* 즉, 2N번 만큼 nums의 숫자에 접근합니다.
Comment on lines +4 to +5
Copy link
Contributor

Choose a reason for hiding this comment

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

설명과 결론이 잘 부합하지 않는 것 같은데요? 중첩 루프 잖아요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

다시 보니 설명이 부족해서 오해를 살 수 있었던 것 같습니다!
N^2이 아닌 2N으로 생각한 이유는 2번에서 N번의 순회를 하지만 각 순회가 서로에게 영향을 미치기 때문에 최대 순회는 2N으로 계산했습니다. (1번의 N 순회 제외)

  1. [1, 2, 3, 4, 5, 6, 7, 8, 9] 인 경우 => 2N
1 방문 -> 1~9까지 순회
2 방문 -> 끝
3 방문 -> 끝
...
9 방문 -> 끝
  1. [1, 3, 5, 7, 9] 인 경우 => N
1 방문 => 끝
3 방문 => 끝
...
9 방문 => 끝
  1. [1, 2, 3, 4, 5, 7, 8, 9] 인 경우 (6제외) => N ~ 2N
1 방문 => 1~5까지 순회
2 방문 => 끝
...
7 방문 => 7~9까지 순회
8 방문 => 끝
9 방문 => 끝

* N^2이 아닌 이유: N^2이 아닌 2N으로 생각한 이유는 2번에서 N번의 순회를 하지만 각 순회가 서로에게 영향을 미치기 때문에 최대 순회는 2N으로 계산했습니다. (1번의 N 순회 제외)
* @see https://github.com/DaleStudy/leetcode-study/pull/408#discussion_r1747071917
*/

/**
* @param {number[]} nums
* @return {number}
*/
var longestConsecutive = function (nums) {
let maxCount = 0;
const obj = {};

// 1. 숫자의 존재 여부를 키로 접근하기 위해 저장
for (const num of nums) {
obj[num] = true;
}

// 2. 시작점들을 찾기 위해 순회
for (const num of nums) {
// 3. 연속적인 배열의 시작점인지 확인
if (obj[num - 1]) {
continue;
}

// 4. 연속적인 배열의 시작점부터 끝점까지 순회
let count = 1;
let next = num + 1;
while (obj[next]) {
count += 1;
next += 1;
}

// 5. 가장 긴 배열의 길이 갱신
maxCount = Math.max(maxCount, count);
}

return maxCount;
};
39 changes: 39 additions & 0 deletions maximum-product-subarray/wogha95.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// TC: O(N)
// SC: O(1)

/**
* @param {number[]} nums
* @return {number}
*/
var maxProduct = function (nums) {
let maximumProduct = Number.MIN_SAFE_INTEGER;
let subProduct = 1;
// 1. 좌에서 우로 누적곱을 저장하기 위해 순회
for (let index = 0; index < nums.length; index++) {
// 2. 0을 만나면 누적곱에 곱하지 않고 1로 초기화
if (nums[index] === 0) {
maximumProduct = Math.max(maximumProduct, 0);
subProduct = 1;
continue;
}
// 3. 매번 누적곱을 갱신
subProduct *= nums[index];
maximumProduct = Math.max(maximumProduct, subProduct);
}

subProduct = 1;
// 4. 우에서 좌로 누적곱을 저장하기 위해 순회
for (let index = nums.length - 1; index >= 0; index--) {
// 5. 0을 만나면 누적곱에 곱하지 않고 1로 초기화
if (nums[index] === 0) {
maximumProduct = Math.max(maximumProduct, 0);
subProduct = 1;
continue;
}
// 6. 매번 누적곱을 갱신
subProduct *= nums[index];
maximumProduct = Math.max(maximumProduct, subProduct);
}

return maximumProduct;
};
Comment on lines +8 to +39
Copy link
Contributor

@DaleSeo DaleSeo Sep 4, 2024

Choose a reason for hiding this comment

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

양방향 훑기! 신선한 답안입니다! 👏 모임 때 소개해주셔도 재밌을 것 같아용~

Copy link
Contributor Author

Choose a reason for hiding this comment

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

모임에서 소개해주실 분이 계셔서 기회가 되면 소개드리도록 하겠습니다!!

21 changes: 21 additions & 0 deletions missing-number/wogha95.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// TC: O(N)
// SC: O(1)

/**
* @param {number[]} nums
* @return {number}
*/
var missingNumber = function (nums) {
// result: subN - num의 누적합을 저장하는 변수
let result = 0;
// subN: 1부터 N까지 올라가는 변수
let subN = 1;

for (const num of nums) {
result += subN - num;
subN += 1;
}

// 최종 누적합은 (1~N까지 합) - (nums의 모든 원소 합) 이므로 누락된 숫자만 남게 됩니다.
return result;
};
DaleSeo marked this conversation as resolved.
Show resolved Hide resolved
149 changes: 149 additions & 0 deletions valid-palindrome/wogha95.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
// 3차
// TC: O(N)
// SC: O(1)
// N: s.length

/**
* @param {string} s
* @return {boolean}
*/
var isPalindrome = function (s) {
// 1. 투포인터를 양끝에서 시작합니다.
let left = 0;
let right = s.length - 1;

while (left < right) {
// 2. 현재 가리키는 문자가 영대소문자, 숫자가 아니면 건너뜁니다.
while (left < right && !isValid(s[left])) {
left += 1;
}
while (left < right && !isValid(s[right])) {
right -= 1;
}

// 3. 문자의 갯수가 홀수인 경우 투 포인터가 모두 가운데를 가리키는 경우 순회를 끝났다고 생각합니다.
if (left === right) {
break;
}

// 4. 투 포인터가 가리키는 문자가 다른 경우 정답(false)를 반환합니다.
if (!isSame(s[left], s[right])) {
return false;
}

// 5. 다음 문자로 넘어갑니다.
left += 1;
right -= 1;
}

// 6. 모든 순회가 끝냈다면 palindrome이라고 판단합니다.
return true;

function isValid(spell) {
if ("0" <= spell && spell <= "9") {
return true;
}
if ("a" <= spell && spell <= "z") {
return true;
}
if ("A" <= spell && spell <= "Z") {
return true;
}

return false;
}

function isSame(spellA, spellB) {
if (spellA === spellB) {
return true;
}
if (Math.abs(s[left].charCodeAt() - s[right].charCodeAt()) === 32) {
return true;
}

return false;
}
};

// 2차
// TC: O(N)
// SC: O(N)
// N: s.length

/**
* @param {string} s
* @return {boolean}
*/
var isPalindrome = function (s) {
const phrase = s.toLowerCase();
Copy link
Contributor

Choose a reason for hiding this comment

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

선재적으로 소문자 변환을 하는 대신에 31번째 줄에서 비교할 때 하면 공간 효율을 개선할 수 있지 않을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오, 검사하는 부분에서 더 조건을 추가하면 되었군요!
같은 문자이거나 아스키 코드로 같은 알파벳인지 판단하게 개선하였습니다.
감사합니다~!!

// 1. 투포인터를 양끝에서 시작합니다.
let left = 0;
let right = phrase.length - 1;

while (left < right) {
// 2. 현재 가리키는 문자가 영소문자, 숫자가 아니면 건너뜁니다.
while (left < right && !isValid(phrase[left])) {
left += 1;
}
while (left < right && !isValid(phrase[right])) {
right -= 1;
}

// 3. 문자의 갯수가 홀수인 경우 투 포인터가 모두 가운데를 가리키는 경우 순회를 끝났다고 생각합니다.
if (left === right) {
break;
}

// 4. 투 포인터가 가리키는 문자가 다른 경우 정답(false)를 반환합니다.
if (phrase[left] !== phrase[right]) {
return false;
}

// 5. 다음 문자로 넘어갑니다.
left += 1;
right -= 1;
}

// 6. 모든 순회가 끝냈다면 palindrome이라고 판단합니다.
return true;

function isValid(spell) {
if ("0" <= spell && spell <= "9") {
return true;
}
if ("a" <= spell && spell <= "z") {
return true;
}

return false;
}
};

// 1차
// TC: O(N)
// SC: O(N)
// N: s.length

/**
* @param {string} s
* @return {boolean}
*/
var isPalindrome = function (s) {
// 1. 문자열을 모두 소문자로 바꾸고 영소문자, 숫자만 남기고 모두 제거합니다.
const phrase = s
.toLowerCase()
.split("")
.filter(
(spell) =>
("0" <= spell && spell <= "9") || ("a" <= spell && spell <= "z")
);

// 2. 양끝의 문자를 확인해서 palindrome인지 판별합니다.
for (let index = 0; index < Math.floor(phrase.length / 2); index++) {
if (phrase[index] !== phrase[phrase.length - index - 1]) {
return false;
}
}

return true;
};
121 changes: 121 additions & 0 deletions word-search/wogha95.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// 2차
// result flag 변수를 활용 => boolean 반환으로 개선
// TC: O(M * N * 4^W)
// SC: O(MIN)
// W: word.length, MIN: min(M * N, W)

/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function (board, word) {
for (let r = 0; r < board.length; r++) {
for (let c = 0; c < board[0].length; c++) {
if (dfs(r, c, 0)) {
return true;
}
}
}

return false;

function dfs(row, column, wordIndex) {
if (!isValid(row, column)) {
return false;
}
if (board[row][column] !== word[wordIndex]) {
return false;
}
if (wordIndex === word.length - 1) {
return true;
}

const temp = board[row][column];
board[row][column] = "#";

if (
dfs(row + 1, column, wordIndex + 1) ||
dfs(row - 1, column, wordIndex + 1) ||
dfs(row, column + 1, wordIndex + 1) ||
dfs(row, column - 1, wordIndex + 1)
) {
return true;
}

board[row][column] = temp;

return false;
}

function isValid(row, column) {
if (row < 0 || board.length <= row) {
return false;
}
if (column < 0 || board[0].length <= column) {
return false;
}
return true;
}
};

// 1차
// TC: O(M * N * 4^W)
// SC: O(MIN)
// W: word.length, MIN: min(M * N, W)

/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function (board, word) {
let result = false;

// 1. 2차원 배열의 모든 좌표를 순회
for (let r = 0; r < board.length; r++) {
for (let c = 0; c < board[0].length; c++) {
dfs(r, c, 0);
}
}
Comment on lines +72 to +80
Copy link
Contributor

Choose a reason for hiding this comment

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

처음 result 라는 변수를 선언 하시고 시작하시는데
dfs 자체가 true를 반환하면 true를 반환하도록 하는 것도 괜찮을것 같아요!
설명이 너무 친절하게 달려있어서 유일하게 하나 말씀드리자면 이정도 일것 같습니다.
문제 푸시느라 고생 많으셨습니다 ㅎㅎ

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오, 그럼 flag 변수를 사용하지 않을 수 있군요!
유용한 아이디어 감사합니다!!


return result;

function dfs(row, column, wordIndex) {
// 2. 범위를 벗어난 좌표인지 검증
if (!isValid(row, column)) {
return;
}
// 3. word에서 찾고 있는 문자인지 확인
if (board[row][column] !== word[wordIndex]) {
return;
}
// 4. word 문자를 다 찾았는지 확인
if (wordIndex === word.length - 1) {
result = true;
return;
}

// 5. 방문한 좌표를 표시하기 위해 다른 문자로 바꿔치기 해둠
const temp = board[row][column];
board[row][column] = "#";
// 6. 상하좌우로 좌표 방문
dfs(row + 1, column, wordIndex + 1);
dfs(row - 1, column, wordIndex + 1);
dfs(row, column + 1, wordIndex + 1);
dfs(row, column - 1, wordIndex + 1);
// 7. 해당 좌표의 재귀 방문이 끝나면 미방문으로 표시하기 위해 원래 문자로 되돌리기
board[row][column] = temp;
}

// 유효한 좌표인지 확인하는 함수
function isValid(row, column) {
if (row < 0 || board.length <= row) {
return false;
}
if (column < 0 || board[0].length <= column) {
return false;
}
return true;
}
};