Skip to content

[yhkee0404] WEEK 03 solutions #1792

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions combination-sum/yhkee0404.ts
Copy link
Contributor Author

@yhkee0404 yhkee0404 Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Top-Down DP 풀이인데 최악의 시간 복잡도와 공간 복잡도는 Bottom-UP 풀이와 같습니다: DaleSeo/AlgoDale#29
Top-Down DP 풀이는 부분 해를 완전히 전처리한 뒤 참조하므로 중복 원소 제거를 위해 부등식 검사가 추가로 필요했어요: https://github.com/yhkee0404/leetcode-study/blob/87da9b6db48f37cd4bda21c8b29d88a0d1b79419/combination-sum/yhkee0404.ts#L16
반면에 위와 같이 부분 해가 완성되기 전에 참조하는 경우는 처음 봐서 신기합니다! 덕분에 중복 원소가 제거되네요.
한편 재귀 호출 시 Default Argument인 dp를 전달하지 않으면 40배 정도 느려졌으니 참고해 주세요.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function combinationSum(candidates: number[], target: number, dp = new Map()): number[][] {
if (target <= 0) {
return [];
}
let ans = dp.get(target);
if (ans !== undefined) {
return ans;
}
ans = [];
for (const candidate of candidates) {
if (target == candidate) {
ans.push([candidate]);
continue;
}
for (const combination of combinationSum(candidates, target - candidate, dp)) {
if (combination[combination.length - 1] > candidate) {
continue;
}
ans.push([...combination, candidate]);
}
}
dp.set(target, ans);
return ans;
};
20 changes: 20 additions & 0 deletions decode-ways/yhkee0404.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
func numDecodings(s string) int {
dp := make([]int, len(s) + 1)
dp[0] = 1
const cnt = rune('Z') - rune('A') + 1
for i, c := range s {
a := 0
if i != 0 {
b := (rune(s[i - 1]) - rune('0')) * 10 + rune(c) - rune('0')
if b > 9 && b <= cnt {
a += dp[i - 1]
}
}
b := rune(c) - rune('0')
if b != 0 && b < cnt {
a += dp[i]
}
dp[i + 1] = a
}
return dp[len(s)]
}
35 changes: 35 additions & 0 deletions maximum-subarray/yhkee0404.rs
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kadane's Algorithm의 선형 풀이 대신 문제의 Follow Up에서 요구한 대로 분할 정복으로 풀어 봤습니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
impl Solution {
pub fn max_sub_array(nums: Vec<i32>) -> i32 {
return Self::solve(&nums, 0, nums.len()).unwrap_or(0);
}
fn solve(nums: &Vec<i32>, l: usize, r: usize) -> Option<i32> {
if l >= r {
return None
}
if l + 1 == r {
return Some(nums[l])
}
let mid = l + ((r - l) >> 1);
let a = Self::solve(nums, l, mid);
let b = Self::solve(nums, mid, r);
if a.is_none() || b.is_none() {
return a.or(b)
}
let mut ans = a.max(b);
let mut c = 0;
let mut d = 0;
for i in (l..mid).rev() {
c += nums[i];
d = d.max(c);
}
if d == 0 {
return ans
}
c = d;
for i in mid..r {
c += nums[i];
d = d.max(c);
}
ans.max(Some(d))
}
}
4 changes: 4 additions & 0 deletions number-of-1-bits/yhkee0404.py
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

공유해주신 풀이 방법이군요 :) 덕분에 배웠습니다!

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
class Solution:
@lru_cache
def hammingWeight(self, n: int) -> int:
return 1 + self.hammingWeight(n - (n & - n)) if n else 0
Comment on lines +2 to +4
Copy link
Contributor Author

@yhkee0404 yhkee0404 Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
@lru_cache
def hammingWeight(self, n: int) -> int:
return 1 + self.hammingWeight(n - (n & - n)) if n else 0
def __init__(self, bucket_mask_size = 6) -> int:
self.bucket_mask_size = bucket_mask_size
self.bucket_mask = (1 << bucket_mask_size) - 1
self.dp = [0] * (self.bucket_mask + 1)
for i in range(len(self.dp)):
j = i
while j:
self.dp[i] += 1
j -= j & - j
def hammingWeight(self, n: int) -> int:
ans = 0
while n:
ans += self.dp[n & self.bucket_mask]
n >>= self.bucket_mask_size
return ans

이 문제의 Follow Up 도 최적화를 요구하는데 댓글에서 또다른 방법을 찾아서 공유합니다: https://leetcode.com/problems/number-of-1-bits/description/comments/1882536/?parent=1568380
저는 잘 몰라서 대충 @lru_cache만 적어뒀었는데 더 효과적인 방법인 것 같아요.
0부터 63까지 2^6=64 개에 대한 정답을 전처리해 두면 그만큼 희생한 공간에 대한 Trade-Off로 시간이 6배 줄어듭니다. 2^m개에 대해서는 m배입니다.
0b000000부터 0b111111까지의 정답을 상수 시간에 구할 수 있게 되므로 n >> 1 연산을 logn 번 수행하며 1의 개수를 세는 대신에 n >> 6 연산을 ceil(logn / 6) 번만 수행하면서 각 Bucket의 부분 해를 더해도 정답을 알 수 있습니다.
logn <= 32 비트 수에 대한 Square Root Decomposition은 sqrt(logn) < 6 이므로 2^6=64 개만 전처리해도 O(logn)에서 O(sqrt(logn))으로 거의 제곱만큼 시간을 줄이는 효과가 생깁니다.
나아가 저장 공간을 2배씩 더 소모하는 만큼 연산 시간의 분모에는 1씩 더하는 효과가 있네요.
O(hammingWeight(n))은 최대 32번 수행할 수도 있는 반면에 O(sqrt(logn))만 해도 최대 6번밖에 수행하지 않으므로 전처리를 전제한다면 최악의 시간 복잡도에서 더 유리한 Bucket 알고리즘이 되네요.

19 changes: 19 additions & 0 deletions valid-palindrome/yhkee0404.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
function isPalindrome(s: string): boolean {
const isAlpha = x => x.toLowerCase() >= 'a' && x.toLowerCase() <= 'z';
const isNumeric = x => x >= '0' && x <= '9';
const isAlphanumeric = x => isAlpha(x) || isNumeric(x);
let i = 0, j = s.length - 1;
while (i < j) {
while (i !== j && ! isAlphanumeric(s[i])) {
i++;
}
while (i !== j && ! isAlphanumeric(s[j])) {
j--;
}
if (s[i].toLowerCase() !== s[j].toLowerCase()) {
return false;
}
i++, j--;
}
return true;
};