Skip to content
Merged
73 changes: 73 additions & 0 deletions coin-change/grapefruitgreentealoe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@

//1. top-down
var coinChange = function(coins, amount) {
if(amount == 0 ) return 0
/*
점화식 : dp[n] = Math.min(dp[n],dp[n-k]+1)
*/
const dp = new Array(amount+1).fill(Math.pow(10,4));
for(let c of coins){
dp[c] = 1
}
function dfs(idx,coin){
if(idx + coin > amount) return
dp[idx+coin] = Math.min(dp[idx+coin],dp[idx]+1)
if(idx + coin == amount){
return
}else{
for(let c of coins){
dfs(idx+coin,c)
}
}
}
dfs(0,coins[0])

if(dp[amount] > amount) return -1

return dp[amount]

};

// 타임리밋에 걸린다. dfs의 가지치기가 덜 되어서 그렇다.
// 그렇다면, 순차적인 dp를 좀더 활용하는 것이 좋아보인다. 현재로서는 메모이제이션의 이점을 제대로 활용할 수 없어보인다.



//2. bottom-up
var coinChange = function(coins, amount) {
coins = coins.filter(x=> x<=amount)
if(amount == 0) return 0
/*
점화식 : dp[n] = Math.min(dp[n],dp[n-k]+1)
*/
const dp = new Array(amount+1).fill(Math.pow(10,4)+1);
dp[0] = 0
//dfs를 쓰고 싶지 않다면, amount만큼의 for문을 돌면서 dp를 초기화해주면 된다.
for(let i=1; i <amount+1; i++){
for(let coin of coins){
if(i-coin >= 0){
dp[i] = Math.min(dp[i-coin] + 1,dp[i])
}
}
}
if(dp[amount] > amount) return -1
return dp[amount]

};

/**
dfs를 쓰고 싶지 않다면, amount만큼의 for문을 돌면서 dp를 초기화해주면 된다.
처음에 coins = coins.filter(x=> x<=amount)
이렇게 쓴 이유는, amount보다 작은 코인만 필터하여 최적화한다.

이 문제 풀이 소요시간이 1시간이 넘었는데,
그 이유가, i-coin이 아니라, i+coin을 기준을 삼았었다.
그러다보니 그러다 보니 각 금액 `i`의 최솟값을 찾기 위해 `dp[i]`를 `dp[i-coin]` 기반으로 갱신해야 한다는 핵심적인 아이디어를 적용하기 어려웠다.
`dp[i+coin]` 방식으로 접근하면, `dp[i]` 자체가 아직 최적의 상태가 아닌데도 그 값을 기반으로 미래의 `dp[i+coin]`을 업데이트하려 하기 때문에, 정확한 최적해를 찾아내기 어려웠다.
현재 금액 `i`를 만들기 위한 가장 효율적인 방법을 과거의 `dp[i-coin]`에서 찾는 '바텀업(Bottom-Up)' 방식이 이 문제에 훨씬 적합하다는 것을 깨닫는 데 시간이 걸렸다.



시간복잡도: O(n * m)
공간 복잡도 : O(n)
*/
29 changes: 29 additions & 0 deletions find-minimum-in-rotated-sorted-array/grapefruitgreentealoe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
회전한 데이터에서 제일 작은 값을 찾는 문제라는 것을 기억해야한다.
그렇게 때문에, left, right를 각각 0, nums.length -1 로 두어서, left와 right가 같아질때 탈출하고, 그 같아진 index에 대한 nums[index]를 찾아내면 될 것 같다.
*/
/**
*
* @param {*} nums
* @returns
*/
function findMin(nums) {
let left = 0;
let right = nums.length - 1;

while (left !== right) {
let mid = Math.floor((left + right) / 2);
Copy link
Contributor

Choose a reason for hiding this comment

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

Binary Search에서 흔히 사용하는 식이네요!
해당 식과 관련하여 공유드리고 싶은 정보가 있어서 디스코드로 공유해 둘께요 :)


if (nums[mid] > nums[right]) {
// 최소값은 오른쪽에 있다
left = mid + 1;
} else {
// 최소값은 왼쪽 구간에 있다 (mid도 포함 가능)
right = mid;
}
}

return nums[left]; // 또는 nums[right]도 같음
}
//시간복잡도 : 이진탐색을 썼으므로 O(logn)
//공간복잡도 : O(1)
22 changes: 22 additions & 0 deletions maximum-depth-of-binary-tree/grapefruitgreentealoe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* 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)
* }
*/
/**
* @param {TreeNode} root
* @return {number}
*/
var maxDepth = function (root) {
if (!root) return 0;

// 왼쪽과 오른쪽 서브트리의 최대 깊이를 구한다.
const leftDepth = maxDepth(root.left);
const rightDepth = maxDepth(root.right);

// 현재 노드의 깊이는 왼쪽과 오른쪽 깊이 중 큰 값에 1을 더한 값이다.
return Math.max(leftDepth, rightDepth) + 1;
};
36 changes: 36 additions & 0 deletions merge-two-sorted-lists/grapefruitgreentealoe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//두개의 정렬된 링크드 리스트 list1, list2의 Head를 받았다.(리스트가 아니라, 연결리스트의 헤드)
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
function mergeTwoLists(
list1,
list2,
) {
if (!(list1 && list2)) return list1 || list2;
if (list1.val < list2.val) {
list1.next = mergeTwoLists(list1.next, list2);
return list1;
} else {
list2.next = mergeTwoLists(list1, list2.next);
return list2;
}
}

/*
list1.val이 list2.val보다 작으면
list1.next 다음에 list2.val이 온다.
만약 아니라면, list2.next 다음에 list1의 Head를 붙여준다.
이렇게 next만 바꿔주면서 연결지어주는 것이다.

시간 복잡도: O(log n)
공간 복잡도: O(h)
Comment on lines +34 to +35
Copy link
Contributor

Choose a reason for hiding this comment

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

LinkedList의 정렬을 위해서는 각 list1, list2의 모든 요소를 순회해야 하고, 따로 메모리를 사용하지 않으신것 같은데 시간 복잡도 O(m+n), m은 list1의 길이, n은 list2의 길이 / 공간 복잡도 O(1) 라고 생각되요!

*/
75 changes: 75 additions & 0 deletions word-search/grapefruitgreentealoe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/**
* @param {character[][]} board
* @param {string} word
* @return {boolean}
*/
var exist = function(board, word) {
//word를 순회해서 board의 x,y를 조절하여, 값이 있으면 계속 true
const xLength = board[0].length;
const yLength = board.length

// 체크 배열: 방문 여부를 저장. 각 DFS마다 새로 초기화될 필요는 없음 (DFS 내에서 백트래킹 처리하므로)
const check = Array.from({ length: yLength }, () => Array(xLength).fill(false));


// 단어의 첫 글자와 일치하는 시작점을 모두 수집
const startPoints = []
for(let i = 0; i<xLength;i++){
for(let j = 0; j<yLength;j++){
if(board[j][i] == word[0]){
startPoints.push([j,i])
}
}
}
// 각 시작점에서 DFS 시도
for(let startPoint of startPoints){
// 잘못된 길로 가는 경우도 있기 때문에 백트렉킹은 무조건 필요하다.
if(backTracking(startPoint,board,word,check)) return true
}

return false
};

function backTracking(startPoint,board,word,check){
const xLength = board[0].length;
const yLength = board.length
const upDownLeftRight = [[-1,0],[1,0],[0,-1],[0,1]]
//스타트 포인트부터 해서, 완성이 가능한지를 dfs를 통해서 진행한다.
//idx는 현재 확인해야할 word의 idx이다.
function dfs(y,x,idx){
if(idx == word.length) return true

if(y < 0 || y >= yLength ||
x < 0 || x >= xLength ||
check[y][x] ||
board[y][x] !== word[idx]
) return false;

check[y][x] = true
// 4방향 탐색
for(let [dy,dx] of upDownLeftRight){
//만약 타겟에 해당하는지를 확인을 먼저 해야할것같음!
if(dfs(y+dy,x+dx,idx+1)) return true //4방향에 대해서 모두 dfs를 진행한다.

}

//백트레킹
check[y][x] = false;
return false

}
return dfs(...startPoint,0)
}

/*
시간 복잡도: O(m × n × 3^{L-1})
- m × n: 시작점 후보 개수
- 3^{L-1}: 첫 번째 이후로는 3방향으로만 뻗어나가는 경우의 수


공간 복잡도: O(m × n)
- 방문 체크 배열: O(m × n)
- 재귀 호출 스택: 최악의 경우 단어 길이 L까지 깊어질 수 있으므로 O(L)

여기서 m, n은 보드 크기, L은 단어 길이
*/