diff --git a/coin-change/EcoFriendlyAppleSu.kt b/coin-change/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..e19a3e9d4 --- /dev/null +++ b/coin-change/EcoFriendlyAppleSu.kt @@ -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() + + 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] +} diff --git a/combination-sum/EcoFriendlyAppleSu.kt b/combination-sum/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..0a94fe2be --- /dev/null +++ b/combination-sum/EcoFriendlyAppleSu.kt @@ -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> { + val result = mutableListOf>() + + fun combination(target: Int, current: List) { + 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>() + + 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> { + val result = mutableListOf>() + + fun combination(target: Int, current: MutableList, 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 +} diff --git a/decode-ways/EcoFriendlyAppleSu.kt b/decode-ways/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..2766ee74d --- /dev/null +++ b/decode-ways/EcoFriendlyAppleSu.kt @@ -0,0 +1,83 @@ +package leetcode_study + +/* +* 디코딩할 수 있는 경우의 수를 구하는 문제 +* 아래 풀이는 구할 수 있는 모든 경우의 수를 구한 뒤 예외 상황을 제외해 답을 구하는 풀이 +* 주어진 문자열을 재귀를 통해 나눌 때, 중복의 경우를 모두 계산하기 때문에 "Memory Limit Exceeded" 발생 +* 예를 들어, 길이가 50인 문자열에 대해 2^{50} 에 가까운 경우의 수가 생성될 수 있음. +* */ +fun numDecodings(s: String): Int { + val alphabetList = mutableListOf() + 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> { + val result = mutableListOf>() + + fun divide(target: Int, current: List) { + 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] +} diff --git a/maximum-subarray/EcoFriendlyAppleSu.kt b/maximum-subarray/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..363842336 --- /dev/null +++ b/maximum-subarray/EcoFriendlyAppleSu.kt @@ -0,0 +1,22 @@ +package leetcode_study + +/* +* 주어진 숫자 배열에서 Subarray가 가장 큰 수를 구하는 문제 +* 시간 복잡도: O(n) +* -> 주어진 배열만큼 계산 +* 공간 복잡도: O(n) +* -> 가중치를 더하는 배열 필요 +* */ +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() +} diff --git a/merge-two-sorted-lists/EcoFriendlyAppleSu.kt b/merge-two-sorted-lists/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..db39193f4 --- /dev/null +++ b/merge-two-sorted-lists/EcoFriendlyAppleSu.kt @@ -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 +} diff --git a/missing-number/EcoFriendlyAppleSu.kt b/missing-number/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..f24499d1f --- /dev/null +++ b/missing-number/EcoFriendlyAppleSu.kt @@ -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) +* -> 입력 배열 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 +} diff --git a/palindromic-substrings/EcoFriendlyAppleSu.kt b/palindromic-substrings/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..01458f106 --- /dev/null +++ b/palindromic-substrings/EcoFriendlyAppleSu.kt @@ -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() + 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 +} diff --git a/word-search/EcoFriendlyAppleSu.kt b/word-search/EcoFriendlyAppleSu.kt new file mode 100644 index 000000000..56635b3ae --- /dev/null +++ b/word-search/EcoFriendlyAppleSu.kt @@ -0,0 +1,54 @@ +package leetcode_study + + +/* +* dfs로 문제 해결 +* 정해진 순서의 단어를 처리하는 부분에서 대부분의 시간 사용 +* 시간 복잡도: O(m *n) +* -> 주어진 배열에서 첫 번째 글자를 찾는 반복문: 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, 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, word: String, x: Int, y: Int, index: Int, visited: Array): 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 +}