Skip to content

Commit bde1ed2

Browse files
authored
Merge pull request #410 from jdalma/main
[정현준] 4주차 답안 제출
2 parents 08a3a2b + 5782186 commit bde1ed2

File tree

5 files changed

+349
-0
lines changed

5 files changed

+349
-0
lines changed
+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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 `longest-consecutive-sequence` {
8+
fun longestConsecutive(nums: IntArray): Int {
9+
if (nums.isEmpty()) return 0
10+
return usingUnionFind(nums)
11+
}
12+
13+
/**
14+
* 1. 배열을 정렬하여 순서대로 순회하며 연속 수열 길이를 확인한다.
15+
* TC: O(n * log(n)), SC: O(1)
16+
*/
17+
private fun usingSort(nums: IntArray): Int {
18+
nums.sort()
19+
20+
var (length, maxLength) = 1 to 0
21+
for (index in 0 until nums.size - 1) {
22+
if (nums[index] == nums[index + 1]) {
23+
continue
24+
} else if (nums[index] + 1 == nums[index + 1]) {
25+
length++
26+
} else {
27+
maxLength = max(length, maxLength)
28+
length = 1
29+
}
30+
}
31+
return max(length, maxLength)
32+
}
33+
34+
/**
35+
* 2. Set의 자료구조를 활용하여 가장 작은 값부터 while문을 통한 최대 증가 값을 반환한다.
36+
* TC: O(n), SC: O(n)
37+
*/
38+
private fun usingSet(nums: IntArray): Int {
39+
val numberSet = nums.toSet()
40+
var maxLength = 0
41+
42+
for (number in nums) {
43+
if (numberSet.contains(number - 1)) {
44+
continue
45+
}
46+
var length = 1
47+
while (numberSet.contains(number + length)) {
48+
length++
49+
}
50+
maxLength = max(maxLength, length)
51+
}
52+
53+
return maxLength
54+
}
55+
56+
/**
57+
* 3. Union-Find
58+
* TC: O(n), SC: O(n)
59+
*/
60+
private fun usingUnionFind(nums: IntArray): Int {
61+
val nodes = mutableMapOf<Int, Int>()
62+
val dsu = DSU(nums.size)
63+
64+
for ((i,n) in nums.withIndex()) {
65+
if (n in nodes) continue
66+
67+
nodes[n - 1]?.let { dsu.union(i, it) }
68+
nodes[n + 1]?.let { dsu.union(i, it) }
69+
70+
nodes[n] = i
71+
}
72+
73+
return dsu.maxLength()
74+
}
75+
76+
@Test
77+
fun `입력받은 정수 배열의 최대 연속 수열 길이를 반환한다`() {
78+
longestConsecutive(intArrayOf()) shouldBe 0
79+
longestConsecutive(intArrayOf(100,4,200,1,3,2)) shouldBe 4
80+
longestConsecutive(intArrayOf(11,23,12,13,14,21)) shouldBe 4
81+
longestConsecutive(intArrayOf(0,3,7,2,5,8,4,6,0,1)) shouldBe 9
82+
longestConsecutive(intArrayOf(11,64,43,12,13,10,9,8,7)) shouldBe 7
83+
}
84+
}
85+
86+
class DSU(val n: Int) {
87+
private val parent = IntArray(n) { it }
88+
private val size = IntArray(n) { 1 }
89+
90+
private fun find(x: Int): Int {
91+
if (parent[x] != x)
92+
parent[x] = find(parent[x])
93+
return parent[x]
94+
}
95+
96+
fun union(x: Int, y: Int) {
97+
val root = find(x)
98+
val child = find(y)
99+
if(root != child) {
100+
parent[child] = root
101+
size[root] += size[child]
102+
}
103+
}
104+
105+
fun maxLength(): Int {
106+
var res = 0
107+
for (i in parent.indices) {
108+
if (parent[i] == i)
109+
res = maxOf(res, size[i])
110+
}
111+
return res
112+
}
113+
}

maximum-product-subarray/jdalma.kt

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
import kotlin.math.max
6+
import kotlin.math.min
7+
8+
class `maximum-product-subarray` {
9+
10+
fun maxProduct(nums: IntArray): Int {
11+
return usingOptimizedDP(nums)
12+
}
13+
14+
/**
15+
* 현재의 값, 이전 위치의 최대 누적곱, 이전 위치의 최소 누적곱 이 세 개를 비교하여 한 번의 순회로 최대 값을 반환한다.
16+
* 음수와 음수가 곱해져 최대 값이 도출될 수 있기에 DP 배열을 두 개 생성한다.
17+
* TC: O(n), SC: O(n)
18+
*/
19+
private fun usingDP(nums: IntArray): Int {
20+
val (min, max) = IntArray(nums.size) { 11 }.apply { this[0] = nums[0] } to IntArray(nums.size) { -11 }.apply { this[0] = nums[0] }
21+
var result = nums[0]
22+
for (index in 1 until nums.size) {
23+
max[index] = max(max(nums[index], nums[index] * max[index - 1]), nums[index] * min[index - 1])
24+
min[index] = min(min(nums[index], nums[index] * max[index - 1]), nums[index] * min[index - 1])
25+
result = max(max(min[index], max[index]), result)
26+
}
27+
28+
return result
29+
}
30+
31+
/**
32+
* DP 배열이 입력받는 정수의 배열만큼 생성할 필요가 없고, 이전 값과 현재 값만 기억하면 되므로 공간복잡도가 개선되었다.
33+
* TC: O(n), SC: O(1)
34+
*/
35+
private fun usingOptimizedDP(nums: IntArray): Int {
36+
var (min, max) = nums[0] to nums[0]
37+
var result = nums[0]
38+
for (index in 1 until nums.size) {
39+
val (tmpMin, tmpMax) = min to max
40+
max = max(max(nums[index], nums[index] * tmpMax), nums[index] * tmpMin)
41+
min = min(min(nums[index], nums[index] * tmpMax), nums[index] * tmpMin)
42+
result = max(max(min, max), result)
43+
}
44+
45+
return result
46+
}
47+
48+
@Test
49+
fun `입력받은 정수 배열의 가장 큰 곱을 반환한다`() {
50+
maxProduct(intArrayOf(2,3,-2,4)) shouldBe 6
51+
maxProduct(intArrayOf(-2,0,-1)) shouldBe 0
52+
maxProduct(intArrayOf(-10)) shouldBe -10
53+
maxProduct(intArrayOf(-2,3,-4)) shouldBe 24
54+
maxProduct(intArrayOf(-4,-3,-2)) shouldBe 12
55+
}
56+
}

missing-number/jdalma.kt

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `missing-number` {
7+
8+
fun missingNumber(nums: IntArray): Int {
9+
return usingSum(nums)
10+
}
11+
12+
/**
13+
* 1. 배열을 추가로 생성하여 존재하는 정수의 인덱스에 true를 저장하여, false인 인덱스를 반환한다.
14+
* TC: O(n), SC: O(n)
15+
*/
16+
private fun usingIndex(nums: IntArray): Int {
17+
val existed = BooleanArray(nums.size + 1)
18+
nums.forEach { existed[it] = true }
19+
20+
existed.forEachIndexed { i, e ->
21+
if (!e) return i
22+
}
23+
return -1
24+
}
25+
26+
/**
27+
* 2. 0부터 정수 배열의 사이즈만큼 정수를 합산하여 기대하는 합산과 뺀 결과를 반환한다.
28+
* TC: O(n), SC: O(1)
29+
*/
30+
private fun usingSum(nums: IntArray): Int {
31+
val size = nums.size
32+
return nums.fold((size + 1) * size / 2) { acc , i -> acc - i }
33+
}
34+
35+
@Test
36+
fun `입력받은 정수 배열에서 비어있는 정수를 반환한다`() {
37+
missingNumber(intArrayOf(3,2,1)) shouldBe 0
38+
missingNumber(intArrayOf(3,0,1)) shouldBe 2
39+
missingNumber(intArrayOf(9,6,4,2,3,5,7,0,1)) shouldBe 8
40+
}
41+
}

valid-palindrome/jdalma.kt

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import org.junit.jupiter.api.Test
5+
6+
class `valid-palindrome` {
7+
8+
/**
9+
* 입력받은 문자열의 양 끝부터 투 포인터를 이용하여 비교한다.
10+
* TC: O(n), SC: O(1)
11+
*/
12+
fun isPalindrome(s: String): Boolean {
13+
var (left, right) = 0 to s.length - 1
14+
while (left < right) {
15+
while (left < right && !isAlphabetOrDigit(s[left])) {
16+
left++
17+
}
18+
while (left < right && !isAlphabetOrDigit(s[right])) {
19+
right--
20+
}
21+
if (s[left].lowercaseChar() != s[right].lowercaseChar()) {
22+
return false
23+
}
24+
left++
25+
right--
26+
}
27+
return true
28+
}
29+
30+
private fun isAlphabetOrDigit(c: Char): Boolean = c.isDigit() || (c in 'a' .. 'z') || (c in 'A' .. 'Z')
31+
32+
@Test
33+
fun `입력받은 문자열의 영숫자가 팰린드롬 여부를 반환한다`() {
34+
isPalindrome("A man, a plan, a canal: Panama") shouldBe true
35+
isPalindrome("race a car") shouldBe false
36+
isPalindrome("ㅁaㄹㅁb듐노+_c$#&$%#b*&@!!@a$") shouldBe true
37+
}
38+
39+
@Test
40+
fun `입력받은 문자열이 공백만 존재한다면 참을 반환한다`() {
41+
isPalindrome(" ") shouldBe true
42+
isPalindrome(" ") shouldBe true
43+
}
44+
}

word-search/jdalma.kt

+95
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package leetcode_study
2+
3+
import io.kotest.matchers.shouldBe
4+
import leetcode_study.`word-search`.Position.Companion.MOVES
5+
import org.junit.jupiter.api.Test
6+
7+
class `word-search` {
8+
9+
/**
10+
* 격자에 존재하는 문자를 사용하여 word 를 만들 수 있는지 확인하기 위해 DFS를 통한 visited 배열 백트래킹을 사용하여 해결
11+
* TC: O(너비 * 높이 * 4^word), SC: O(너비 * 높이 * 4^word)
12+
*/
13+
fun exist(board: Array<CharArray>, word: String): Boolean {
14+
return usingBacktracking(board, word)
15+
}
16+
17+
private fun usingBacktracking(board: Array<CharArray>, word: String): Boolean {
18+
fun dfs(board: Array<CharArray>, visited: Array<BooleanArray>, word: String, position: Position, index: Int): Boolean {
19+
if (index == word.length) return true
20+
for (move in MOVES) {
21+
val next = position + move
22+
if (next.isNotOutOfIndexed(board) && !visited[next.x][next.y] && board[next.x][next.y] == word[index]) {
23+
visited[next.x][next.y] = true
24+
if (dfs(board, visited, word, next, index + 1)) return true
25+
visited[next.x][next.y] = false
26+
}
27+
}
28+
return false
29+
}
30+
31+
val visited = Array(board.size) {
32+
BooleanArray(board[0].size)
33+
}
34+
35+
for (x in board.indices) {
36+
for (y in board[x].indices) {
37+
visited[x][y] = true
38+
if (board[x][y] == word[0] && dfs(board, visited, word, Position(x,y), 1)) {
39+
return true
40+
}
41+
visited[x][y] = false
42+
}
43+
}
44+
return false
45+
}
46+
47+
@Test
48+
fun `문자로 구성된 2차원 배열에서 word 문자열 존재 유무를 반환한다`() {
49+
exist(arrayOf(
50+
charArrayOf('A','B','C','E'),
51+
charArrayOf('S','F','C','S'),
52+
charArrayOf('A','D','E','E')
53+
), "ABCCED") shouldBe true
54+
exist(arrayOf(
55+
charArrayOf('A','B','C','E'),
56+
charArrayOf('S','F','C','S'),
57+
charArrayOf('A','D','E','E')
58+
), "SEE") shouldBe true
59+
exist(arrayOf(
60+
charArrayOf('A','B','C','E'),
61+
charArrayOf('S','F','C','S'),
62+
charArrayOf('A','D','E','E')
63+
), "SES") shouldBe false
64+
exist(arrayOf(
65+
charArrayOf('A','B','C','E'),
66+
charArrayOf('S','F','E','S'),
67+
charArrayOf('A','D','E','E')
68+
), "ABCESEEEFS") shouldBe true
69+
exist(arrayOf(
70+
charArrayOf('C','A','A'),
71+
charArrayOf('A','A','A'),
72+
charArrayOf('B','C','D')
73+
), "AAB") shouldBe true
74+
}
75+
76+
data class Position(
77+
val x: Int,
78+
val y: Int
79+
) {
80+
81+
operator fun plus(other: Position) = Position(this.x + other.x, this.y + other.y)
82+
83+
fun isNotOutOfIndexed(board: Array<CharArray>) =
84+
this.x < board.size && this.x >= 0 && this.y < board[0].size && this.y >= 0
85+
86+
companion object {
87+
val MOVES: List<Position> = listOf(
88+
Position(-1, 0),
89+
Position(0, 1),
90+
Position(1, 0),
91+
Position(0, -1),
92+
)
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)