Skip to content

Commit 5651493

Browse files
authored
[Zero-1016] WEEK 03 Solutions (#2708)
* feat: 기본 파일 생성 * feat: valid-palindrome * feat: number-of-1-bits * feat: combination-sum * feat: maximum-subarray * feat: numDecodings
1 parent b2d8c3d commit 5651493

5 files changed

Lines changed: 185 additions & 0 deletions

File tree

combination-sum/Zero-1016.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Combination Sum1 (같은 숫자 재사용 가능) — 비효율 버전
3+
*
4+
* 표기:
5+
* n = candidates 길이
6+
* T = target
7+
* M = candidates 중 최솟값
8+
* D = T / M (재귀 트리의 최대 깊이)
9+
* 시간 복잡도: O(nᴰ × D)
10+
* - start 인덱스가 없어 매번 0부터 탐색 → [2,2,3], [2,3,2], [3,2,2] 같은
11+
* 순열을 전부 방문 (조합 하나당 최대 D! 개)
12+
* - 정답 발견 시 경로 복사 O(D), 후처리(sort + join + Set) 비용은 탐색량에 묻힘
13+
*
14+
* 공간 복잡도: O(D! × 정답 개수 × D)
15+
* - 중복 조합이 제거 전까지 combinations에 전부 쌓임 → OOM 위험
16+
* - 재귀 스택: O(D)
17+
*/
18+
function combinationSum1(candidates: number[], target: number): number[][] {
19+
const combinations: number[][] = [];
20+
21+
const backtrack = (currentPath: number[], currentSum: number) => {
22+
const pathCopy = [...currentPath];
23+
const remainingTarget = target - currentSum;
24+
25+
if (remainingTarget === 0) {
26+
combinations.push(pathCopy);
27+
}
28+
29+
for (let i = 0; i < candidates.length; i++) {
30+
const candidate = candidates[i];
31+
32+
if (remainingTarget - candidate >= 0) {
33+
pathCopy.push(candidate);
34+
backtrack(pathCopy, currentSum + candidate);
35+
pathCopy.pop();
36+
}
37+
}
38+
};
39+
40+
backtrack([], 0);
41+
42+
// 중복 조합 제거 후 반환
43+
return [...new Set(combinations.map((path) => path.sort().join(",")))].map(
44+
(str) => str.split(",").map(Number),
45+
);
46+
}
47+
48+
/**
49+
* Combination Sum2 (같은 숫자 재사용 가능) — 최적화 버전
50+
*
51+
* 표기:
52+
* n = candidates 길이
53+
* T = target
54+
* M = candidates 중 최솟값
55+
* D = T / M (재귀 트리의 최대 깊이)
56+
*
57+
* 시간 복잡도: O(nᴰ × D)
58+
* - Big-O 상한은 1번과 같지만 실제 탐색량은 훨씬 작음:
59+
* 1) start 인덱스 → 순열 중복 탐색 원천 차단 (조합당 정확히 1회 방문)
60+
* 2) 정렬 + break → 무효한 가지를 통째로 절단
61+
* - 정렬 비용 O(n log n)은 지수 항에 묻힘
62+
*
63+
* 공간 복잡도: O(D + 정답 개수 × D)
64+
* - 재귀 스택 + 공유 path: O(D)
65+
* - 출력 배열: 정답 개수 × 평균 길이 (출력 자체라 최소 비용)
66+
*/
67+
function combinationSum2(candidates: number[], target: number): number[][] {
68+
const combinations: number[][] = [];
69+
candidates.sort((a, b) => a - b);
70+
71+
const backtrack = (start: number, path: number[], remaining: number) => {
72+
if (remaining === 0) {
73+
combinations.push([...path]);
74+
return;
75+
}
76+
77+
for (let i = start; i < candidates.length; i++) {
78+
if (candidates[i] > remaining) break;
79+
80+
path.push(candidates[i]);
81+
backtrack(i, path, remaining - candidates[i]);
82+
path.pop();
83+
}
84+
};
85+
86+
backtrack(0, [], target);
87+
return combinations;
88+
}

decode-ways/Zero-1016.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/**
2+
* 시간 복잡도 O(n)
3+
* 공간 복잡도 O(1)
4+
*/
5+
function numDecodings(s: string): number {
6+
const n = s.length;
7+
if (n === 0 || s[0] === "0") return 0;
8+
9+
// prev2 = dp[i-2], prev1 = dp[i-1]
10+
let prev2 = 1; // dp[0]
11+
let prev1 = 1; // dp[1] (s[0]이 '0'이 아님을 위에서 보장)
12+
13+
for (let i = 2; i <= n; i++) {
14+
let cur = 0;
15+
const one = s[i - 1]; // 마지막 한 자리
16+
const two = Number(s.slice(i - 2, i)); // 마지막 두 자리
17+
18+
if (one !== "0") cur += prev1; // 한 자리로 끊기
19+
if (two >= 10 && two <= 26) cur += prev2; // 두 자리로 끊기
20+
21+
prev2 = prev1;
22+
prev1 = cur;
23+
}
24+
return prev1;
25+
}

maximum-subarray/Zero-1016.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* 시간 복잡도 O(n)
3+
* 공간 복잡도 O(1)
4+
*/
5+
function maxSubArray(nums: number[]): number {
6+
let currentSum = 0;
7+
let maxSum = -Infinity;
8+
9+
for (let i = 0; i < nums.length; i++) {
10+
let n = nums[i];
11+
12+
currentSum = Math.max(currentSum + n, n);
13+
maxSum = Math.max(maxSum, currentSum);
14+
}
15+
16+
return maxSum;
17+
}

number-of-1-bits/Zero-1016.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* 시간 복잡도 O(N)
3+
* 공간 복잡도 O(N)
4+
*/
5+
function hammingWeight1(n: number): number {
6+
const bitNumber = n.toString(2);
7+
return bitNumber.split("").filter((char) => char === "1").length;
8+
}
9+
10+
/**
11+
* 시간 복잡도 O(N)
12+
* 공간 복잡도 O(1)
13+
*/
14+
function hammingWeight2(n: number): number {
15+
let count = 0;
16+
while (n > 0) {
17+
count += n & 1; // 마지막 비트가 1인지 확인
18+
n = n >>> 1; // 우측으로 1비트 시프트 (부호 없는 우측 시프트)
19+
}
20+
return count;
21+
}

valid-palindrome/Zero-1016.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/**
2+
* 시간 복잡도 O(N)
3+
* 공간 복잡도 O(1)
4+
*/
5+
const isNotAlphanumeric = /[^a-zA-Z0-9]/;
6+
7+
function isPalindrome(s: string): boolean {
8+
let leftCursor = 0;
9+
let rightCursor = s.length - 1;
10+
11+
while (leftCursor < rightCursor) {
12+
const currentLeftAlpha = s[leftCursor];
13+
const currentRightAlpha = s[rightCursor];
14+
15+
if (isNotAlphanumeric.test(currentLeftAlpha)) {
16+
leftCursor++;
17+
continue;
18+
}
19+
20+
if (isNotAlphanumeric.test(currentRightAlpha)) {
21+
rightCursor--;
22+
continue;
23+
}
24+
25+
if (currentLeftAlpha.toLowerCase() === currentRightAlpha.toLowerCase()) {
26+
leftCursor++;
27+
rightCursor--;
28+
} else {
29+
return false;
30+
}
31+
}
32+
33+
return true;
34+
}

0 commit comments

Comments
 (0)