Skip to content

[JisooPyo] Week 1 #643

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

Merged
merged 5 commits into from
Dec 9, 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
38 changes: 38 additions & 0 deletions contains-duplicate/JisooPyo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package leetcode_study

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

/**
* Leetcode
* 217. Contains Duplicate
* Easy
*/
class ContainsDuplicate {
/**
* Runtime: 17 ms(Beats: 80.99 %)
* Time Complexity: O(n)
* - 배열 순회
*
* Memory: 50.63 MB(Beats: 70.32 %)
* Space Complexity: O(n)
* - HashSet에 최악의 경우 배열 원소 모두 저장
*/
fun containsDuplicate(nums: IntArray): Boolean {
val set = hashSetOf<Int>()
for (i in nums) {
if (set.contains(i)) {
return true
}
set.add(i)
}
return false
}

@Test
fun test() {
containsDuplicate(intArrayOf(1, 2, 3, 1)) shouldBe true
containsDuplicate(intArrayOf(1, 2, 3, 4)) shouldBe false
containsDuplicate(intArrayOf(1, 1, 1, 3, 3, 4, 3, 2, 4, 2)) shouldBe true
}
}
74 changes: 74 additions & 0 deletions house-robber/JisooPyo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package leetcode_study

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

/**
* Leetcode
* 198. House Robber
* Medium
*
* 사용된 알고리즘: Dynamic Programming
*
* i번째 집에서 얻을 수 있는 최대 금액은 다음 두 가지 중 큰 값입니다.
* - (i-2)번째 집까지의 최대 금액 + 현재 집의 금액
* - (i-1)번째 집까지의 최대 금액
*/
class HouseRobber {
/**
* Runtime: 0 ms(Beats: 100.00 %)
* Time Complexity: O(n)
*
* Memory: 34.65 MB(Beats: 40.50 %)
* Space Complexity: O(n)
*/
fun rob(nums: IntArray): Int {
if (nums.size == 1) {
return nums[0]
}
if (nums.size == 2) {
return max(nums[0], nums[1])
}
val dp = IntArray(nums.size)
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for (i in 2 until nums.size) {
dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
}
return dp[nums.size - 1]
}

/**
* 공간 복잡도를 개선
* Runtime: 0 ms(Beats: 100.00 %)
* Time Complexity: O(n)
*
* Memory: 34.95 MB(Beats: 36.98 %)
* Space Complexity: O(1)
*/
fun rob2(nums: IntArray): Int {
if (nums.size == 1) return nums[0]
if (nums.size == 2) return max(nums[0], nums[1])

var twoBack = nums[0]
var oneBack = max(nums[0], nums[1])
var current = oneBack

for (i in 2 until nums.size) {
current = max(twoBack + nums[i], oneBack)
twoBack = oneBack
oneBack = current
}

return current
}

@Test
fun test() {
rob(intArrayOf(1, 2, 3, 1)) shouldBe 4
rob(intArrayOf(2, 7, 9, 3, 1)) shouldBe 12
rob2(intArrayOf(1, 2, 3, 1)) shouldBe 4
rob2(intArrayOf(2, 7, 9, 3, 1)) shouldBe 12
}
}
81 changes: 81 additions & 0 deletions longest-consecutive-sequence/JisooPyo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package leetcode_study

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

/**
* Leetcode
* 128. Longest Consecutive Sequence
* Medium
*/
class LongestConsecutiveSequence {
/**
* Runtime: 58 ms(Beats: 79.06 %)
* Time Complexity: O(n)
* - while 루프의 총 반복 횟수는 n을 넘을 수 없다.
*
* Memory: 62.65 MB(Beats: 10.48 %)
* Space Complexity: O(n)
*/
fun longestConsecutive(nums: IntArray): Int {
val numsSet: MutableSet<Int> = nums.toHashSet()
val startSet: MutableSet<Int> = hashSetOf()

// 수열의 시작점이 될 수 있는 수를 찾는다.
for (num in numsSet) {
if (!numsSet.contains(num - 1)) {
startSet.add(num)
}
}
var answer = 0
for (start in startSet) {
// 수열의 시작점부터 몇 개 까지 numsSet에 있는지 확인한다.
var count = 0
var first = start
while (numsSet.contains(first)) {
first++
count++
}
// 최대 수열의 개수를 업데이트한다.
answer = max(answer, count)
}
return answer
}

/**
* 위 풀이에서 startSet을 제거하여 공간적으로 효율적인 풀이
* Runtime: 63 ms(Beats: 65.70 %)
* Time Complexity: O(n)
*
* Memory: 58.47 MB(Beats: 70.81 %)
* Space Complexity: O(n)
*/
fun longestConsecutive2(nums: IntArray): Int {
val numsSet = nums.toHashSet()
var maxLength = 0

for (num in numsSet) {
if (!numsSet.contains(num - 1)) {
var currentNum = num
var currentLength = 0

while (numsSet.contains(currentNum)) {
Copy link
Member

@DaleSeo DaleSeo Dec 8, 2024

Choose a reason for hiding this comment

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

요렇게 하면 currentNum 변수를 사용할 필요가 없어져서 코드가 더 간단해지지 않을까 생각해보았습니다. (안 중요)

Suggested change
while (numsSet.contains(currentNum)) {
while (numsSet.contains(num + currentLength)) {

Copy link
Member Author

Choose a reason for hiding this comment

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

@DaleSeo 오 그렇네요! 리뷰 감사합니다!!

(ps. 안 중요 -> 중요)

currentLength++
currentNum++
}
maxLength = max(maxLength, currentLength)
}
}
return maxLength
}

@Test
fun test() {
longestConsecutive(intArrayOf(100, 4, 200, 1, 3, 2)) shouldBe 4
longestConsecutive(intArrayOf(0, 3, 7, 2, 5, 8, 4, 6, 0, 1)) shouldBe 9

longestConsecutive2(intArrayOf(100, 4, 200, 1, 3, 2)) shouldBe 4
longestConsecutive2(intArrayOf(0, 3, 7, 2, 5, 8, 4, 6, 0, 1)) shouldBe 9
}
}
76 changes: 76 additions & 0 deletions top-k-frequent-elements/JisooPyo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package leetcode_study

import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import java.util.*

/**
* Leetcode
* 347. Top K Frequent Elements
* Medium
*/
class TopKFrequentElements {
/**
* Runtime: 30 ms(Beats: 68.62 %)
* Time Complexity: O(n log n)
* - list 정렬
*
* Memory: 42.20 MB(Beats: 58.82 %)
* Space Complexity: O(n)
*/
fun topKFrequent(nums: IntArray, k: Int): IntArray {
val countMap: MutableMap<Int, Int> = HashMap()

for (num in nums) {
countMap[num] = countMap.getOrDefault(num, 0) + 1
}

val list = mutableListOf<Node>()
for (key in countMap.keys) {
list.add(Node(key, countMap[key]!!))
}
list.sortDescending()

val answer = IntArray(k)
for (i in 0 until k) {
answer[i] = list[i].value
}
return answer
}

/**
* 개선된 버전: 우선순위 큐를 사용
*
* Runtime: 19 ms(Beats: 96.30 %)
* Time Complexity: O(n log n)
Copy link
Contributor

Choose a reason for hiding this comment

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

PriorityQueue에 n개를 삽입하고 k개를 꺼냈으니 O(n log k)가 맞지 않을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

@EgonD3V 리뷰 감사합니다!

  1. k개를 꺼내기 전 PriorityQueue에 값들을 삽입 연산을 생각해보면,
    • nums에서 고유한 값의 개수가 m개라고 가정합니다.
    • offer()의 시간복잡도는 O(log m), 따라서 m개를 삽입하니 O(m log m) 의 시간복잡도로 계산할 수 있습니다.
  2. k개의 요소를 추출할 때의 시간 복잡도를 생각해보면,
    • 각 추출 연산 역시 O(log m)이 되며, k번 수행하게 되므로 O(k log m)이 됩니다.

따라서 전체 시간 복잡도는
  • 배열 순회, 그룹화: O(n)
  • 우선순위 큐 연산: O(m log m + k log m)
  • 최종: O(n + m log m)

여기서 m은 최대 n이 될 수 있으므로 O(n log n)이 됩니다!

Copy link
Member Author

Choose a reason for hiding this comment

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

아! PriorityQueue의 크기를 k로 제한하면 O(n log k)로 더 최적화 할 수 있겠네요..!

*
* Memory: 44.83 MB(Beats: 18.35 %)
* Space Complexity: O(n)
*/
fun topKFrequent2(nums: IntArray, k: Int): IntArray {
val countMap = nums.groupBy { it }
.mapValues { it.value.size }

val pq = PriorityQueue<Node>(compareByDescending { it.count })
countMap.forEach { (num, count) ->
pq.offer(Node(num, count))
}

return IntArray(k) { pq.poll().value }
}

data class Node(var value: Int, var count: Int) : Comparable<Node> {
override fun compareTo(other: Node): Int {
return this.count.compareTo(other.count)
}
}

@Test
fun test() {
topKFrequent(intArrayOf(1, 1, 1, 2, 2, 3), 2) shouldBe intArrayOf(1, 2)
topKFrequent(intArrayOf(1), 1) shouldBe intArrayOf(1)

topKFrequent2(intArrayOf(1, 1, 1, 2, 2, 3), 2) shouldBe intArrayOf(1, 2)
topKFrequent2(intArrayOf(1), 1) shouldBe intArrayOf(1)
}
}
101 changes: 101 additions & 0 deletions valid-palindrome/JisooPyo.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package leetcode_study

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

/**
* Leetcode
* 125. Valid Palindrome
* Easy
*/
class ValidPalindrome {
/**
* Runtime: 4 ms(Beats: 98.27 %)
* Time Complexity: O(n)
*
* Memory: 38.22 MB(Beats: 46.74 %)
* Space Complexity: O(1)
*/
fun isPalindrome(s: String): Boolean {
var left = 0
var right = s.length - 1
while (left <= right) {
when (s[left]) {
// 왼쪽의 문자가 alphanumeric일 때
in 'a'..'z', in 'A'..'Z', in '0'..'9' -> {

when (s[right]) {
// 오른쪽의 문자가 alphanumeric일 때
in 'a'..'z', in 'A'..'Z', in '0'..'9' -> {
// 문자 비교
if (s[left].equals(s[right], true)) {
left++
right--
continue
} else {
return false
}
}
// 오른쪽의 문자가 alphanumeric이 아닐 때
else -> {
right--
continue
}
}
}

// 왼쪽의 문자가 alphanumeric이 아닐 때
else -> {
left++
continue
}
}
}
return true
}

/**
* 개선한 버전
* Runtime: 5 ms(Beats: 87.14 %)
* Time Complexity: O(n)
*
* Memory: 37.76 MB(Beats: 61.52 %)
* Space Complexity: O(1)
*/
fun isPalindrome2(s: String): Boolean {
var left = 0
var right = s.length - 1

while (left < right) {
// 왼쪽에서 유효한 문자를 찾음
while (left < right && !s[left].isLetterOrDigit()) {
left++
}

// 오른쪽에서 유효한 문자를 찾음
while (left < right && !s[right].isLetterOrDigit()) {
right--
}

// 문자 비교
if (!s[left].equals(s[right], ignoreCase = true)) {
return false
}

left++
right--
}
return true
}

@Test
fun test() {
isPalindrome("A man, a plan, a canal: Panama") shouldBe true
isPalindrome("race a car") shouldBe false
isPalindrome(" ") shouldBe true

isPalindrome2("A man, a plan, a canal: Panama") shouldBe true
isPalindrome2("race a car") shouldBe false
isPalindrome2(" ") shouldBe true
}
}
Loading