Skip to content

Commit c31a82f

Browse files
committed
3주차 답안 추가
1 parent bb0db1a commit c31a82f

File tree

5 files changed

+290
-0
lines changed

5 files changed

+290
-0
lines changed

climbing-stairs/jdalma.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `climbing-stairs` {
7+
8+
/**
9+
* 1. bottom-up 방식으로 가능한 경우의 수를 누적한다.
10+
* TC: O(n), SC: O(n)
11+
*/
12+
fun climbStairs(n: Int): Int {
13+
val dp = IntArray(n + 1).apply {
14+
this[1] = 1
15+
if (n >= 2) this[2] = 2
16+
}
17+
18+
for (num in 3 .. n) {
19+
dp[num] = dp[num - 1] + dp[num - 2]
20+
}
21+
22+
return dp[n]
23+
}
24+
25+
@Test
26+
fun `입력받은 목푯값에 1과 2만 더하여 도달할 수 있는 경우의 수를 반환한다`() {
27+
climbStairs(1) shouldBe 1
28+
climbStairs(2) shouldBe 2
29+
climbStairs(3) shouldBe 3
30+
climbStairs(4) shouldBe 5
31+
}
32+
}

coin-change/jdalma.kt

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
import java.util.ArrayDeque
6+
import kotlin.math.min
7+
8+
class `coin-change` {
9+
10+
fun coinChange(coins: IntArray, amount: Int): Int {
11+
return topDown(coins, amount)
12+
}
13+
14+
/**
15+
* 1. 모든 코인을 반복적으로 사용하면서 0원에 도달하면 그 때의 step 중 최솟값을 반환한다.
16+
* TC: O(coins^amount), SC: O(amount)
17+
*/
18+
private fun bruteForce(coins: IntArray, amount: Int): Int {
19+
fun dfs(coins: IntArray, remain: Int, step: Int): Int =
20+
if (remain < 0) Int.MAX_VALUE
21+
else if (remain == 0) step
22+
else coins.map { dfs(coins, remain - it, step + 1) }.min()
23+
24+
val result = dfs(coins, amount, 0)
25+
return if (result == Int.MAX_VALUE) -1 else result
26+
}
27+
28+
/**
29+
* 2. 모든 코인들을 종류별로 누적하면서 가장 빨리 목푯값에 도달하는 코인의 수를 반환한다.
30+
* TC: O(amount * coins), SC: O(amount)
31+
*/
32+
private fun bfs(coins: IntArray, amount: Int): Int {
33+
if (amount == 0) return 0
34+
35+
val visited = BooleanArray(amount + 1) { false }
36+
val queue = ArrayDeque<Int>().apply {
37+
this.offer(0)
38+
}
39+
40+
var coinCount = 0
41+
while (queue.isNotEmpty()) {
42+
var size = queue.size
43+
while (size-- > 0) {
44+
val sum = queue.poll()
45+
if (sum == amount) return coinCount
46+
else if (sum < amount && !visited[sum] ) {
47+
visited[sum] = true
48+
for (coin in coins) {
49+
queue.offer(sum + coin)
50+
}
51+
}
52+
}
53+
coinCount++
54+
}
55+
return -1
56+
}
57+
58+
/**
59+
* 3. 반복되는 연산을 DP를 활용하여 bottom-up으로 해결
60+
* 1원부터 목푯값까지 각 가치를 구성하기 위한 최소 코인을 누적하면서 목푯값을 구성하는 최소 코인 개수를 구하는 것이다.
61+
* TC: O(amount*coins), SC: O(amount)
62+
*/
63+
private fun bottomUp(coins: IntArray, amount: Int): Int {
64+
val dp = IntArray(amount + 1) { 10000 + 1 }.apply {
65+
this[0] = 0
66+
}
67+
68+
for (target in 1 .. amount) {
69+
coins.forEach { coin ->
70+
if (coin <= target) {
71+
dp[target] = min(dp[target], dp[target - coin] + 1)
72+
}
73+
}
74+
}
75+
76+
return if (dp[amount] == 10001) -1 else dp[amount]
77+
}
78+
79+
/**
80+
* 4. 목표금액부터 코인만큼 차감하여 0원에 도달하면 백트래킹으로 DP 배열을 갱신한다
81+
* TC: O(amount * coins), SC: O(amount)
82+
*/
83+
private fun topDown(coins: IntArray, amount: Int): Int {
84+
fun recursive(coins: IntArray, remain: Int, dp: IntArray): Int {
85+
if (remain == 0) return 0
86+
else if (remain < 0) return -1
87+
else if (dp[remain] != 10001) return dp[remain]
88+
89+
var minCoins = 10001
90+
coins.forEach { coin ->
91+
val result = recursive(coins, remain - coin, dp)
92+
if (result in 0 until minCoins) {
93+
minCoins = result + 1
94+
}
95+
}
96+
dp[remain] = if (minCoins == 10001) -1 else minCoins
97+
return dp[remain]
98+
}
99+
100+
if (amount < 1) return 0
101+
return recursive(coins, amount, IntArray(amount + 1) { 10000 + 1 })
102+
}
103+
104+
@Test
105+
fun `코인의 종류와 목표값을 입력하면 목푯값을 구성하는 코인의 최소 개수를 반환한다`() {
106+
coinChange(intArrayOf(1,2,5), 11) shouldBe 3
107+
coinChange(intArrayOf(1,3,5), 15) shouldBe 3
108+
coinChange(intArrayOf(5,3,1), 15) shouldBe 3
109+
coinChange(intArrayOf(1,2), 4) shouldBe 2
110+
coinChange(intArrayOf(2,5,10,1), 27) shouldBe 4
111+
}
112+
113+
@Test
114+
fun `코인의 종류로 목표값을 완성할 수 없다면 -1을 반환한다`() {
115+
coinChange(intArrayOf(2), 3) shouldBe -1
116+
}
117+
118+
@Test
119+
fun `목푯값이 0이라면 0을 반환한다`() {
120+
coinChange(intArrayOf(1,2,3), 0) shouldBe 0
121+
}
122+
}

combination-sum/jdalma.kt

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `combination-sum` {
7+
8+
/**
9+
* 후보자를 중복으로 사용할 수 있기에 0부터 후보자들을 누적하면서 target보다 크면 탈출하는 방식이다.
10+
* 만약 target과 동일하다면 누적할 때 사용된 후보자들을 numbers에 저장해뒀기에 결과에 복사한다.
11+
* 시간복잡도: O(n^t), 공간복잡도: O(t)
12+
*/
13+
fun combinationSum(candidates: IntArray, target: Int): List<List<Int>> {
14+
15+
fun backtracking(candidates: IntArray, target: Int, result: MutableList<List<Int>>, numbers: MutableList<Int>, start: Int, total: Int) {
16+
if (total > target) return
17+
else if (total == target) {
18+
result.add(numbers.toList())
19+
} else {
20+
(start until candidates.size).forEach {
21+
numbers.add(candidates[it])
22+
backtracking(candidates, target, result, numbers, it, total + candidates[it])
23+
numbers.removeLast()
24+
}
25+
}
26+
}
27+
28+
val result = mutableListOf<List<Int>>()
29+
backtracking(candidates, target, result, mutableListOf(), 0, 0)
30+
return result
31+
}
32+
33+
@Test
34+
fun `입력받은 정수 리스트를 사용하여 목푯값을 만들어낼 수 있는 모든 경우를 리스트로 반환한다`() {
35+
combinationSum(intArrayOf(2,3,6,7), 7) shouldBe listOf(
36+
listOf(2,2,3),
37+
listOf(7)
38+
)
39+
combinationSum(intArrayOf(2,3,5), 8) shouldBe listOf(
40+
listOf(2,2,2,2),
41+
listOf(2,3,3),
42+
listOf(3,5)
43+
)
44+
combinationSum(intArrayOf(2), 1) shouldBe listOf()
45+
}
46+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `product-of-array-except-self` {
7+
8+
fun productExceptSelf(nums: IntArray): IntArray {
9+
return usingPrefixSum(nums)
10+
}
11+
12+
/**
13+
* 각 인덱스의 전,후를 누적곱을 합하여 이전 연산 결과를 재활용한다.
14+
* 시간복잡도: O(n), 공간복잡도: O(n)
15+
*/
16+
private fun usingPrefixSum(nums: IntArray): IntArray {
17+
val toRight = Array(nums.size) { 1 }
18+
val toLeft = Array(nums.size) { 1 }
19+
20+
(0 until nums.size - 1).forEach {
21+
toRight[it + 1] = toRight[it] * nums[it]
22+
}
23+
(nums.size - 1 downTo 1).forEach {
24+
toLeft[it - 1] = toLeft[it] * nums[it]
25+
}
26+
27+
return nums.indices
28+
.map { toRight[it] * toLeft[it] }
29+
.toIntArray()
30+
}
31+
32+
@Test
33+
fun `입력받은 배열을 순회하며 자기 자신을 제외한 나머지 원소들의 곱한 값을 배열로 반환한다`() {
34+
productExceptSelf(intArrayOf(2,3,4,5)) shouldBe intArrayOf(60,40,30,24)
35+
productExceptSelf(intArrayOf(1,2,3,4)) shouldBe intArrayOf(24,12,8,6)
36+
productExceptSelf(intArrayOf(-1,1,0,-3,3)) shouldBe intArrayOf(0,0,9,0,0)
37+
}
38+
}

two-sum/jdalma.kt

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `two-sum` {
7+
8+
fun twoSum(nums: IntArray, target: Int): IntArray {
9+
return usingMapOptimized(nums, target)
10+
}
11+
12+
/**
13+
* 1. map을 활용
14+
* TC: O(n), SC: O(n)
15+
*/
16+
private fun usingMap(nums: IntArray, target: Int): IntArray {
17+
val map = nums.withIndex().associate { it.value to it.index }
18+
19+
nums.forEachIndexed { i, e ->
20+
val diff: Int = target - e
21+
if (map.containsKey(diff) && map[diff] != i) {
22+
return intArrayOf(i , map[diff]!!)
23+
}
24+
}
25+
return intArrayOf()
26+
}
27+
28+
/**
29+
* 2. map에 모든 값을 초기화할 필요가 없기에, nums를 순회하며 확인한다.
30+
* TC: O(n), SC: O(n)
31+
*/
32+
private fun usingMapOptimized(nums: IntArray, target: Int): IntArray {
33+
val map = mutableMapOf<Int, Int>()
34+
35+
for (index in nums.indices) {
36+
val diff = target - nums[index]
37+
if (map.containsKey(diff)) {
38+
return intArrayOf(map[diff]!!, index)
39+
}
40+
map[nums[index]] = index
41+
}
42+
43+
return intArrayOf()
44+
}
45+
46+
@Test
47+
fun `정수 배열과 목푯값을 입력받아 목푯값을 만들 수 있는 정수 배열의 원소들 중 두 개의 원소의 인덱스를 반환한다`() {
48+
twoSum(intArrayOf(2,7,11,15), 9) shouldBe intArrayOf(0,1)
49+
twoSum(intArrayOf(3,2,4), 6) shouldBe intArrayOf(1,2)
50+
twoSum(intArrayOf(3,3), 6) shouldBe intArrayOf(0,1)
51+
}
52+
}

0 commit comments

Comments
 (0)