Skip to content

Commit

Permalink
Merge pull request #410 from jdalma/main
Browse files Browse the repository at this point in the history
[정현준] 4주차 답안 제출
  • Loading branch information
jdalma authored Sep 4, 2024
2 parents 08a3a2b + 5782186 commit bde1ed2
Show file tree
Hide file tree
Showing 5 changed files with 349 additions and 0 deletions.
113 changes: 113 additions & 0 deletions longest-consecutive-sequence/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package leetcode_study

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

class `longest-consecutive-sequence` {
fun longestConsecutive(nums: IntArray): Int {
if (nums.isEmpty()) return 0
return usingUnionFind(nums)
}

/**
* 1. 배열을 정렬하여 순서대로 순회하며 연속 수열 길이를 확인한다.
* TC: O(n * log(n)), SC: O(1)
*/
private fun usingSort(nums: IntArray): Int {
nums.sort()

var (length, maxLength) = 1 to 0
for (index in 0 until nums.size - 1) {
if (nums[index] == nums[index + 1]) {
continue
} else if (nums[index] + 1 == nums[index + 1]) {
length++
} else {
maxLength = max(length, maxLength)
length = 1
}
}
return max(length, maxLength)
}

/**
* 2. Set의 자료구조를 활용하여 가장 작은 값부터 while문을 통한 최대 증가 값을 반환한다.
* TC: O(n), SC: O(n)
*/
private fun usingSet(nums: IntArray): Int {
val numberSet = nums.toSet()
var maxLength = 0

for (number in nums) {
if (numberSet.contains(number - 1)) {
continue
}
var length = 1
while (numberSet.contains(number + length)) {
length++
}
maxLength = max(maxLength, length)
}

return maxLength
}

/**
* 3. Union-Find
* TC: O(n), SC: O(n)
*/
private fun usingUnionFind(nums: IntArray): Int {
val nodes = mutableMapOf<Int, Int>()
val dsu = DSU(nums.size)

for ((i,n) in nums.withIndex()) {
if (n in nodes) continue

nodes[n - 1]?.let { dsu.union(i, it) }
nodes[n + 1]?.let { dsu.union(i, it) }

nodes[n] = i
}

return dsu.maxLength()
}

@Test
fun `입력받은 정수 배열의 최대 연속 수열 길이를 반환한다`() {
longestConsecutive(intArrayOf()) shouldBe 0
longestConsecutive(intArrayOf(100,4,200,1,3,2)) shouldBe 4
longestConsecutive(intArrayOf(11,23,12,13,14,21)) shouldBe 4
longestConsecutive(intArrayOf(0,3,7,2,5,8,4,6,0,1)) shouldBe 9
longestConsecutive(intArrayOf(11,64,43,12,13,10,9,8,7)) shouldBe 7
}
}

class DSU(val n: Int) {
private val parent = IntArray(n) { it }
private val size = IntArray(n) { 1 }

private fun find(x: Int): Int {
if (parent[x] != x)
parent[x] = find(parent[x])
return parent[x]
}

fun union(x: Int, y: Int) {
val root = find(x)
val child = find(y)
if(root != child) {
parent[child] = root
size[root] += size[child]
}
}

fun maxLength(): Int {
var res = 0
for (i in parent.indices) {
if (parent[i] == i)
res = maxOf(res, size[i])
}
return res
}
}
56 changes: 56 additions & 0 deletions maximum-product-subarray/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package leetcode_study

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

class `maximum-product-subarray` {

fun maxProduct(nums: IntArray): Int {
return usingOptimizedDP(nums)
}

/**
* 현재의 값, 이전 위치의 최대 누적곱, 이전 위치의 최소 누적곱 이 세 개를 비교하여 한 번의 순회로 최대 값을 반환한다.
* 음수와 음수가 곱해져 최대 값이 도출될 수 있기에 DP 배열을 두 개 생성한다.
* TC: O(n), SC: O(n)
*/
private fun usingDP(nums: IntArray): Int {
val (min, max) = IntArray(nums.size) { 11 }.apply { this[0] = nums[0] } to IntArray(nums.size) { -11 }.apply { this[0] = nums[0] }
var result = nums[0]
for (index in 1 until nums.size) {
max[index] = max(max(nums[index], nums[index] * max[index - 1]), nums[index] * min[index - 1])
min[index] = min(min(nums[index], nums[index] * max[index - 1]), nums[index] * min[index - 1])
result = max(max(min[index], max[index]), result)
}

return result
}

/**
* DP 배열이 입력받는 정수의 배열만큼 생성할 필요가 없고, 이전 값과 현재 값만 기억하면 되므로 공간복잡도가 개선되었다.
* TC: O(n), SC: O(1)
*/
private fun usingOptimizedDP(nums: IntArray): Int {
var (min, max) = nums[0] to nums[0]
var result = nums[0]
for (index in 1 until nums.size) {
val (tmpMin, tmpMax) = min to max
max = max(max(nums[index], nums[index] * tmpMax), nums[index] * tmpMin)
min = min(min(nums[index], nums[index] * tmpMax), nums[index] * tmpMin)
result = max(max(min, max), result)
}

return result
}

@Test
fun `입력받은 정수 배열의 가장 큰 곱을 반환한다`() {
maxProduct(intArrayOf(2,3,-2,4)) shouldBe 6
maxProduct(intArrayOf(-2,0,-1)) shouldBe 0
maxProduct(intArrayOf(-10)) shouldBe -10
maxProduct(intArrayOf(-2,3,-4)) shouldBe 24
maxProduct(intArrayOf(-4,-3,-2)) shouldBe 12
}
}
41 changes: 41 additions & 0 deletions missing-number/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package leetcode_study

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

class `missing-number` {

fun missingNumber(nums: IntArray): Int {
return usingSum(nums)
}

/**
* 1. 배열을 추가로 생성하여 존재하는 정수의 인덱스에 true를 저장하여, false인 인덱스를 반환한다.
* TC: O(n), SC: O(n)
*/
private fun usingIndex(nums: IntArray): Int {
val existed = BooleanArray(nums.size + 1)
nums.forEach { existed[it] = true }

existed.forEachIndexed { i, e ->
if (!e) return i
}
return -1
}

/**
* 2. 0부터 정수 배열의 사이즈만큼 정수를 합산하여 기대하는 합산과 뺀 결과를 반환한다.
* TC: O(n), SC: O(1)
*/
private fun usingSum(nums: IntArray): Int {
val size = nums.size
return nums.fold((size + 1) * size / 2) { acc , i -> acc - i }
}

@Test
fun `입력받은 정수 배열에서 비어있는 정수를 반환한다`() {
missingNumber(intArrayOf(3,2,1)) shouldBe 0
missingNumber(intArrayOf(3,0,1)) shouldBe 2
missingNumber(intArrayOf(9,6,4,2,3,5,7,0,1)) shouldBe 8
}
}
44 changes: 44 additions & 0 deletions valid-palindrome/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package leetcode_study

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

class `valid-palindrome` {

/**
* 입력받은 문자열의 양 끝부터 투 포인터를 이용하여 비교한다.
* TC: O(n), SC: O(1)
*/
fun isPalindrome(s: String): Boolean {
var (left, right) = 0 to s.length - 1
while (left < right) {
while (left < right && !isAlphabetOrDigit(s[left])) {
left++
}
while (left < right && !isAlphabetOrDigit(s[right])) {
right--
}
if (s[left].lowercaseChar() != s[right].lowercaseChar()) {
return false
}
left++
right--
}
return true
}

private fun isAlphabetOrDigit(c: Char): Boolean = c.isDigit() || (c in 'a' .. 'z') || (c in 'A' .. 'Z')

@Test
fun `입력받은 문자열의 영숫자가 팰린드롬 여부를 반환한다`() {
isPalindrome("A man, a plan, a canal: Panama") shouldBe true
isPalindrome("race a car") shouldBe false
isPalindrome("ㅁaㄹㅁb듐노+_c$#&$%#b*&@!!@a$") shouldBe true
}

@Test
fun `입력받은 문자열이 공백만 존재한다면 참을 반환한다`() {
isPalindrome(" ") shouldBe true
isPalindrome(" ") shouldBe true
}
}
95 changes: 95 additions & 0 deletions word-search/jdalma.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package leetcode_study

import io.kotest.matchers.shouldBe
import leetcode_study.`word-search`.Position.Companion.MOVES
import org.junit.jupiter.api.Test

class `word-search` {

/**
* 격자에 존재하는 문자를 사용하여 word 를 만들 수 있는지 확인하기 위해 DFS를 통한 visited 배열 백트래킹을 사용하여 해결
* TC: O(너비 * 높이 * 4^word), SC: O(너비 * 높이 * 4^word)
*/
fun exist(board: Array<CharArray>, word: String): Boolean {
return usingBacktracking(board, word)
}

private fun usingBacktracking(board: Array<CharArray>, word: String): Boolean {
fun dfs(board: Array<CharArray>, visited: Array<BooleanArray>, word: String, position: Position, index: Int): Boolean {
if (index == word.length) return true
for (move in MOVES) {
val next = position + move
if (next.isNotOutOfIndexed(board) && !visited[next.x][next.y] && board[next.x][next.y] == word[index]) {
visited[next.x][next.y] = true
if (dfs(board, visited, word, next, index + 1)) return true
visited[next.x][next.y] = false
}
}
return false
}

val visited = Array(board.size) {
BooleanArray(board[0].size)
}

for (x in board.indices) {
for (y in board[x].indices) {
visited[x][y] = true
if (board[x][y] == word[0] && dfs(board, visited, word, Position(x,y), 1)) {
return true
}
visited[x][y] = false
}
}
return false
}

@Test
fun `문자로 구성된 2차원 배열에서 word 문자열 존재 유무를 반환한다`() {
exist(arrayOf(
charArrayOf('A','B','C','E'),
charArrayOf('S','F','C','S'),
charArrayOf('A','D','E','E')
), "ABCCED") shouldBe true
exist(arrayOf(
charArrayOf('A','B','C','E'),
charArrayOf('S','F','C','S'),
charArrayOf('A','D','E','E')
), "SEE") shouldBe true
exist(arrayOf(
charArrayOf('A','B','C','E'),
charArrayOf('S','F','C','S'),
charArrayOf('A','D','E','E')
), "SES") shouldBe false
exist(arrayOf(
charArrayOf('A','B','C','E'),
charArrayOf('S','F','E','S'),
charArrayOf('A','D','E','E')
), "ABCESEEEFS") shouldBe true
exist(arrayOf(
charArrayOf('C','A','A'),
charArrayOf('A','A','A'),
charArrayOf('B','C','D')
), "AAB") shouldBe true
}

data class Position(
val x: Int,
val y: Int
) {

operator fun plus(other: Position) = Position(this.x + other.x, this.y + other.y)

fun isNotOutOfIndexed(board: Array<CharArray>) =
this.x < board.size && this.x >= 0 && this.y < board[0].size && this.y >= 0

companion object {
val MOVES: List<Position> = listOf(
Position(-1, 0),
Position(0, 1),
Position(1, 0),
Position(0, -1),
)
}
}
}

0 comments on commit bde1ed2

Please sign in to comment.