Skip to content

[clara-shin] WEEK 02 solutions #1259

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 5 commits into from
Apr 13, 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
76 changes: 76 additions & 0 deletions 3sum/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 배열에서 합이 0이 되는 세 숫자를 찾기
*
* 요구사항 & 제약조건:
* 세 개의 다른 인덱스에 있는 숫자들의 합이 0이 되어야 함
* 결과에 중복된 조합이 없어야 함
* 배열 크기는 3에서 3000까지
* 배열 요소는 -10^5부터 10^5까지의 정수
*
* 접근방법:
* 1. 브루트포스(이중for문, 완전탐색) - O(n^3) 시간복잡도
* 2. 정렬 후 투 포인터 - O(n^2) 시간복잡도 ✅
*
* 배열을 정렬 후, 배열을 순회하며 각 요소를 첫 번째 숫자로 고정
* 고정된 숫자 다음부터 ~ 배열 끝까지 투 포인터를 사용 ➡️ 나머지 두 수의 합이 첫 번째 수의 음수가 되는 조합 찾기
* 중복을 피하기 위해 같은 값을 가진 연속된 요소는 스킵
*/

/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function (nums) {
const result = [];
const n = nums.length;

if (n < 3) return result;

nums.sort((a, b) => a - b); // 오름차순 정렬

if (nums[0] > 0 || nums[n - 1] < 0) return result;

// 배열을 순회하며 첫 번째 숫자 고정
for (let i = 0; i < n - 2; i++) {
// 중복된 값 건너뛰기 (첫 번째 숫자)
if (i > 0 && nums[i] === nums[i - 1]) continue;

// 첫 번째 숫자가 양수면 for문 탈출
if (nums[i] > 0) break;

// 현재 숫자와 가장 작은 두 숫자의 합 > 0 => 탈출
if (nums[i] + nums[i + 1] + nums[i + 2] > 0) break;

// 현재 숫자와 가장 큰 두 숫자의 합 < 0 => 건너뛰기
if (nums[i] + nums[n - 2] + nums[n - 1] < 0) continue;

// 두 번째, 세 번째 숫자를 찾기 위한 투 포인터
let left = i + 1;
let right = n - 1;

while (left < right) {
const sum = nums[i] + nums[left] + nums[right];

if (sum < 0) {
// 합이 0보다 작으면 left 포인터를 오른쪽으로 이동
left++;
} else if (sum > 0) {
// 합이 0보다 크면 right 포인터를 왼쪽으로 이동
right--;
} else {
// 합이 0이면 결과에 추가
result.push([nums[i], nums[left], nums[right]]);

// 중복된 값 건너뛰기 (두 번째, 세 번째 숫자)
while (left < right && nums[left] === nums[left + 1]) left++;
while (left < right && nums[right] === nums[right - 1]) right--;

// 다음 조합을 찾기 위해 포인터 이동
left++;
right--;
}
}
}

return result;
};
41 changes: 41 additions & 0 deletions climbing-stairs/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/**
* 계단을 오르는 방법은 몇 가지?
* 총 n개의 계단
* 한 번에 1계단 또는 2계단을 오를 수 있다
* 계단 꼭대기에 도달하는 서로 다른 방법의 수를 구해보자
*
* ✨동적 프로그래밍(Dynamic Programming)✨
* 재귀 접근법
* 메모이제이션을 사용한 재귀
* 동적 프로그래밍(바텀업)
* 피보나치 수열: n번째 계단에 도달하는 방법의 수는 (n-1)번째와 (n-2)번째 계단에 도달하는 방법의 수의 합과 같음
*/

/**
* @param {number} n
* @return {number}
*/
function climbStairs(n) {
// 예외 처리: n이 1 이하인 경우 빨리 1 리턴하고 탈출
if (n <= 1) {
return 1;
}

// 피보나치 수열 계산을 위한 변수
let first = 1; // f(0) = 1
let second = 1; // f(1) = 1
Comment on lines +24 to +26
Copy link
Contributor

Choose a reason for hiding this comment

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

저는 배열을 사용해서 모든 과정에서 나오는 값들을 다 저장했는데 이렇게 변수 2개만 사용하는 방법도 있네요!! 좋은 방법 배워갑니다 👍

let result;

// 피보나치 수열 계산
for (let i = 2; i <= n; i++) {
result = first + second;
first = second;
second = result;
}

return result;
}

// 시간 복잡도: O(n) - n번의 반복문을 수행
// 공간 복잡도: O(1) - 상수 개의 변수만 사용
// 제약조건이 n은 최대 45까지니까 피보나치 접근법이 좋은 선택인 듯
45 changes: 45 additions & 0 deletions product-of-array-except-self/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* 문제: 배열에서 자신을 제외한 모든 원소의 곱을 구하는 함수 구하기
* 제약조건: 나눗셈 사용하지 않고, O(n) 시간 복잡도로 풀어야 함
*
* Brute Force 방법으로는 각 원소에 대해 나머지 원소들의 곱을 구하는 방법이 있지만,
* 이는 O(n^2) 시간 복잡도를 가지므로 제약조건에 어긋남
*
* O(n) 시간 복잡도로 해결하기 위해서는,
* 두 번의 순회를 통해 각 원소의 왼쪽과 오른쪽 곱을 따로 계산한 후,
* 최종적으로 두 곱을 곱해주는 방법을 사용할 수 있음
*
* 이 방법은 추가적인 공간을 사용하지 않고,
* 결과를 저장하는 배열을 사용하여 각 원소의 왼쪽 곱을 저장한 후
* 오른쪽 곱을 계산하여 결과 배열에 곱해주는 방식으로 구현됨
*
* 접근방법:
* 1. 왼쪽에서 오른쪽으로 누적 곱을 계산 (prefix 곱).
* 2. 오른쪽에서 왼쪽으로 누적 곱을 계산 (suffix 곱).
* 3. 각 위치 i에 대해 i 위치 이전까지의 prefix 곱과 i 위치 이후의 suffix 곱을 곱하여 결과를 저장
*/

/**
* @param {number[]} nums - 입력 배열
* @return {number[]} - 결과 배열 (answer[i]는 nums[i]를 제외한 모든 원소의 곱)
*/
var productExceptSelf = function (nums) {
const n = nums.length;

// 최종 리턴 할 결과 배열 초기화 (O(1) 추가 공간을 위해 출력 배열만 사용)
const result = Array.from({ length: n }, () => 1);

let leftProduct = 1;
for (let i = 0; i < n; i++) {
result[i] = leftProduct; // 현재 위치 이전까지의 누적 곱 저장
leftProduct *= nums[i]; // 다음 위치를 위해 현재 원소 곱해서 중간결과(result) 업데이트
}

let rightProduct = 1;
for (let i = n - 1; i >= 0; i--) {
result[i] *= rightProduct; // 현재 결과에 오른쪽 누적 곱을 곱해줌
rightProduct *= nums[i]; // 다음 위치를 위해 현재 원소 곱함
}

return result;
};
54 changes: 54 additions & 0 deletions valid-anagram/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* 두 문자열 s와 t가 서로 애너그램(anagram)인지 확인하는 문제
* 애너그램: 글자의 배열 순서는 다르지만 구성하는 문자와 각 문자의 개수가 동일한 문자열
*
* 접근 방법 몇 가지:
* 1. 정렬 방식: 두 문자열을 알파벳 순으로 정렬한 후 비교하는 방법
* 2-1. 문자 카운팅 방식 (객체활용): 각 문자열에 등장하는 문자들의 빈도수를 계산하여 비교하는 방법
* 2-2. 문자 카운팅 방식 (해시맵 객체활용): 문자를 키로, 빈도수를 값으로 하는 해시맵을 만들어 비교하는 방법
*
* 팔로우업 질문: 입력에 유니 코드 문자가 포함되어 있으면 어떻게 해? 그러한 경우에 솔루션을 어떻게 조정할꺼야?
* JavaScript의 문자열은 기본적으로 UTF-16으로 인코딩되서 유니코드 문자도 처리 가능
* Map 객체는 어떤 타입의 키도 허용 ➡️ 유니코드 문자를 키로 사용하는 데 문제가 없음
* (방법 2-1)의 일반 객체도 유니코드 문자를 키로 지원하지만, Map을 사용하는 것이 좀 더 안전 => 해시맵으로 구현 ㄱㄱ
*/

/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
function isAnagram(s, t) {
// 길이가 다르면 애너그램이 될 수 없음 (빠른 리턴)
if (s.length !== t.length) {
return false;
}
Comment on lines +22 to +25
Copy link
Contributor

Choose a reason for hiding this comment

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

이렇게 빠르게 판단 가능한 경우에 바로 리턴해주는 것 좋은 것 같아요 👏


const charMap = new Map();

// 첫 번째 문자열의 각 문자 카운트 증가
for (let char of s) {
charMap.set(char, (charMap.get(char) || 0) + 1);
}

// 두 번째 문자열의 각 문자 카운트 감소
for (let char of t) {
const count = charMap.get(char);

// 해당 문자가 없거나 카운트가 0이면 애너그램이 아님
if (!count) {
return false;
}

charMap.set(char, count - 1);
}

// 모든 카운트가 0인지 확인
for (let count of charMap.values()) {
if (count !== 0) {
return false;
}
}
Comment on lines +47 to +51
Copy link
Contributor

Choose a reason for hiding this comment

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

동일한 방법이긴 한데 array의 내장 메서드인 every를 사용하는 방법도 있어서 코멘트 남깁니다 😁
기존 코드와 비슷하게 조건에 맞지 않는 요소가 있으면 바로 false를 반환해서 성능적으로도 비슷할 것 같아요..!
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Array/every


return true;
}
32 changes: 32 additions & 0 deletions validate-binary-search-tree/clara-shin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* 이진탐색트리 유효성 검사 (Valid Binary Search Tree) 확인하는 함수 만들기
* 검사할 조건 3가지
* 1. 왼쪽 서브트리의 모든 노드 값은 루트 노드 값보다 작아야 한다.
* 2. 오른쪽 서브트리의 모든 노드 값은 루트 노드 값보다 커야 한다.
* 3. 왼쪽 서브트리와 오른쪽 서브트리도 각각 이진탐색트리여야 한다.
*/

/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
* val: 노드가 저장하는 값입니다.
* left: 왼쪽 자식 노드를 가리키는 참조
* right: 오른쪽 자식 노드를 가리키는 참조
*/
/**
* @param {TreeNode} root
* @return {boolean}
*/
var isValidBST = function (root) {
const searchBST = (node, min, max) => {
if (node === null) return true;
if (node.val <= min || node.val >= max) return false;
return searchBST(node.left, min, node.val) && searchBST(node.right, node.val, max);
};

return searchBST(root, -Infinity, Infinity);
};