diff --git a/best-time-to-buy-and-sell-stock/JANGSEYEONG.js b/best-time-to-buy-and-sell-stock/JANGSEYEONG.js new file mode 100644 index 000000000..de73688fc --- /dev/null +++ b/best-time-to-buy-and-sell-stock/JANGSEYEONG.js @@ -0,0 +1,22 @@ +/** + * 시간 복잡도: O(n) - 배열을 한 번만 순회함 + * 공간 복잡도: O(1) - 추가 배열 없이 변수만 사용하므로 입력 크기와 무관한 상수 공간 + */ +/** + * @param {number[]} prices + * @return {number} + */ +var maxProfit = function (prices) { + let minPrice = prices[0]; // 지금까지 본 가장 낮은 가격 + let maxProfit = 0; // 최대 이익 + for (let i = 1; i < prices.length; i++) { + if (prices[i] > minPrice) { + // 현재 가격이 minPrice보다 큰 경우에 이익 갱신 + maxProfit = Math.max(maxProfit, prices[i] - minPrice); + } else { + // 그렇지 않다면 최소 가격 갱신 + minPrice = prices[i]; + } + } + return maxProfit; +}; diff --git a/encode-and-decode-strings/JANGSEYEONG.js b/encode-and-decode-strings/JANGSEYEONG.js new file mode 100644 index 000000000..815a4d790 --- /dev/null +++ b/encode-and-decode-strings/JANGSEYEONG.js @@ -0,0 +1,32 @@ +/** + * 시간 복잡도: O(n) - n은 모든 문자열의 총 길이. 각 문자를 한 번씩만 처리함 + * 공간 복잡도: O(n) - 인코딩/디코딩 결과를 저장하는 데 원본 데이터 크기만큼 공간 필요 + */ +var encode = function (strs) { + let text = ""; + for (let str of strs) { + // 각 문자열 앞에 길이와 구분자(:)를 붙여서 저장 + // 예: ["abc", "de"] -> "3:abc2:de" + text += `${str.length}:${str}`; + } + return text; +}; + +var decode = function (s) { + const result = []; + let start = 0; + + while (start < s.length) { + // 가장 첫번째에 등장하는 콜론 위치를 찾아 길이 정보 추출 + const mid = s.indexOf(":", start); + const length = parseInt(s.substring(start, mid)); + + // 길이 정보를 이용해 원래 문자열 추출하여 결과 배열에 추가 + result.push(s.substring(mid + 1, mid + 1 + length)); + + // 다음 문자열의 시작 위치로 이동 + start = mid + 1 + length; + } + + return result; +}; diff --git a/group-anagrams/JANGSEYEONG.js b/group-anagrams/JANGSEYEONG.js new file mode 100644 index 000000000..2304264fa --- /dev/null +++ b/group-anagrams/JANGSEYEONG.js @@ -0,0 +1,71 @@ +/* + * 첫번쨰 풀이: 각 단어마다 이전에 처리한 단어들과 일일이 비교하여 애너그램 여부 확인 + * 첫번째 풀이로도 통과는 함 + * 시간복잡도: O(n²*k), 공간복잡도: O(n*k) (n: 단어 수, k: 평균 단어 길이) + */ +/*var groupAnagrams = function (strs) { + if (strs.length === 1) return [strs]; + + const answer = [[strs[0]]]; + + for (let i = 1; i < strs.length; i++) { + let flag = false; + for (let k = 0; k < answer.length; k++) { + const word = answer[k][0]; + if (isAnagram(word, strs[i])) { + answer[k].push(strs[i]); + flag = true; + } + } + if (!flag) answer.push([strs[i]]); + } + + return answer; +}; + +var isAnagram = function (s1, s2) { + if (s1.length !== s2.length) return false; + + // 영어 소문자 개수 총 26개 + const code = new Array(26).fill(0); + + for (let i = 0; i < s1.length; i++) { + code[s1[i].charCodeAt() - 97]++; + code[s2[i].charCodeAt() - 97]--; + } + return code.every((x) => x === 0); +}; +*/ +/////////////////////////////////////////////////////////////// + +/* + * 두번쨰 풀이: 정렬된 문자열을 키로 사용하여 해시맵에 그룹화 + * 시간복잡도: O(n*k*log(k)), 공간복잡도: O(n*k) (n: 단어 수, k: 평균 단어 길이) + */ +/** + * @param {string[]} strs + * @return {string[][]} + */ +var groupAnagrams = function (strs) { + if (strs.length === 1) return [strs]; + + // 해시맵으로 애너그램 그룹 관리 + const answer = {}; + + for (let i = 0; i < strs.length; i++) { + // 애너그램은 정렬하면 동일한 문자열이 됨 + // k는 최대 100으로 제한되어 있어 정렬(O(k log k))이 효율적이므로 + // 첫 번째 풀이의 카운팅 방식 대신 간단히 정렬로 판별 가능 + const sort = [...strs[i]].sort().join(""); + + // 이미 같은 키가 있으면 해당 그룹에 추가 + if (sort in answer) { + answer[sort].push(strs[i]); + } else { + // 새로운 그룹 생성 + answer[sort] = [strs[i]]; + } + } + + return Object.values(answer); +}; diff --git a/implement-trie-prefix-tree/JANGSEYEONG.js b/implement-trie-prefix-tree/JANGSEYEONG.js new file mode 100644 index 000000000..aae2d3ef9 --- /dev/null +++ b/implement-trie-prefix-tree/JANGSEYEONG.js @@ -0,0 +1,73 @@ +/** + * Trie(트라이) 데이터 구조 구현 + * + * 원리: 트라이는 문자열을 효율적으로 저장하고 검색하기 위한 트리 구조 + * 각 노드는 한 문자를 나타내며, 루트에서 특정 노드까지의 경로가 하나의 문자열이 된다 + * 공통 접두사를 공유하여 메모리를 절약하고, 접두사 검색을 O(k) 시간에 수행할 수 있음 + */ +var Trie = function () { + this.root = {}; // 루트 노드 +}; + +/** + * @param {string} word + * @return {void} + */ +Trie.prototype.insert = function (word) { + let node = this.root; + + for (let char of word) { + if (!node[char]) { + // 처음 들어오는 단어일 경우, 새 트리구조 생성 + node[char] = {}; + } + // 현재 단어 위치로 이동 + node = node[char]; + } + node.isEnd = true; // 단어 끝 표시 +}; + +/** + * @param {string} word + * @return {boolean} + */ +Trie.prototype.search = function (word) { + let node = this.traverse(word); + return node !== null && node.isEnd === true; +}; + +/** + * @param {string} prefix + * @return {boolean} + */ +Trie.prototype.startsWith = function (prefix) { + return this.traverse(prefix) !== null; +}; + +/** + * 주어진 문자열을 따라 트라이를 탐색하는 함수 + * @param {string} str - 트라이에서 탐색할 문자열 + * @return {object|null} - 문자열 경로의 마지막 노드 또는 경로가 없으면 null + */ +Trie.prototype.traverse = function (str) { + let node = this.root; + + // 문자열의 각 문자를 하나씩 순회 + for (let char of str) { + // 현재 문자에 대한 경로가 없으면 탐색 실패 + if (!node[char]) { + return null; + } + // 해당 문자의 노드로 이동하여 탐색 계속 + node = node[char]; + } + return node; +}; + +/** + * Your Trie object will be instantiated and called as such: + * var obj = new Trie() + * obj.insert(word) + * var param_2 = obj.search(word) + * var param_3 = obj.startsWith(prefix) + */ diff --git a/word-break/JANGSEYEONG.js b/word-break/JANGSEYEONG.js new file mode 100644 index 000000000..1f1fd613e --- /dev/null +++ b/word-break/JANGSEYEONG.js @@ -0,0 +1,48 @@ +/** + * 문자열이 단어 사전 내의 단어들로 분할 가능한지 확인 + * + * 시간복잡도: O(n * m * k) + * - n: 문자열 길이 + * - m: 단어 사전 크기 + * - k: 단어 사전 내 가장 긴 단어의 길이 + * + * 공간복잡도: O(n) + * - n: 문자열 길이 (메모이제이션 저장 공간) + * + * @param {string} s - 분할하려는 문자열 + * @param {string[]} wordDict - 단어 사전 + * @return {boolean} - 문자열을 단어 사전 내 단어들로 분할 가능한지 여부 + */ +var wordBreak = function (s, wordDict) { + // 메모이제이션을 위한 객체 + const memo = {}; + + const dfs = function (start) { + // 이미 계산한 위치라면 저장된 결과 반환 + if (start in memo) return memo[start]; + + // 문자열 끝까지 도달했다면 성공 + if (start === s.length) { + memo[start] = true; + return true; + } + + // 모든 단어를 시도 + for (const word of wordDict) { + // 현재 위치에서 시작하는 부분 문자열이 단어와 일치하는지 확인 + if (s.substring(start, start + word.length) === word) { + // 남은 문자열도 분할 가능한지 재귀적으로 확인 + if (dfs(start + word.length)) { + memo[start] = true; + return true; + } + } + } + + // 현재 위치에서 가능한 분할이 없음 + memo[start] = false; + return false; + }; + + return dfs(0); +};