Skip to content
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

Merged
merged 3 commits into from
Aug 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
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? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 현준님
이 문제에 대해서도 Big-O analysis를 추가해주시면 좋을 것 같습니다 :D

val inorderIndices = inorder.withIndex().associate { it.value to it.index }
return traversal(preorder, inorder, inorderIndices)
}

/**
* preorder에서 조회한 부모 노드의 값은 inorder의 중간에 위치한다.
* 그 중간 위치 기준으로 왼쪽 노드, 오른쪽 노드로 분리하여 재귀적으로 탐색할 수 있다.
* 시간복잡도: O(n), 공간복잡도: O(n)
*/
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) {
return null
}
val value = preorder[preStart]
val rootIndexInInorder = inorderIndices[value]!!

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
}
}
62 changes: 62 additions & 0 deletions counting-bits/jdalma.kt
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. 최하위 비트를 제거한 결과를 재활용한다. (최하위 비트를 제거한 결과) + (현재 십진수의 최하위비트)
Copy link
Contributor

Choose a reason for hiding this comment

The 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)
}
}
95 changes: 95 additions & 0 deletions decode-ways/jdalma.kt
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 적용
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 풀이도 정말 좋네요
memoisation 배열의 모든 값이 필요하지 않다는 점을 잘 캐치하신 것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

대부분 달래님 해설로 풀었습니다 ㅎㅎ

Copy link
Contributor

Choose a reason for hiding this comment

The 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
}
}
39 changes: 39 additions & 0 deletions encode-and-decode-strings/jdalma.kt
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()
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

테스트 케이스가 아주 적절하네요! 💯

}
55 changes: 55 additions & 0 deletions valid-anagram/jdalma.kt
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")
}
}