Skip to content

Commit 2e25eef

Browse files
committed
5주차 답안 제출
1 parent 92359ca commit 2e25eef

File tree

5 files changed

+352
-0
lines changed

5 files changed

+352
-0
lines changed

3sum/jdalma.kt

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
4+
import org.junit.jupiter.api.Test
5+
6+
class `3sum` {
7+
8+
fun threeSum(nums: IntArray): List<List<Int>> {
9+
return usingTwoPointer(nums)
10+
}
11+
12+
/**
13+
* 1. 정수 배열을 순회하며 모두 확인한다. (시간초과)
14+
* TC: O(n^3), SC: O(n)
15+
*/
16+
private fun usingIterative(nums: IntArray): List<List<Int>> {
17+
val result = mutableSetOf<List<Int>>()
18+
for (first in nums.indices) {
19+
for (second in first + 1 until nums.size) {
20+
for (third in second + 1 until nums.size) {
21+
if (nums[first] + nums[second] + nums[third] == 0) {
22+
result.add(listOf(nums[first], nums[second], nums[third]).sorted())
23+
}
24+
}
25+
}
26+
}
27+
return result.toList()
28+
}
29+
30+
/**
31+
* 2. 입력받은 정수 배열을 정렬하여 순회하면서 원소를 합산하여 0과 비교한 결과를 기준으로 투 포인터의 값을 조작한다.
32+
* TC: O(n * log(n) + n^2), SC: O(n)
33+
*/
34+
private fun usingTwoPointer(nums: IntArray): List<List<Int>> {
35+
val sortedNumbers = nums.sorted()
36+
val result = mutableSetOf<List<Int>>()
37+
for (index in nums.indices) {
38+
var left = index + 1
39+
var right = nums.size - 1
40+
while (left < right) {
41+
val sum = sortedNumbers[index] + sortedNumbers[left] + sortedNumbers[right]
42+
if (sum == 0) {
43+
result.add(listOf(sortedNumbers[index], sortedNumbers[left], sortedNumbers[right]))
44+
left++
45+
right--
46+
} else if (sum < 0) {
47+
left++
48+
} else {
49+
right--
50+
}
51+
}
52+
}
53+
return result.toList()
54+
}
55+
56+
@Test
57+
fun `입력받은 정수 배열의 세 개의 원소의 합이 0이 되는 리스트를 반환한다`() {
58+
threeSum(intArrayOf(-1,0,1,2,-1,-4)) shouldContainExactlyInAnyOrder listOf(
59+
listOf(-1,-1,2),
60+
listOf(-1,0,1)
61+
)
62+
threeSum(intArrayOf(0,0,0)) shouldContainExactlyInAnyOrder listOf(
63+
listOf(0,0,0)
64+
)
65+
}
66+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
import kotlin.math.max
6+
7+
class `best-time-to-buy-and-sell-stock` {
8+
9+
fun maxProfit(prices: IntArray): Int {
10+
if (prices.size == 1) return 0
11+
return usingKadaneAlgorithm(prices)
12+
}
13+
14+
/**
15+
* 1. 방향이 존재하기 때문에 투 포인터를 활용하여 주식을 팔 수 있는 경우라면 최대 값을 계산하고 만약 산 가격보다 싼 가격을 만나면 다시 산다
16+
* TC: O(n), SC: O(1)
17+
*/
18+
private fun usingTwoPointer(prices: IntArray): Int {
19+
var (left, right) = 0 to 1
20+
var maxProfit = 0
21+
22+
while (right < prices.size) {
23+
if (prices[left] < prices[right]) {
24+
maxProfit = max(prices[right] - prices[left], maxProfit)
25+
right++
26+
} else if (prices[left] >= prices[right]) {
27+
left = right
28+
right++
29+
}
30+
}
31+
32+
return maxProfit
33+
}
34+
35+
/**
36+
* 2. 카데인 알고리즘의 변형된 버전으로 가장 싼 경우를 buy에 저장하고 현재 최대 수익을 초과하면 업데이트한다
37+
* TC: O(n), SC: O(1)
38+
*/
39+
private fun usingKadaneAlgorithm(prices: IntArray): Int {
40+
var buy = prices[0]
41+
var maxProfit = 0
42+
43+
for (index in 1 until prices.size) {
44+
if (prices[index] < buy) {
45+
buy = prices[index]
46+
} else if (prices[index] - buy > maxProfit) {
47+
maxProfit = prices[index] - buy
48+
}
49+
}
50+
return maxProfit
51+
}
52+
53+
@Test
54+
fun `주어진 가격 배열을 통해 최대의 수익을 반환한다`() {
55+
maxProfit(intArrayOf(3,3)) shouldBe 0
56+
maxProfit(intArrayOf(7,6,5,4,3,2,1,0)) shouldBe 0
57+
maxProfit(intArrayOf(7,1,5,3,6,4)) shouldBe 5
58+
maxProfit(intArrayOf(1,2,4,2,5,7,2,4,9,0,9)) shouldBe 9
59+
}
60+
}

group-anagrams/jdalma.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
4+
import org.junit.jupiter.api.Test
5+
6+
class `group-anagrams` {
7+
8+
fun groupAnagrams(strs: Array<String>): List<List<String>> {
9+
return usingArray(strs)
10+
}
11+
12+
/**
13+
* 1. 입력받은 문자열들을 문자 배열로 변환하여 정렬된 결과를 map 의 키로 정하여 키 기준으로 문자열들을 그룹화한다.
14+
* TC: O(n * k log(k)), SC: O(n)
15+
*/
16+
private fun usingSort(strs: Array<String>): List<List<String>> {
17+
val map = strs.groupBy { it.toCharArray().sorted() }
18+
return map.values.toList()
19+
}
20+
21+
/**
22+
* 2. 입력받은 문자열들을 순회하며 문자열의 문자 갯수를 카운트하여 애너그램인지 구별한다.
23+
* TC: O(n), SC: O(n)
24+
*/
25+
private fun usingArray(strs: Array<String>): List<List<String>> {
26+
val map = strs.groupBy { it ->
27+
val counter = IntArray(26)
28+
for (ch in it) {
29+
counter[ch - 'a']++
30+
}
31+
counter.joinToString(",") // 구분자를 넣지 않으면 arrayOf("bdddddddddd","bbbbbbbbbbc") 테케를 실패함
32+
}
33+
34+
return map.values.toList()
35+
}
36+
37+
@Test
38+
fun `입력받은 문자열들을 애너그램 기준 그룹별로 반환한다`() {
39+
groupAnagrams(arrayOf("eat","tea","tan","ate","nat","bat")) shouldContainExactlyInAnyOrder listOf(
40+
listOf("tan","nat"),
41+
listOf("bat"),
42+
listOf("eat","tea","ate"),
43+
)
44+
groupAnagrams(arrayOf("cab","tin","pew","duh","may","ill","buy","bar","max","doc")) shouldContainExactlyInAnyOrder listOf(
45+
listOf("max"),listOf("buy"),listOf("doc"),listOf("may"),listOf("ill"),
46+
listOf("duh"),listOf("tin"),listOf("bar"),listOf("pew"),listOf("cab")
47+
)
48+
groupAnagrams(arrayOf("bdddddddddd","bbbbbbbbbbc")) shouldContainExactlyInAnyOrder listOf(
49+
listOf("bbbbbbbbbbc"),
50+
listOf("bdddddddddd")
51+
)
52+
}
53+
}

implement-trie-prefix-tree/jdalma.kt

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `implement-trie-prefix-tree` {
7+
8+
/**
9+
* 영어 소문자만 입력된다.
10+
*/
11+
class Trie {
12+
13+
private val node = Node()
14+
15+
/**
16+
* TC: O(n), SC: O(n)
17+
*/
18+
fun insert(word: String) {
19+
var now = node
20+
21+
for (char in word) {
22+
val index = char - 'a'
23+
if (now.next[index] == null) {
24+
now.next[index] = Node()
25+
}
26+
now.next[index]?.apply { now = this }
27+
}
28+
now.isEnd = true
29+
}
30+
31+
/**
32+
* TC: O(n), SC: O(1)
33+
*/
34+
fun search(word: String): Boolean {
35+
var now = node
36+
37+
for (char in word) {
38+
val index = char - 'a'
39+
if (now.next[index] == null) {
40+
return false
41+
}
42+
now.next[index]?.apply { now = this }
43+
}
44+
45+
return now.isEnd
46+
}
47+
48+
/**
49+
* TC: O(n), SC: O(1)
50+
*/
51+
fun startsWith(prefix: String): Boolean {
52+
var now = node
53+
54+
for (char in prefix) {
55+
val index = char - 'a'
56+
if (now.next[index] == null) {
57+
return false
58+
}
59+
now.next[index]?.apply { now = this }
60+
}
61+
62+
return true
63+
}
64+
65+
}
66+
67+
@Test
68+
fun `접두사 트리를 구현하라`() {
69+
val trie = Trie()
70+
trie.insert("apple")
71+
trie.search("apple") shouldBe true
72+
trie.search("app") shouldBe false
73+
trie.startsWith("app") shouldBe true
74+
trie.insert("app")
75+
trie.search("app") shouldBe true
76+
}
77+
}
78+
79+
private class Node {
80+
val next = Array<Node?>(26) { null }
81+
var isEnd = false
82+
}

word-break/jdalma.kt

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `word-break` {
7+
8+
fun wordBreak(s: String, wordDict: List<String>): Boolean {
9+
return usingDP(s, wordDict)
10+
}
11+
12+
/**
13+
* 1. DFS 사용 (시간초과)
14+
* TC: O(s^w * wordDict 단어의 길이), SC: O(s)
15+
*/
16+
private fun usingDFS(s: String, wordDict: List<String>): Boolean {
17+
18+
fun recursion(s: String, wordDict: List<String>, index: Int): Boolean =
19+
if (index == s.length) true
20+
else {
21+
wordDict.map { word ->
22+
var result = false
23+
if (index + word.length < s.length + 1 && s.substring(index, index + word.length) == word) {
24+
result = recursion(s, wordDict, index + word.length)
25+
}
26+
result
27+
}.find { it } ?: false
28+
}
29+
30+
return recursion(s, wordDict, 0)
31+
}
32+
33+
/**
34+
* 2, 사전에 담겨있는 문자열들을 기준으로 인덱스를 증가시키면서 문자열을 완성시킨다. 한 번 탐색하여 문자열을 완성시키지 못한 인덱스를 저장하여 해당 인덱스는 다시 탐색하지 않도록 하여 성능을 개선한다.
35+
* TC: O(s * w * wordDict 단어의 길이), SC: O(s)
36+
*/
37+
private fun usingMemoizationDFS(s: String, wordDict: List<String>): Boolean{
38+
39+
fun dfs(s: String, wordDict: List<String>, index: Int, memo: MutableSet<Int>): Boolean {
40+
val len = s.length
41+
if(index == len) return true
42+
else if(memo.contains(index)) return false
43+
44+
for (word in wordDict) {
45+
if (index + word.length < s.length + 1 &&
46+
s.substring(index, index + word.length) == word &&
47+
dfs(s, wordDict, index + word.length, memo)) {
48+
return true
49+
}
50+
}
51+
memo.add(index)
52+
return false
53+
}
54+
55+
if(s.isEmpty()) return false
56+
return dfs(s, wordDict, 0, mutableSetOf())
57+
}
58+
59+
/**
60+
* 3. 문자열의 끝부터 0까지 순회하면서 순회하는 범위의 문자열을 만들 수 있다면 해당 인덱스를 true로 변환하여 이전에 사용한 연산의 결과를 재활용한다.
61+
* TC: O(s * w * wordDict 단어의 길이) TC: O(s)
62+
*/
63+
private fun usingDP(s: String, wordDict: List<String>): Boolean {
64+
val dp = BooleanArray(s.length + 1).apply {
65+
this[s.length] = true
66+
}
67+
68+
for (index in s.length - 1 downTo 0) {
69+
for (word in wordDict) {
70+
if (dp[index]) break
71+
else if (index + word.length <= s.length && s.substring(index, index + word.length) == word) {
72+
dp[index] = dp[index + word.length]
73+
}
74+
}
75+
}
76+
return dp[0]
77+
}
78+
79+
@Test
80+
fun `문자열과 문자열 사전이 주어졌을 때 문자열 사전을 이용하여 문자열을 완성할 수 있으면 참을 반환한다`() {
81+
wordBreak("applepenapple", listOf("apple", "pen")) shouldBe true
82+
wordBreak("leetcode", listOf("leet", "co", "de")) shouldBe true
83+
wordBreak("abcd", listOf("a","abc","b","cd")) shouldBe true
84+
wordBreak("cars", listOf("car","ca","rs")) shouldBe true
85+
}
86+
87+
@Test
88+
fun `문자열과 문자열 사전이 주어졌을 때 문자열 사전을 이용하여 문자열을 완성할 수 없다면 거짓을 반환한다`() {
89+
wordBreak("catsandog", listOf("cats", "dog", "sand", "and", "cat")) shouldBe false
90+
}
91+
}

0 commit comments

Comments
 (0)