-
Notifications
You must be signed in to change notification settings - Fork 126
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
[정현준] 10주차 #538
[정현준] 10주차 #538
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<IntArray>): Boolean { | ||
if (prerequisites.isEmpty()) return true | ||
|
||
return usingTopologySort(numCourses, prerequisites) | ||
} | ||
|
||
private fun usingTopologySort(numCourses: Int, prerequisites: Array<IntArray>): Boolean { | ||
val adj = List(numCourses) { mutableListOf<Int>() } | ||
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<Int>().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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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? { | ||
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. 저는 inorder traversal 로 풀었는데 문제의 핵심인 |
||
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<TreeNode>().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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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?>): 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?>): 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?>): 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) | ||
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. 분할정복 방식에서 시간 복잡도는 O( log(lists.size) * listNode.size))가 걸리지 않을까요? 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. 흠 말씀해주셔서 다시 생각해보니 SC는 콜 스택 깊이가 절반으로 줄어들어서 log로 볼 수 있을 것 같네요 |
||
*/ | ||
private fun mergeDivideAndConquer(lists: Array<ListNode?>): 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<ListNode?>, 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) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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]) { | ||
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. 코틀린을 모르는 입장에서 이부분이 순회처럼 보이는데 단순 비교라서 O(1)라는게 신기하네요 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. 저도 순회하는 것 처럼 보이길래 자바로 디컴파일해보니 조건문으로 적용되더라구요 ㅎㅎ int var10000;
int var10002;
if (nums[low] <= nums[mid]) {
var10000 = nums[low];
var10002 = nums[mid];
if (var10000 <= target) {
if (var10002 >= target) {
high = mid;
continue;
}
}
low = mid;
} else {
var10000 = nums[mid];
var10002 = nums[high];
if (var10000 <= target) {
if (var10002 >= target) {
low = mid;
continue;
}
}
high = 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 | ||
} | ||
} |
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.
while문을 순회하며 로직을 순차적으로 진행하기 위해 queue특성을 활용한걸로 이해가 되요. 그런의미에서 dequeue를 사용하신 이유가 궁금합니다!
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.
말씀하신대로 단순히 Queue의 특성을 이용하고 싶어서 사용하였는데 내부 구현의 차이점을 확실히 이해하고 있진 않지만 ArrayDeque가 LinkedList보다 빠르다는 말이 있어서 Queue를 사용할 때는 항상 ArrayDeque를 사용했습니다 ㅎㅎ
한번 구현을 확인해 볼 필요도 있겠네요 ㅎㅎ