-
-
Notifications
You must be signed in to change notification settings - Fork 305
[hongseoupyun] WEEK 03 Solutions #2101
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
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
92b6029
fix: correct loop range in climbStairs method to prevent unnecessary …
hongseoupyun d858e59
feat: implement isPalindrome function with detailed comments on appro…
hongseoupyun 1acbb5e
feat: implement isPalindrome function with detailed comments on appro…
hongseoupyun 6944cfd
feat: add two-pointer approach for isPalindrome function with detaile…
hongseoupyun dec9da9
feat: implement combinationSum function using DFS/backtracking with d…
hongseoupyun e3d9010
fix: add a newline at the end of the file for better formatting
hongseoupyun 2dff60a
feat: add detailed backtracking example in comments for combinationSu…
hongseoupyun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,95 @@ | ||
| # // Input: candidates = [2,3,5], target = 8 | ||
| # // Output: [[2,2,2,2],[2,3,3],[3,5]] | ||
|
|
||
| # // DFS/backtracking should be used - Find possilbe combinations till the end and backtrack to previous step to find other combinations | ||
| # // For example, if we have candidates [2,3,5] and target 8 | ||
| # // Start with empty combination [], target 8 | ||
| # // Add 2 -> [2], remaining 6 | ||
| # // Add 2 -> [2,2], remaining 4 | ||
| # // Add 2 -> [2,2,2], remaining 2 | ||
| # // Add 2 -> [2,2,2,2], remaining 0 (found a valid combination) | ||
| # // Backtrack to [2,2,2,3], remaining -1 (remaining is negative, backtrack) | ||
| # // Backtrack to [2,2,2,5], remaining -3 (remaining is negative, backtrack) | ||
| # // Backtrack to [2,2,3], remaining 1 (dead end, backtrack) | ||
| # // Backtrack to [2,2,5], remaining -3 (remaining is negative, backtrack) | ||
| # // Backtrack to [2,3], remaining 3 | ||
| # // Add 3 -> [2,3,3], remaining 0 (found a valid combination) | ||
| # // so on .. | ||
|
|
||
| # // When you dfs, always start from current index and allow reusing the same element or next index only to avoid duplicates / to get unique combination | ||
| # // Hence, if we starts from 2, next dfs calls can start from index of 2 or index of 3, but not index of 5 directly | ||
| # // Moreover, if we start from 3, next dfs calls can start from index of 3 or index of 5, but not index of 2 directly | ||
|
|
||
| # backtrack(8, [], 0) | ||
| # │ | ||
| # ├─ i = 0 pick 2 → backtrack(6, [2], 0) | ||
| # │ │ | ||
| # │ ├─ i = 0 pick 2 → backtrack(4, [2,2], 0) | ||
| # │ │ │ | ||
| # │ │ ├─ i = 0 pick 2 → backtrack(2, [2,2,2], 0) | ||
| # │ │ │ │ | ||
| # │ │ │ ├─ i = 0 pick 2 → backtrack(0, [2,2,2,2], 0) 🟢 success → return | ||
| # │ │ │ │ | ||
| # │ │ │ ├─ i = 1 pick 3 → backtrack(-1, [2,2,2,3], 1) 🔴 → return | ||
| # │ │ │ │ | ||
| # │ │ │ └─ i = 2 pick 5 → backtrack(-3, [2,2,2,5], 2) 🔴 → return | ||
| # │ │ │ | ||
| # │ │ └─ loop end → pop → combination = [2,2] | ||
| # │ │ | ||
| # │ ├─ i = 1 pick 3 → backtrack(1, [2,2,3], 1) | ||
| # │ │ │ | ||
| # │ │ ├─ i = 1 pick 3 → backtrack(0, [2,2,3,3], 1) 🟢 success → return | ||
| # │ │ │ | ||
| # │ │ └─ i = 2 pick 5 → backtrack(-4, [2,2,3,5], 2) 🔴 → return | ||
| # │ │ | ||
| # │ ├─ loop end → pop → [2,2] | ||
| # │ │ | ||
| # │ └─ i = 2 pick 5 → backtrack(-1, [2,2,5], 2) 🔴 → return | ||
| # │ | ||
| # ├─ loop end → pop → [2] | ||
| # │ | ||
| # ├─ i = 1 pick 3 → backtrack(3, [2,3], 1) | ||
| # │ │ | ||
| # │ ├─ i = 1 pick 3 → backtrack(0, [2,3,3], 1) 🟢 success → return | ||
| # │ │ | ||
| # │ └─ i = 2 pick 5 → backtrack(-2, [2,3,5], 2) 🔴 → return | ||
| # │ | ||
| # ├─ loop end → pop → [2] | ||
| # │ | ||
| # ├─ i = 2 pick 5 → backtrack(1, [2,5], 2) | ||
| # │ │ | ||
| # │ └─ i = 2 pick 5 → backtrack(-4, [2,5,5], 2) 🔴 → return | ||
| # │ | ||
| # └─ loop end → pop → [] | ||
|
|
||
|
|
||
|
|
||
|
|
||
| from typing import List | ||
|
|
||
| class Solution: | ||
| def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]: | ||
| result: List[List[int]] = [] | ||
|
|
||
| def backtrack(remaining: int, combination: List[int], start_index: int): | ||
| # base cases | ||
| if remaining == 0: | ||
| # append a COPY of combination | ||
| result.append(combination[:]) | ||
| return | ||
| if remaining < 0: | ||
| return | ||
|
|
||
| # try candidates from start_index onward | ||
| for i in range(start_index, len(candidates)): | ||
| current_number = candidates[i] | ||
| # choose | ||
| combination.append(current_number) | ||
| # explore (i, not i+1, because we can reuse same element) | ||
| backtrack(remaining - current_number, combination, i) | ||
| # undo (backtrack) | ||
| combination.pop() | ||
|
|
||
| # initial call | ||
| backtrack(target, [], 0) | ||
| return result |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,100 @@ | ||
| // Input: candidates = [2,3,5], target = 8 | ||
| // Output: [[2,2,2,2],[2,3,3],[3,5]] | ||
|
|
||
| // DFS/backtracking should be used - Find possilbe combinations till the end and backtrack to previous step to find other combinations | ||
| // For example, if we have candidates [2,3,5] and target 8 | ||
| // Start with empty combination [], target 8 | ||
| // Add 2 -> [2], remaining 6 | ||
| // Add 2 -> [2,2], remaining 4 | ||
| // Add 2 -> [2,2,2], remaining 2 | ||
| // Add 2 -> [2,2,2,2], remaining 0 (found a valid combination) | ||
| // Backtrack to [2,2,2,3], remaining -1 (remaining is negative, backtrack) | ||
| // Backtrack to [2,2,2,5], remaining -3 (remaining is negative, backtrack) | ||
| // Backtrack to [2,2,3], remaining 1 (dead end, backtrack) | ||
| // Backtrack to [2,2,5], remaining -3 (remaining is negative, backtrack) | ||
| // Backtrack to [2,3], remaining 3 | ||
| // Add 3 -> [2,3,3], remaining 0 (found a valid combination) | ||
| // so on .. | ||
|
|
||
| // When you dfs, always start from current index and allow reusing the same element or next index only to avoid duplicates / to get unique combination | ||
| // Hence, if we starts from 2, next dfs calls can start from index of 2 or index of 3, but not index of 5 directly | ||
| // Moreover, if we start from 3, next dfs calls can start from index of 3 or index of 5, but not index of 2 directly | ||
|
|
||
| /* | ||
| backtrack(8, [], 0) | ||
| │ | ||
| ├─ i = 0 pick 2 → backtrack(6, [2], 0) | ||
| │ │ | ||
| │ ├─ i = 0 pick 2 → backtrack(4, [2,2], 0) | ||
| │ │ │ | ||
| │ │ ├─ i = 0 pick 2 → backtrack(2, [2,2,2], 0) | ||
| │ │ │ │ | ||
| │ │ │ ├─ i = 0 pick 2 → backtrack(0, [2,2,2,2], 0) 🟢 success → return | ||
| │ │ │ │ | ||
| │ │ │ ├─ i = 1 pick 3 → backtrack(-1, [2,2,2,3], 1) 🔴 → return | ||
| │ │ │ │ | ||
| │ │ │ └─ i = 2 pick 5 → backtrack(-3, [2,2,2,5], 2) 🔴 → return | ||
| │ │ │ | ||
| │ │ └─ loop end → pop → combination = [2,2] | ||
| │ │ | ||
| │ ├─ i = 1 pick 3 → backtrack(1, [2,2,3], 1) | ||
| │ │ │ | ||
| │ │ ├─ i = 1 pick 3 → backtrack(0, [2,2,3,3], 1) 🟢 success → return | ||
| │ │ │ | ||
| │ │ └─ i = 2 pick 5 → backtrack(-4, [2,2,3,5], 2) 🔴 → return | ||
| │ │ | ||
| │ ├─ loop end → pop → [2,2] | ||
| │ │ | ||
| │ └─ i = 2 pick 5 → backtrack(-1, [2,2,5], 2) 🔴 → return | ||
| │ | ||
| ├─ loop end → pop → [2] | ||
| │ | ||
| ├─ i = 1 pick 3 → backtrack(3, [2,3], 1) | ||
| │ │ | ||
| │ ├─ i = 1 pick 3 → backtrack(0, [2,3,3], 1) 🟢 success → return | ||
| │ │ | ||
| │ └─ i = 2 pick 5 → backtrack(-2, [2,3,5], 2) 🔴 → return | ||
| │ | ||
| ├─ loop end → pop → [2] | ||
| │ | ||
| ├─ i = 2 pick 5 → backtrack(1, [2,5], 2) | ||
| │ │ | ||
| │ └─ i = 2 pick 5 → backtrack(-4, [2,5,5], 2) 🔴 → return | ||
| │ | ||
| └─ loop end → pop → [] | ||
| */ | ||
|
|
||
|
|
||
| function combinationSum(candidates: number[], target: number): number[][] { | ||
| const result: number[][] = [] | ||
|
|
||
| function backtrack(remaining: number, combination: number[], startindex: number) { | ||
| // Remaining is target - sum of combination | ||
| // Base Case1: If remaining is 0, we found a valid combination -> add that combinations to result | ||
| if (remaining === 0) { | ||
| result.push([...combination]); | ||
| return; | ||
| } | ||
|
|
||
| // Base Case2: If remaining is negative, no valid combination -> backtrack | ||
| if (remaining < 0) { | ||
| return; | ||
| } | ||
|
|
||
| // Explore further by trying each candidate starting from startindex | ||
| // candidates = [2,3,5], target = 8 | ||
| for (let i = startindex; i < candidates.length; i++) { | ||
| // Choose the current number | ||
| const currentNumber = candidates[i]; | ||
| // Add the current number to the combination | ||
| combination.push(currentNumber); | ||
|
|
||
| // Explore further with updated remaining and current combination | ||
| backtrack(remaining - currentNumber, combination, i); | ||
| // Backtrack: remove the last added candidate to try next candidate | ||
| combination.pop(); | ||
| } | ||
| } | ||
| backtrack(target, [], 0); | ||
| return result; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
|
|
||
| import re | ||
|
|
||
| class Solution: | ||
| # Palindrome is a word, phrase, number, or other sequences of characters that reads the same forward and backward (ignoring non-alphanumeric characters - letters and number). | ||
| # the solution should first filter out non-alphanumeric characters and convert the string to lowercase. | ||
| # Then, it should check if the cleaned string is equal to its reverse. | ||
|
|
||
| # Time complexity = O(n), as it checks, replaces, converts to lowercase, and reverses all characters in the string | ||
| # Space complexity = O(n), as it creates a new string with a maximum length equal to the original string | ||
| def isPalindrome(self, s: str) -> bool: | ||
| filtered_string = re.sub(r'[^a-zA-Z0-9]','',s).lower() | ||
| reversed_string = filtered_string[::-1] | ||
| return reversed_string == filtered_string | ||
|
|
||
|
|
||
| # Using Two-pointer; It searches from given string directly so the Space complexity is O(n) - better method | ||
| # By using two index, searchs and compares from start and end of the string like this -><- | ||
| # Do this while start index is less than end index | ||
| # Skip non-alphanumeric characters | ||
| # If both pointer are in characters, then convert to lowercase and compare | ||
| # If not equal, return false | ||
| # If all characters are equal, return true | ||
|
|
||
| # Time complexity = O(n), as it checks and converts to lowercase all characters in the string | ||
| # Space complexity = O(1), as it uses only a constant amount of extra space | ||
| def isPalindromeTwoPointer(self, s: str) -> bool: | ||
| left = 0 | ||
| right = len(s) - 1 | ||
|
|
||
| while left < right: | ||
| if not s[left].isalnum(): | ||
| left+=1 | ||
| continue | ||
| if not s[right].isalnum(): | ||
| right-=1 | ||
| continue | ||
|
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. 이렇게 if-continue 문 대신 내부에서 다시 while 문으로 |
||
| if s[left].lower() != s[right].lower(): | ||
| return False | ||
|
|
||
| left+=1 | ||
| right-=1 | ||
|
|
||
| return True | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| // # Palindrome is a word, phrase, number, or other sequences of characters that reads the same forward and backward (ignoring non-alphanumeric characters - letters and number). | ||
| // # the solution should first filter out non-alphanumeric characters and convert the string to lowercase. | ||
| // # Then, it should check if the cleaned string is equal to its reverse. | ||
|
|
||
|
|
||
| // Time complexity = O(n), as it checks, replaces, converts to lowercase, and reverses all characters in the string | ||
| // Space complexity = O(n), as it creates a new string with a maximum length equal to the original string | ||
| function isPalindrome(s: string): boolean { | ||
| let filteredString: string = s.replace(/[^a-zA-Z0-9]/g, '').toLowerCase(); | ||
| let reversed: string = filteredString.split("").reverse().join("") | ||
|
|
||
| if (filteredString === reversed) { | ||
| return true | ||
| } else { | ||
| return false | ||
| } | ||
| }; | ||
|
|
||
| //Palindrom example: "A man, a plan, a canal: Panama" | ||
|
|
||
| // Using Two-pointer; It searches from given string directly so the Space complexity is O(n) - better method | ||
| // By using two index, searchs and compares from start and end of the string like this-><- | ||
| // Do this while start index is less than end index | ||
| // Skip non-alphanumeric characters | ||
| // If both pointer are in characters, then convert to lowercase and compare | ||
| // If not equal, return false | ||
| // If all characters are equal, return true | ||
|
|
||
| // Time complexity = O(n), as it checks and converts to lowercase all characters in the string | ||
| // Space complexity = O(1), as it uses only a constant amount of extra space | ||
| function isPalindrome2(s: string): boolean { | ||
|
|
||
| let left: number = 0; | ||
| let right: number = s.length - 1; | ||
|
|
||
| let isAlphaNum = (str: string): boolean => { | ||
| return /^[a-zA-Z0-9]$/.test(str) | ||
| } | ||
|
|
||
| while (left < right) { | ||
| if (!isAlphaNum(s[left])) { | ||
| left++ | ||
| continue | ||
| } | ||
| if (!isAlphaNum(s[right])) { | ||
| right-- | ||
| continue | ||
| } | ||
| if (s[left].toLowerCase() !== s[right].toLowerCase()) { | ||
| return false | ||
| } | ||
| left++ | ||
| right-- | ||
| } | ||
| return true | ||
| }; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
이렇게 two pointer를 사용한다면 공간 복잡도를 O(1)으로 최적화 할 수 있네요! 덕분에 좋은 풀이법 알아갑니다 ><