diff --git a/course-schedule/TonyKim9401.java b/course-schedule/TonyKim9401.java new file mode 100644 index 000000000..edbff43de --- /dev/null +++ b/course-schedule/TonyKim9401.java @@ -0,0 +1,37 @@ +// TC: O(n + p) +// n -> number of courses, p -> the length of prerequisites +// SC: O(n + m) +// n -> the length of the graph size, m -> the length of nested list's size +class Solution { + public boolean canFinish(int numCourses, int[][] prerequisites) { + List> graph = new ArrayList<>(); + int[] inDegree = new int[numCourses]; + + for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>()); + + for (int[] prerequisite : prerequisites) { + int course = prerequisite[0]; + int pre = prerequisite[1]; + graph.get(pre).add(course); + inDegree[course] += 1; + } + + Queue q = new LinkedList<>(); + for (int i = 0; i < numCourses; i++) { + if (inDegree[i] == 0) q.offer(i); + } + + int visitedCourses = 0; + while (!q.isEmpty()) { + int course = q.poll(); + visitedCourses += 1; + + for (int nextCourse : graph.get(course)) { + inDegree[nextCourse] -= 1; + if (inDegree[nextCourse] == 0) q.offer(nextCourse); + } + } + + return visitedCourses == numCourses; + } +} diff --git a/course-schedule/haklee.py b/course-schedule/haklee.py new file mode 100644 index 000000000..fd40fbdac --- /dev/null +++ b/course-schedule/haklee.py @@ -0,0 +1,31 @@ +"""TC: O(node + edge), SC: O(node + edge) + +유명한 위상 정렬 알고리즘이므로 설명은 생략한다. +""" + + +class Solution: + def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool: + # 위상 정렬. + + # init + adj_list = [[] for _ in range(numCourses)] # SC: O(edge) + in_deg = [0] * numCourses # SC: O(node) + + for edge in prerequisites: + adj_list[edge[0]].append(edge[1]) + in_deg[edge[1]] += 1 + + node_to_search = [i for i, v in enumerate(in_deg) if v == 0] # TC: O(node) + sorted_list = [] + + # process + while node_to_search: + cur = node_to_search.pop() # TC: 최악의 경우 총 O(node)만큼 실행 + sorted_list.append(cur) + for node in adj_list[cur]: + in_deg[node] -= 1 # TC: 최악의 경우 총 O(edge)만큼 실행 + if in_deg[node] == 0: + node_to_search.append(node) + + return len(sorted_list) == numCourses 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/course-schedule/sunjae95.js b/course-schedule/sunjae95.js new file mode 100644 index 000000000..dccf03d8a --- /dev/null +++ b/course-schedule/sunjae95.js @@ -0,0 +1,47 @@ +/** + * @description + * memoization + dfs + * + * n = length of nums + * p = length of prerequisites + * + * time complexity: O(n) + * space complexity: O(p) + */ +var canFinish = function (numCourses, prerequisites) { + const memo = Array.from({ length: numCourses + 1 }, () => false); + const visited = Array.from({ length: numCourses + 1 }, () => false); + // graph setting + const graph = prerequisites.reduce((map, [linkedNode, current]) => { + const list = map.get(current) ?? []; + list.push(linkedNode); + map.set(current, list); + return map; + }, new Map()); + + const dfs = (current) => { + const linkedNode = graph.get(current); + + if (memo[current] || !linkedNode || linkedNode.length === 0) return true; + + for (const node of linkedNode) { + if (visited[node]) return false; + + visited[node] = true; + if (!dfs(node)) return false; + visited[node] = false; + memo[node] = true; + } + + return true; + }; + + for (const [current] of graph) { + visited[current] = true; + if (!dfs(current)) return false; + visited[current] = false; + memo[current] = true; + } + + return true; +}; diff --git a/invert-binary-tree/TonyKim9401.java b/invert-binary-tree/TonyKim9401.java new file mode 100644 index 000000000..878b68bb7 --- /dev/null +++ b/invert-binary-tree/TonyKim9401.java @@ -0,0 +1,16 @@ +// TC: O(n) +// -> visit all nodes to invert +// SC: O(n) +// -> create all nodes again to exchange +class Solution { + public TreeNode invertTree(TreeNode root) { + if (root == null) return null; + invertTree(root.left); + invertTree(root.right); + TreeNode left = root.left; + TreeNode right = root.right; + root.left = right; + root.right = left; + return root; + } +} diff --git a/invert-binary-tree/haklee.py b/invert-binary-tree/haklee.py new file mode 100644 index 000000000..0b8b3e58e --- /dev/null +++ b/invert-binary-tree/haklee.py @@ -0,0 +1,32 @@ +"""TC: O(n), SC: O(h) + +h는 이진 트리의 높이. +n이 전체 노드 개수라고 할때 +- 최악의 경우 한 쪽 자식 노드만 채워짐. 이 경우 h = n. +- 최선의 경우 완전 이진 트리. h = log(n). + +아이디어: +양쪽 자식 노드에 접근해서 재귀적으로 invert를 진행하고, 두 자식 노드를 바꾼다. + +SC: +- 호출 스택 깊이는 트리의 깊이까지 깊어질 수 있다. 즉, O(h). + +TC: +- 모든 노드에 접근. O(n). +""" + + +# Definition for a binary tree node. +# class TreeNode: +# def __init__(self, val=0, left=None, right=None): +# self.val = val +# self.left = left +# self.right = right +class Solution: + def invertTree(self, root: Optional[TreeNode]) -> Optional[TreeNode]: + def invert(node: Optional[TreeNode]) -> None: + if node is not None: + node.left, node.right = invert(node.right), invert(node.left) + return node + + return invert(root) 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/invert-binary-tree/sunjae95.js b/invert-binary-tree/sunjae95.js new file mode 100644 index 000000000..2abf8f65a --- /dev/null +++ b/invert-binary-tree/sunjae95.js @@ -0,0 +1,23 @@ +/** + * @description + * brainstorming: + * preorder traverse + * + * n = length of root + * time complexity: O(n) + * space complexity: O(n) + */ +var invertTree = function (root) { + const preOrder = (tree) => { + if (tree === null) return null; + + const currentNode = new TreeNode(tree.val); + + currentNode.right = preOrder(tree.left); + currentNode.left = preOrder(tree.right); + + return currentNode; + }; + + return preOrder(root); +}; diff --git a/jump-game/TonyKim9401.java b/jump-game/TonyKim9401.java new file mode 100644 index 000000000..7df98f72c --- /dev/null +++ b/jump-game/TonyKim9401.java @@ -0,0 +1,13 @@ +// TC: O(n) +// SC: O(1) +class Solution { + public boolean canJump(int[] nums) { + int jump = 0; + + for (int i = 0; i < nums.length; i++) { + if (i > jump) return false; + jump = Math.max(jump, i + nums[i]); + } + return true; + } +} diff --git a/jump-game/haklee.py b/jump-game/haklee.py new file mode 100644 index 000000000..18029c522 --- /dev/null +++ b/jump-game/haklee.py @@ -0,0 +1,44 @@ +"""TC: O(n), SC: O(1) + +n은 주어진 리스트의 길이 + +아이디어: +- 끝에 있는 아이템부터 시작해서 '최소 어디까지는 도달해야 끝 칸까지 점프 가능한지'를 업데이트 한다. +- example들로 이해해보자. index는 0부터 시작. + - example 1: [2,3,1,1,4] + - 4번째 칸에 도달할 수 있으면 성공이다. reach_at_least 값을 4로 초기화 한다. + - 3번째 칸에서는 최대 4번째 칸까지 갈 수 있다. 즉, 적어도 3번 칸까지 가면 성공이므로 + reach_at_least를 3으로 업데이트 한다. + - 2번째 칸에서는 최대 3번째 칸까지 갈 수 있다. reach_at_least를 2로 업데이트 한다. + - 1번째 칸에서는 최대 1+3=4번째 칸까지 갈 수 있다. 이 칸에서 현 reach_at_least 값인 2번째 칸까지 + 충분히 갈 수 있으므로 reach_at_least 값을 1로 업데이트 한다. + - 0번째 칸에서는 최대 0+2=2번째 칸까지 갈 수 있다. 현 reach_at_least 값인 1번째 칸까지 충분히 + 갈 수 있으므로 reach_at_least 값을 0으로 업데이트 한다. + - 0번째 칸에서 끝 칸까지 갈 수 있다. + - example 2: [3,2,1,0,4] + - 4번째 칸에 도달할 수 있으면 성공이다. reach_at_least 값을 4로 초기화 한다. + - 3번째 칸에서는 최대 3번째 칸까지 갈 수 있다. 여기서는 현 reach_at_least 값인 4까지 갈 수 없으니 + 아무 일도 일어나지 않는다. + - 2번째 칸에서는 최대 2+1=3번째 칸까지 갈 수 있다. 여기서도 현 reach_at_least 값인 4까지 갈 수 없고, + 아무 일도 일어나지 않는다. + - 1번째 칸에서는 최대 1+2=3번째 칸까지 갈 수 있다. 비슷하게 아무 일도 일어나지 않는다. + - 0번째 칸에서는 최대 0+3=3번째 칸까지 갈 수 있다. 비슷하게 아무 일도 일어나지 않는다. + - reach_at_least 값이 0이 아니다. 즉, 0번째 칸에서는 끝 칸까지 갈 수 없다. + +SC: +- reach_at_least 값에 인덱스 하나만 관리한다. 즉, O(1). + +TC: +- nums의 끝에서 두 번째 아이템부터 첫 번째 아이템까지 순차적으로 접근하면서 reach_at_least값을 업데이트 한다. O(n). +""" + + +class Solution: + def canJump(self, nums: List[int]) -> bool: + reach_at_least = len(nums) - 1 + + for i in range(len(nums) - 2, -1, -1): + if nums[i] + i >= reach_at_least: + reach_at_least = i + + return reach_at_least == 0 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/jump-game/sunjae95.js b/jump-game/sunjae95.js new file mode 100644 index 000000000..da38aad4d --- /dev/null +++ b/jump-game/sunjae95.js @@ -0,0 +1,17 @@ +/** + * @description + * + * n = length of nums + * time complexity: O(n) + * space complexity: O(1) + */ +var canJump = function (nums) { + let cur = 1; + for (const num of nums) { + if (cur === 0) return false; + cur--; + cur = cur > num ? cur : num; + } + + return true; +}; diff --git a/merge-k-sorted-lists/TonyKim9401.java b/merge-k-sorted-lists/TonyKim9401.java new file mode 100644 index 000000000..ac513085c --- /dev/null +++ b/merge-k-sorted-lists/TonyKim9401.java @@ -0,0 +1,30 @@ +// TC: O(n * log m) +// m -> the number of the lists, n -> the number of node +// SC: O(m) +// m -> the number of the lists (max m) +class Solution { + public ListNode mergeKLists(ListNode[] lists) { + if (lists == null || lists.length == 0) return null; + + PriorityQueue pq = new PriorityQueue<>((a, b) -> a.val - b.val); + + for (ListNode node : lists) { + if (node != null) pq.offer(node); + } + + ListNode dummy = new ListNode(0); + ListNode current = dummy; + + while (!pq.isEmpty()) { + ListNode minNode = pq.poll(); + current.next = minNode; + current = current.next; + + if (minNode.next != null) { + pq.offer(minNode.next); + } + } + + return dummy.next; + } +} diff --git a/merge-k-sorted-lists/haklee.py b/merge-k-sorted-lists/haklee.py new file mode 100644 index 000000000..33ce6818e --- /dev/null +++ b/merge-k-sorted-lists/haklee.py @@ -0,0 +1,56 @@ +"""TC: O(n*log(l)), SC: O(l) + +l은 리스트 개수, n은 전체 아이템 개수 + +아이디어: +- 각 리스트에서 제일 앞에 있는 값을 뽑아서 우선순위 큐에 넣는다. +- 우선순위 큐의 제일 앞에 있는 값을 뽑아서 + - 이 값이 어느 리스트에서 나왔는지 확인해서 해당 리스트의 제일 앞에 있는 값을 새로 뽑아서 우선순위 큐를 채운다. + - 우선순위 큐에서 뽑은 값은 결과 리스트에 더한다. + +SC: +- 우선순위 큐에 최대 list의 개수 만큼의 아이템 존재 가능. O(l). +- + +TC: +- heap 크기는 최대 l이다. +- 이 heap에 아이템을 push하고 pop할때 O(log(l)) 시간 소요. +- 위의 시행을 전체 아이템 개수 만큼 한다. +- 종합하면 O(n*log(l)) +""" + +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next + +from heapq import heappush, heappop + + +class Solution: + def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: + heap = [] + head = ListNode() + tail = head + + # init + for i in range(len(lists)): + if lists[i]: + heappush(heap, (lists[i].val, i)) + lists[i] = lists[i].next + + # process + while heap: + v, idx = heappop(heap) + + # heap 다시 채워넣기 + if lists[idx]: + heappush(heap, (lists[idx].val, idx)) + lists[idx] = lists[idx].next + + # 결과물 채워넣기 + tail.next = ListNode(v) + tail = tail.next + + return head.next 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/merge-k-sorted-lists/sunjae95.js b/merge-k-sorted-lists/sunjae95.js new file mode 100644 index 000000000..c058a0f88 --- /dev/null +++ b/merge-k-sorted-lists/sunjae95.js @@ -0,0 +1,43 @@ +/** + * @description + * queue의 특성을 활용하여 풀이 + * + * n = length of lists + * m = length of lists[i] + * time complexity: O(n * n * m) + * space complexity: O(n*m) + */ +var mergeKLists = function (lists) { + let answer = null; + let tail = null; + let totalSize = lists.reduce((size, list) => { + let head = list; + let count = 0; + + while (head) { + head = head.next; + count++; + } + + return size + count; + }, 0); + + while (totalSize--) { + let minIndex = lists.reduce((acc, list, i) => { + if (list === null) return acc; + if (acc === null) return { value: list.val, index: i }; + return acc.value < list.val ? acc : { value: list.val, index: i }; + }, null).index; + + if (answer === null) { + answer = lists[minIndex]; + tail = answer; + } else { + tail.next = lists[minIndex]; + tail = lists[minIndex]; + } + + lists[minIndex] = lists[minIndex].next; + } + return answer; +}; diff --git a/search-in-rotated-sorted-array/TonyKim9401.java b/search-in-rotated-sorted-array/TonyKim9401.java new file mode 100644 index 000000000..e2d145cf1 --- /dev/null +++ b/search-in-rotated-sorted-array/TonyKim9401.java @@ -0,0 +1,25 @@ +// TC: O(log n) +// -> binary search +// SC: O(1) +class Solution { + public int search(int[] nums, int target) { + + int start = 0; + int end = nums.length - 1; + + while (start <= end) { + int mid = start + (end - start) / 2; + + if (nums[mid] == target) return mid; + + if (nums[start] <= nums[mid]) { + if (nums[start] <= target && target < nums[mid]) end = mid - 1; + else start = mid + 1; + } else { + if (nums[mid] < target && target <= nums[end]) start = mid + 1; + else end = mid - 1; + } + } + return -1; + } +} diff --git a/search-in-rotated-sorted-array/haklee.py b/search-in-rotated-sorted-array/haklee.py new file mode 100644 index 000000000..137bc9292 --- /dev/null +++ b/search-in-rotated-sorted-array/haklee.py @@ -0,0 +1,53 @@ +"""TC: O(n), SC: O(1) + +n은 주어진 리스트의 길이. + +아이디어: +- Rotated Sorted Array는 특성상 `값이 증가하다가 -> 갑자기 값이 한 번 감소 -> 이후 다시 쭉 증가`한다. +- 위의 관찰에 따르면 값이 감소하는 `절점`은 최대 한 군데 있을 수 있다. + - rotate 시행을 0번 한 경우 절점이 없음. 그 외에는 절점이 한 번 생김. +- 즉, 리스트에서 두 구간을 겹치지 않게 잡으면 이 두 구간 중 적어도 한 구간은 ascending order가 보장된다. +- ascending하는 구간에 찾고자 하는 값이 있는지 판별하는 방식으로 binary search와 비슷한 방식으로 search 가능. +- 자세한 내용은 코드를 참조하면 된다. + +SC: +- binary search와 비슷하게, 탐색 구간의 시작, 끝, 중간 인덱스를 관리. O(1). + +TC: +- binary search와 비슷하게 구간이 계속 절반 크기로 줄어든다. O(log(n)). +""" + + +class Solution: + def search(self, nums: List[int], target: int) -> int: + s, e = 0, len(nums) - 1 + while s < e: + m = (s + e) // 2 + + # 절점은 하나다. [s, m]과 [m+1, e]구간 중 한 곳에 절점존재. + # 절점이 없는 구간은 ascending order가 보장되므로, + # 이 구간에 target이 있는지 여부로 둘 중 한 구간을 탐색 구간에서 제외한다. + if nums[s] < nums[m]: + # [s, m] 구간이 ascending order. + if (nums[s] > target and nums[m] > target) or ( + nums[s] < target and nums[m] < target + ): + # nums[s]와 nums[m]이 target보다 둘 다 크거나 둘 다 작으면 + # [m + 1, e] 구간에서 탐색을 이어간다. + s = m + 1 + else: + # 아니면 [s, m] 구간에서 탐색을 이어간다. + e = m + else: + # [m + 1, e] 구간이 ascending order. + if (nums[m + 1] > target and nums[e] > target) or ( + nums[m + 1] < target and nums[e] < target + ): + # nums[m + 1]과 nums[e]가 target보다 둘 다 크거나 둘 다 작으면 + # [s, m] 구간에서 탐색을 이어간다. + e = m + else: + # 아니면 [m + 1, e] 구간에서 탐색을 이어간다. + s = m + 1 + + return s if nums[s] == target else -1 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 + } +} diff --git a/search-in-rotated-sorted-array/sunjae95.js b/search-in-rotated-sorted-array/sunjae95.js new file mode 100644 index 000000000..2578b588d --- /dev/null +++ b/search-in-rotated-sorted-array/sunjae95.js @@ -0,0 +1,22 @@ +/** + * @description + * https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/ + * n = length of nums + * time complexity: O(n) + * space complexity: O(1) + */ +var search = function (nums, target) { + let [start, end] = [0, nums.length - 1]; + let answer = -1; + + while (start !== end) { + if (nums[start] === target) answer = start; + if (nums[end] === target) answer = end; + if (nums[start] > nums[end]) end--; + else start++; + } + + if (nums[start] === target) answer = start; + + return answer; +};