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

[친환경사과] Week 4 #816

Merged
merged 9 commits into from
Jan 4, 2025
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
40 changes: 40 additions & 0 deletions coin-change/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package leetcode_study

/*
* 주어진 동전의 종류와 개수를 사용하여 주어진 금액을 만들 때, 중복을 허용한 최소 동전 개수를 구하는 문제
* 너비 우선 탐색을 사용해 문제 해결
* 시간 복잡도: O(n)
* -> queue 자료구조에서 각 동전(n)을 꺼내고 목표 금액(amount == k)까지 도달하는 경우: O(n * k)
* 공간 복잡도: O(k)
* -> 동전 사용 횟수를 저장하는 board의 크기
* */
fun coinChange(coins: IntArray, amount: Int): Int {
if (amount == 0) return 0
if (coins.isEmpty() || coins.any { it <= 0 }) return -1

val board = IntArray(amount + 1) { -1 }
val queue = ArrayDeque<Int>()

for (coin in coins) {
if (coin <= amount) {
queue.add(coin)
board[coin] = 1 // 동전 하나로 구성 가능
}
}

while (queue.isNotEmpty()) {
val currentPosition = queue.pollFirst()
for (coin in coins) {
val nextPosition = currentPosition + coin
if (nextPosition in 1..amount) {
// 아직 방문하지 않았거나 더 적은 동전으로 구성 가능하면 업데이트
if (board[nextPosition] == -1 || board[nextPosition] > board[currentPosition] + 1) {
queue.add(nextPosition)
board[nextPosition] = board[currentPosition] + 1
}
}
}
}

return board[amount]
}
62 changes: 62 additions & 0 deletions combination-sum/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package leetcode_study

/*
* 주어진 배열로 만들 수 있는 값(target)을 구성할 수 있는 모든 조합의 수를 구하는 문제
* 재귀를 사용해 모든 경우의 수를 구한 후 구한 결과값에서 중복을 제거하는 방식으로 문제 해결
* 시간 복잡도: O(2^n)
* -> target 값을 0으로 만들기 위해 가능한 모든 조합을 찾는 과정
* 공간 복잡도: O(2^n)
* -> removeDuplicates는 중복을 제거하고 결과를 저장하는 데 사용됨. 중복을 제외하는 과정에서 O(2^n)개의 리스트 사용
* */
fun combinationSum(candidates: IntArray, target: Int): List<List<Int>> {
val result = mutableListOf<List<Int>>()

fun combination(target: Int, current: List<Int>) {
if (target == 0) {
result.add(current)
return
}
if (target < 0) return

for (candidate in candidates) {
combination(target - candidate, current + candidate)
}
}
combination(target, emptyList())

val removeDuplicates = mutableSetOf<List<Int>>()

for (i in result) {
val temp = i.sorted()
removeDuplicates.add(temp)
}
return removeDuplicates.toList()
}

/*
* 재귀를 사용하여 문제를 해결할 때, 재귀 작성 시 중복을 제거하는 방식으로 문제 해결
* 시간 복잡도: O(2^n)
* -> target 값을 0으로 만들기 위해 가능한 모든 조합을 찾는 과정
* 공간 복잡도: O(n)
* -> 재귀 호출 스택에서 사용하는 공간이 target 값에 비례하기 때문에, 재귀 깊이는 O(n)
* */
fun combinationSumUsingBackTracking(candidates: IntArray, target: Int): List<List<Int>> {
val result = mutableListOf<List<Int>>()

fun combination(target: Int, current: MutableList<Int>, start: Int) {
if (target == 0) {
result.add(current.toList()) // 현재 조합을 결과에 추가
return
}
if (target < 0) return

for (i in start until candidates.size) {
current.add(candidates[i]) // 후보 추가
combination(target - candidates[i], current, i) // 현재 후보를 다시 사용할 수 있음
current.removeAt(current.lastIndex) // 백트래킹
}
}

combination(target, mutableListOf(), 0)
return result
}
83 changes: 83 additions & 0 deletions decode-ways/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package leetcode_study

/*
* 디코딩할 수 있는 경우의 수를 구하는 문제
* 아래 풀이는 구할 수 있는 모든 경우의 수를 구한 뒤 예외 상황을 제외해 답을 구하는 풀이
* 주어진 문자열을 재귀를 통해 나눌 때, 중복의 경우를 모두 계산하기 때문에 "Memory Limit Exceeded" 발생
* 예를 들어, 길이가 50인 문자열에 대해 2^{50} 에 가까운 경우의 수가 생성될 수 있음.
* */
fun numDecodings(s: String): Int {
val alphabetList = mutableListOf<String>()
var answer = 0
for (i in 1..26) {
alphabetList.add(i.toString())
}
val dividedList = lengthDivider(s)

for (dividedElement in dividedList) {
var index = 0
var value = 0
for (each in dividedElement) {
val subString = s.substring(index, index + each)
if (subString.first() != '0' && alphabetList.contains(subString)) {
index += each
value += 1
}
}
if (value == dividedElement.size) {
answer += 1
}
}
return answer
}

fun lengthDivider(s: String): List<List<Int>> {
val result = mutableListOf<List<Int>>()

fun divide(target: Int, current: List<Int>) {
if (target == 0) {
result.add(current)
return
}
if (target < 0) return

divide(target - 1, current + 1)
divide(target - 2, current + 2)
}

divide(s.length, emptyList())
return result
}

/*
* 메모이제이션을 사용한 문제 해결
* 시간 복잡도: O(n)
* -> 주어진 문자열의 길이 만큼 순회
* 공간 복잡도: O(n)
* -> 주어진 길이 만큼 기존 정보를 '메모'할 수 있는 공간
* */
fun numDecodings(s: String): Int {
if (s.isEmpty() || s.first() == '0') return 0 // 빈 문자열 또는 '0'으로 시작하는 경우 디코딩 불가

val n = s.length
val dp = IntArray(n + 1) // dp[i]는 s[0..i-1]까지 디코딩 가능한 경우의 수를 저장
dp[0] = 1 // 빈 문자열은 하나의 방법으로 디코딩 가능
dp[1] = if (s[0] != '0') 1 else 0 // 첫 문자가 '0'이 아니면 한 가지 방법 가능

for (i in 2..n) {
val oneDigit = s.substring(i - 1, i).toInt()
val twoDigits = s.substring(i - 2, i).toInt()

// 한 자리 숫자가 유효한 경우
if (oneDigit in 1..9) {
dp[i] += dp[i - 1]
}

// 두 자리 숫자가 유효한 경우
if (twoDigits in 10..26) {
dp[i] += dp[i - 2]
}
}

return dp[n]
}
22 changes: 22 additions & 0 deletions maximum-subarray/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package leetcode_study

/*
* 주어진 숫자 배열에서 Subarray가 가장 큰 수를 구하는 문제
* 시간 복잡도: O(n)
* -> 주어진 배열만큼 계산
* 공간 복잡도: 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.

지금 제출해주신 풀이도 충분히 훌륭한데요,
코드 내에서 dp[i]와 dp[i-1]만을 사용하고 있는것 같아요.
그렇다면 좀 더 최적화가 가능하지 않을까요?

* -> 가중치를 더하는 배열 필요
* */
fun maxSubArray(nums: IntArray): Int {
val dp = IntArray(nums.size)
dp[0] = nums[0]

for (i in 1 until nums.size) {
if (dp[i - 1] + nums[i] >= 0 && dp[i-1] >= 0) {
dp[i] = dp[i - 1] + nums[i]
} else {
dp[i] = nums[i]
}
}
return dp.max()
}
41 changes: 41 additions & 0 deletions merge-two-sorted-lists/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package leetcode_study

/*
* 오름차순으로 정렬된 두 노드 리스트를 크기 순서대로 병합하는 문제
* 기준 노드와 다음 노드를 가리키는 포인터 노드를 상용해 문제 해결
* 시간 복잡도: O(m + n)
* -> 주어진 두 노드 숫자만큼 순회
* 공간 복잡도: O(1)
* */
fun mergeTwoLists(list1: ListNode?, list2: ListNode?): ListNode? {
val resultNode = ListNode(0)
var currentNode = resultNode

var firstCurrentNode = list1
var secondCurrentNode = list2

while (firstCurrentNode != null && secondCurrentNode != null) {
if (firstCurrentNode.value <= secondCurrentNode.value) {
currentNode.next = ListNode(firstCurrentNode.value)
firstCurrentNode = firstCurrentNode.next
} else {
currentNode.next = ListNode(secondCurrentNode.value)
secondCurrentNode = secondCurrentNode.next
}
currentNode = currentNode.next!!
}

if (firstCurrentNode != null) {
currentNode.next = firstCurrentNode
} else if(secondCurrentNode != null) {
currentNode.next = secondCurrentNode
}
return resultNode.next
}


class ListNode(
var value: Int,
) {
var next: ListNode? = null
}
26 changes: 26 additions & 0 deletions missing-number/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package leetcode_study

/*
* 주어진 숫자 배열에서 존재하지 않은 수 판별하는 문제
* 0부터 n의 범위에서 모든 수는 유일하게 존재
* 시간 복잡도: O(n)
* -> nums 배열을 순회하며, 각 요소에 대해 board 배열에 값을 설정: O(n)
* -> board 배열을 순회하며, 값이 0인 인덱스 검색: O(n)
* --> O(n) + O(n) = O(n)
* 공간 복잡도: 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.

열정이 넘치셔서, 괜시리 더 해주셨으면 하는 욕심이 좀 나네요..ㅎㅎ
가능하시다면 공간복잡도도 더 최적화 해 주시면 어떨까요?!

* -> 입력 배열 nums의 크기가 n일 때, board 배열의 크기 n + 1: O(n)
* */
fun missingNumber(nums: IntArray): Int {
val size = nums.size
val board = IntArray(size+1)
for (index in nums.indices) {
board[nums[index]] = 1
}

for (i in board.indices) {
if (board[i] == 0) {
return i
}
}
return 0
}
46 changes: 46 additions & 0 deletions palindromic-substrings/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package leetcode_study

/*
* 주어진 문자열에서 자른 개별 문자열이 회문일 경우를 판단하는 문제
* 시간 복잡도: O(n^3)
* -> 주어진 문자열을 자를 때 이중 반복문 수행: O(n^2)
* -> subString() 함수는 내부적으로 주어진 k 만큼 복사된 문자열 객체를 만들기 때문에 O(n) 소요
* --> O(n) * O(n^2) = O(n^3)
* 공간 복잡도: O(n)
* -> subString() 함수가 호출될 때마다 길이 k의 새로운 문자열 객체가 생성되기 때문에 subString 최대 길이인 O(n)을 가짐.
* */
fun countSubstrings(s: String): Int {
var count = 0

val maxLength = s.length
for (startIndex in 0 until maxLength) {
for (endIndex in startIndex + 1..maxLength) {
val temp = s.substring(startIndex, endIndex)
if (temp == temp.reversed()) {
count++
}
}
}
return count
}

/*
* 자른 문자열을 개별적으로 회문인지 판단해야하는데 주어진 문자열에서 포함하는 것으로 문제를 해석하고
* 해결했기 때문에 통과하지 못함
* */
fun countSubstrings01(s: String): Int {
val result = mutableListOf<String>()
val maxLength = s.length
var startIndex = 0
while (startIndex < maxLength) {
val endIndex = startIndex + 1
for (i in endIndex until maxLength + 1) {
val temp = s.substring(startIndex, i)
if (s.contains(temp.reversed())) {
result.add(temp)
}
}
startIndex += 1
}
return result.size
}
54 changes: 54 additions & 0 deletions word-search/EcoFriendlyAppleSu.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package leetcode_study


/*
* dfs로 문제 해결
* 정해진 순서의 단어를 처리하는 부분에서 대부분의 시간 사용
* 시간 복잡도: O(m *n)
Copy link
Contributor

Choose a reason for hiding this comment

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

m * n 크기의 지도를 주어진 단어의 길이(l) 만큼 반복해서 읽어야 하는 만큼, 단어의 길이도 복잡도에 영향을 꽤나 주지 않을까요?

또한 11 line의 공간복잡도에서도 단어의 길이가 영향을 끼칠것같아요.
최소한 단어 길이만큼은 재귀 스택이 반복 생성될 것으로 예상됩니다!

* -> 주어진 배열에서 첫 번째 글자를 찾는 반복문: O(m *n)
* -> 최악의 경우 모든 칸을 방문 처리(dfs): O(m *n)
* --> O(m *n) + O(m *n) == O(m *n)
* 공간 복잡도: O(m * n * l)
* -> 방문을 체크하기 위한 공간: O(m * n)
* -> DFS는 단어의 길이 l 만큼 최대 깊이로 호출되므로, 재귀 스택의 공간 복잡도: O(l)
* */
val dx = listOf(0, 1, -1, 0)
val dy = listOf(1, 0, 0, -1)

fun exist(board: Array<CharArray>, word: String): Boolean {
val rowSize = board.size
val colSize = board[0].size
val visited = Array(rowSize) { BooleanArray(colSize) }

// 모든 위치에서 DFS 탐색 시작
for (i in board.indices) {
for (j in board[0].indices) {
if (board[i][j] == word[0] && dfs(board, word, i, j, 0, visited)) {
return true
}
}
}
return false
}

fun dfs(board: Array<CharArray>, word: String, x: Int, y: Int, index: Int, visited: Array<BooleanArray>): Boolean {
// 탐색 종료 조건
if (index == word.length) return true
if (x !in board.indices || y !in board[0].indices || visited[x][y] || board[x][y] != word[index]) return false

// 현재 위치 방문 처리
visited[x][y] = true

// 4방향으로 탐색
for (i in 0..3) {
val nx = x + dx[i]
val ny = y + dy[i]
if (dfs(board, word, nx, ny, index + 1, visited)) {
return true
}
}

// 방문 복구
visited[x][y] = false
return false
}
Loading