-
-
Notifications
You must be signed in to change notification settings - Fork 361
[ICE0208] WEEK 03 solutions #2712
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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); | ||
| } | ||
| } | ||
| } |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 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; | ||
| } | ||
| } |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,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) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 분할 정복으로도 풀 수 있다는 걸 처음 알았네요!! 제가 이해한 이 풀이 방식은 이렇게 믄제를 "분해"하는 사고틀은 다른 문제에도 응용할 수 있을 것 같습니다. 👍👍👍
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
| } | ||
| } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 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; | ||
| } | ||
| } |
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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,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; | ||
|
|
||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필터링된 문자열을 새로 만들지 않고 투 포인터로 in-place 비교해서 공간 O(1)로 풀 수 있었군요! |
||
| 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; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🏷️ 알고리즘 패턴 분석
📊 시간/공간 복잡도 분석
피드백: 백트래킹으로 중복 없이 각 후보를 시작 인덱스부터 탐색하고, 동일 원소 재사용이 가능하도록 인덱스를 계속 유지한다.
개선 제안: 현재 구현이 적절해 보입니다.