diff --git a/course-schedule/jdalma.kt b/course-schedule/jdalma.kt new file mode 100644 index 000000000..ce58ed609 --- /dev/null +++ b/course-schedule/jdalma.kt @@ -0,0 +1,79 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class `course-schedule` { + + /** + * TC: O(node + edge), SC: O(node + edge) + */ + fun canFinish(numCourses: Int, prerequisites: Array): Boolean { + if (prerequisites.isEmpty()) return true + + return usingTopologySort(numCourses, prerequisites) + } + + private fun usingTopologySort(numCourses: Int, prerequisites: Array): Boolean { + val adj = List(numCourses) { mutableListOf() } + val degree = IntArray(numCourses) + for (e in prerequisites) { + val (course, pre) = e[0] to e[1] + adj[pre].add(course) + degree[course]++ + } + + val queue = ArrayDeque().apply { + degree.forEachIndexed { index, i -> + if (i == 0) { + this.add(index) + } + } + } + + var answer = 0 + while (queue.isNotEmpty()) { + val now = queue.removeFirst() + answer++ + + queue.addAll(adj[now].filter { --degree[it] == 0 }) + } + + return answer == numCourses + } + + @Test + fun `코스의 개수와 코스 간 의존성을 전달하면 코스를 완료할 수 있는지 여부를 반환한다`() { + canFinish(5, + arrayOf( + intArrayOf(0,1), + intArrayOf(0,2), + intArrayOf(1,3), + intArrayOf(1,4), + intArrayOf(3,4) + ) + ) shouldBe true + canFinish(5, + arrayOf( + intArrayOf(1,4), + intArrayOf(2,4), + intArrayOf(3,1), + intArrayOf(3,2) + ) + ) shouldBe true + canFinish(2, arrayOf(intArrayOf(1, 0))) shouldBe true + canFinish(2, arrayOf(intArrayOf(1, 0), intArrayOf(0, 1))) shouldBe false + canFinish(20, + arrayOf( + intArrayOf(0,10), + intArrayOf(3,18), + intArrayOf(5,5), + intArrayOf(6,11), + intArrayOf(11,14), + intArrayOf(13,1), + intArrayOf(15,1), + intArrayOf(17,4) + ) + ) shouldBe false + } +} diff --git a/invert-binary-tree/jdalma.kt b/invert-binary-tree/jdalma.kt new file mode 100644 index 000000000..0f707ca6e --- /dev/null +++ b/invert-binary-tree/jdalma.kt @@ -0,0 +1,58 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class `invert-binary-tree` { + + fun invertTree(root: TreeNode?): TreeNode? { + if (root == null) return null + + return usingStack(root) + } + + /** + * TC: O(n), SC: O(n) + */ + private fun usingDFS(node: TreeNode?): TreeNode? { + if (node == null) return null + + val (left, right) = node.left to node.right + node.left = usingDFS(right) + node.right = usingDFS(left) + + return node + } + + /** + * TC: O(n), SC: O(n) + */ + private fun usingStack(node: TreeNode): TreeNode { + val stack= ArrayDeque().apply { + this.add(node) + } + + while (stack.isNotEmpty()) { + val now = stack.removeLast() + val tmp = now.left + now.left = now.right + now.right = tmp + + now.left?.let { stack.add(it) } + now.right?.let { stack.add(it) } + } + return node + } + + @Test + fun `전달된 노드의 하위 노드들의 반전된 값을 반환한다`() { + val actual = TreeNode.of(4,2,7,1,3,6,9) + val expect = TreeNode.of(4,7,2,9,6,3,1) + invertTree(actual) shouldBe expect + + val actual1 = TreeNode.of(1,2) + val expect1 = TreeNode.of(1,null,2) + + invertTree(actual1) shouldBe expect1 + } +} diff --git a/jump-game/jdalma.kt b/jump-game/jdalma.kt new file mode 100644 index 000000000..7920cc280 --- /dev/null +++ b/jump-game/jdalma.kt @@ -0,0 +1,54 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import kotlin.math.max + +class `jump-game` { + + fun canJump(nums: IntArray): Boolean { + return usingGreedy(nums) + } + + /** + * TC: O(n), SC: O(1) + */ + private fun usingGreedy(nums: IntArray): Boolean { + var reachable = 0 + for (index in nums.indices) { + if (index > reachable) return false + reachable = max(reachable, index + nums[index]) + } + + return true + } + + /** + * TC: O(n^2), SC: O(n) + */ + private fun usingMemoization(nums: IntArray): Boolean { + val memo = IntArray(nums.size) { -1 } + fun dfs(now: Int): Boolean { + if (now >= nums.size - 1) { + return true + } else if (memo[now] != -1) { + return memo[now] != 0 + } + for (next in 1 .. nums[now]) { + if (dfs(now + next)) { + memo[now] = 1 + return true + } + } + memo[now] = 0 + return false + } + return dfs(0) + } + + @Test + fun `첫 번째 인덱스에서 마지막 인덱스에 도달할 수 있는지 여부를 반환한다`() { + canJump(intArrayOf(2,3,1,1,4)) shouldBe true + canJump(intArrayOf(3,2,1,0,4)) shouldBe false + } +} diff --git a/merge-k-sorted-lists/jdalma.kt b/merge-k-sorted-lists/jdalma.kt new file mode 100644 index 000000000..682e045ca --- /dev/null +++ b/merge-k-sorted-lists/jdalma.kt @@ -0,0 +1,130 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test +import java.lang.RuntimeException + +class `merge-k-sorted-lists` { + + data class ListNode(var `val`: Int) { + var next: ListNode? = null + + companion object { + fun of(vararg `val`: Int): ListNode { + val dummy = ListNode(-1) + var prev = dummy + for (v in `val`) { + prev.next = ListNode(v) + prev = prev.next ?: throw RuntimeException() + } + return dummy.next ?: throw RuntimeException() + } + } + } + + fun mergeKLists(lists: Array): ListNode? { + return if (lists.isEmpty()) null + else if (lists.size == 1) lists.first() + else mergeDivideAndConquer(lists) + } + + /** + * TC: O(lists.size * ListNode.size), SC: O(1) + */ + private fun usingBruteForce(lists: Array): ListNode? { + val dummy = ListNode(-1) + var prev = dummy + + while (true) { + var minNode: ListNode? = null + var minIndex = -1 + + for (index in lists.indices) { + val curr = lists[index] ?: continue + if (minNode == null || curr.`val` < minNode.`val`) { + minNode = curr + minIndex = index + } + } + prev.next = minNode ?: break + prev = prev.next ?: throw RuntimeException() + + lists[minIndex] = minNode.next + } + + return dummy.next + } + + /** + * TC: O(lists.size * ListNode.size), SC: O(1) + */ + private fun mergeLists(lists: Array): ListNode? { + fun merge(node1: ListNode?, node2: ListNode?): ListNode? { + val dummy = ListNode(-1) + var prev = dummy + var (n1, n2) = node1 to node2 + while (n1 != null && n2 != null) { + if (n1.`val` < n2.`val`) { + prev.next = n1 + n1 = n1.next + } else { + prev.next = n2 + n2 = n2.next + } + prev.next?.let { prev = it } + } + prev.next = n1 ?: n2 + return dummy.next + } + for (index in 1 until lists.size) { + lists[0] = merge(lists[0], lists[index]) + } + return lists[0] + } + + /** + * TC: O(lists.size * ListNode.size), SC: O(lists.size) + */ + private fun mergeDivideAndConquer(lists: Array): ListNode? { + fun merge(node1: ListNode?, node2: ListNode?): ListNode? { + val dummy = ListNode(-1) + var prev = dummy + var (n1, n2) = node1 to node2 + while (n1 != null && n2 != null) { + if (n1.`val` < n2.`val`) { + prev.next = n1 + n1 = n1.next + } else { + prev.next = n2 + n2 = n2.next + } + prev.next?.let { prev = it } + } + prev.next = n1 ?: n2 + return dummy.next + } + + fun divideAndConquer(lists: Array, s: Int, e: Int): ListNode? { + if (s > e) return null + else if (s == e) return lists[s] + + val mid = (s + e) / 2 + val left = divideAndConquer(lists, s, mid) + val right = divideAndConquer(lists, mid + 1, e) + return merge(left, right) + } + + return divideAndConquer(lists, 0, lists.size - 1) + } + + @Test + fun `전달받은 노드들을 정렬하고 병합된 결과를 반환한다`() { + mergeKLists( + arrayOf( + ListNode.of(1,4,5), + ListNode.of(1,3,4), + ListNode.of(2,6) + ) + ) shouldBe ListNode.of(1,1,2,3,4,4,5,6) + } +} diff --git a/search-in-rotated-sorted-array/jdalma.kt b/search-in-rotated-sorted-array/jdalma.kt new file mode 100644 index 000000000..36eb8c1ef --- /dev/null +++ b/search-in-rotated-sorted-array/jdalma.kt @@ -0,0 +1,50 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class `search-in-rotated-sorted-array` { + + /** + * O(log n)만큼의 시간만으로 해결해야 한다. + * nums 배열은 정렬되어있지만 순환된 배열이므로 target 을 찾기 위한 일반적인 이분 탐색으로는 해결할 수 없다. + * TC: O(log n), SC: O(1) + */ + fun search(nums: IntArray, target: Int): Int { + var (low, high) = 0 to nums.size - 1 + + while (low + 1 < high) { + val mid = (low + high) / 2 + + if (nums[mid] == target) { + return mid + } + if (nums[low] <= nums[mid]) { + if (target in nums[low] .. nums[mid]) { + high = mid + } else { + low = mid + } + } else { + if (target in nums[mid] .. nums[high]) { + low = mid + } else { + high = mid + } + } + } + return when (target) { + nums[low] -> low + nums[high] -> high + else -> -1 + } + } + + @Test + fun `배열에서 타겟의 인덱스를 반환한다`() { + search(intArrayOf(4,5,6,7,0,1,2), 0) shouldBe 4 + search(intArrayOf(4,5,6,7,0,1,2), 3) shouldBe -1 + search(intArrayOf(2,3,4,5,6,0,1), 1) shouldBe 6 + search(intArrayOf(1,2,3,4,5,6,7), 6) shouldBe 5 + } +}