Skip to content

[JANGSEYEONG] WEEK 01 solutions #1162

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

Merged
merged 6 commits into from
Apr 5, 2025
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
14 changes: 14 additions & 0 deletions contains-duplicate/JANGSEYEONG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
시간복잡도: O(n) - new Set(nums)에서 배열 요소 순회하며 Set 생성 O(n) + 길이 비교 O(1)
- Set 자료구조는 중복된 값을 자동으로 제거
*/

/**
* @param {number[]} nums
* @return {boolean}
*/
var containsDuplicate = function (nums) {
// Set으로 만들었을 때, 기존 배열과 사이즈가 다르면 중복이 제거된거임
const numsSet = new Set(nums);
return nums.length !== numsSet.size;
Copy link
Contributor

Choose a reason for hiding this comment

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

시간, 공간복잡도를 모두 잡은 코드네요 👍👍

};
60 changes: 60 additions & 0 deletions house-robber/JANGSEYEONG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* 시간복잡도: O(n) - 배열을 한 번만 순회
* 공간복잡도: O(n) - 길이가 n+1인 DP 배열 사용
*
* @param {number[]} nums
* @return {number}
*/
var rob = function (nums) {
// arr[i] = i번째 집부터 마지막 집까지 고려했을 때 훔칠 수 있는 최대 금액
const arr = new Array(nums.length + 1);

// 존재하지 않는 집(n+1번째)은 0, 점화식을 위한 계산용 공간임
arr[nums.length] = 0;

// 마지막 집만 고려하면 그 집의 금액이 최대값 (이후 집들은 없으니까 0으로 계산한 결과)
arr[nums.length - 1] = nums[nums.length - 1];

// 뒤에서부터 계산
for (let i = nums.length - 2; i >= 0; i--) {
// i번째 집에서의 결정:
// 1. i번째 집을 털고 (i+2)번째 집부터 훔치는 경우: nums[i] + arr[i+2]
// 2. i번째 집을 털지 않고 (i+1)번째 집부터 훔치는 경우: arr[i+1]
// 두 가지 중 최대값을 선택
arr[i] = Math.max(nums[i] + arr[i + 2], arr[i + 1]);
}

// arr[0]은 0번째 집부터 고려했을 때 훔칠 수 있는 최대 금액
return arr[0];
};

/* 풀이 설명:
* 문제의 핵심: 인접한 집은 연속해서 털 수 없을 때 최대로 털 수 있는 금액 찾기
*
* 접근 방식: 다이나믹 프로그래밍
*
* 1. 상태 정의:
* - f(n) = n번째 집부터 마지막 집까지 고려했을 때 털 수 있는 최대 금액
*
* 2. 점화식 도출:
* - 각 집마다 두 가지 선택이 있음:
* 1) 현재 집을 털기: 현재 집의 돈 + 두 집 이후부터의 최대 금액
* 2) 현재 집을 털지 않기: 다음 집부터의 최대 금액
* - 따라서 점화식: f(n) = max(nums[n] + f(n+2), f(n+1))
*
* 3. 베이스 케이스:
* - 존재하지 않는 집: f(n+1) = 0
* - 마지막 집: f(n) = nums[n]
*
* 4. 계산 방향:
* - 뒤에서부터 앞으로 계산 (Bottom-up)
* - 마지막 집부터 시작해서 첫 번째 집까지 각 위치에서의 최대 금액 계산
*
* 예시 [1,2,3,1]:
* - f(4) = 0 (존재하지 않는 집)
* - f(3) = 1 (마지막 집)
* - f(2) = max(3 + f(4), f(3)) = max(3 + 0, 1) = 3
* - f(1) = max(2 + f(3), f(2)) = max(2 + 1, 3) = 3
* - f(0) = max(1 + f(2), f(1)) = max(1 + 3, 3) = 4
* - 결과: 4 (최적의 선택은 0번째와 2번째 집 털기)
*/
26 changes: 26 additions & 0 deletions longest-consecutive-sequence/JANGSEYEONG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
시간복잡도: O(n) - 모든 요소에 대해 최대 한 번씩만 검사하기 때문
- Set을 사용하여 O(1) 시간에 요소 존재 여부 확인 가능
- 각 숫자는 연속 수열의 시작점인 경우에만 while 루프를 실행
-> 모든 요소에 대해 while 루프의 총 반복 횟수는 전체 요소 수를 초과하지 않음
*/
/**
* @param {number[]} nums
* @return {number}
*/

var longestConsecutive = function (nums) {
const numSet = new Set(nums);
let longest = 0;

for (let n of [...numSet]) {
if (!numSet.has(n - 1)) {
let length = 0;
while (numSet.has(n + length)) {
length += 1;
}
longest = Math.max(length, longest);
}
}
return longest;
};
42 changes: 42 additions & 0 deletions product-of-array-except-self/JANGSEYEONG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
시간복잡도: O(n) - 배열을 두 번 순회하지만 여전히 선형
배열에서 0의 개수에 따라 결과가 달라지는 점을 이용
- 0이 두 개 이상: 모든 결과는 0
- 0이 하나: 0 위치만 전체곱, 나머지는 0
- 0이 없음: 전체곱 ÷ 현재 원소
*/

/**
* @param {number[]} nums
* @return {number[]}
*/
var productExceptSelf = function (nums) {
// 0이 있는 위치들 체크용
const zeros = new Map();

// 전체 곱 구하기 - O(n)
const allProductExceptZero = nums.reduce((acc, x, i) => {
if (x === 0) zeros.set(i, true);
return x !== 0 ? acc * x : acc;
}, 1);

// 배열 전체 돌면서 조건에 맞게 return 시키기 - O(n)
return nums.map((x, i) => {
// 0이 2개 이상이라면 무조건 0
if (zeros.size > 1) {
Copy link
Contributor

Choose a reason for hiding this comment

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

(이미 빠른 코드이지만) 전체곱을 구하는 reduce에서 0의 개수를 미리 카운트 해놓으면 0이 2개 이상인경우
map을 돌지 않고 함수를 종료 할 수 있어보입니다!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

아 그러네요 !! 0개일 땐 위에서 미리 return 시켜도 무방한 코드인데 map 돌면서 조건 하나하나 분기하면서 생각하다보니 넣어버린 것 같습니다 ㅎㅎ.. 리뷰 감사합니다!

return 0;
}

// 0이 1개일 때
if (zeros.size === 1) {
// 그게 나일 때
if (zeros.has(i)) return allProductExceptZero;

// 그게 다른애일 때
return 0;
}

// 0이 없을 때
return allProductExceptZero / x;
});
};
42 changes: 42 additions & 0 deletions two-sum/JANGSEYEONG.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
시간복잡도: O(n²)
- nums.indexOf()는 배열 전체를 순회하는 O(n) 작업
Copy link
Contributor

Choose a reason for hiding this comment

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

O(n^2) 복잡도로는 시간초과가 나는지 궁금합니다..!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

리트코드에서는 시간 초과가 안납니다!

O(n²)일 떄: 75ms, O(n)일 때: 2ms 로 차이가 굉장히 많이 나는건 확인했습니다 ㅎ_ㅎ

문제 하단에 "Follow-up: Can you come up with an algorithm that is less than O(n2) time complexity?" 라고 되어있어서 더 줄여봤습니다!

- 이 작업이 for 루프(O(n)) 내부에서 실행되므로 전체 시간복잡도는 O(n) * O(n) = O(n²)

var twoSum = function (nums, target) {
for (let i = 0; i < nums.length; i++) {
let x = nums.indexOf(target - nums[i]);
if (x > -1 && x !== i) {
return [i, x];
}
}
return [];
};

*/

/*
시간복잡도: O(n) - 전체 배열을 한 번만 순회 O(n) + 키-값 쌍 저장에 O(1)
- Map 대신 객체(Object)를 사용해도 될듯 함
- 일반 객체는 {}, 접근은 obj[key]로 가능, has() 대신 (key in obj) 또는 obj[key] !== undefined 또는 key in obj 사용 가능 */
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function (nums, target) {
let minus = new Map();
for (let i = 0; i < nums.length; i++) {
let current = nums[i];

// 현재 숫자가 이전에 저장된 보수(complement)와 일치하는지 확인 - Map 객체의 has는 O(1) 연산
if (minus.has(current)) {
return [minus.get(current), i];
}

// 현재 숫자의 보수(target-current)와 인덱스를 맵에 저장 - Map 객체의 set은 O(1) 연산
minus.set(target - current, i);
}

return [];
};