Skip to content
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
64 changes: 64 additions & 0 deletions combination-sum/ICE0208.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Backtracking
  • 설명: 재귀적으로 후보를 시도하고, 가능한 조합을 탐색하며 조건을 만족하면 해를 저장하고 되돌아오는 백트래킹 방식이 핵심 패턴이다. 같은 숫자를 재사용 가능하도록 시작 인덱스를 i로 두고 가지치기 조건으로 남은 합을 확인한다.

📊 시간/공간 복잡도 분석

복잡도
Time O(N^D)
Space O(D)

피드백: 백트래킹으로 중복 없이 각 후보를 시작 인덱스부터 탐색하고, 동일 원소 재사용이 가능하도록 인덱스를 계속 유지한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import java.util.ArrayList;
import java.util.List;

class Solution {
/*
* [백트래킹 풀이]
* N: candidates의 길이
* M: candidates에서 가장 작은 값
*
* 시간 복잡도: O(N^(target / M))
* - 재귀 한 단계에서 최대 N개의 후보를 선택할 수 있다.
* - 가장 작은 숫자를 반복해서 선택하면 재귀 깊이는 최대 target / M이다.
* - 실제로는 startIndex와 가지치기로 인해 이보다 적은 경우만 탐색한다.
*
* 공간 복잡도: O(target / M)
* - 재귀 호출 스택과 현재 조합 리스트가 최대 재귀 깊이만큼 사용된다.
* - 반환할 결과 리스트는 제외한다.
*/

private int[] candidates;
private List<List<Integer>> combinations;

public List<List<Integer>> combinationSum(int[] candidates, int target) {
this.candidates = candidates;
this.combinations = new ArrayList<>();

findCombinations(0, target, new ArrayList<>());

return combinations;
}

private void findCombinations(
int startIndex,
int remainingTarget,
List<Integer> currentCombination
) {
// 목표 합을 정확히 완성한 경우
if (remainingTarget == 0) {
combinations.add(new ArrayList<>(currentCombination));
return;
}

for (int i = startIndex; i < candidates.length; i++) {
int candidate = candidates[i];

// 현재 숫자를 선택하면 목표 합을 초과하는 경우
if (candidate > remainingTarget) {
continue;
}

currentCombination.add(candidate);

// 같은 숫자를 여러 번 사용할 수 있으므로 i부터 다시 탐색한다.
findCombinations(
i,
remainingTarget - candidate,
currentCombination
);

// 다음 조합을 탐색하기 위해 현재 선택을 되돌린다.
currentCombination.remove(currentCombination.size() - 1);
}
}
}
51 changes: 51 additions & 0 deletions decode-ways/ICE0208.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Dynamic Programming, Memoization
  • 설명: 두 가지 경우의 수를 재귀로 탐색하고 중간 결과를 memo 배열에 저장하여 중복 계산을 방지하는 전형적인 DP + Memoization 패턴으로 분할 가능 경우를 다룹니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n)
Space O(n)

피드백: dfs와 memo를 활용해 중복 계산을 제거하고, 0으로 시작하는 경우를 예외 처리한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
class Solution {
private String s;
private Integer[] memo;

/**
* 현재 위치에서 숫자 한 자리 또는 두 자리를 해석하며 가능한 경우의 수를 구한다.
* 각 index의 결과를 memo에 저장하여 동일한 위치를 중복 계산하지 않는다.
*
* 시간 복잡도: O(n) - 각 index를 한 번씩만 계산한다.
* 공간 복잡도: O(n) - memo 배열과 최대 n 깊이의 재귀 호출 스택
*/
public int numDecodings(String s) {
this.s = s;
this.memo = new Integer[s.length()];

return dfs(0);
}

private int dfs(int index) {
// 문자열 끝까지 유효하게 해석한 경우
if (index == s.length()) {
return 1;
}

// 0으로 시작하는 코드는 해석할 수 없다.
if (s.charAt(index) == '0') {
return 0;
}

if (memo[index] != null) {
return memo[index];
}

// 현재 숫자 한 자리만 해석하는 경우
int count = dfs(index + 1);

// 현재 숫자와 다음 숫자를 함께 해석하는 경우
if (index + 1 < s.length()) {
int twoDigitNumber =
(s.charAt(index) - '0') * 10
+ (s.charAt(index + 1) - '0');

if (twoDigitNumber <= 26) {
count += dfs(index + 2);
}
}

memo[index] = count;
return count;
}
}
54 changes: 54 additions & 0 deletions maximum-subarray/ICE0208.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Divide and Conquer
  • 설명: 배열을 절반으로 나눠 왼쪽, 오른쪽, 가운데를 고려하는 분할 정복 패턴으로 최대 부분 배열을 재귀적으로 계산합니다. 각 부분 문제의 해를 합쳐 최종 해를 구합니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n log n)
Space O(log n)

피드백: 분할 정복 구조에서 교차 구간을 계산하고 전체 최댓값을 합산한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
class Solution {
/**
* 배열을 절반으로 나누고 최대 부분 배열이
* 왼쪽, 오른쪽, 가운데를 걸치는 경우를 각각 구한다.
*
* 시간 복잡도: O(n log n)
* 공간 복잡도: O(log n)
*/
public int maxSubArray(int[] nums) {
return findMaxSum(nums, 0, nums.length - 1);
}

private int findMaxSum(int[] nums, int left, int right) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

분할 정복으로도 풀 수 있다는 걸 처음 알았네요!!

제가 이해한 이 풀이 방식은
배열을 가운데로 자르면 최대 부분배열은 완전히 왼쪽, 완전히 오른쪽, 가운데를 걸치는 경우 셋 중 하나이다. 앞의 둘은 재귀로 구하고, 걸치는 경우는 mid에서 좌우로 뻗어가며 최대 누적합을 더해 구한 뒤, 이 셋의 최댓값을 고른다. 로 이해하였습니다

이렇게 믄제를 "분해"하는 사고틀은 다른 문제에도 응용할 수 있을 것 같습니다. 👍👍👍

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

저도 이번에 처음으로 분할 정복 방식으로 풀어봐서 오류도 많이 나고, 이해하는 데 시간도 꽤 걸렸습니다ㅎㅎ 🤣
그래도 직접 구현해보면서 많이 배울 수 있었던 문제였던 것 같아요.
꼼꼼하게 리뷰해주셔서 감사합니다! 이번 주도 고생 많으셨습니다 🙌

if (left == right) {
return nums[left];
}

int mid = left + (right - left) / 2;

int leftMaxSum = findMaxSum(nums, left, mid);
int rightMaxSum = findMaxSum(nums, mid + 1, right);
int crossingMaxSum = findCrossingMaxSum(nums, left, mid, right);

return Math.max(
Math.max(leftMaxSum, rightMaxSum),
crossingMaxSum
);
}

private int findCrossingMaxSum(
int[] nums,
int left,
int mid,
int right
) {
int sum = 0;
int leftMaxSum = Integer.MIN_VALUE;

for (int i = mid; i >= left; i--) {
sum += nums[i];
leftMaxSum = Math.max(leftMaxSum, sum);
}

sum = 0;
int rightMaxSum = Integer.MIN_VALUE;

for (int i = mid + 1; i <= right; i++) {
sum += nums[i];
rightMaxSum = Math.max(rightMaxSum, sum);
}

return leftMaxSum + rightMaxSum;
}
}
62 changes: 62 additions & 0 deletions number-of-1-bits/ICE0208.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Bit Manipulation
  • 설명: 두 코드 모두 이진수의 특정 비트를 직접 다루는 비트 연산을 활용합니다. n과 1의 비트 연산을 통해 1의 개수를 세는 과정으로 비트 조작 패턴에 해당합니다.

📊 시간/공간 복잡도 분석

복잡도
Time O(k)
Space O(1)

피드백: n 와 n-1의 비트를 제거하는 기법을 이용해 비트 수만큼 반복한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
class Solution2 {
/*
* 시간 복잡도: O(k)
* - k는 n의 이진 표현에서 1인 비트의 개수이다.
* - n & (n - 1)을 할 때마다 가장 오른쪽 1 비트가 하나씩 제거되므로,
* 1의 개수만큼 반복한다.
* - 문제 조건상 n은 최대 31비트 정수이므로 실질적으로는 O(1)로 볼 수 있다.
*
* 공간 복잡도: O(1)
* - 추가 변수만 사용한다.
*/
public int hammingWeight(int n) {
int answer = 0;

while (n != 0) {
/*
* n & (n - 1)은 n의 가장 오른쪽 1 비트를 제거한다.
*
* 예를 들어 n = 1100일 때,
* n - 1 = 1011이 되고,
*
* 1100
* & 1011
* ------
* 1000
*
* 처럼 가장 오른쪽 1 비트가 제거된다.
*/
n = n & (n - 1);
answer++;
}

return answer;
}
}

class Solution1 {
/*
* 시간 복잡도: O(b)
* - b는 n의 이진 표현 길이이다.
* - 오른쪽 비트부터 하나씩 확인하므로 비트 수만큼 반복한다.
* - 문제 조건상 n은 최대 31비트 정수이므로 실질적으로는 O(1)로 볼 수 있다.
*
* 공간 복잡도: O(1)
* - 추가 변수만 사용한다.
*/
public int hammingWeight(int n) {
int answer = 0;

while (n != 0) {
// 가장 오른쪽 비트가 1이면 카운트한다.
if ((n & 1) == 1) {
answer++;
}

// 다음 비트를 확인하기 위해 오른쪽으로 한 칸 이동한다.
n >>>= 1;
}

return answer;
}
}
43 changes: 43 additions & 0 deletions valid-palindrome/ICE0208.java

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🏷️ 알고리즘 패턴 분석

  • 패턴: Two Pointers, Greedy
  • 설명: 좌우 포인터를 이용해 문자열의 양 끝에서부터 비교하며 불필요한 문자 건너뛰고 대소문자 통일 후 대칭 여부를 판단한다. 추가 보조 자료구조 없이 O(n) 시간, O(1) 공간으로 해결된다.

📊 시간/공간 복잡도 분석

복잡도
Time O(n)
Space O(1)

피드백: 공백이나 구두점은 건너뛰고 문자 비교를 수행한다.

개선 제안: 현재 구현이 적절해 보입니다.

💡 풀이에 시간/공간 복잡도를 주석으로 남겨보세요!

Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
class Solution {

public boolean isPalindrome(String s) {
/*
* 문제에서는 대소문자를 구분하지 않고,
* 알파벳과 숫자만 남긴 뒤 팰린드롬인지 확인한다.
*
* Character.isLetterOrDigit(ch)는
* 현재 문자가 알파벳 또는 숫자인지 확인하는 함수다.
* 공백, 쉼표, 콜론 같은 문자는 비교 대상이 아니므로 건너뛴다.
*
* 입력은 printable ASCII 문자로 제한되어 있으므로
* charAt()으로 한 문자씩 확인해도 된다.
*
* 시간 복잡도: O(n)
* 공간 복잡도: O(1)
*/
int left = 0;
int right = s.length() - 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

필터링된 문자열을 새로 만들지 않고 투 포인터로 in-place 비교해서 공간 O(1)로 풀 수 있었군요!
안쪽 while에 left < right 가드를 둬서 포인터가 만나는(교차) 케이스에서도 안전하게 처리된 것도 꼼꼼하네요. 복잡도 주석까지 정확하게 달려 있어서 읽기 편했습니다.

while (left < right) {
while (left < right && !Character.isLetterOrDigit(s.charAt(left))) {
left++;
}

while (left < right && !Character.isLetterOrDigit(s.charAt(right))) {
right--;
}

char leftChar = Character.toLowerCase(s.charAt(left));
char rightChar = Character.toLowerCase(s.charAt(right));

if (leftChar != rightChar) {
return false;
}

left++;
right--;
}

return true;
}
}
Loading