-
Notifications
You must be signed in to change notification settings - Fork 126
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
[친환경사과] Week 4 #816
Changes from all commits
cf5e717
f56cf9e
aa83d96
c627e15
c2e5272
7740c13
dafbd2f
7e17953
7b94450
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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] | ||
} |
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 | ||
} |
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] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() | ||
} |
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 | ||
} |
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) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
} |
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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
package leetcode_study | ||
|
||
|
||
/* | ||
* dfs로 문제 해결 | ||
* 정해진 순서의 단어를 처리하는 부분에서 대부분의 시간 사용 | ||
* 시간 복잡도: O(m *n) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
또한 |
||
* -> 주어진 배열에서 첫 번째 글자를 찾는 반복문: 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 | ||
} |
There was a problem hiding this comment.
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]만을 사용하고 있는것 같아요.
그렇다면 좀 더 최적화가 가능하지 않을까요?