diff --git a/combination-sum/yoouyeon.ts b/combination-sum/yoouyeon.ts new file mode 100644 index 000000000..e17cbe566 --- /dev/null +++ b/combination-sum/yoouyeon.ts @@ -0,0 +1,25 @@ +// [39] Combination Sum + +function combinationSum(candidates: number[], target: number): number[][] { + const n = candidates.length; + const output: number[][] = []; + + function dfs(arr: number[], startIdx: number, sum: number) { + if (sum === target) { + output.push([...arr]); + return; + } + if (sum > target) return; + + // 현재 숫자부터 다시 시작 (중복 선택이 가능하기 때문) + for (let idx = startIdx; idx < n; idx++) { + arr.push(candidates[idx]); + dfs(arr, idx, sum + candidates[idx]); + arr.pop(); + } + } + + dfs([], 0, 0); + + return output; +} diff --git a/decode-ways/yoouyeon.ts b/decode-ways/yoouyeon.ts new file mode 100644 index 000000000..fc59bb79c --- /dev/null +++ b/decode-ways/yoouyeon.ts @@ -0,0 +1,34 @@ +// [91] Decode Ways + +/** + * [Time Complexity] + * O(n) + * + * [Space Complexity] + * O(n) + */ +function numDecodings(s: string): number { + const n = s.length; + const memo = new Array(n + 1).fill(0); + + // 첫번째 숫자가 0인 경우 디코딩이 불가능하므로 미리 0 반환 + if (s[0] === "0") return 0; + + memo[0] = 1; // 빈 문자열 + memo[1] = 1; // 0이 아닌 첫번째 숫자 디코딩 + + for (let idx = 2; idx <= n; idx++) { + const oneDigit = Number(s.slice(idx - 1, idx)); // 현재 숫자 + const twoDigits = Number(s.slice(idx - 2, idx)); // 현재 숫자 앞의 수와 합침 + + // 변환할 수 있는 경우의 수를 더해준다. + if (oneDigit >= 1 && oneDigit <= 9) { + memo[idx] += memo[idx - 1]; + } + if (twoDigits >= 10 && twoDigits <= 26) { + memo[idx] += memo[idx - 2]; + } + } + + return memo[n]; +} diff --git a/number-of-1-bits/yoouyeon.ts b/number-of-1-bits/yoouyeon.ts new file mode 100644 index 000000000..768c61a99 --- /dev/null +++ b/number-of-1-bits/yoouyeon.ts @@ -0,0 +1,19 @@ +// [191] Number of 1 Bits + +/** + * [Idea] + * 숫자를 2진수로 변환해서 (toString) 1인 비트를 직접 세줬다. + * + * [Time Complexity] + * toString 내부에서 진법 변환을 수행하는 과정에서 나눗셈을 반복적으로 수행하기 때문에 시간복잡도는 O(log n) + * + * [Space Complexity] + * 변환된 수의 자릿수만큼의 공간이 필요하므로 공간복잡도는 O(log n) + */ +function hammingWeight(n: number): number { + const binary = n.toString(2); + return [...binary].reduce( + (count, bit) => (bit === "1" ? count + 1 : count), + 0 + ); +} diff --git a/valid-palindrome/yoouyeon.ts b/valid-palindrome/yoouyeon.ts new file mode 100644 index 000000000..98b52d2c8 --- /dev/null +++ b/valid-palindrome/yoouyeon.ts @@ -0,0 +1,62 @@ +// [125] Valid Palindrome + +/** + * Solution 1. 문자열 직접 만들어서 비교하기 + * + * [Idea] + * 문제에 주어진 조건을 그대로 구현해서 풀었다. + * 소문자로 변환한 뒤에 Alphanumeric character만 남긴 cleaned 문자열과 + * cleaned 문자열을 뒤집은 reversed 문자열을 만들어서 + * 둘이 비교해서 같으면 팰린드롬, 아니면 팰린드롬이 아니라고 판단했다. + * + * [Time Complexity] + * 문자열을 조건에 맞게 가공하는 과정에서 s의 길이만큼 상수 번 순회하기 때문에 시간 복잡도는 O(n) + * + * [Space Complexity] + * reversed 문자열을 따로 만들어 저장하므로 공간 복잡도는 O(n) + * + */ +function isPalindrome1(s: string): boolean { + // replace: 소문자가 아니고 숫자가 아닌 것을 모두 ''으로 치환(제거)한다. + const cleaned = s.toLowerCase().replace(/[^a-z0-9]/g, ""); + // string에는 reverse 내장함수가 없으므로, string -> array -> reverse -> string (join) 과정으로 뒤집는다. + const reversed = cleaned.split("").reverse().join(""); + + return cleaned === reversed; +} + +/** + * Solution 2. 포인터를 활용하기 + * [Idea] + * 문자열을 가공하는 데 필요한 추가적인 순회 시간을 줄이고 reversed를 따로 저장하지 않아도 되는 방법 + * s의 왼쪽, 오른쪽을 순서대로 비교하면서 아래와 같은 과정을 거친다. + * 1. 알파벳(소문자로 변환하지 않았으므로 대소문자 모두) 또는 숫자가 아닌 경우를 제외한다 (포인터 이동) + * 2. 현재 포인터가 가리키는 두 알파벳이 소문자로 변환했을 때 같지 않으면 팰린드롬이 아니다. + * 3. 알파벳이 같다면 다음 문자를 비교하기 위해 포인터를 옮긴다. + * + * [Time Complexity] + * Solution 1과 동일하게 O(n)이지만 s를 1번만 순회한다 + * + * [Space Complexity] + * left, right 포인터를 저장할 상수 크기의 메모리가 필요하므로 공간 복잡도는 O(1)이다. + */ +function isPalindrome2(s: string): boolean { + let left = 0, + right = s.length - 1; + + while (left < right) { + while (left < right && s[left].match(/[^A-Za-z0-9]/)) { + left++; + } + while (left < right && s[right].match(/[^A-Za-z0-9]/)) { + right--; + } + if (s[left].toLowerCase() !== s[right].toLowerCase()) { + return false; + } + left++; + right--; + } + + return true; +}