diff --git a/combination-sum/smosco.js b/combination-sum/smosco.js new file mode 100644 index 0000000000..954884cacb --- /dev/null +++ b/combination-sum/smosco.js @@ -0,0 +1,44 @@ +/** + * Combination Sum + * + * 핵심 아이디어: + * - DFS + 백트래킹으로 모든 조합 탐색 + * - 같은 숫자를 여러 번 사용 가능 + * - start 인덱스로 중복 조합 방지 ([2,3]과 [3,2] 같은 것) + * + * 시간 복잡도: O(N^(T/M)) - N: 후보 개수, T: 타겟, M: 최소값 + * 공간 복잡도: O(T/M) - 재귀 깊이 + */ + +const combinationSum = (candidates, target) => { + const result = []; + + const dfs = (remain, start, path) => { + // Base case: 타겟 달성 + if (remain === 0) { + result.push([...path]); // 현재 경로 저장 + return; + } + + // Base case: 타겟 초과 (pruning) + if (remain < 0) return; + + // 모든 후보 탐색 + for (let i = start; i < candidates.length; i++) { + // 가지치기: 남은 값보다 크면 스킵 + if (candidates[i] > remain) continue; + + // 선택 + path.push(candidates[i]); + + // 탐색 (i부터 시작 - 같은 숫자 재사용 가능) + dfs(remain - candidates[i], i, path); + + // 되돌리기 (백트래킹) + path.pop(); + } + }; + + dfs(target, 0, []); + return result; +}; diff --git a/decode-ways/smosco.js b/decode-ways/smosco.js new file mode 100644 index 0000000000..581c55d84e --- /dev/null +++ b/decode-ways/smosco.js @@ -0,0 +1,43 @@ +/** + * Decode Ways + * + * 핵심 아이디어: + * - 숫자 문자열을 1-26(A-Z)로 디코딩하는 경우의 수를 구하는 문제 + * - dp[i] = 앞에서부터 i개의 문자를 디코딩하는 방법 수 + * - 각 위치에서 2가지 선택 가능: + * 1) 한 자리 숫자로 해석 (1-9) + * 2) 두 자리 숫자로 해석 (10-26) + * + * 시간 복잡도: O(n) - 문자열을 한 번만 순회 + * 공간 복잡도: O(n) - dp 배열 사용 + */ + +function numDecodings(s) { + // 예외 처리: 빈 문자열이거나 '0'으로 시작하면 불가능 + if (!s || s[0] === '0') return 0; + + const n = s.length; + const dp = new Array(n + 1).fill(0); + + // 초기값 설정 + dp[0] = 1; // 빈 문자열 (base case) + dp[1] = 1; // 첫 번째 문자 (이미 '0' 체크 완료) + + // i번째 위치까지의 디코딩 방법 수 계산 + for (let i = 2; i <= n; i++) { + // 1) 한 자리 숫자로 해석 (1~9만 가능, 0은 단독 불가) + const oneDigit = s[i - 1]; + if (oneDigit !== '0') { + dp[i] += dp[i - 1]; // 이전까지의 모든 방법에 현재 숫자 추가 + } + + // 2) 두 자리 숫자로 해석 (10~26만 가능) + const twoDigits = s[i - 2] + s[i - 1]; + const twoDigitsNum = parseInt(twoDigits); + if (twoDigitsNum >= 10 && twoDigitsNum <= 26) { + dp[i] += dp[i - 2]; // i-2까지의 모든 방법에 두 자리 숫자 추가 + } + } + + return dp[n]; +} diff --git a/maximum-subarray/smosco.js b/maximum-subarray/smosco.js new file mode 100644 index 0000000000..2937867c87 --- /dev/null +++ b/maximum-subarray/smosco.js @@ -0,0 +1,48 @@ +/** + * Maximum Subarray - 브루트포스 (완전탐색) + * + * 핵심 아이디어: + * - 모든 가능한 연속 부분 배열의 합을 계산하여 최댓값 찾기 + * - i번째부터 j번째까지의 합을 누적하면서 계산 + * + * 시간 복잡도: O(n²) - 이중 반복문으로 모든 구간 탐색 + * 공간 복잡도: O(1) - 변수 몇 개만 사용 + */ +const maxSubArray = (nums) => { + const n = nums.length; + let maxSoFar = nums[0]; + + for (let i = 0; i < n; i++) { + let currentSum = 0; + for (let j = i; j < n; j++) { + currentSum += nums[j]; // i부터 j까지의 누적 합 + maxSoFar = Math.max(maxSoFar, currentSum); + } + } + + return maxSoFar; +}; + +/** + * Maximum Subarray - Kadane's Algorithm + * + * 핵심 아이디어: + * - 각 위치에서 "현재 숫자만으로 새로 시작 vs 이전 합에 현재 숫자 추가" 중 더 큰 값 선택 + * - currentMax = max(현재 숫자, 지금까지 합 + 현재 숫자) + * - 즉, 이전까지의 합이 현재에 도움이 안 되면 버리고 새로 시작 + * + * 시간 복잡도: O(n) - 배열을 한 번만 순회 + * 공간 복잡도: O(1) - 변수 두 개만 사용 + */ +const maxSubArrayKadane = (nums) => { + let currentMax = nums[0]; // 현재 위치까지의 최대 합 + let globalMax = nums[0]; // 전체 최대 합 + + for (let i = 1; i < nums.length; i++) { + // 핵심: 이전 합에 더할지 vs 새로 시작할지 + currentMax = Math.max(nums[i], currentMax + nums[i]); + globalMax = Math.max(globalMax, currentMax); + } + + return globalMax; +}; diff --git a/number-of-1-bits/smosco.js b/number-of-1-bits/smosco.js new file mode 100644 index 0000000000..a71a1d0e1c --- /dev/null +++ b/number-of-1-bits/smosco.js @@ -0,0 +1,21 @@ +/** + * @param {number} n + * @return {number} + */ +const hammingWeight = (n) => { + const binaryString = n.toString(2); + let oneCount = 0; + + for (const bit of binaryString) { + if (bit === '1') { + oneCount += 1; + } + } + + return oneCount; +}; + +// 다른 풀이 +// const hammingWeight = (n) => { +// return n.toString(2).split('1').length - 1; +// }; diff --git a/valid-palindrome/smosco.js b/valid-palindrome/smosco.js new file mode 100644 index 0000000000..0160d276b5 --- /dev/null +++ b/valid-palindrome/smosco.js @@ -0,0 +1,40 @@ +/** + * 핵심 아이디어: + * 1. 정규표현식으로 알파벳과 숫자만 추출하여 소문자로 변환 + * 2. Two Pointer 방식으로 양 끝에서 중앙으로 이동하며 비교 + * + * @param {string} s - 검사할 문자열 + * @return {boolean} - 팰린드롬 여부 + * + * 시간 복잡도: O(n) + * - replace() 메서드: O(n) - 문자열 전체 순회 + * - toLowerCase(): O(n) - cleaned 문자열 순회 + * - while 루프: O(n/2) → O(n) - 최대 문자열 길이의 절반만큼 반복 + * - 전체: O(n) + O(n) + O(n) = O(n) + * + * 공간 복잡도: O(n) + * - cleaned 문자열: O(n) - 최악의 경우 입력 문자열의 모든 문자가 알파벳/숫자 + * - left, right 포인터 변수: O(1) + * - 전체: O(n) + */ +const isPalindrome = (s) => { + // 알파벳과 숫자만 남기고 소문자로 변환 + // \W는 알파벳, 숫자, 언더스코어를 제외한 모든 문자 + // _도 제거해야 하므로 [^a-zA-Z0-9] 사용 + const cleaned = s.replace(/[^a-zA-Z0-9]/g, '').toLowerCase(); + + // Two Pointer 접근법 + let left = 0; + let right = cleaned.length - 1; + + // 양 끝에서 중앙으로 이동하며 비교 + while (left < right) { + if (cleaned[left] !== cleaned[right]) { + return false; + } + left++; + right--; + } + + return true; +};