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

[정현준] 5주차 답안 제출 #449

Merged
merged 2 commits into from
Sep 14, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
66 changes: 66 additions & 0 deletions 3sum/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package leetcode_study

import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import org.junit.jupiter.api.Test

class `3sum` {

fun threeSum(nums: IntArray): List<List<Int>> {
return usingTwoPointer(nums)
}

/**
* 1. 정수 배열을 순회하며 모두 확인한다. (시간초과)
* TC: O(n^3), SC: O(n)
*/
private fun usingIterative(nums: IntArray): List<List<Int>> {
val result = mutableSetOf<List<Int>>()
for (first in nums.indices) {
for (second in first + 1 until nums.size) {
for (third in second + 1 until nums.size) {
if (nums[first] + nums[second] + nums[third] == 0) {
result.add(listOf(nums[first], nums[second], nums[third]).sorted())
}
}
}
}
return result.toList()
}

/**
* 2. 입력받은 정수 배열을 정렬하여 순회하면서 원소를 합산하여 0과 비교한 결과를 기준으로 투 포인터의 값을 조작한다.
* TC: O(n * log(n) + n^2), SC: O(n)
Copy link
Contributor

@bky373 bky373 Sep 12, 2024

Choose a reason for hiding this comment

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

n 이 커지면 n^2 항이 n log n 항보다 훨씬 더 빠르게 증가하고 n log n 영향력이 줄기 때문에,
이런 경우 TC 를 O(n^2) 으로 적는 게 더 일반적인 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

아 그렇군요 ㅎㅎ 이렇게 시간 복잡도를 나열하는 것이 맞나 생각했었는데 가장 영향력이 큰 것만 골라적어야하는군요 ㅎㅎ 감사합니당

*/
private fun usingTwoPointer(nums: IntArray): List<List<Int>> {
val sortedNumbers = nums.sorted()
val result = mutableSetOf<List<Int>>()
for (index in nums.indices) {
var left = index + 1
var right = nums.size - 1
while (left < right) {
val sum = sortedNumbers[index] + sortedNumbers[left] + sortedNumbers[right]
if (sum == 0) {
result.add(listOf(sortedNumbers[index], sortedNumbers[left], sortedNumbers[right]))
left++
right--
} else if (sum < 0) {
left++
} else {
right--
}
}
}
return result.toList()
}

@Test
fun `입력받은 정수 배열의 세 개의 원소의 합이 0이 되는 리스트를 반환한다`() {
threeSum(intArrayOf(-1,0,1,2,-1,-4)) shouldContainExactlyInAnyOrder listOf(
listOf(-1,-1,2),
listOf(-1,0,1)
)
threeSum(intArrayOf(0,0,0)) shouldContainExactlyInAnyOrder listOf(
listOf(0,0,0)
)
}
}
60 changes: 60 additions & 0 deletions best-time-to-buy-and-sell-stock/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package leetcode_study

import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import kotlin.math.max

class `best-time-to-buy-and-sell-stock` {

fun maxProfit(prices: IntArray): Int {
if (prices.size == 1) return 0
return usingKadaneAlgorithm(prices)
}

/**
* 1. 방향이 존재하기 때문에 투 포인터를 활용하여 주식을 팔 수 있는 경우라면 최대 값을 계산하고 만약 산 가격보다 싼 가격을 만나면 다시 산다
* TC: O(n), SC: O(1)
*/
private fun usingTwoPointer(prices: IntArray): Int {
var (left, right) = 0 to 1
var maxProfit = 0

while (right < prices.size) {
if (prices[left] < prices[right]) {
maxProfit = max(prices[right] - prices[left], maxProfit)
right++
} else if (prices[left] >= prices[right]) {
left = right
right++
}
}

return maxProfit
}

/**
* 2. 카데인 알고리즘의 변형된 버전으로 가장 싼 경우를 buy에 저장하고 현재 최대 수익을 초과하면 업데이트한다
Copy link
Contributor

Choose a reason for hiding this comment

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

이 방식을 카데인 알고리즘으로 부르는군요! 배웠습니다!

* TC: O(n), SC: O(1)
*/
private fun usingKadaneAlgorithm(prices: IntArray): Int {
var buy = prices[0]
var maxProfit = 0

for (index in 1 until prices.size) {
if (prices[index] < buy) {
buy = prices[index]
} else if (prices[index] - buy > maxProfit) {
maxProfit = prices[index] - buy
}
}
return maxProfit
}

@Test
fun `주어진 가격 배열을 통해 최대의 수익을 반환한다`() {
maxProfit(intArrayOf(3,3)) shouldBe 0
maxProfit(intArrayOf(7,6,5,4,3,2,1,0)) shouldBe 0
maxProfit(intArrayOf(7,1,5,3,6,4)) shouldBe 5
maxProfit(intArrayOf(1,2,4,2,5,7,2,4,9,0,9)) shouldBe 9
}
}
53 changes: 53 additions & 0 deletions group-anagrams/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package leetcode_study

import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
import org.junit.jupiter.api.Test

class `group-anagrams` {

fun groupAnagrams(strs: Array<String>): List<List<String>> {
return usingArray(strs)
}

/**
* 1. 입력받은 문자열들을 문자 배열로 변환하여 정렬된 결과를 map 의 키로 정하여 키 기준으로 문자열들을 그룹화한다.
* TC: O(n * k log(k)), SC: O(n)
*/
private fun usingSort(strs: Array<String>): List<List<String>> {
val map = strs.groupBy { it.toCharArray().sorted() }
return map.values.toList()
}

/**
* 2. 입력받은 문자열들을 순회하며 문자열의 문자 갯수를 카운트하여 애너그램인지 구별한다.
* TC: O(n), SC: O(n)
Copy link
Contributor

Choose a reason for hiding this comment

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

배열을 사용한 방법도 있었군요!

Copy link
Contributor

@kjb512 kjb512 Sep 13, 2024

Choose a reason for hiding this comment

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

위에 k처럼, 이것도 O(n*k)일 것 같습니다!

Copy link
Member Author

@jdalma jdalma Sep 14, 2024

Choose a reason for hiding this comment

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

아 그렇네요 ㅎㅎ 감사합니당

*/
private fun usingArray(strs: Array<String>): List<List<String>> {
val map = strs.groupBy { it ->
val counter = IntArray(26)
for (ch in it) {
counter[ch - 'a']++
}
counter.joinToString(",") // 구분자를 넣지 않으면 arrayOf("bdddddddddd","bbbbbbbbbbc") 테케를 실패함
}

return map.values.toList()
}

@Test
fun `입력받은 문자열들을 애너그램 기준 그룹별로 반환한다`() {
groupAnagrams(arrayOf("eat","tea","tan","ate","nat","bat")) shouldContainExactlyInAnyOrder listOf(
listOf("tan","nat"),
listOf("bat"),
listOf("eat","tea","ate"),
)
groupAnagrams(arrayOf("cab","tin","pew","duh","may","ill","buy","bar","max","doc")) shouldContainExactlyInAnyOrder listOf(
listOf("max"),listOf("buy"),listOf("doc"),listOf("may"),listOf("ill"),
listOf("duh"),listOf("tin"),listOf("bar"),listOf("pew"),listOf("cab")
)
groupAnagrams(arrayOf("bdddddddddd","bbbbbbbbbbc")) shouldContainExactlyInAnyOrder listOf(
listOf("bbbbbbbbbbc"),
listOf("bdddddddddd")
)
}
}
82 changes: 82 additions & 0 deletions implement-trie-prefix-tree/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package leetcode_study

import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test

class `implement-trie-prefix-tree` {

/**
* 영어 소문자만 입력된다.
*/
class Trie {

private val node = Node()

/**
* TC: O(n), SC: O(n)
*/
fun insert(word: String) {
var now = node

for (char in word) {
val index = char - 'a'
if (now.next[index] == null) {
now.next[index] = Node()
}
now.next[index]?.apply { now = this }
}
now.isEnd = true
}

/**
* TC: O(n), SC: O(1)
*/
fun search(word: String): Boolean {
var now = node

for (char in word) {
val index = char - 'a'
if (now.next[index] == null) {
return false
}
now.next[index]?.apply { now = this }
}

return now.isEnd
}

/**
* TC: O(n), SC: O(1)
*/
fun startsWith(prefix: String): Boolean {
var now = node

for (char in prefix) {
val index = char - 'a'
if (now.next[index] == null) {
return false
}
now.next[index]?.apply { now = this }
}

return true
}

}

@Test
fun `접두사 트리를 구현하라`() {
val trie = Trie()
trie.insert("apple")
trie.search("apple") shouldBe true
trie.search("app") shouldBe false
trie.startsWith("app") shouldBe true
trie.insert("app")
trie.search("app") shouldBe true
}
}

private class Node {
val next = Array<Node?>(26) { null }
var isEnd = false
}
91 changes: 91 additions & 0 deletions word-break/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package leetcode_study

import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test

class `word-break` {

fun wordBreak(s: String, wordDict: List<String>): Boolean {
return usingDP(s, wordDict)
}

/**
* 1. DFS 사용 (시간초과)
* TC: O(s^w * wordDict 단어의 길이), SC: O(s)
Copy link
Contributor

@kjb512 kjb512 Sep 13, 2024

Choose a reason for hiding this comment

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

최악의 경우, 각 인덱스에서 w번 모두 선택할 수 있다고 생각해서 w * w * w *.... = w^s라고 이해했는데요
s^w로 표현된 이유도 혹시 설명해주실 수 있을까요? 궁금해서 여쭤봅니다. 😊

Copy link
Member Author

Choose a reason for hiding this comment

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

아 맞네요 ㅎㅎ 말씀하신 s^w가 맞는 것 같습니다 ㅎㅎ w 길이만큼 s 길이까지 재귀호출을 하니 말씀하신게 맞는 것 같아요 수정하겠습니당

*/
private fun usingDFS(s: String, wordDict: List<String>): Boolean {

fun recursion(s: String, wordDict: List<String>, index: Int): Boolean =
if (index == s.length) true
else {
wordDict.map { word ->
var result = false
if (index + word.length < s.length + 1 && s.substring(index, index + word.length) == word) {
result = recursion(s, wordDict, index + word.length)
}
result
}.find { it } ?: false
}

return recursion(s, wordDict, 0)
}

/**
* 2, 사전에 담겨있는 문자열들을 기준으로 인덱스를 증가시키면서 문자열을 완성시킨다. 한 번 탐색하여 문자열을 완성시키지 못한 인덱스를 저장하여 해당 인덱스는 다시 탐색하지 않도록 하여 성능을 개선한다.
* TC: O(s * w * wordDict 단어의 길이), SC: O(s)
*/
private fun usingMemoizationDFS(s: String, wordDict: List<String>): Boolean{

fun dfs(s: String, wordDict: List<String>, index: Int, memo: MutableSet<Int>): Boolean {
val len = s.length
if(index == len) return true
else if(memo.contains(index)) return false

for (word in wordDict) {
if (index + word.length < s.length + 1 &&
s.substring(index, index + word.length) == word &&
dfs(s, wordDict, index + word.length, memo)) {
return true
}
}
memo.add(index)
return false
}

if(s.isEmpty()) return false
return dfs(s, wordDict, 0, mutableSetOf())
}

/**
* 3. 문자열의 끝부터 0까지 순회하면서 순회하는 범위의 문자열을 만들 수 있다면 해당 인덱스를 true로 변환하여 이전에 사용한 연산의 결과를 재활용한다.
* TC: O(s * w * wordDict 단어의 길이) TC: O(s)
Copy link
Contributor

Choose a reason for hiding this comment

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

SC 오타 있습니다!

*/
private fun usingDP(s: String, wordDict: List<String>): Boolean {
val dp = BooleanArray(s.length + 1).apply {
this[s.length] = true
}

for (index in s.length - 1 downTo 0) {
for (word in wordDict) {
if (dp[index]) break
else if (index + word.length <= s.length && s.substring(index, index + word.length) == word) {
dp[index] = dp[index + word.length]
}
}
}
return dp[0]
}

@Test
fun `문자열과 문자열 사전이 주어졌을 때 문자열 사전을 이용하여 문자열을 완성할 수 있으면 참을 반환한다`() {
wordBreak("applepenapple", listOf("apple", "pen")) shouldBe true
wordBreak("leetcode", listOf("leet", "co", "de")) shouldBe true
wordBreak("abcd", listOf("a","abc","b","cd")) shouldBe true
wordBreak("cars", listOf("car","ca","rs")) shouldBe true
}

@Test
fun `문자열과 문자열 사전이 주어졌을 때 문자열 사전을 이용하여 문자열을 완성할 수 없다면 거짓을 반환한다`() {
wordBreak("catsandog", listOf("cats", "dog", "sand", "and", "cat")) shouldBe false
}
}