Skip to content

Commit 3e80c0b

Browse files
committed
2주차 답안 제출
1 parent e55594c commit 3e80c0b

File tree

5 files changed

+308
-0
lines changed

5 files changed

+308
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.equals.shouldBeEqual
4+
import org.junit.jupiter.api.Test
5+
6+
class `construct-binary-tree-from-preorder-and-inorder-traversal` {
7+
8+
/**
9+
* preorder : 현재(부모) 노드부터 왼쪽 자식 노드, 오른쪽 자식 노드
10+
* inorder : 왼쪽 자식 노드 부터 부모 노드, 오른쪽 자식 노드
11+
*/
12+
fun buildTree(preorder: IntArray, inorder: IntArray): TreeNode? {
13+
val inorderIndices = inorder.withIndex().associate { it.value to it.index }
14+
return traversal(preorder, inorder, inorderIndices)
15+
}
16+
17+
/**
18+
* preorder에서 조회한 부모 노드의 값은 inorder의 중간에 위치한다.
19+
* 그 중간 위치 기준으로 왼쪽 노드, 오른쪽 노드로 분리하여 재귀적으로 탐색할 수 있다.
20+
*/
21+
private fun traversal(
22+
preorder: IntArray, inorder: IntArray, inorderIndices: Map<Int, Int>,
23+
preStart: Int = 0, inStart: Int = 0, inEnd: Int = inorder.size - 1
24+
): TreeNode? {
25+
if (preStart > preorder.size - 1 || inStart > inEnd) {
26+
println("preStart: $preStart, inStart: $inStart, inEnd: $inEnd --- return null")
27+
return null
28+
}
29+
val value = preorder[preStart]
30+
val rootIndexInInorder = inorderIndices[value]!!
31+
32+
println("value: $value, preStart: $preStart, rootIndexInInorder: $rootIndexInInorder, inStart: $inStart, inEnd: $inEnd")
33+
return TreeNode(value).apply {
34+
this.left = traversal(
35+
preorder, inorder, inorderIndices,
36+
preStart + 1, inStart, rootIndexInInorder - 1
37+
)
38+
this.right = traversal(
39+
preorder, inorder, inorderIndices,
40+
preStart + rootIndexInInorder - inStart + 1, rootIndexInInorder + 1, inEnd
41+
)
42+
}
43+
}
44+
45+
@Test
46+
fun `전위 순회, 중위 순회 순서의 정수 배열을 기준으로 이진트리를 생성하여 반환한다`() {
47+
val actual = buildTree(intArrayOf(3,9,20,15,7), intArrayOf(9,3,15,20,7))!!
48+
val expect = TreeNode.of(3,9,20,null,null,15,7)!!
49+
50+
actual shouldBeEqual expect
51+
52+
val actual1 = buildTree(intArrayOf(3,9,8,10,20,15,7), intArrayOf(8,9,10,3,15,20,7))!!
53+
val expect1 = TreeNode.of(3,9,20,8,10,15,7)!!
54+
55+
actual1 shouldBeEqual expect1
56+
}
57+
}

counting-bits/jdalma.kt

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `counting-bits` {
7+
8+
fun countBits(n: Int): IntArray {
9+
return usingDPAndLeastSignificantBit(n)
10+
}
11+
12+
// 1. 입력받은 정수만큼 순회하며 bit 카운트
13+
// 시간복잡도: O(n * log(n)), 공간복잡도: O(1)
14+
private fun usingBinary(n: Int): IntArray {
15+
fun binary(n: Int): Int {
16+
var calc = n
17+
var count = 0
18+
while(calc > 0) {
19+
if (calc % 2 != 0) {
20+
count++
21+
}
22+
calc /= 2
23+
}
24+
return count
25+
}
26+
27+
return (0 .. n).map { binary(it) }.toIntArray()
28+
}
29+
30+
// 2. MSB, 즉 최상위 비트를 활용하여 십진수가 두 배가 될때마다 MSB를 갱신하여 이전의 결과를 활용
31+
// 시간복잡도: O(n), 공간복잡도: O(n)
32+
private fun usingDPAndMostSignificantBit(n: Int): IntArray {
33+
val dp = IntArray(n + 1)
34+
var msb = 1
35+
36+
for (index in 1 .. n) {
37+
if (index == msb shl 1) {
38+
msb = index
39+
}
40+
dp[index] = 1 + dp[index - msb]
41+
}
42+
43+
return dp
44+
}
45+
46+
// 3. 최하위 비트를 제거한 결과를 재활용한다. (최하위 비트를 제거한 결과) + (현재 십진수의 최하위비트)
47+
// 시간복잡도: O(n), 공간복잡도: O(n)
48+
private fun usingDPAndLeastSignificantBit(n: Int): IntArray {
49+
val dp = IntArray(n + 1)
50+
for (index in 1 .. n) {
51+
dp[index] = dp[index shr 1] + (index and 1)
52+
}
53+
54+
return dp
55+
}
56+
57+
@Test
58+
fun `정수가 주어지면 각 i(0 ~ i)에 대해 이진 표현에서 1의 개수를 저장하는 배열을 반환한다`() {
59+
countBits(2) shouldBe intArrayOf(0,1,1)
60+
countBits(5) shouldBe intArrayOf(0,1,1,2,1,2)
61+
}
62+
}

decode-ways/jdalma.kt

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `decode-ways` {
7+
8+
fun numDecodings(s: String): Int {
9+
return usingOptimizedDP(s)
10+
}
11+
12+
/**
13+
* 1. 문자열의 첫 인덱스부터 DFS로 확인하면서 결과를 증가시킨다. → 시간초과
14+
* 0부터 시작하는 문자열은 존재하지 않기에 바로 0으로 반환하고 그 뒤 숫자부터 DFS를 연이어 실행한다.
15+
* 시간복잡도: O(2^n), 공간복잡도: O(n)
16+
*/
17+
private fun usingDfs(s: String): Int {
18+
fun dfs(index: Int): Int =
19+
if (index == s.length) 1
20+
else if (s[index] == '0') 0
21+
else if (index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7')) )
22+
dfs(index + 1) + dfs(index + 2)
23+
else dfs(index + 1)
24+
25+
return dfs(0)
26+
}
27+
28+
/**
29+
* 2. 1번 풀이에서 중복되는 연산에 top-down 방향으로 메모이제이션 적용
30+
* 시간복잡도: O(n), 공간복잡도: O(n)
31+
*/
32+
private fun usingMemoization(s: String): Int {
33+
fun dfs(index: Int, mem: IntArray): Int {
34+
println(index)
35+
mem[index] = if (index == s.length) 1
36+
else if (s[index] == '0') 0
37+
else if (mem[index] != 0) mem[index]
38+
else if (index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7')) )
39+
dfs(index + 1, mem) + dfs(index + 2, mem)
40+
else dfs(index + 1, mem)
41+
42+
return mem[index]
43+
}
44+
return dfs(0, IntArray(s.length + 1) { 0 })
45+
}
46+
47+
/**
48+
* 3. 마지막 숫자부터 bottom-up 방향 DP
49+
* 시간복잡도: O(n), 공간복잡도: O(n)
50+
*/
51+
private fun usingDP(s: String): Int {
52+
val dp = IntArray(s.length + 1).apply {
53+
this[s.length] = 1
54+
}
55+
56+
(s.length - 1 downTo 0).forEach { index ->
57+
if (s[index] == '0') dp[index] = 0
58+
else if(index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7')))
59+
dp[index] = dp[index + 1] + dp[index + 2]
60+
else dp[index] = dp[index + 1]
61+
}
62+
63+
return dp[0]
64+
}
65+
66+
/**
67+
* 4. 배열을 사용하지 않고 DP 적용
68+
* 시간복잡도: O(n), 공간복잡도: O(1)
69+
*/
70+
private fun usingOptimizedDP(s: String): Int {
71+
var (memo, result) = 0 to 1
72+
73+
(s.length - 1 downTo 0).forEach { index ->
74+
var tmp = if (s[index] == '0') 0 else result
75+
76+
if (index + 1 < s.length && (s[index] == '1' || (s[index] == '2' && s[index + 1] < '7'))) {
77+
tmp += memo
78+
}
79+
memo = result
80+
result = tmp
81+
}
82+
83+
return result
84+
}
85+
86+
@Test
87+
fun `입력받은 문자열의 디코딩 가능한 경우의 수를 반환한다`() {
88+
numDecodings("12") shouldBe 2
89+
numDecodings("226") shouldBe 3
90+
numDecodings("06") shouldBe 0
91+
numDecodings("1011") shouldBe 2
92+
numDecodings("10112266") shouldBe 8
93+
numDecodings("1025") shouldBe 2
94+
}
95+
}

encode-and-decode-strings/jdalma.kt

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.equals.shouldBeEqual
4+
import org.junit.jupiter.api.Test
5+
6+
/**
7+
* 인코딩과 디코딩을 해결할 때 구분자를 256개의 ASCII 문자 중 하나를 사용해야한다면 아래와 같은 방법을 사용할 수 있다.
8+
* 시간복잡도: O(n), 공간복잡도: O(1)
9+
*/
10+
class `encode-and-decode-strings` {
11+
12+
private val DELIMITER = ":"
13+
14+
fun encode(strings: List<String>): String {
15+
return strings.joinToString(separator = "") { e -> "${e.length}$DELIMITER$e" }
16+
}
17+
18+
fun decode(string: String): List<String> {
19+
var index = 0
20+
val result = mutableListOf<String>()
21+
while (index < string.length) {
22+
val delimiterIndex = string.indexOf(DELIMITER, startIndex = index)
23+
val size = string.substring(index , delimiterIndex).toInt()
24+
result.add(string.substring(delimiterIndex + 1, delimiterIndex + size + 1))
25+
index = delimiterIndex + size + 1
26+
}
27+
return result
28+
}
29+
30+
@Test
31+
fun `문자열 목록을 하나의 문자열로 인코딩한다`() {
32+
encode(listOf("leet","co:de","l:o:v:e","you")) shouldBeEqual "4:leet5:co:de7:l:o:v:e3:you"
33+
}
34+
35+
@Test
36+
fun `문자열을 문자열 목록으로 디코딩한다`() {
37+
decode("4:leet5:co:de7:l:o:v:e3:you") shouldBeEqual listOf("leet","co:de","l:o:v:e","you")
38+
}
39+
}

valid-anagram/jdalma.kt

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package leetcode_study
2+
3+
import org.junit.jupiter.api.Test
4+
5+
class `valid-anagram` {
6+
7+
fun isAnagram(s: String, t: String): Boolean {
8+
return usingArray(s, t)
9+
}
10+
11+
// 1. 두 문자열을 정렬하여 비교한다.
12+
// 시간복잡도: O(n * log(n)), 공간복잡도: O(n)
13+
private fun usingSort(s: String, t: String): Boolean {
14+
val sorted1 = s.toCharArray().apply { this.sort() }
15+
val sorted2 = t.toCharArray().apply { this.sort() }
16+
17+
return sorted1.contentEquals(sorted2)
18+
}
19+
20+
// 2. 배열에 문자 수 가감
21+
// 시간복잡도: O(n), 공간복잡도: O(n)
22+
private fun usingArray(s: String, t: String): Boolean {
23+
if (s.length != t.length) {
24+
return false
25+
}
26+
27+
/* 해시맵 사용
28+
val map: Map<Char, Int> = mutableMapOf<Char, Int>().apply {
29+
(s.indices).forEach { index ->
30+
this[s[index]] = this.getOrDefault(s[index], 0) + 1
31+
this[t[index]] = this.getOrDefault(t[index], 0) - 1
32+
}
33+
}
34+
return map.values.find { it > 0 } == null
35+
*/
36+
37+
return IntArray(26).apply {
38+
for (index in s.indices) {
39+
this[s[index] - 'a'] = this[s[index] - 'a'] + 1
40+
this[t[index] - 'a'] = this[t[index] - 'a'] - 1
41+
}
42+
}.find { it > 0 } == null
43+
}
44+
45+
@Test
46+
fun `입력받은 두 문자열이 애너그램이라면 참을 반환한다`() {
47+
isAnagram("anagram", "nagaram")
48+
isAnagram("test", "estt")
49+
}
50+
51+
@Test
52+
fun `입력받은 두 문자열이 애너그램이 아니라면 거짓을 반환한다`() {
53+
isAnagram("cat", "rat")
54+
}
55+
}

0 commit comments

Comments
 (0)