diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index 3a5371562..b74a5f646 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -85,7 +85,7 @@ jobs: - name: Check filename rules if: ${{ steps.pr-labels.outputs.has_maintenance != 'true' }} run: | - files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.sha }} | tr -d '"') + files=$(git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | tr -d '"') pr_author="${{ github.event.pull_request.user.login }}" success=true diff --git a/contains-duplicate/5YoonCheol.java b/contains-duplicate/5YoonCheol.java new file mode 100644 index 000000000..c3c1cbd85 --- /dev/null +++ b/contains-duplicate/5YoonCheol.java @@ -0,0 +1,16 @@ +class Solution { + /** + * 시간 복잡도: O(N) + * 공간 복잡도: O(N) + */ + public boolean containsDuplicate(int[] nums) { + Set set = new HashSet<>(); + + for (int num : nums) { + if (set.contains(num)) return true; + set.add(num); + } + + return false; + } +} diff --git a/contains-duplicate/JisooPyo.kt b/contains-duplicate/JisooPyo.kt new file mode 100644 index 000000000..a15e170e8 --- /dev/null +++ b/contains-duplicate/JisooPyo.kt @@ -0,0 +1,38 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +/** + * Leetcode + * 217. Contains Duplicate + * Easy + */ +class ContainsDuplicate { + /** + * Runtime: 17 ms(Beats: 80.99 %) + * Time Complexity: O(n) + * - 배열 순회 + * + * Memory: 50.63 MB(Beats: 70.32 %) + * Space Complexity: O(n) + * - HashSet에 최악의 경우 배열 원소 모두 저장 + */ + fun containsDuplicate(nums: IntArray): Boolean { + val set = hashSetOf() + for (i in nums) { + if (set.contains(i)) { + return true + } + set.add(i) + } + return false + } + + @Test + fun test() { + containsDuplicate(intArrayOf(1, 2, 3, 1)) shouldBe true + containsDuplicate(intArrayOf(1, 2, 3, 4)) shouldBe false + containsDuplicate(intArrayOf(1, 1, 1, 3, 3, 4, 3, 2, 4, 2)) shouldBe true + } +} diff --git a/contains-duplicate/csucom.cpp b/contains-duplicate/csucom.cpp new file mode 100644 index 000000000..18057c2f1 --- /dev/null +++ b/contains-duplicate/csucom.cpp @@ -0,0 +1,32 @@ +#include +#include + +bool containsDuplicate(int* nums, int numsSize) { + char* pflag = (char*)malloc(1000000001); + char* mflag = (char*)malloc(1000000001); + memset(pflag, 0, 1000000001); + memset(mflag, 0, 1000000001); + for (int i = 0; i < numsSize; ++i) { + if (nums[i] < 0) { + if (mflag[-nums[i]] == 1) { + free(pflag); + free(mflag); + return true; + } + mflag[-nums[i]] = 1; + } + else { + if (pflag[nums[i]] == 1) { + free(pflag); + free(mflag); + return true; + } + pflag[nums[i]] = 1; + } + } + free(pflag); + free(mflag); + return false; +} + + diff --git a/contains-duplicate/dusunax.py b/contains-duplicate/dusunax.py new file mode 100644 index 000000000..fb9255c70 --- /dev/null +++ b/contains-duplicate/dusunax.py @@ -0,0 +1,23 @@ +''' +# Leetcode 217. Contains Duplicate + +use set to store distinct elements 🗂️ + +## Time and Space Complexity + +``` +TC: O(n) +SC: O(n) +``` + +### TC is O(n): +- iterating through the list just once to convert it to a set. + +### SC is O(n): +- creating a set to store the distinct elements of the list. +''' + +class Solution: + def containsDuplicate(self, nums: List[int]) -> bool: + return len(nums) != len(set(nums)) + diff --git a/house-robber/5YoonCheol.java b/house-robber/5YoonCheol.java new file mode 100644 index 000000000..358308310 --- /dev/null +++ b/house-robber/5YoonCheol.java @@ -0,0 +1,19 @@ +class Solution { + public int rob(int[] nums) { + //배열 길이 0이면 털 수 있는 집이 없음. + if (nums.length == 0) return 0; + //배열 길이가 1이면 한 집만 털 수 있음. + if (nums.length == 1) return nums[0]; + + //동적 계획법으로 풀이 + int[] dp = new int[nums.length]; + dp[0] = nums[0]; + dp[1] = Math.max(nums[0], nums[1]); + + //배열 크기가 2이상일 경우 최대 금액의 범위 확장 + for (int i = 2; i < nums.length; i++) { + dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]); + } + return dp[nums.length - 1]; + } +} diff --git a/house-robber/JisooPyo.kt b/house-robber/JisooPyo.kt new file mode 100644 index 000000000..6c2b049d6 --- /dev/null +++ b/house-robber/JisooPyo.kt @@ -0,0 +1,74 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import kotlin.math.max + +/** + * Leetcode + * 198. House Robber + * Medium + * + * 사용된 알고리즘: Dynamic Programming + * + * i번째 집에서 얻을 수 있는 최대 금액은 다음 두 가지 중 큰 값입니다. + * - (i-2)번째 집까지의 최대 금액 + 현재 집의 금액 + * - (i-1)번째 집까지의 최대 금액 + */ +class HouseRobber { + /** + * Runtime: 0 ms(Beats: 100.00 %) + * Time Complexity: O(n) + * + * Memory: 34.65 MB(Beats: 40.50 %) + * Space Complexity: O(n) + */ + fun rob(nums: IntArray): Int { + if (nums.size == 1) { + return nums[0] + } + if (nums.size == 2) { + return max(nums[0], nums[1]) + } + val dp = IntArray(nums.size) + dp[0] = nums[0] + dp[1] = max(nums[0], nums[1]) + for (i in 2 until nums.size) { + dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) + } + return dp[nums.size - 1] + } + + /** + * 공간 복잡도를 개선 + * Runtime: 0 ms(Beats: 100.00 %) + * Time Complexity: O(n) + * + * Memory: 34.95 MB(Beats: 36.98 %) + * Space Complexity: O(1) + */ + fun rob2(nums: IntArray): Int { + if (nums.size == 1) return nums[0] + if (nums.size == 2) return max(nums[0], nums[1]) + + var twoBack = nums[0] + var oneBack = max(nums[0], nums[1]) + var current = oneBack + + for (i in 2 until nums.size) { + current = max(twoBack + nums[i], oneBack) + twoBack = oneBack + oneBack = current + } + + return current + } + + @Test + fun test() { + rob(intArrayOf(1, 2, 3, 1)) shouldBe 4 + rob(intArrayOf(2, 7, 9, 3, 1)) shouldBe 12 + rob2(intArrayOf(1, 2, 3, 1)) shouldBe 4 + rob2(intArrayOf(2, 7, 9, 3, 1)) shouldBe 12 + } +} diff --git a/house-robber/dusunax.py b/house-robber/dusunax.py new file mode 100644 index 000000000..38c238942 --- /dev/null +++ b/house-robber/dusunax.py @@ -0,0 +1,44 @@ +''' +# Leetcode 198. House Robber + +use **dynamic programming** to solve this problem. (bottom-up approach) 🧩 + +choose bottom-up approach for less space complexity. + +## DP relation + +``` +dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) +``` + +- **dp[i - 1]:** skip and take the value from the previous house +- **dp[i - 2]:** rob the current house, add its value to the maximum money from two houses before + +## Time and Space Complexity + +``` +TC: O(n) +SC: O(n) +``` + +### TC is O(n): +- iterating through the list just once to calculate the maximum money. = O(n) + +### SC is O(n): +- using a list to store the maximum money at each house. = O(n) + +''' + +class Solution: + def rob(self, nums: List[int]) -> int: + if len(nums) == 1: + return nums[0] + + dp = [0] * len(nums) + dp[0] = nums[0] + dp[1] = max(nums[0], nums[1]) + + for i in range(2, len(nums)): + dp[i] = max(dp[i - 1], dp[i - 2] + nums[i]) + + return dp[-1] diff --git a/longest-consecutive-sequence/5YoonCheol.java b/longest-consecutive-sequence/5YoonCheol.java new file mode 100644 index 000000000..6932478b1 --- /dev/null +++ b/longest-consecutive-sequence/5YoonCheol.java @@ -0,0 +1,34 @@ +import java.util.*; + +class Solution { + public int longestConsecutive(int[] nums) { + if (nums == null || nums.length == 0) return 0; + + //모든 요소 HashSet 삽입 + HashSet set = new HashSet<>(); + for (int num : nums) { + set.add(num); + } + + int longest = 0; + + // 시작지점 체크 + for (int num : nums) { + //배열 요소보다 1 작은 수 가 없는 경우 새로운 시작 지점이 됨 + if (!set.contains(num - 1)) { + int start = num; + int currentLength = 1; + + // 1씩 증가시키면서 연속된 수의 개수 탐색 + while (set.contains(start + 1)) { + start++; + currentLength++; + } + // 기존 longest와 현재 연속된 수를 비교 + longest = Math.max(longest, currentLength); + } + } + + return longest; + } +} diff --git a/longest-consecutive-sequence/JisooPyo.kt b/longest-consecutive-sequence/JisooPyo.kt new file mode 100644 index 000000000..7bf137517 --- /dev/null +++ b/longest-consecutive-sequence/JisooPyo.kt @@ -0,0 +1,81 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import kotlin.math.max + +/** + * Leetcode + * 128. Longest Consecutive Sequence + * Medium + */ +class LongestConsecutiveSequence { + /** + * Runtime: 58 ms(Beats: 79.06 %) + * Time Complexity: O(n) + * - while 루프의 총 반복 횟수는 n을 넘을 수 없다. + * + * Memory: 62.65 MB(Beats: 10.48 %) + * Space Complexity: O(n) + */ + fun longestConsecutive(nums: IntArray): Int { + val numsSet: MutableSet = nums.toHashSet() + val startSet: MutableSet = hashSetOf() + + // 수열의 시작점이 될 수 있는 수를 찾는다. + for (num in numsSet) { + if (!numsSet.contains(num - 1)) { + startSet.add(num) + } + } + var answer = 0 + for (start in startSet) { + // 수열의 시작점부터 몇 개 까지 numsSet에 있는지 확인한다. + var count = 0 + var first = start + while (numsSet.contains(first)) { + first++ + count++ + } + // 최대 수열의 개수를 업데이트한다. + answer = max(answer, count) + } + return answer + } + + /** + * 위 풀이에서 startSet을 제거하여 공간적으로 효율적인 풀이 + * Runtime: 63 ms(Beats: 65.70 %) + * Time Complexity: O(n) + * + * Memory: 58.47 MB(Beats: 70.81 %) + * Space Complexity: O(n) + */ + fun longestConsecutive2(nums: IntArray): Int { + val numsSet = nums.toHashSet() + var maxLength = 0 + + for (num in numsSet) { + if (!numsSet.contains(num - 1)) { + var currentNum = num + var currentLength = 0 + + while (numsSet.contains(currentNum)) { + currentLength++ + currentNum++ + } + maxLength = max(maxLength, currentLength) + } + } + return maxLength + } + + @Test + fun test() { + longestConsecutive(intArrayOf(100, 4, 200, 1, 3, 2)) shouldBe 4 + longestConsecutive(intArrayOf(0, 3, 7, 2, 5, 8, 4, 6, 0, 1)) shouldBe 9 + + longestConsecutive2(intArrayOf(100, 4, 200, 1, 3, 2)) shouldBe 4 + longestConsecutive2(intArrayOf(0, 3, 7, 2, 5, 8, 4, 6, 0, 1)) shouldBe 9 + } +} diff --git a/longest-consecutive-sequence/dusunax.py b/longest-consecutive-sequence/dusunax.py new file mode 100644 index 000000000..b912c4807 --- /dev/null +++ b/longest-consecutive-sequence/dusunax.py @@ -0,0 +1,38 @@ +''' +# Leetcode 128. Longest Consecutive Sequence + +keep time complexity O(n) by iterating through the set and accessing elements in O(1) time. ⚖️ + +## Time and Space Complexity +``` +TC: O(n) +SC: O(n) +``` + +### TC is O(n): +- iterating through the set. O(n) +- accessing elements in the set. O(1) +- while loop incrementing `current_num` while `current_num + 1 in nums_set`. O(1) + +### SC is O(n): +- creating a set from the list of numbers. O(n) +''' + +class Solution: + def longestConsecutive(self, nums: List[int]) -> int: + nums_set = set(nums) # O(n) + longest_sequence = 0 + + for num in nums_set: # O(n) + if (num - 1) not in nums_set: + current_num = num + current_sequence = 1 + + while current_num + 1 in nums_set: # O(1) + current_num += 1 + current_sequence += 1 + + longest_sequence = max(current_sequence, longest_sequence) + + return longest_sequence + diff --git a/top-k-frequent-elements/5YoonCheol.java b/top-k-frequent-elements/5YoonCheol.java new file mode 100644 index 000000000..55e4f1598 --- /dev/null +++ b/top-k-frequent-elements/5YoonCheol.java @@ -0,0 +1,21 @@ +import java.util.*; + +class Solution { + public int[] topKFrequent(int[] nums, int k) { + Map map = new HashMap<>(); + for (int num : nums) { + map.put(num, map.getOrDefault(num, 0) + 1); + } + + //value값을 통한 key 정렬 + List list = new ArrayList<>(map.keySet()); + list.sort((a,b)->map.get(b) - map.get(a)); + + //상위 k개 key 추출 + int[] res = new int[k]; + for (int i = 0; i < k; i++) { + res[i] = list.get(i); + } + return res; + } +} diff --git a/top-k-frequent-elements/JisooPyo.kt b/top-k-frequent-elements/JisooPyo.kt new file mode 100644 index 000000000..d600753f7 --- /dev/null +++ b/top-k-frequent-elements/JisooPyo.kt @@ -0,0 +1,76 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import java.util.* + +/** + * Leetcode + * 347. Top K Frequent Elements + * Medium + */ +class TopKFrequentElements { + /** + * Runtime: 30 ms(Beats: 68.62 %) + * Time Complexity: O(n log n) + * - list 정렬 + * + * Memory: 42.20 MB(Beats: 58.82 %) + * Space Complexity: O(n) + */ + fun topKFrequent(nums: IntArray, k: Int): IntArray { + val countMap: MutableMap = HashMap() + + for (num in nums) { + countMap[num] = countMap.getOrDefault(num, 0) + 1 + } + + val list = mutableListOf() + for (key in countMap.keys) { + list.add(Node(key, countMap[key]!!)) + } + list.sortDescending() + + val answer = IntArray(k) + for (i in 0 until k) { + answer[i] = list[i].value + } + return answer + } + + /** + * 개선된 버전: 우선순위 큐를 사용 + * + * Runtime: 19 ms(Beats: 96.30 %) + * Time Complexity: O(n log n) + * + * Memory: 44.83 MB(Beats: 18.35 %) + * Space Complexity: O(n) + */ + fun topKFrequent2(nums: IntArray, k: Int): IntArray { + val countMap = nums.groupBy { it } + .mapValues { it.value.size } + + val pq = PriorityQueue(compareByDescending { it.count }) + countMap.forEach { (num, count) -> + pq.offer(Node(num, count)) + } + + return IntArray(k) { pq.poll().value } + } + + data class Node(var value: Int, var count: Int) : Comparable { + override fun compareTo(other: Node): Int { + return this.count.compareTo(other.count) + } + } + + @Test + fun test() { + topKFrequent(intArrayOf(1, 1, 1, 2, 2, 3), 2) shouldBe intArrayOf(1, 2) + topKFrequent(intArrayOf(1), 1) shouldBe intArrayOf(1) + + topKFrequent2(intArrayOf(1, 1, 1, 2, 2, 3), 2) shouldBe intArrayOf(1, 2) + topKFrequent2(intArrayOf(1), 1) shouldBe intArrayOf(1) + } +} diff --git a/top-k-frequent-elements/dusunax.py b/top-k-frequent-elements/dusunax.py new file mode 100644 index 000000000..1e8ee7a4c --- /dev/null +++ b/top-k-frequent-elements/dusunax.py @@ -0,0 +1,69 @@ +''' +# Leetcode 347. Top K Frequent Elements + +use **Counter** to count the frequency of each element in the list 🚀 + +- **solution 1**: use **sorted()** to sort the elements by their frequency in descending order. +- **solution 2**: use **bucket sort** to sort the elements by their frequency in descending order. (efficient!) + +## Time and Space Complexity + +### solution 1: topKFrequent() + +``` +TC: O(n log n) +SC: O(n) +``` + +#### TC is O(n log n): +- iterating through the list just once to count the frequency of each element. = O(n) +- sorting the elements by their frequency in descending order. = O(n log n) + +#### SC is O(n): +- using a Counter to store the frequency of each element. = O(n) +- sorted() creates a new list that holds the elements of frequency_map. = O(n) +- result list that holds the top k frequent elements. = O(k) + +### solution 2: topKFrequentBucketSort() + +``` +TC: O(n) +SC: O(n) +``` + +#### TC is O(n): +- iterating through the list just once to count the frequency of each element. = O(n) +- creating **buckets** to store the elements by their frequency. = O(n) +- iterating through the buckets in reverse order to get only the top k frequent elements. = O(n) + +#### SC is O(n): +- using a Counter to store the frequency of each element. = O(n) +- using buckets to store the elements by their frequency. = O(n) +- result list that holds only the top k frequent elements. = O(k) +''' + +class Solution: + def topKFrequent(self, nums: List[int], k: int) -> List[int]: + frequency_map = Counter(nums) # TC: O(n), SC: O(n) + sorted_frequencies = sorted(frequency_map.items(), key=lambda x: x[1], reverse=True) # TC: O(n log n), SC: O(n) + + result = [] # SC: O(k) + for e in sorted_frequencies: # TC: O(k) + result.append(e[0]) + + return result[0:k] + + def topKFrequentBucketSort(self, nums: List[int], k: int) -> List[int]: + frequency_map = Counter(nums) + n = len(nums) + buckets = [[] for _ in range(n + 1)] + + for num, freq in frequency_map.items(): + buckets[freq].append(num) + + result = [] + for i in range(len(buckets) - 1, 0, -1): + for num in buckets[i]: + result.append(num) + if len(result) == k: + return result diff --git a/valid-palindrome/5YoonCheol.java b/valid-palindrome/5YoonCheol.java new file mode 100644 index 000000000..ed5a82675 --- /dev/null +++ b/valid-palindrome/5YoonCheol.java @@ -0,0 +1,16 @@ +class Solution { + public boolean isPalindrome(String s) { + //정규식으로 영문,숫자 아닌 경우 "" 치환 + //StringBuilder를 통해 문자열 가공 + //거꾸로 뒤집은 것과 원래의 문자열이 일치할 경우 팰린드롬 + s = s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase(); + StringBuilder sb = new StringBuilder(s); + if(sb.reverse().toString().equals(s)){ + return true; + } + + return false; + } +} + + diff --git a/valid-palindrome/JisooPyo.kt b/valid-palindrome/JisooPyo.kt new file mode 100644 index 000000000..2997dec8f --- /dev/null +++ b/valid-palindrome/JisooPyo.kt @@ -0,0 +1,101 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +/** + * Leetcode + * 125. Valid Palindrome + * Easy + */ +class ValidPalindrome { + /** + * Runtime: 4 ms(Beats: 98.27 %) + * Time Complexity: O(n) + * + * Memory: 38.22 MB(Beats: 46.74 %) + * Space Complexity: O(1) + */ + fun isPalindrome(s: String): Boolean { + var left = 0 + var right = s.length - 1 + while (left <= right) { + when (s[left]) { + // 왼쪽의 문자가 alphanumeric일 때 + in 'a'..'z', in 'A'..'Z', in '0'..'9' -> { + + when (s[right]) { + // 오른쪽의 문자가 alphanumeric일 때 + in 'a'..'z', in 'A'..'Z', in '0'..'9' -> { + // 문자 비교 + if (s[left].equals(s[right], true)) { + left++ + right-- + continue + } else { + return false + } + } + // 오른쪽의 문자가 alphanumeric이 아닐 때 + else -> { + right-- + continue + } + } + } + + // 왼쪽의 문자가 alphanumeric이 아닐 때 + else -> { + left++ + continue + } + } + } + return true + } + + /** + * 개선한 버전 + * Runtime: 5 ms(Beats: 87.14 %) + * Time Complexity: O(n) + * + * Memory: 37.76 MB(Beats: 61.52 %) + * Space Complexity: O(1) + */ + fun isPalindrome2(s: String): Boolean { + var left = 0 + var right = s.length - 1 + + while (left < right) { + // 왼쪽에서 유효한 문자를 찾음 + while (left < right && !s[left].isLetterOrDigit()) { + left++ + } + + // 오른쪽에서 유효한 문자를 찾음 + while (left < right && !s[right].isLetterOrDigit()) { + right-- + } + + // 문자 비교 + if (!s[left].equals(s[right], ignoreCase = true)) { + return false + } + + left++ + right-- + } + return true + } + + @Test + fun test() { + isPalindrome("A man, a plan, a canal: Panama") shouldBe true + isPalindrome("race a car") shouldBe false + isPalindrome(" ") shouldBe true + + isPalindrome2("A man, a plan, a canal: Panama") shouldBe true + isPalindrome2("race a car") shouldBe false + isPalindrome2(" ") shouldBe true + } +} diff --git a/valid-palindrome/dusunax.py b/valid-palindrome/dusunax.py new file mode 100644 index 000000000..52ee0fd1a --- /dev/null +++ b/valid-palindrome/dusunax.py @@ -0,0 +1,31 @@ +''' +# Leetcode 125. Valid Palindrome + +use `isalnum()` to filter out non-alphanumeric characters 🔍 + +## Time and Space Complexity + +``` +TC: O(n) +SC: O(n) +``` + +### TC is O(n): +- iterating through the string just once to filter out non-alphanumeric characters. O(n) + +### SC is O(n): +- `s.lower()` creates a new string. O(n) +- creating a new string `converted_s` to store the filtered characters. O(n) +- `converted_s[::-1]` creates a new reversed string. O(n) +''' + +class Solution: + def isPalindrome(self, s: str) -> bool: + if s == " ": + return True + + s = s.lower() + converted_s = ''.join(c for c in s if c.isalnum()) + + return converted_s == converted_s[::-1] +