From 3e80c0b9508f54a48a0d1e681f3f0230a9ceae47 Mon Sep 17 00:00:00 2001 From: jeongdalma Date: Mon, 19 Aug 2024 17:22:59 +0900 Subject: [PATCH 1/3] =?UTF-8?q?2=EC=A3=BC=EC=B0=A8=20=EB=8B=B5=EC=95=88=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jdalma.kt | 57 +++++++++++ counting-bits/jdalma.kt | 62 ++++++++++++ decode-ways/jdalma.kt | 95 +++++++++++++++++++ encode-and-decode-strings/jdalma.kt | 39 ++++++++ valid-anagram/jdalma.kt | 55 +++++++++++ 5 files changed, 308 insertions(+) create mode 100644 construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt create mode 100644 counting-bits/jdalma.kt create mode 100644 decode-ways/jdalma.kt create mode 100644 encode-and-decode-strings/jdalma.kt create mode 100644 valid-anagram/jdalma.kt diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt b/construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt new file mode 100644 index 00000000..586423d1 --- /dev/null +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt @@ -0,0 +1,57 @@ +package leetcode_study + +import io.kotest.matchers.equals.shouldBeEqual +import org.junit.jupiter.api.Test + +class `construct-binary-tree-from-preorder-and-inorder-traversal` { + + /** + * preorder : 현재(부모) 노드부터 왼쪽 자식 노드, 오른쪽 자식 노드 + * inorder : 왼쪽 자식 노드 부터 부모 노드, 오른쪽 자식 노드 + */ + fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? { + val inorderIndices = inorder.withIndex().associate { it.value to it.index } + return traversal(preorder, inorder, inorderIndices) + } + + /** + * preorder에서 조회한 부모 노드의 값은 inorder의 중간에 위치한다. + * 그 중간 위치 기준으로 왼쪽 노드, 오른쪽 노드로 분리하여 재귀적으로 탐색할 수 있다. + */ + private fun traversal( + preorder: IntArray, inorder: IntArray, inorderIndices: Map, + preStart: Int = 0, inStart: Int = 0, inEnd: Int = inorder.size - 1 + ): TreeNode? { + if (preStart > preorder.size - 1 || inStart > inEnd) { + println("preStart: $preStart, inStart: $inStart, inEnd: $inEnd --- return null") + return null + } + val value = preorder[preStart] + val rootIndexInInorder = inorderIndices[value]!! + + println("value: $value, preStart: $preStart, rootIndexInInorder: $rootIndexInInorder, inStart: $inStart, inEnd: $inEnd") + return TreeNode(value).apply { + this.left = traversal( + preorder, inorder, inorderIndices, + preStart + 1, inStart, rootIndexInInorder - 1 + ) + this.right = traversal( + preorder, inorder, inorderIndices, + preStart + rootIndexInInorder - inStart + 1, rootIndexInInorder + 1, inEnd + ) + } + } + + @Test + fun `전위 순회, 중위 순회 순서의 정수 배열을 기준으로 이진트리를 생성하여 반환한다`() { + val actual = buildTree(intArrayOf(3,9,20,15,7), intArrayOf(9,3,15,20,7))!! + val expect = TreeNode.of(3,9,20,null,null,15,7)!! + + actual shouldBeEqual expect + + val actual1 = buildTree(intArrayOf(3,9,8,10,20,15,7), intArrayOf(8,9,10,3,15,20,7))!! + val expect1 = TreeNode.of(3,9,20,8,10,15,7)!! + + actual1 shouldBeEqual expect1 + } +} diff --git a/counting-bits/jdalma.kt b/counting-bits/jdalma.kt new file mode 100644 index 00000000..13d93e7f --- /dev/null +++ b/counting-bits/jdalma.kt @@ -0,0 +1,62 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class `counting-bits` { + + fun countBits(n: Int): IntArray { + return usingDPAndLeastSignificantBit(n) + } + + // 1. 입력받은 정수만큼 순회하며 bit 카운트 + // 시간복잡도: O(n * log(n)), 공간복잡도: O(1) + private fun usingBinary(n: Int): IntArray { + fun binary(n: Int): Int { + var calc = n + var count = 0 + while(calc > 0) { + if (calc % 2 != 0) { + count++ + } + calc /= 2 + } + return count + } + + return (0 .. n).map { binary(it) }.toIntArray() + } + + // 2. MSB, 즉 최상위 비트를 활용하여 십진수가 두 배가 될때마다 MSB를 갱신하여 이전의 결과를 활용 + // 시간복잡도: O(n), 공간복잡도: O(n) + private fun usingDPAndMostSignificantBit(n: Int): IntArray { + val dp = IntArray(n + 1) + var msb = 1 + + for (index in 1 .. n) { + if (index == msb shl 1) { + msb = index + } + dp[index] = 1 + dp[index - msb] + } + + return dp + } + + // 3. 최하위 비트를 제거한 결과를 재활용한다. (최하위 비트를 제거한 결과) + (현재 십진수의 최하위비트) + // 시간복잡도: O(n), 공간복잡도: O(n) + private fun usingDPAndLeastSignificantBit(n: Int): IntArray { + val dp = IntArray(n + 1) + for (index in 1 .. n) { + dp[index] = dp[index shr 1] + (index and 1) + } + + return dp + } + + @Test + fun `정수가 주어지면 각 i(0 ~ i)에 대해 이진 표현에서 1의 개수를 저장하는 배열을 반환한다`() { + countBits(2) shouldBe intArrayOf(0,1,1) + countBits(5) shouldBe intArrayOf(0,1,1,2,1,2) + } +} diff --git a/decode-ways/jdalma.kt b/decode-ways/jdalma.kt new file mode 100644 index 00000000..75808315 --- /dev/null +++ b/decode-ways/jdalma.kt @@ -0,0 +1,95 @@ +package leetcode_study + +import io.kotest.matchers.shouldBe +import org.junit.jupiter.api.Test + +class `decode-ways` { + + fun numDecodings(s: String): Int { + return usingOptimizedDP(s) + } + + /** + * 1. 문자열의 첫 인덱스부터 DFS로 확인하면서 결과를 증가시킨다. → 시간초과 + * 0부터 시작하는 문자열은 존재하지 않기에 바로 0으로 반환하고 그 뒤 숫자부터 DFS를 연이어 실행한다. + * 시간복잡도: O(2^n), 공간복잡도: O(n) + */ + private fun usingDfs(s: String): Int { + fun dfs(index: Int): Int = + if (index == s.length) 1 + else if (s[index] == '0') 0 + else if (index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7')) ) + dfs(index + 1) + dfs(index + 2) + else dfs(index + 1) + + return dfs(0) + } + + /** + * 2. 1번 풀이에서 중복되는 연산에 top-down 방향으로 메모이제이션 적용 + * 시간복잡도: O(n), 공간복잡도: O(n) + */ + private fun usingMemoization(s: String): Int { + fun dfs(index: Int, mem: IntArray): Int { + println(index) + mem[index] = if (index == s.length) 1 + else if (s[index] == '0') 0 + else if (mem[index] != 0) mem[index] + else if (index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7')) ) + dfs(index + 1, mem) + dfs(index + 2, mem) + else dfs(index + 1, mem) + + return mem[index] + } + return dfs(0, IntArray(s.length + 1) { 0 }) + } + + /** + * 3. 마지막 숫자부터 bottom-up 방향 DP + * 시간복잡도: O(n), 공간복잡도: O(n) + */ + private fun usingDP(s: String): Int { + val dp = IntArray(s.length + 1).apply { + this[s.length] = 1 + } + + (s.length - 1 downTo 0).forEach { index -> + if (s[index] == '0') dp[index] = 0 + else if(index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7'))) + dp[index] = dp[index + 1] + dp[index + 2] + else dp[index] = dp[index + 1] + } + + return dp[0] + } + + /** + * 4. 배열을 사용하지 않고 DP 적용 + * 시간복잡도: O(n), 공간복잡도: O(1) + */ + private fun usingOptimizedDP(s: String): Int { + var (memo, result) = 0 to 1 + + (s.length - 1 downTo 0).forEach { index -> + var tmp = if (s[index] == '0') 0 else result + + if (index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7'))) { + tmp += memo + } + memo = result + result = tmp + } + + return result + } + + @Test + fun `입력받은 문자열의 디코딩 가능한 경우의 수를 반환한다`() { + numDecodings("12") shouldBe 2 + numDecodings("226") shouldBe 3 + numDecodings("06") shouldBe 0 + numDecodings("1011") shouldBe 2 + numDecodings("10112266") shouldBe 8 + numDecodings("1025") shouldBe 2 + } +} diff --git a/encode-and-decode-strings/jdalma.kt b/encode-and-decode-strings/jdalma.kt new file mode 100644 index 00000000..6710b29a --- /dev/null +++ b/encode-and-decode-strings/jdalma.kt @@ -0,0 +1,39 @@ +package leetcode_study + +import io.kotest.matchers.equals.shouldBeEqual +import org.junit.jupiter.api.Test + +/** + * 인코딩과 디코딩을 해결할 때 구분자를 256개의 ASCII 문자 중 하나를 사용해야한다면 아래와 같은 방법을 사용할 수 있다. + * 시간복잡도: O(n), 공간복잡도: O(1) + */ +class `encode-and-decode-strings` { + + private val DELIMITER = ":" + + fun encode(strings: List): String { + return strings.joinToString(separator = "") { e -> "${e.length}$DELIMITER$e" } + } + + fun decode(string: String): List { + var index = 0 + val result = mutableListOf() + while (index < string.length) { + val delimiterIndex = string.indexOf(DELIMITER, startIndex = index) + val size = string.substring(index , delimiterIndex).toInt() + result.add(string.substring(delimiterIndex + 1, delimiterIndex + size + 1)) + index = delimiterIndex + size + 1 + } + return result + } + + @Test + fun `문자열 목록을 하나의 문자열로 인코딩한다`() { + encode(listOf("leet","co:de","l:o:v:e","you")) shouldBeEqual "4:leet5:co:de7:l:o:v:e3:you" + } + + @Test + fun `문자열을 문자열 목록으로 디코딩한다`() { + decode("4:leet5:co:de7:l:o:v:e3:you") shouldBeEqual listOf("leet","co:de","l:o:v:e","you") + } +} diff --git a/valid-anagram/jdalma.kt b/valid-anagram/jdalma.kt new file mode 100644 index 00000000..4c460228 --- /dev/null +++ b/valid-anagram/jdalma.kt @@ -0,0 +1,55 @@ +package leetcode_study + +import org.junit.jupiter.api.Test + +class `valid-anagram` { + + fun isAnagram(s: String, t: String): Boolean { + return usingArray(s, t) + } + + // 1. 두 문자열을 정렬하여 비교한다. + // 시간복잡도: O(n * log(n)), 공간복잡도: O(n) + private fun usingSort(s: String, t: String): Boolean { + val sorted1 = s.toCharArray().apply { this.sort() } + val sorted2 = t.toCharArray().apply { this.sort() } + + return sorted1.contentEquals(sorted2) + } + + // 2. 배열에 문자 수 가감 + // 시간복잡도: O(n), 공간복잡도: O(n) + private fun usingArray(s: String, t: String): Boolean { + if (s.length != t.length) { + return false + } + + /* 해시맵 사용 + val map: Map = mutableMapOf().apply { + (s.indices).forEach { index -> + this[s[index]] = this.getOrDefault(s[index], 0) + 1 + this[t[index]] = this.getOrDefault(t[index], 0) - 1 + } + } + return map.values.find { it > 0 } == null + */ + + return IntArray(26).apply { + for (index in s.indices) { + this[s[index] - 'a'] = this[s[index] - 'a'] + 1 + this[t[index] - 'a'] = this[t[index] - 'a'] - 1 + } + }.find { it > 0 } == null + } + + @Test + fun `입력받은 두 문자열이 애너그램이라면 참을 반환한다`() { + isAnagram("anagram", "nagaram") + isAnagram("test", "estt") + } + + @Test + fun `입력받은 두 문자열이 애너그램이 아니라면 거짓을 반환한다`() { + isAnagram("cat", "rat") + } +} From 3eadda09115fee9445532d0dab6c0f391f8f8925 Mon Sep 17 00:00:00 2001 From: jeongdalma Date: Wed, 21 Aug 2024 15:28:37 +0900 Subject: [PATCH 2/3] =?UTF-8?q?=EB=B9=85=EC=98=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jdalma.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt b/construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt index 586423d1..d2327cb6 100644 --- a/construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt +++ b/construct-binary-tree-from-preorder-and-inorder-traversal/jdalma.kt @@ -17,19 +17,18 @@ class `construct-binary-tree-from-preorder-and-inorder-traversal` { /** * preorder에서 조회한 부모 노드의 값은 inorder의 중간에 위치한다. * 그 중간 위치 기준으로 왼쪽 노드, 오른쪽 노드로 분리하여 재귀적으로 탐색할 수 있다. + * 시간복잡도: O(n), 공간복잡도: O(n) */ private fun traversal( preorder: IntArray, inorder: IntArray, inorderIndices: Map, preStart: Int = 0, inStart: Int = 0, inEnd: Int = inorder.size - 1 ): TreeNode? { if (preStart > preorder.size - 1 || inStart > inEnd) { - println("preStart: $preStart, inStart: $inStart, inEnd: $inEnd --- return null") return null } val value = preorder[preStart] val rootIndexInInorder = inorderIndices[value]!! - println("value: $value, preStart: $preStart, rootIndexInInorder: $rootIndexInInorder, inStart: $inStart, inEnd: $inEnd") return TreeNode(value).apply { this.left = traversal( preorder, inorder, inorderIndices, From b02d06f62dbd1d85ad9ea2d97bddd688eb6ef7d8 Mon Sep 17 00:00:00 2001 From: Hyunjun Jeong Date: Wed, 21 Aug 2024 15:29:02 +0900 Subject: [PATCH 3/3] Update encode-and-decode-strings/jdalma.kt Co-authored-by: Dale Seo <5466341+DaleSeo@users.noreply.github.com> --- encode-and-decode-strings/jdalma.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/encode-and-decode-strings/jdalma.kt b/encode-and-decode-strings/jdalma.kt index 6710b29a..72470023 100644 --- a/encode-and-decode-strings/jdalma.kt +++ b/encode-and-decode-strings/jdalma.kt @@ -20,7 +20,7 @@ class `encode-and-decode-strings` { val result = mutableListOf() while (index < string.length) { val delimiterIndex = string.indexOf(DELIMITER, startIndex = index) - val size = string.substring(index , delimiterIndex).toInt() + val size = string.substring(index, delimiterIndex).toInt() result.add(string.substring(delimiterIndex + 1, delimiterIndex + size + 1)) index = delimiterIndex + size + 1 }