diff --git a/contains-duplicate/jdalma.kt b/contains-duplicate/jdalma.kt new file mode 100644 index 000000000..d4684df3f --- /dev/null +++ b/contains-duplicate/jdalma.kt @@ -0,0 +1,65 @@ +package leetcode_study + +import io.kotest.matchers.equals.shouldBeEqual +import org.junit.jupiter.api.Test + +class `contains-duplicate`{ + + fun containsDuplicate(nums: IntArray): Boolean { + return usingSet(nums) + } + + // 1. 중첩 반복문 - 시간초과 + // 시간복잡도: O(n^2), 공간복잡도: O(1) + private fun usingNestedLoop(nums: IntArray): Boolean { + nums.forEachIndexed { i, e1 -> + nums.forEachIndexed { j, e2 -> + if (i != j && e1 == e2) { + return true + } + } + } + return false + } + + // 2. 정렬 후 순회 + // 시간복잡도: O(n * log(n)), 공간복잡도: O(1) + private fun usingSort(nums: IntArray): Boolean { + nums.sort() // DualPivotQuicksort -> O(n log(n)) + for (index in 1 until nums.size) { + val prev = nums[index - 1] + val curr = nums[index] + if (prev == curr) { + return true + } + } + return false + } + + // 3. 자료구조 Set 사용 + // 시간복잡도: O(n), 공간복잡도: O(n) + private fun usingSet(nums: IntArray): Boolean { + val set = nums.toSet() + return nums.size != set.size + } + + @Test + fun 동일한_원소가_존재하면_true를_반환한다() { + val nums1 = intArrayOf(1, 2, 3, 1) + val nums2 = intArrayOf(1, 2, 3, 2) + val nums3 = intArrayOf(1, 1, 1, 1, 3, 3, 4, 3, 2, 4, 2) + + containsDuplicate(nums1) shouldBeEqual true + containsDuplicate(nums2) shouldBeEqual true + containsDuplicate(nums3) shouldBeEqual true + } + + @Test + fun 동일한_원소가_존재하지_않으면_false를_반환한다() { + val nums1 = intArrayOf(1, 2, 3, 4) + val nums2 = intArrayOf(1, 5, 7) + + containsDuplicate(nums1) shouldBeEqual false + containsDuplicate(nums2) shouldBeEqual false + } +} diff --git a/kth-smallest-element-in-a-bst/jdalma.kt b/kth-smallest-element-in-a-bst/jdalma.kt new file mode 100644 index 000000000..ddc76f37c --- /dev/null +++ b/kth-smallest-element-in-a-bst/jdalma.kt @@ -0,0 +1,98 @@ +package leetcode_study + +import io.kotest.matchers.equals.shouldBeEqual +import org.junit.jupiter.api.Test +import java.util.PriorityQueue + +class `kth-smallest-element-in-a-bst` { + + fun kthSmallest(root: TreeNode?, k: Int): Int { + return inorderTraversal(root, k) + } + + // 1. 재귀 호출로 모든 트리를 조회 후 정렬 + // 시간복잡도: O(n * log(n)), 공간복잡도: O(n) + private fun recursionAndSort(root: TreeNode?, k: Int) = mutableSetOf().apply { + dfs(root, this) + }.sorted()[k - 1] + + private fun dfs(node: TreeNode?, set: MutableSet) { + if (node == null) return + + set.add(node.`val`) + dfs(node.left, set) + dfs(node.right, set) + } + + // 2. 재귀 호출로 모든 트리의 값을 우선순위 큐에 삽입하고 작은 값으로 계속 큐를 갱신 + // 시간복잡도: O(n * log(k)), 공간복잡도: O(n + k) + // 트리 순회 : O(n), 우선순위 큐 삽입: O(log k) + private fun usingPriorityQueue(node: TreeNode?, k: Int): Int { + fun dfs(node: TreeNode, k: Int, pq: PriorityQueue) { + pq.offer(node.`val`) + + if (pq.size > k) { + pq.poll() + } + if (node.left != null) { + dfs(node.left!!, k, pq) + } + if (node.right != null) { + dfs(node.right!!, k, pq) + } + } + + val pq = PriorityQueue { v1: Int, v2: Int -> v2 - v1 } + dfs(node!!, k, pq) + return pq.first() + } + + // 3. 문제의 전제가 이진탐색트리이기에, 중위순회로 탐색하여 값을 누적하면 오름차순의 값이 된다. + // 시간복잡도: O(n), 공간복잡도: O(n) + private fun inorderTraversal(node: TreeNode?, k: Int): Int { + fun dfs(node: TreeNode, k: Int, list: MutableList) { + if (node.left != null) { + dfs(node.left!!, k, list) + } + list.add(node.`val`) + if (node.right != null) { + dfs(node.right!!, k, list) + } + } + + return mutableListOf().apply { + dfs(node!!, k, this) + }[k - 1] + } + + @Test + fun `루트와 정수 k가 주어지면 트리에 있는 모든 노드의 값 중 가장 작은 값을 반환한다`() { + kthSmallest(TreeNode.of(listOf(0, 3,1,4,null,2)), 1) shouldBeEqual 1 + kthSmallest(TreeNode.of(listOf(0, 5,3,6,2,4,null,null,1)), 3) shouldBeEqual 3 + } +} + +class TreeNode(var `val`: Int) { + var left: TreeNode? = null + var right: TreeNode? = null + + companion object { + fun of(numbers: List): TreeNode? { + fun setChild(node: TreeNode?, nums: List, index: Int): TreeNode? { + if (node == null) return null + val (leftIndex, rightIndex) = index * 2 to index * 2 + 1 + + if (leftIndex < nums.size && nums[leftIndex] != null) { + node.left = TreeNode(nums[leftIndex]!!) + setChild(node.left, nums, leftIndex) + } + if (rightIndex < nums.size && nums[rightIndex] != null) { + node.right = TreeNode(nums[rightIndex]!!) + setChild(node.right, nums, rightIndex) + } + return node + } + return setChild(TreeNode(numbers[1]!!), numbers, 1) + } + } +} diff --git a/number-of-1-bits/jdalma.kt b/number-of-1-bits/jdalma.kt new file mode 100644 index 000000000..eea9462fb --- /dev/null +++ b/number-of-1-bits/jdalma.kt @@ -0,0 +1,46 @@ +package leetcode_study + +import io.kotest.matchers.equals.shouldBeEqual +import org.junit.jupiter.api.Test + +class `number-of-1-bits` { + + fun hammingWeight(n: Int): Int { + return bitOperation(n) + } + + // 1. 직접 2진수를 구함 + // 시간복잡도: O(log n), 공간복잡도: O(1) + private fun binary(n: Int): Int { + var calc = n + var count = 0 + while(calc > 0) { + if (calc % 2 != 0) { + count++ + } + calc /= 2 + } + return count + } + + // 2. 비트 논리 연산자 사용 + // 시간복잡도: O(log n), 공간복잡도: O(1) + private fun bitOperation(n: Int): Int { + var calc = n + var count = 0 + while (calc > 0) { + if (calc and 1 == 1) { + count ++ + } + calc = calc shr 1 + } + return count + } + + @Test + fun `이진수에서_0이_아닌_성분의_개수를 반환한다`() { + hammingWeight(11) shouldBeEqual 3 + hammingWeight(128) shouldBeEqual 1 + hammingWeight(2147483645) shouldBeEqual 30 + } +} diff --git a/palindromic-substrings/jdalma.kt b/palindromic-substrings/jdalma.kt new file mode 100644 index 000000000..6aa5200ff --- /dev/null +++ b/palindromic-substrings/jdalma.kt @@ -0,0 +1,90 @@ +package leetcode_study + +import io.kotest.matchers.equals.shouldBeEqual +import org.junit.jupiter.api.Test + +class `palindromic-substrings` { + + fun countSubstrings(s: String): Int { + return dp(s) + } + + // 1. 브루트포스 3중 반복문 + // 시간복잡도: O(n^3), 공간복잡도: O(1) + private fun bruteForce(s: String): Int { + + fun isPalindrome(text: String, left: Int, right: Int): Boolean { + var (start, end) = left to right + while (start < end) { + if (text[start] != text[end]) { + return false + } + start++ + end-- + } + return true + } + + var count = 0 + s.indices.forEachIndexed { left, _ -> + (left until s.length).forEach { right -> + if (isPalindrome(s, left, right)) { + count++ + } + } + } + return count + } + + // 2. 문자열의 길이가 홀수,짝수를 감안하여 특정 지점부터 두 개의 포인터 left , right를 비교한다. + // 시간복잡도: O(n^2), 공간복잡도: O(1) + private fun twoPointer(s: String): Int { + + fun palindromeCount(text: String, left: Int, right: Int): Int { + var count = 0 + var (l , r) = left to right + while (l >= 0 && r < text.length && text[l--] == text[r++]) { + count++ + } + return count + } + + var result = 0 + s.indices.forEachIndexed { index, e -> + val even = palindromeCount(s, index, index + 1) + val odd = palindromeCount(s, index - 1, index + 1) + result += even + odd + 1 + } + + return result + } + + // 3. DP : 이전에 비교했던 결과를 기억하여 (현재 주어진 start, end 비교) && (이전 start, end 비교) + // 시간복잡도: O(n^2), 공간복잡도: O(n^2) + private fun dp(s: String): Int { + val len = s.length + val dp = Array(len) { BooleanArray(len) { false } } + + for (end in 0 until len) { + for (start in end downTo 0) { + if (start == end) { + dp[start][end] = true + } else if (start + 1 == end) { + dp[start][end] = s[start] == s[end] + } else { + dp[start][end] = s[start] == s[end] && dp[start + 1][end - 1] + } + } + } + + return dp.sumOf { row -> row.count { it } } + } + + @Test + fun `주어진 문자열의 회문인 부분 문자열의 개수를 반환한다`() { + countSubstrings("abc") shouldBeEqual 3 + countSubstrings("aaa") shouldBeEqual 6 + countSubstrings("ababa") shouldBeEqual 9 + countSubstrings("abcda") shouldBeEqual 5 + } +} diff --git a/top-k-frequent-elements/jdalma.kt b/top-k-frequent-elements/jdalma.kt new file mode 100644 index 000000000..4f1e05d49 --- /dev/null +++ b/top-k-frequent-elements/jdalma.kt @@ -0,0 +1,82 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import java.util.PriorityQueue + +class `top-k-frequent-elements` { + + fun topKFrequent(nums: IntArray, k: Int): IntArray { + return third(nums, k) + } + + // 1. Map 정렬 + // 시간복잡도: O(n * log(n)), 공간복잡도: O(n) + private fun first(nums: IntArray, k: Int): IntArray { + val map = mutableMapOf() + + nums.forEach { + map.compute(it) { _, oldValue -> + if (oldValue == null) 1 + else oldValue + 1 + } + } + + return map.entries.sortedByDescending { it.value } + .map { it.key } + .slice(0 until k) + .toIntArray() + } + + // 2. 우선순위 큐 + // 시간복잡도: O(n * log(k)), 공간복잡도: O(n + k) + private fun second(nums: IntArray, k: Int): IntArray { + val map = mutableMapOf() + + nums.forEach { map.put(it, map.getOrDefault(it, 0) + 1) } + + val heap: PriorityQueue> = PriorityQueue> { + v1, v2 -> v2.value.compareTo(v1.value) + }.apply { + this.addAll(map.entries) + } + + return (0 until k).map { heap.poll().key }.toIntArray() + } + + // 3. 이차원배열로 빈번도 저장 + // 시간복잡도: O(n), 공간복잡도: O(n) + private fun third(nums: IntArray, k: Int): IntArray { + val map = mutableMapOf() + + nums.forEach { map.put(it, map.getOrDefault(it, 0) + 1) } + + val freq = Array>(nums.size + 1) { mutableListOf() } + map.entries.forEach { + val frequency = it.value + freq[frequency].add(it.key) + } + + val result = IntArray(k) + var index = 0 + (freq.size - 1 downTo 0).forEach { i -> + freq[i].forEach { + result[index++] = it + if (index == k) { + return result + } + } + } + + return IntArray(0) + } + + @Test + fun `배열에서_가장_빈도가_높은_K개의_원소를_출력한다`() { + topKFrequent(intArrayOf(1,1,1,2,2,3), 2) shouldBe intArrayOf(1,2) + topKFrequent(intArrayOf(1,1,1,2,2,3,3,4), 3) shouldBe intArrayOf(1,2,3) + topKFrequent(intArrayOf(2,2,3,3,1,1,4), 3) shouldBe intArrayOf(2,3,1) + topKFrequent(intArrayOf(4,1,-1,2,-1,2,3), 2) shouldBe intArrayOf(-1,2) + + } +}