diff --git a/coin-change/grapefruitgreentealoe.js b/coin-change/grapefruitgreentealoe.js new file mode 100644 index 000000000..5d0542e32 --- /dev/null +++ b/coin-change/grapefruitgreentealoe.js @@ -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 = 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) + */ diff --git a/find-minimum-in-rotated-sorted-array/grapefruitgreentealoe.js b/find-minimum-in-rotated-sorted-array/grapefruitgreentealoe.js new file mode 100644 index 000000000..e799346a2 --- /dev/null +++ b/find-minimum-in-rotated-sorted-array/grapefruitgreentealoe.js @@ -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); + + if (nums[mid] > nums[right]) { + // 최소값은 오른쪽에 있다 + left = mid + 1; + } else { + // 최소값은 왼쪽 구간에 있다 (mid도 포함 가능) + right = mid; + } + } + + return nums[left]; // 또는 nums[right]도 같음 +} +//시간복잡도 : 이진탐색을 썼으므로 O(logn) +//공간복잡도 : O(1) diff --git a/maximum-depth-of-binary-tree/grapefruitgreentealoe.js b/maximum-depth-of-binary-tree/grapefruitgreentealoe.js new file mode 100644 index 000000000..75857cc5d --- /dev/null +++ b/maximum-depth-of-binary-tree/grapefruitgreentealoe.js @@ -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; +}; diff --git a/merge-two-sorted-lists/grapefruitgreentealoe.js b/merge-two-sorted-lists/grapefruitgreentealoe.js new file mode 100644 index 000000000..b78a521bc --- /dev/null +++ b/merge-two-sorted-lists/grapefruitgreentealoe.js @@ -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) +*/ diff --git a/word-search/grapefruitgreentealoe.js b/word-search/grapefruitgreentealoe.js new file mode 100644 index 000000000..1eb8e767e --- /dev/null +++ b/word-search/grapefruitgreentealoe.js @@ -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= 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은 단어 길이 + */