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/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/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/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/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 + } +}