-
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
[정현준] 2주차 답안 제출 #347
[정현준] 2주차 답안 제출 #347
Changes from 1 commit
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,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<Int, Int>, | ||
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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. 최하위 비트를 제거한 결과를 재활용한다. (최하위 비트를 제거한 결과) + (현재 십진수의 최하위비트) | ||
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. 이 방법 너무 좋은 것 같습니다 :D |
||
// 시간복잡도: 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) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 적용 | ||
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. 이 풀이도 정말 좋네요 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. 대부분 달래님 해설로 풀었습니다 ㅎㅎ 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(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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>): String { | ||
return strings.joinToString(separator = "") { e -> "${e.length}$DELIMITER$e" } | ||
} | ||
|
||
fun decode(string: String): List<String> { | ||
var index = 0 | ||
val result = mutableListOf<String>() | ||
while (index < string.length) { | ||
val delimiterIndex = string.indexOf(DELIMITER, startIndex = index) | ||
val size = string.substring(index , delimiterIndex).toInt() | ||
jdalma marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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") | ||
} | ||
Comment on lines
+30
to
+38
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. 테스트 케이스가 아주 적절하네요! 💯 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<Char, Int> = mutableMapOf<Char, Int>().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") | ||
} | ||
} |
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.
안녕하세요 현준님
이 문제에 대해서도 Big-O analysis를 추가해주시면 좋을 것 같습니다 :D