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/coin-change/GangBean.java b/coin-change/GangBean.java new file mode 100644 index 000000000..635f9e8d4 --- /dev/null +++ b/coin-change/GangBean.java @@ -0,0 +1,31 @@ +class Solution { + public int coinChange(int[] coins, int amount) { + /** + 1. understanding + - given coins that can be used, find the minimum count of coins sum up to input amount value. + - [1,2,5]: 11 + - 2 * 5 + 1 * 1: 3 -> use high value coin as much as possible if the remain can be sumed up by remain coins. + 2. strategy + - If you search in greedy way, it will takes over O(min(amount/coin) ^ N), given N is the length of coins. + - Let dp[k] is the number of coins which are sum up to amount k, in a given coin set. + - Then, dp[k] = min(dp[k], dp[k-coin] + 1) + 3. complexity + - time: O(CA), where C is the length of coins, A is amount value + - space: O(A), where A is amount value + */ + + int[] dp = new int[amount + 1]; + for (int i = 1; i <= amount; i++) { + dp[i] = amount + 1; + } + + for (int coin: coins) { // O(C) + for (int k = coin; k <= amount; k++) { // O(A) + dp[k] = Math.min(dp[k], dp[k-coin] + 1); + } + } + + return (dp[amount] >= amount + 1) ? -1 : dp[amount]; + } +} + diff --git a/coin-change/HerrineKim.js b/coin-change/HerrineKim.js new file mode 100644 index 000000000..de894daca --- /dev/null +++ b/coin-change/HerrineKim.js @@ -0,0 +1,21 @@ +// 시간 복잡도: O(n * m) +// 공간 복잡도: O(n) + +/** + * @param {number[]} coins + * @param {number} amount + * @return {number} + */ +var coinChange = function(coins, amount) { + const dp = new Array(amount + 1).fill(Infinity); + dp[0] = 0; + + for (let coin of coins) { + for (let i = coin; i <= amount; i++) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1); + } + } + + return dp[amount] === Infinity ? -1 : dp[amount]; +}; + diff --git a/coin-change/HodaeSsi.py b/coin-change/HodaeSsi.py new file mode 100644 index 000000000..55ba105c8 --- /dev/null +++ b/coin-change/HodaeSsi.py @@ -0,0 +1,17 @@ +# space complexity: O(n) (여기서 n은 amount) +# time complexity: O(n * m) (여기서 n은 amount, m은 coins의 길이) +from typing import List + + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + + for i in range(1, amount + 1): + for coin in coins: + if coin <= i: + dp[i] = min(dp[i], dp[i - coin] + 1) + + return dp[amount] if dp[amount] != float('inf') else -1 + diff --git a/coin-change/Jeehay28.js b/coin-change/Jeehay28.js new file mode 100644 index 000000000..b80c66441 --- /dev/null +++ b/coin-change/Jeehay28.js @@ -0,0 +1,31 @@ +/** + * @param {number[]} coins + * @param {number} amount + * @return {number} + */ + +// TC : O(c*a), where c is the number of coins, and a is amount +// SC : O(a) // dp array requires O(a) space + +var coinChange = function (coins, amount) { + // dynamic programming approach + + // dp[amount] : the minimum number of coins + // as a default, dp[0] = 0, for other amounts, dp[amount] = amount + 1 () + // [0, amount+1, amount+1, ...] + const dp = [0, ...new Array(amount).fill(amount + 1)]; + + // start from coin because i - coin >= 0 + for (const coin of coins) { + for (let i = coin; i <= amount; i++) { + // dp[i] : not using the current coin + // dp[i - coin] + 1 : using the current coin + dp[i] = Math.min(dp[i - coin] + 1, dp[i]); + } + } + + // dp[amount] === amount + 1 : that amount of money cannot be made up by any combination of the coins + return dp[amount] < amount + 1 ? dp[amount] : -1; +}; + + diff --git a/coin-change/KwonNayeon.py b/coin-change/KwonNayeon.py new file mode 100644 index 000000000..fe9fd838e --- /dev/null +++ b/coin-change/KwonNayeon.py @@ -0,0 +1,30 @@ +""" +Constraints: +1. 1 <= coins.length <= 12 +2. 1 <= coins[i] <= 2^31 - 1 +3. 0 <= amount <= 10^4 + +Time Complexity: O(N*M) +- N은 amount +- M은 동전의 개수 (coins.length) + +Space Complexity: O(N) +- N은 amount + +To Do: +- DP 문제 복습하기 +- Bottom-up과 Top-down 방식의 차이점 이해하기 +- 그리디와 DP의 차이점 복습하기 +""" + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [sys.maxsize // 2] * (amount + 1) + dp[0] = 0 + + for i in range(1, amount + 1): + for coin in coins: + if i >= coin: + dp[i] = min(dp[i], dp[i-coin] + 1) + + return dp[amount] if dp[amount] != sys.maxsize // 2 else -1 diff --git a/coin-change/YeomChaeeun.ts b/coin-change/YeomChaeeun.ts new file mode 100644 index 000000000..bc0b21f2b --- /dev/null +++ b/coin-change/YeomChaeeun.ts @@ -0,0 +1,20 @@ +/** + * 동전들로 금액을 만들때 필요한 최소 동전의 개수 찾기 + * 알고리즘 복잡도 + * - 시간 복잡도: O(nxm) 동전의 개수 x 만들어야하는 금액의 크기 + * - 공간 복잡도: O(m) 주어진 금액에 비례함 + * @param coins + * @param amount + */ +function coinChange(coins: number[], amount: number): number { + const dp = new Array(amount + 1).fill(amount + 1) + dp[0] = 0 // 0원은 0개 + + for (const coin of coins) { + for (let i = coin; i <= amount; i++) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1) + } + } + + return dp[amount] === amount + 1 ? -1 : dp[amount] +} diff --git a/coin-change/Yjason-K.ts b/coin-change/Yjason-K.ts new file mode 100644 index 000000000..46531901c --- /dev/null +++ b/coin-change/Yjason-K.ts @@ -0,0 +1,46 @@ +/** + * 가지고 있는 동전을 최대한 활용하여 최소의 조합으로 amount를 만드는 최소 동전 개수 구하는 함수 + * + * @param {number[]} coins - 사용 가능한 동전 배열 + * @param {number} amount - 만들어야 하는 총합 + * @returns {number} + * + * 시간 복잡도 O(n * m) + * - n은 동전 배열의 크기 + * - m은 amount + * + * 공간 복잡도 (n); + * - 큐에 최대 n개의 요소가 들어갈 수 있음 + */ +function coinChange(coins: number[], amount: number): number { + // 총합이 0인 경우 0 반환 + if (amount === 0) return 0; + + // 너비 우선 탐색을 활용한 풀이 + + const queue: [number, number] [] = [[0, 0]]; // [현재 총합, 깊이] + const visited = new Set(); + + while (queue.length > 0) { + const [currentSum, depth] = queue.shift()!; + + // 동전을 하나씩 더해서 다음 깊이을 탐색 + for (const coin of coins) { + const nextSum = currentSum + coin; + + // 목표 금액에 도달하면 현재 깊이를 반환 + if (nextSum === amount) return depth + 1; + + // 아직 총합에 도달하지 않았고, 중복되지 않아 탐색 가능한 경우 + if (nextSum < amount && !visited.has(nextSum)) { + queue.push([nextSum, depth + 1]); + visited.add(nextSum) + } + + } + } + + // 탐색 조건을 완료 해도 경우의 수를 찾지 못한 경우 + return -1; +} + diff --git a/coin-change/dusunax.py b/coin-change/dusunax.py new file mode 100644 index 000000000..f6b6fd0d8 --- /dev/null +++ b/coin-change/dusunax.py @@ -0,0 +1,49 @@ +''' +# 322. Coin Change + +use a queue for BFS & iterate through the coins and check the amount is down to 0. +use a set to the visited check. + +## Time and Space Complexity + +``` +TC: O(n * Amount) +SC: O(Amount) +``` + +#### TC is O(n * Amount): +- sorting the coins = O(n log n) +- reversing the coins = O(n) +- iterating through the queue = O(Amount) +- iterating through the coins and check the remaining amount is down to 0 = O(n) + +#### SC is O(Amount): +- using a queue to store (the remaining amount, the count of coins) tuple = O(Amount) +- using a set to store the visited check = O(Amount) +''' +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + if amount == 0: + return 0 + if len(coins) == 1 and coins[0] == amount: + return 1 + + coins.sort() # TC: O(n log n) + coins.reverse() # TC: O(n) + + queue = deque([(amount, 0)]) # SC: O(Amount) + visited = set() # SC: O(Amount) + + while queue: # TC: O(Amount) + remain, count = queue.popleft() + + for coin in coins: # TC: O(n) + next_remain = remain - coin + + if next_remain == 0: + return count + 1 + if next_remain > 0 and next_remain not in visited: + queue.append((next_remain, count + 1)) + visited.add(next_remain) + + return -1 diff --git a/coin-change/ekgns33.java b/coin-change/ekgns33.java new file mode 100644 index 000000000..35cb39760 --- /dev/null +++ b/coin-change/ekgns33.java @@ -0,0 +1,48 @@ +/* +input : array of integers each element represents coins, single integer amount +output : fewest number of coin to make up the amount +constraint +1) elements are positive? +yes +2) coins are in integer range? +yes +3) range of amoutn +integer range? +[0, 10^4] +4) at least one valid answer exists? +nope. if there no exist than return -1 + +edge. case +1) if amount == 0 return 0; + + solution 1) top-down approach + +amount - each coin + until amount == 0 + return min +tc : O(n * k) when n is amount, k is unique coin numbers +sc : O(n) call stack + +solution 2) bottom-up +tc : O(n*k) +sc : O(n) +let dp[i] the minimum number of coins to make up amount i + + */ +class Solution { + public int coinChange(int[] coins, int amount) { + //edge + if(amount == 0) return 0; + int[] dp = new int[amount+1]; + dp[0] = 0; + for(int i = 1; i<= amount; i++) { + dp[i] = amount+1; + for(int coin: coins) { + if(i - coin >= 0) { + dp[i] = Math.min(dp[i], dp[i-coin] + 1); + } + } + } + return dp[amount] == amount+1 ? -1 : dp[amount]; + } +} diff --git a/coin-change/forest000014.java b/coin-change/forest000014.java new file mode 100644 index 000000000..45d4cff91 --- /dev/null +++ b/coin-change/forest000014.java @@ -0,0 +1,28 @@ +/* +Time Complexity: O(coins.length * amount) +Space Complexity: O(amount) + +1 ~ i-1원까지의 최적해를 알고 있다면, i원의 최적해를 구할 때 coins 배열 iteration으로 구할 수 있음 +*/ +class Solution { + public int coinChange(int[] coins, int amount) { + int c = coins.length; + + int[] dp = new int[amount + 1]; + Arrays.fill(dp, 99999); + dp[0] = 0; + + for (int i = 1; i <= amount; i++) { + for (int j = 0; j < c; j++) { + if (i - coins[j] < 0) { + continue; + } + if (dp[i - coins[j]] >= 0 && dp[i - coins[j]] + 1 < dp[i]) { + dp[i] = dp[i - coins[j]] + 1; + } + } + } + + return dp[amount] == 99999 ? -1 : dp[amount]; + } +} diff --git a/coin-change/gmlwls96.kt b/coin-change/gmlwls96.kt new file mode 100644 index 000000000..f9414aacf --- /dev/null +++ b/coin-change/gmlwls96.kt @@ -0,0 +1,25 @@ +class Solution { + // 알고리즘 : dp + /** 풀이 + * dp배열에 최소한의 동전의 개수를 저장. + * dp[i] = min(dp[i - 동전값], dp[i]) 중 더 작은값이 최소 동전의 개수. + * */ + // 시간 : O(coins.len*amount), 공간 : O(amount) + fun coinChange(coins: IntArray, amount: Int): Int { + val initValue = Int.MAX_VALUE / 2 + val dp = IntArray(amount + 1) { initValue } + dp[0] = 0 + for (i in 1..amount) { + coins.forEach { c -> + if (c <= i) { + dp[i] = min(dp[i - c] + 1, dp[i]) + } + } + } + return if (dp[amount] == initValue) { + -1 + } else { + dp[amount] + } + } +} diff --git a/coin-change/gwbaik9717.js b/coin-change/gwbaik9717.js new file mode 100644 index 000000000..752060ad9 --- /dev/null +++ b/coin-change/gwbaik9717.js @@ -0,0 +1,23 @@ +// n: amount m: length of coins +// Time complexity: O(n * m) + O(mlogm) +// Space complexity: O(n) + +/** + * @param {number[]} coins + * @param {number} amount + * @return {number} + */ +var coinChange = function (coins, amount) { + const dp = Array.from({ length: amount + 1 }, () => Infinity); + dp[0] = 0; + + coins.sort((a, b) => a - b); + + for (const coin of coins) { + for (let i = coin; i <= amount; i++) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1); + } + } + + return dp.at(-1) === Infinity ? -1 : dp.at(-1); +}; diff --git a/coin-change/imsosleepy.java b/coin-change/imsosleepy.java new file mode 100644 index 000000000..ef8eaf4be --- /dev/null +++ b/coin-change/imsosleepy.java @@ -0,0 +1,21 @@ +// 비슷한 문제를 푼 적이 있어서 쉽게 해결 +// https://www.acmicpc.net/problem/2294 +// O(N * amount) 시간복잡도가 배열 크기와 amount에 종속된다. +// dp[N]만 사용하므로 공간복잡도는 O(N) +class Solution { + public int coinChange(int[] coins, int amount) { + int[] dp = new int[amount + 1]; + Arrays.fill(dp, amount + 1); 불가능한 큰 값 + dp[0] = 0; + + for (int i = 1; i <= amount; i++) { + for (int coin : coins) { + if (i >= coin) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1); + } + } + } + + return dp[amount] > amount ? -1 : dp[amount]; + } +} diff --git a/coin-change/jeldo.py b/coin-change/jeldo.py new file mode 100644 index 000000000..227a542db --- /dev/null +++ b/coin-change/jeldo.py @@ -0,0 +1,10 @@ +class Solution: + # O(n*m), n = len(coins), m = amount + def coinChange(self, coins: list[int], amount: int) -> int: + dp = [amount+1] * (amount+1) + dp[0] = 0 + for i in range(amount+1): + for coin in coins: + if i - coin >= 0: + dp[i] = min(dp[i], dp[i-coin]+1) + return dp[amount] if dp[amount] != amount+1 else -1 diff --git a/coin-change/jinah92.py b/coin-change/jinah92.py new file mode 100644 index 000000000..b528841ea --- /dev/null +++ b/coin-change/jinah92.py @@ -0,0 +1,10 @@ +# O(C*A) times, O(A) spaces +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [0] + [amount + 1] * amount + + for coin in coins: + for i in range(coin, amount + 1): + dp[i] = min(dp[i], dp[i-coin]+1) + + return dp[amount] if dp[amount] < amount + 1 else -1 diff --git a/coin-change/minji-go.java b/coin-change/minji-go.java new file mode 100644 index 000000000..527a23d8e --- /dev/null +++ b/coin-change/minji-go.java @@ -0,0 +1,23 @@ +/* + Problem: https://leetcode.com/problems/coin-change/ + Description: return the fewest number of coins that you need to make up that amount + Concept: Array, Dynamic Programming, Breadth-First Search + Time Complexity: O(NM), Runtime 15ms - M is the amount + Space Complexity: O(M), Memory 44.28MB +*/ +class Solution { + public int coinChange(int[] coins, int amount) { + int[] dp = new int[amount+1]; + Arrays.fill(dp, amount+1); + dp[0]=0; + + for(int i=1; i<=amount; i++){ + for(int coin : coins){ + if(i >= coin) { + dp[i] = Math.min(dp[i], dp[i-coin] +1); + } + } + } + return dp[amount]>amount? -1: dp[amount]; + } +} diff --git a/coin-change/mmyeon.ts b/coin-change/mmyeon.ts new file mode 100644 index 000000000..7e9bc0d1f --- /dev/null +++ b/coin-change/mmyeon.ts @@ -0,0 +1,27 @@ +/** + * + * 접근 방법 : + * - 동전 최소 개수 구하는 문제니까 DP로 풀기 + * - 코인마다 순회하면서 동전 개수 최소값 구하기 + * - 기존값과 코인을 뺀 값 + 1 중 작은 값으로 업데이트 + * + * 시간복잡도 : O(n * m) + * - 코인 개수 n만큼 순회 + * - 각 코인마다 amount 값(m) 될 때까지 순회 + * + * 공간복잡도 : O(m) + * - amount 만큼 dp 배열 생성 + * + */ +function coinChange(coins: number[], amount: number): number { + const dp = Array(amount + 1).fill(Number.MAX_VALUE); + dp[0] = 0; + + for (const coin of coins) { + for (let i = coin; i <= amount; i++) { + dp[i] = Math.min(dp[i], dp[i - coin] + 1); + } + } + + return dp[amount] === Number.MAX_VALUE ? -1 : dp[amount]; +} diff --git a/coin-change/neverlish.go b/coin-change/neverlish.go new file mode 100644 index 000000000..92ec40a4a --- /dev/null +++ b/coin-change/neverlish.go @@ -0,0 +1,50 @@ +// 시간복잡도: O(n*m) +// 공간복잡도: O(n*m) + +package main + +import "testing" + +func TestCoinChange(t *testing.T) { + result1 := coinChange([]int{1, 2, 5}, 11) + if result1 != 3 { + t.Errorf("Expected 3 but got %d", result1) + } + + result2 := coinChange([]int{2}, 3) + + if result2 != -1 { + t.Errorf("Expected -1 but got %d", result2) + } + + result3 := coinChange([]int{1}, 0) + + if result3 != 0 { + t.Errorf("Expected 0 but got %d", result3) + } +} + +func coinChange(coins []int, amount int) int { + if amount == 0 { + return 0 + } + + dp := make([]int, amount+1) + for i := 1; i <= amount; i++ { + dp[i] = amount + 1 + } + + for i := 1; i <= amount; i++ { + for _, coin := range coins { + if coin <= i { + dp[i] = min(dp[i], dp[i-coin]+1) + } + } + } + + if dp[amount] > amount { + return -1 + } + + return dp[amount] +} diff --git a/coin-change/pmjuu.py b/coin-change/pmjuu.py new file mode 100644 index 000000000..e9cc8c483 --- /dev/null +++ b/coin-change/pmjuu.py @@ -0,0 +1,25 @@ +from typing import List + + +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + # dp[i]: i 금액을 만들기 위해 필요한 최소 동전 개수 + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + + for i in range(1, amount + 1): + for coin in coins: + if coin <= i: + dp[i] = min(dp[i], dp[i - coin] + 1) + + return dp[amount] if dp[amount] != float('inf') else -1 + + +# 시간 복잡도: +# - 외부 반복문은 금액(amount)의 범위에 비례하고 -> O(n) (n은 amount) +# - 내부 반복문은 동전의 개수에 비례하므로 -> O(m) (m은 coins의 길이) +# - 총 시간 복잡도: O(n * m) + +# 공간 복잡도: +# - dp 배열은 금액(amount)의 크기만큼의 공간을 사용하므로 O(n) +# - 추가 공간 사용은 없으므로 총 공간 복잡도: O(n) diff --git a/coin-change/sungjinwi.py b/coin-change/sungjinwi.py new file mode 100644 index 000000000..e69de29bb diff --git a/coin-change/thispath98.py b/coin-change/thispath98.py new file mode 100644 index 000000000..a3edc7571 --- /dev/null +++ b/coin-change/thispath98.py @@ -0,0 +1,39 @@ +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + """ + Intuition: + dp 배열에 이전 금액에 대한 최소 개수를 저장해두고 + 갱신하는 방식으로 작동한다. + + for 루프를 돌면서 현재 가격에서 coin만큼의 가격을 + 뺐을 때 거슬러줄 수 있다면, 그 값에서 1개를 더해준 + 개수를 prev_coins에 저장한다. + + 이후 prev_coins가 존재하면 현재 인덱스에서 거슬러줄 수 있는 + 동전의 최소 개수를 갱신한다. + + Time Complexity: + O(amount x coins.length): + amount 만큼 루프를 순회하는데 각 루프마다 + coins.length 만큼 prev_coins 배열을 만든다. + + Space Complexity: + O(amount): + amount만큼의 크기를 가지는 dp 배열을 저장한다. + """ + dp = [0 for _ in range(amount + 1)] + + for coin in coins: + if coin <= amount: + dp[coin] = 1 + + for i in range(1, amount + 1): + if dp[i]: + continue + + prev_coins = [dp[i - coin] + 1 for coin in coins if i >= coin and dp[i - coin] > 0] + if prev_coins: + dp[i] = min(prev_coins) + + answer = -1 if amount > 0 and dp[amount] == 0 else dp[amount] + return answer 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/combination-sum/minji-go.java b/combination-sum/minji-go.java index ab895923a..fac01b813 100644 --- a/combination-sum/minji-go.java +++ b/combination-sum/minji-go.java @@ -2,10 +2,8 @@ Problem: https://leetcode.com/problems/combination-sum/ Description: return a list of all unique combinations of candidates where the chosen numbers sum to target Concept: Array, Backtracking - Time Complexity: O(Nⁿ), Runtime 2ms - Space Complexity: O(N), Memory 44.88MB - - - Time Complexity, Space Complexity를 어떻게 계산해야할지 어렵네요 :( + Time Complexity: O(Nᵀ), Runtime 2ms + Space Complexity: O(T), Memory 44.88MB */ class Solution { public List> answer = new ArrayList<>(); 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/decode-ways/Gotprgmer.java b/decode-ways/Gotprgmer.java new file mode 100644 index 000000000..3c78ca65f --- /dev/null +++ b/decode-ways/Gotprgmer.java @@ -0,0 +1,37 @@ +// 완전탐색을 통해 모든 경우의 수를 구하기 위해 노력하였지만 시간초과가 발생하였습니다. +// dfs를 통해 풀이하려고 했지만 O(2^N)의 시간복잡도로 인해 시간초과가 발생하였습니다. +// 이후 dp로 풀이를 시작하였고 어렵지 않게 풀이하였습니다. +// dp[i] = dp[i-1] + dp[i-2]로 풀이하였습니다. +// 이때 i번째 문자열을 1자리로 취급할지 2자리로 취급할지에 따라 경우의 수가 달라집니다. +// 1자리로 취급할 경우 1~9까지 가능하고 +// 2자리로 취급할 경우 10~26까지 가능합니다. + +// 시간복잡도 : O(N) +// 공간복잡도 : O(N) +class SolutionGotprgmer { + public int numDecodings(String s) { + // 예외 처리: 문자열이 "0"으로 시작하거나 빈 문자열이면 + if (s == null || s.length() == 0 || s.charAt(0) == '0') { + return 0; + } + int[] dp = new int[s.length()+1]; + dp[0] = 1; + for(int i=0;i0){ + String twoDigitStr = s.substring(i-1,i+1); + int twoDigitNum = Integer.valueOf(twoDigitStr); + if(twoDigitNum>=10 && twoDigitNum <27){ + dp[i+1] += dp[i-1]; + } + } + + } + return dp[s.length()]; + } + + +} 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/merge-two-sorted-lists/GangBean.java b/merge-two-sorted-lists/GangBean.java new file mode 100644 index 000000000..60b84752f --- /dev/null +++ b/merge-two-sorted-lists/GangBean.java @@ -0,0 +1,73 @@ +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ +class Solution { + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + /** + 1. understanding + - merge 2 sorted linked list + 2. strategy + - assign return ListNode + - for each node, started in head, compare each node's value, and add smaller value node to return node, and move the node's head to head.next + 3. complexity + - time: O(N + M), N is the length of list1, M is the length of list2 + - space: O(1), exclude the return variable + */ + ListNode curr = null; + ListNode ret = null; + + while (list1 != null && list2 != null) { + if (list1.val < list2.val) { + ListNode node = new ListNode(list1.val); + if (ret == null) { + ret = node; + } else { + curr.next = node; + } + list1 = list1.next; + curr = node; + } else { + ListNode node = new ListNode(list2.val); + if (ret == null) { + ret = node; + } else { + curr.next = node; + } + list2 = list2.next; + curr = node; + } + } + + while (list1 != null) { + ListNode node = new ListNode(list1.val); + if (ret == null) { + ret = node; + } else { + curr.next = node; + } + list1 = list1.next; + curr = node; + } + + while (list2 != null) { + ListNode node = new ListNode(list2.val); + if (ret == null) { + ret = node; + } else { + curr.next = node; + } + list2 = list2.next; + curr = node; + } + + return ret; + } +} + diff --git a/merge-two-sorted-lists/HodaeSsi.py b/merge-two-sorted-lists/HodaeSsi.py new file mode 100644 index 000000000..160f98033 --- /dev/null +++ b/merge-two-sorted-lists/HodaeSsi.py @@ -0,0 +1,22 @@ +from typing import Optional + + +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + if not list1: + return list2 + if not list2: + return list1 + + if list1.val < list2.val: + list1.next = self.mergeTwoLists(list1.next, list2) + return list1 + else: + list2.next = self.mergeTwoLists(list1, list2.next) + return list2 + diff --git a/merge-two-sorted-lists/Jay-Mo-99.py b/merge-two-sorted-lists/Jay-Mo-99.py new file mode 100644 index 000000000..c4ee75756 --- /dev/null +++ b/merge-two-sorted-lists/Jay-Mo-99.py @@ -0,0 +1,27 @@ + #해석 + #list1 과 list2는 이미 오름차순 정렬된 리스트 + #두 리스트의 첫 노드값을 비교해 더 작은값을 첫번째 노드로 선택 + #이후 선택된 노드의 next포인터를 재귀적으로 처리하여 노드 병합 + + + #Big O + #N: 매개변수 n의 크기(계단 갯수) + + #Time Complexity: O(M + N) + #- M: list1의 길이 + #- N: list2의 길이 + #- 각 재귀 호출에 하나의 노드가 다뤄진다, 따라서 최대 M+N 번의 재귀호출 발생 + + + #Space Complexity: O(M+N) + #- 병합 리스트의 최대 길이 M+N이므로, 스택 공간 사용도 이에 비례한다. + +class Solution(object): + def mergeTwoLists(self, list1, list2): + if list1 and list2: ## 두 리스트가 모두 비어있지 않은 경우에만 해당 + if list1.val > list2.val: # list1의 현재 값이 list2보다 크다면 + list1, list2 = list2, list1 # 값을 교환하여 항상 list1이 더 작은 값을 가짐 + list1.next = self.mergeTwoLists(list1.next, list2) # list1의 다음 노드와 list2로 재귀 호출 + return list1 or list2 # 리스트가 하나라도 비어 있다면 비어 있지 않은 리스트를 반환 + + diff --git a/merge-two-sorted-lists/Jeehay28.js b/merge-two-sorted-lists/Jeehay28.js new file mode 100644 index 000000000..cb331161f --- /dev/null +++ b/merge-two-sorted-lists/Jeehay28.js @@ -0,0 +1,34 @@ +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} list1 + * @param {ListNode} list2 + * @return {ListNode} + */ + +// Time Complexity: O(m + n) +// Space Complexity: O(m + n) + +var mergeTwoLists = function(list1, list2) { + + + if(!(list1 && list2)) { + return list1 || list2; + } + + if(list1.val < list2.val) { + list1.next = mergeTwoLists(list1.next, list2); + return list1; + } else { + list2.next = mergeTwoLists(list1, list2.next); + return list2; + } + +}; + + diff --git a/merge-two-sorted-lists/KwonNayeon.py b/merge-two-sorted-lists/KwonNayeon.py new file mode 100644 index 000000000..327e16b34 --- /dev/null +++ b/merge-two-sorted-lists/KwonNayeon.py @@ -0,0 +1,45 @@ +""" +Constraints: + 1. 0 <= list1.length, list2.length <= 50 + 2. -100 <= Node.val <= 100 + 3. list1 and list2 are sorted in non-decreasing order + +Time Complexity: n과 m이 각각 list1과 list2의 길이를 나타낼 때, O(n + m) + - 각 노드를 한 번씩만 방문하기 때문 + +Space Complexity: O(1) + - 새로운 노드를 만들지 않고 기존 노드들의 연결만 바꾸기 때문 + +풀이 방법: + 1. 더미 노드를 만들어서 결과 리스트의 시작점으로 사용 + 2. 두 리스트를 앞에서부터 순회하면서 값을 비교 + 3. 더 작은 값을 가진 노드를 결과 리스트에 연결하고, 해당 리스트의 포인터를 다음으로 이동 + 4. 한쪽 리스트가 끝나면, 남은 리스트를 그대로 결과 리스트 뒤에 연결 + 5. 더미 노드의 next를 반환 (실제 정렬된 리스트의 시작점) +""" + +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + result = ListNode() + current = result + + while list1 and list2: + if list1.val <= list2.val: + current.next = list1 + list1 = list1.next + else: + current.next = list2 + list2 = list2.next + current = current.next + + if list1: + current.next = list1 + if list2: + current.next = list2 + + return result.next diff --git a/merge-two-sorted-lists/YeomChaeeun.ts b/merge-two-sorted-lists/YeomChaeeun.ts new file mode 100644 index 000000000..f2996122a --- /dev/null +++ b/merge-two-sorted-lists/YeomChaeeun.ts @@ -0,0 +1,29 @@ +/** + * Definition for singly-linked list. + * class ListNode { + * val: number + * next: ListNode | null + * constructor(val?: number, next?: ListNode | null) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + * } + */ +/** + * 두개의 리스트 정렬 - 재귀 알고리즘으로 접근 + * 알고리즘 복잡도 + * - 시간 복잡도: O(n+m) - 모든 노드를 한 번씩 들르기 때문 + * - 공간 복잡도: O(n+m) - 함수 호출 스택이 재귀 호출로 인해 사용하기 때문 + * @param list1 + * @param list2 + */ +function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null { + if(!(list1 && list2)) return list1 || list2 + if(list1.val < list2.val) { + list1.next = mergeTwoLists(list1.next, list2); + return list1 + } else { + list2.next = mergeTwoLists(list2.next, list1); + return list2 + } +} diff --git a/merge-two-sorted-lists/Yjason-K.ts b/merge-two-sorted-lists/Yjason-K.ts new file mode 100644 index 000000000..d0cce4804 --- /dev/null +++ b/merge-two-sorted-lists/Yjason-K.ts @@ -0,0 +1,49 @@ +class ListNode { + val: number + next: ListNode | null + constructor(val?: number, next?: ListNode | null) { + this.val = (val===undefined ? 0 : val) + this.next = (next===undefined ? null : next) + } +} + +/** + * 두 개의 정렬된 연결 리스트를 병합하여 하나의 정렬된 연결 리스트를 반환하는 함수 + * @param {ListNode | null} list1 - 첫 번째 정렬된 연결 리스트 + * @param {ListNode | null} list2 - 두 번째 정렬된 연결 리스트 + * @returns {ListNode | null} - 병합된 정렬된 연결 리스트 + * + * 시간 복잡도: O(n + m) + * - n: list1의 노드 개수 + * - m: list2의 노드 개수 + * + * 공간 복잡도: O(1) + * - 새로운 노드를 생성하지 않고 기존 노드의 포인터를 조정하여 병합하므로 추가 공간 사용 없음 + */ + +function mergeTwoLists(list1: ListNode | null, list2: ListNode | null): ListNode | null { + // null 예외 처리 + if (!list1 || !list2) return list1 || list2; + + // 시작점을 위한 더미 헤드 노드 생성 + const start = new ListNode(-101); + let current = start; + + // 두 리스트를 순차적으로 비교하며 병합 + while (list1 !== null && list2 !== null) { + if (list1.val <= list2.val) { + current.next = list1; + list1 = list1.next; + } else { + current.next = list2; + list2 = list2.next; + } + current = current.next; + } + + // 남은 노드를 연결 (list1 또는 list2 중 하나는 null 상태) + current.next = list1 !== null ? list1 : list2; + + // 더미 헤드의 다음 노드가 실제 병합된 리스트의 시작 + return start.next; +} diff --git a/merge-two-sorted-lists/anniemon.js b/merge-two-sorted-lists/anniemon.js new file mode 100644 index 000000000..38aacc8fe --- /dev/null +++ b/merge-two-sorted-lists/anniemon.js @@ -0,0 +1,39 @@ +/** + * 시간 복잡도: + * list1의 길이가 m, list2의 길이가 n이면 + * 포인터가 최대 m + n만큼 순회하므로 O(m + n) + * 공간 복잡도: + * 포인터를 사용하므로 O(1) + */ +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} list1 + * @param {ListNode} list2 + * @return {ListNode} + */ +var mergeTwoLists = function(list1, list2) { + const head = new ListNode(0, null); + let pointer = head; + while(list1 && list2) { + if(list1.val < list2.val) { + pointer.next = list1; + list1 = list1.next; + } else { + pointer.next = list2; + list2 = list2.next; + } + pointer = pointer.next; + } + if(list1) { + pointer.next = list1; + } else { + pointer.next = list2; + } + return head.next; +}; diff --git a/merge-two-sorted-lists/bus710.go b/merge-two-sorted-lists/bus710.go new file mode 100644 index 000000000..9d47a477d --- /dev/null +++ b/merge-two-sorted-lists/bus710.go @@ -0,0 +1,52 @@ +package hello + +type ListNode struct { + Val int + Next *ListNode +} + +func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil && list2 == nil { + return nil + } else if list1 == nil { + return list2 + } else if list2 == nil { + return list1 + } + + newList := &ListNode{} + cur := newList + + for { + switch { + case list1.Val < list2.Val: + cur.Next = &ListNode{Val: list1.Val} + list1 = list1.Next + case list1.Val > list2.Val: + cur.Next = &ListNode{Val: list2.Val} + list2 = list2.Next + default: + cur.Next = &ListNode{Val: list1.Val} + list1 = list1.Next + cur = cur.Next + cur.Next = &ListNode{Val: list2.Val} + list2 = list2.Next + } + cur = cur.Next + if list1 == nil && list2 == nil && cur.Next == nil { + break + } + + if list1 == nil { + cur.Next = list2 + break + } + if list2 == nil { + cur.Next = list1 + break + } + + } + + return newList.Next +} diff --git a/merge-two-sorted-lists/changchanghwang.go b/merge-two-sorted-lists/changchanghwang.go new file mode 100644 index 000000000..a58fc22a9 --- /dev/null +++ b/merge-two-sorted-lists/changchanghwang.go @@ -0,0 +1,20 @@ +type ListNode struct { + Val int + Next *ListNode +} + +func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil { + return list2 + } + if list2 == nil { + return list1 + } + + if list1.Val < list2.Val { + list1.Next = mergeTwoLists(list1.Next, list2) + return list1 + } + list2.Next = mergeTwoLists(list1, list2.Next) + return list2 +} diff --git a/merge-two-sorted-lists/choidabom.ts b/merge-two-sorted-lists/choidabom.ts new file mode 100644 index 000000000..612653892 --- /dev/null +++ b/merge-two-sorted-lists/choidabom.ts @@ -0,0 +1,48 @@ +/** + * Runtime: 0ms, Memory: 52.30MB + * + * Time Complexity: O(n) + * Space Complexity: O(1) + * + * 접근 + * 핵심은 두 리스트를 비교하면서 작은 값부터 정렬되도록 리스트를 만드는 것이다. + * 두 연결 리스트 중 하나가 null이 될 때까지 현재 노드 값을 비교하여 더 작은 값을 새로운 리스트트 추가하고, 남은 리스트를 추가한다. + * + * 평소 접하는 배열이 아닌 링크드 리스트로 풀어야 했기에 접근 방식이 와닿지 않았다. + * 처음에는 list1, list2가 하나의 노드라고 생각하여 헷갈렸지만, 실제로는 각 노드가 next를 통해 연결된 연결 리스트임이 중요하다. + */ + +/** + * Definition for singly-linked list. + * class ListNode { + * val: number + * next: ListNode | null + * constructor(val?: number, next?: ListNode | null) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + * } + */ + +function mergeTwoLists( + list1: ListNode | null, + list2: ListNode | null +): ListNode | null { + let dummy = new ListNode(-1); + let current = dummy; + + while (list1 !== null && list2 !== null) { + if (list1.val < list2.val) { + current.next = list1; + list1 = list1.next; + } else { + current.next = list2; + list2 = list2.next; + } + current = current.next; + } + + current.next = list1 || list2; + + return dummy.next; +} diff --git a/merge-two-sorted-lists/donghyeon95.java b/merge-two-sorted-lists/donghyeon95.java new file mode 100644 index 000000000..e4f43c128 --- /dev/null +++ b/merge-two-sorted-lists/donghyeon95.java @@ -0,0 +1,37 @@ +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ +class Solution { + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + // O(N) + // 2포인터로 지나가용 하면 되는 문제 + ListNode result = new ListNode(); + ListNode nowNode = result; + + + while (list1!=null || list2!=null) { + int first = list1==null? 101: list1.val; + int second = list2==null? 101: list2.val; + + if (first < second) { + nowNode.next = new ListNode(first); + nowNode = nowNode.next; + list1 = list1.next; + } else { + nowNode.next = new ListNode(second); + nowNode = nowNode.next; + list2 = list2.next; + } + } + + return result.next; + } +} + diff --git a/merge-two-sorted-lists/dusunax.py b/merge-two-sorted-lists/dusunax.py new file mode 100644 index 000000000..11bc24dc4 --- /dev/null +++ b/merge-two-sorted-lists/dusunax.py @@ -0,0 +1,77 @@ +''' +# 21. Merge Two Sorted Lists + +A. iterative approach: use a two pointers to merge the two lists. +B. recursive approach: use recursion to merge the two lists. + + +## Time and Space Complexity + +### A. Iterative Approach + +``` +TC: O(n + m) +SC: O(1) +``` + +#### TC is O(n + m): +- iterating through the two lists just once for merge two sorted lists. = O(n + m) + +#### SC is O(1): +- temp node is used to store the result. = O(1) + +### B. Recursive Approach + +``` +TC: O(n + m) +SC: O(n + m) +``` + +#### TC is O(n + m): +- iterating through the two lists just once for merge two sorted lists. = O(n + m) + +#### SC is O(n + m): +- because of the recursive call stack. = O(n + m) +''' +class Solution: + ''' + A. Iterative Approach + - use a temp node to store the result. + - use a current node to iterate through the two lists. + ''' + def mergeTwoListsIterative(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + temp = ListNode(-1) + current = temp + + while list1 is not None and list2 is not None: + if list1.val < list2.val: + current.next = list1 + list1 = list1.next + else: + current.next = list2 + list2 = list2.next + current = current.next + + if list1 is not None: + current.next = list1 + elif list2 is not None: + current.next = list2 + + return temp.next + + ''' + B. Recursive Approach + - use recursion to merge the two lists. + ''' + def mergeTwoListsRecursive(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + if list1 is None: + return list2 + elif list2 is None: + return list1 + + if list1.val < list2.val: + list1.next = self.mergeTwoLists(list1.next, list2) + return list1 + else: + list2.next = self.mergeTwoLists(list1, list2.next) + return list2 diff --git a/merge-two-sorted-lists/ekgns33.java b/merge-two-sorted-lists/ekgns33.java new file mode 100644 index 000000000..7cf1b02fd --- /dev/null +++ b/merge-two-sorted-lists/ekgns33.java @@ -0,0 +1,58 @@ +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ +/** + input : two sorted integer linked list + output : merged single linked list with sorted order + constraints: + 1) both linked list can be empty? + yes + 2) both given lists are sorted? + yes + edge : + 1) if both list are empty return null + 2) if one of input lists is empty return the other one + + solution 1) + using two pointer + + compare the current pointer's value. + get the smaller one, set to the next node + + until both pointer reach the end + + tc : O(n) sc : O(1) + */ +class Solution { + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + ListNode dummy = new ListNode(); + ListNode p1 = list1; + ListNode p2 = list2; + ListNode currNode = dummy; + while(p1 != null && p2 != null) { + if(p1.val < p2.val){ + currNode.next = p1; + p1 = p1.next; + } else { + currNode.next = p2; + p2 = p2.next; + } + currNode = currNode.next; + } + + if(p1 == null) { + currNode.next = p2; + } else { + currNode.next = p1; + } + + return dummy.next; + } +} diff --git a/merge-two-sorted-lists/forest000014.java b/merge-two-sorted-lists/forest000014.java new file mode 100644 index 000000000..4a31f1313 --- /dev/null +++ b/merge-two-sorted-lists/forest000014.java @@ -0,0 +1,35 @@ +/** + + (n = list1.length + list2.length) + 시간 복잡도: O(n) + - 최악의 경우 전체 노드 순회 + 공간 복잡도: O(n) + - 최악의 경우 전체 노드만큼 새 노드 생성 + + */ +class Solution { + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + ListNode head = new ListNode(); + ListNode curr = head; + + while (list1 != null && list2 != null) { + if (list1.val <= list2.val) { + curr.next = new ListNode(list1.val); + curr = curr.next; + list1 = list1.next; + } else { + curr.next = new ListNode(list2.val); + curr = curr.next; + list2 = list2.next; + } + } + + if (list1 == null) { + curr.next = list2; + } else if (list2 == null) { + curr.next = list1; + } + + return head.next; + } +} diff --git a/merge-two-sorted-lists/gmlwls96.kt b/merge-two-sorted-lists/gmlwls96.kt new file mode 100644 index 000000000..77be6b5aa --- /dev/null +++ b/merge-two-sorted-lists/gmlwls96.kt @@ -0,0 +1,50 @@ +/** + * Example: + * var li = ListNode(5) + * var v = li.`val` + * Definition for singly-linked list. + * class ListNode(var `val`: Int) { + * var next: ListNode? = null + * } + */ +class Solution { + // 시간, 공간 : o(n+m), + fun mergeTwoLists(list1: ListNode?, list2: ListNode?): ListNode? { + var currentList1: ListNode? = list1 + var currentList2: ListNode? = list2 + val answerList = LinkedList() + while (currentList1 != null || currentList2 != null) { + when { + currentList1 == null -> { + answerList.offer(currentList2!!.`val`) + currentList2 = currentList2.next + } + currentList2 == null -> { + answerList.offer(currentList1.`val`) + currentList1 = currentList1.next + } + currentList1.`val` <= currentList2.`val` -> { + answerList.offer(currentList1.`val`) + currentList1 = currentList1.next + } + currentList2.`val` < currentList1.`val` -> { + answerList.offer(currentList2.`val`) + currentList2 = currentList2.next + } + } + } + var answer: ListNode? = null + var currentAnswer: ListNode? = null + while (answerList.isNotEmpty()) { + val num = answerList.poll() + if (answer == null) { + answer = ListNode(num) + currentAnswer = answer + } else { + currentAnswer?.next = ListNode(num) + currentAnswer = currentAnswer?.next + } + } + return answer + } +} diff --git a/merge-two-sorted-lists/gwbaik9717.js b/merge-two-sorted-lists/gwbaik9717.js new file mode 100644 index 000000000..b83246666 --- /dev/null +++ b/merge-two-sorted-lists/gwbaik9717.js @@ -0,0 +1,42 @@ +// m: list1, n: list2 +// Time complexity: O(m+n) +// Space complexity: O(m+n) + +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} list1 + * @param {ListNode} list2 + * @return {ListNode} + */ +var mergeTwoLists = function (list1, list2) { + const answer = new ListNode(); + let current = answer; + + while (list1 && list2) { + if (list1.val < list2.val) { + current.next = list1; + list1 = list1.next; + } else { + current.next = list2; + list2 = list2.next; + } + + current = current.next; + } + + if (list1) { + current.next = list1; + } + + if (list2) { + current.next = list2; + } + + return answer.next; +}; diff --git a/merge-two-sorted-lists/higeuni.js b/merge-two-sorted-lists/higeuni.js new file mode 100644 index 000000000..545a240f6 --- /dev/null +++ b/merge-two-sorted-lists/higeuni.js @@ -0,0 +1,34 @@ +/** + * @param {ListNode} list1 + * @param {ListNode} list2 + * @return {ListNode} + * + * complexity + * time: O(n + m) + * space: O(1) + */ + +var mergeTwoLists = function(list1, list2) { + let dummy = new ListNode(); + let current = dummy; + + while (list1 !== null && list2 !== null) { + if (list1.val < list2.val) { + current.next = list1; + list1 = list1.next; + } else { + current.next = list2; + list2 = list2.next; + } + current = current.next; + } + + if (list1 !== null) { + current.next = list1; + } else if (list2 !== null) { + current.next = list2; + } + + return dummy.next; +}; + diff --git a/merge-two-sorted-lists/imsosleepy.java b/merge-two-sorted-lists/imsosleepy.java new file mode 100644 index 000000000..c3a0b7b66 --- /dev/null +++ b/merge-two-sorted-lists/imsosleepy.java @@ -0,0 +1,16 @@ +// 처음에 여러 조건을 붙였으나 기본적인 조건만 필요하다. +// 가장 기본적인 개념은 다음에 이동할곳이 어딘지만 알려주면 됨 +class Solution { + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + if (list1 == null) return list2; + if (list2 == null) return list1; + + if (list1.val <= list2.val) { + list1.next = mergeTwoLists(list1.next, list2); + return list1; + } else { + list2.next = mergeTwoLists(list1, list2.next); + return list2; + } + } +} diff --git a/merge-two-sorted-lists/jeldo.py b/merge-two-sorted-lists/jeldo.py new file mode 100644 index 000000000..371c9ba69 --- /dev/null +++ b/merge-two-sorted-lists/jeldo.py @@ -0,0 +1,17 @@ +class Solution: + # O(n+m), n = len(list1), m = len(list2) + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + head = node = ListNode() + while list1 and list2: + if list1.val <= list2.val: + node.next = list1 + list1 = list1.next + else: + node.next = list2 + list2 = list2.next + node = node.next + if list1: + node.next = list1 + if list2: + node.next = list2 + return head.next diff --git a/merge-two-sorted-lists/jinah92.py b/merge-two-sorted-lists/jinah92.py new file mode 100644 index 000000000..d2fd2b872 --- /dev/null +++ b/merge-two-sorted-lists/jinah92.py @@ -0,0 +1,18 @@ +# O(m+n) times, O(1) spaces +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + dummy = ListNode(None) + node = dummy + + while list1 and list2: + if list1.val < list2.val: + node.next = list1 + list1 = list1.next + else: + node.next = list2 + list2 = list2.next + + node = node.next + + node.next = list1 or list2 + return dummy.next diff --git a/merge-two-sorted-lists/limlimjo.js b/merge-two-sorted-lists/limlimjo.js new file mode 100644 index 000000000..702c88c91 --- /dev/null +++ b/merge-two-sorted-lists/limlimjo.js @@ -0,0 +1,29 @@ +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * @param {ListNode} list1 + * @param {ListNode} list2 + * @return {ListNode} + */ +var mergeTwoLists = function (list1, list2) { + // 리스트가 비었을 때 다른 리스트 반환 + if (list1 === null) return list2; + if (list2 === null) return list1; + + // 작은 값 가진 노드 선택하고 재귀호출 + if (list1.val <= list2.val) { + list1.next = mergeTwoLists(list1.next, list2); + return list1; + } else { + list2.next = mergeTwoLists(list1, list2.next); + return list2; + } +}; + +// 시간 복잡도: O(n1+n2) +// 공간 복잡도: O(1) diff --git a/merge-two-sorted-lists/mike2ox.ts b/merge-two-sorted-lists/mike2ox.ts new file mode 100644 index 000000000..8253fb3ef --- /dev/null +++ b/merge-two-sorted-lists/mike2ox.ts @@ -0,0 +1,43 @@ +/** + * source: https://leetcode.com/problems/merge-two-sorted-lists/ + * 풀이방법: 두 리스트를 비교하면서 작은 값을 결과 리스트에 추가 + * + * 시간복잡도: O(n + m) (n: list1의 길이, m: list2의 길이) + * 공간복잡도: O(1) (상수 공간만 사용) + * + */ + +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +function mergeTwoLists( + list1: ListNode | null, + list2: ListNode | null +): ListNode | null { + const result = new ListNode(); + let current = result; + while (list1 !== null && list2 !== null) { + if (list1.val <= list2.val) { + current.next = list1; + list1 = list1.next; + current = current.next; + } else { + current.next = list2; + list2 = list2.next; + current = current.next; + } + } + if (list1 !== null) { + current.next = list1; + } + if (list2 !== null) { + current.next = list2; + } + return result.next; // 처음에 추가한 더미 노드 제외 +} diff --git a/merge-two-sorted-lists/minji-go.java b/merge-two-sorted-lists/minji-go.java new file mode 100644 index 000000000..d70eac76f --- /dev/null +++ b/merge-two-sorted-lists/minji-go.java @@ -0,0 +1,36 @@ +/* + Problem: https://leetcode.com/problems/merge-two-sorted-lists/ + Description: return the head of the merged linked list of two sorted linked lists + Concept: Linked List, Recursion + Time Complexity: O(N+M), Runtime 0ms + Space Complexity: O(N+M), Memory 42.74MB +*/ + +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ +class Solution { + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + + ListNode head = new ListNode(0); + ListNode tail = head; + + while(list1 != null || list2 != null) { + if (list2 == null || (list1 != null && list1.val <= list2.val)) { + tail = tail.next = new ListNode(list1.val); + list1 = list1.next; + } else { + tail = tail.next = new ListNode(list2.val); + list2 = list2.next; + } + } + return head.next; + } +} diff --git a/merge-two-sorted-lists/mintheon.java b/merge-two-sorted-lists/mintheon.java new file mode 100644 index 000000000..d6ee5a9e0 --- /dev/null +++ b/merge-two-sorted-lists/mintheon.java @@ -0,0 +1,36 @@ +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ +class Solution { + /** + 시간복잡도: O(N) + 공간복잡도: O(1) + */ + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + ListNode answer = new ListNode(-1); + ListNode node = answer; + + while(list1 != null && list2 != null) { + if(list1.val < list2.val) { + node.next = list1; + list1 = list1.next; + } else { + node.next = list2; + list2 = list2.next; + } + + node = node.next; + } + + node.next = list1 != null ? list1 : list2; + + return answer.next; + } +} diff --git a/merge-two-sorted-lists/mmyeon.ts b/merge-two-sorted-lists/mmyeon.ts new file mode 100644 index 000000000..167aa3981 --- /dev/null +++ b/merge-two-sorted-lists/mmyeon.ts @@ -0,0 +1,46 @@ +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} + +/** + * + * 접근 방법 : + * - 2개의 정렬된 링크드 리스트가 주어지니까 각 리스트 값 비교하면서 작은 값을 새로운 링크드 리스트에 추가 + * - 링크드 리스트 head에 접근해야하니까 더미노드와 포인터 변수 분리해서 사용 + * - 포인터 변수 사용해서 노드 연결하기 + * - 두 링크드 리스트가 있는 동안 반복하고, 한 쪽이 끝나면 나머지 노드를 그대로 새로운 링크드 리스트에 추가 + * + * 시간복잡도 : O(n+k) + * - n은 list1 길이, k는 list2 길이 => 두 리스트 모두 반복하니까 O(n+k) + * + * 공간복잡도 : O(1) + * - 기존 노드 연결해서 재사용하니까 O(1) + */ + +function mergeTwoLists( + list1: ListNode | null, + list2: ListNode | null +): ListNode | null { + const dummyNode = new ListNode(); + let current = dummyNode; + + while (list1 !== null && list2 !== null) { + if (list1.val <= list2.val) { + current.next = list1; + list1 = list1.next; + current = current.next; + } else { + current.next = list2; + list2 = list2.next; + current = current.next; + } + } + current.next = list1 !== null ? list1 : list2; + + return dummyNode.next; +} diff --git a/merge-two-sorted-lists/nakjun12.ts b/merge-two-sorted-lists/nakjun12.ts new file mode 100644 index 000000000..03fce8d74 --- /dev/null +++ b/merge-two-sorted-lists/nakjun12.ts @@ -0,0 +1,24 @@ +function mergeTwoLists( + list1: ListNode | null, + list2: ListNode | null +): ListNode | null { + let head = new ListNode(); + let current = head; + + while (list1 && list2) { + if (list1.val <= list2.val) { + current.next = list1; + list1 = list1.next; + } else { + current.next = list2; + list2 = list2.next; + } + current = current.next; + } + current.next = list1 || list2; + + return head.next; +} + +// TC: O(m+n) +// SC: O(1) diff --git a/merge-two-sorted-lists/neverlish.go b/merge-two-sorted-lists/neverlish.go new file mode 100644 index 000000000..be28ad8d3 --- /dev/null +++ b/merge-two-sorted-lists/neverlish.go @@ -0,0 +1,36 @@ +// 시간복잡도: O(n) +// 공간복잡도: O(1) + +package main + +import "testing" + +type ListNode struct { + Val int + Next *ListNode +} + +func TestMergeTwoLists(t *testing.T) { + result1 := mergeTwoLists(&ListNode{Val: 1, Next: &ListNode{Val: 2, Next: &ListNode{Val: 4}}}, &ListNode{Val: 1, Next: &ListNode{Val: 3, Next: &ListNode{Val: 4}}}) + if result1.Val != 1 || result1.Next.Val != 1 || result1.Next.Next.Val != 2 || result1.Next.Next.Next.Val != 3 || result1.Next.Next.Next.Next.Val != 4 || result1.Next.Next.Next.Next.Next.Val != 4 { + t.Error("Test case 1 failed") + } +} + +func mergeTwoLists(list1 *ListNode, list2 *ListNode) *ListNode { + if list1 == nil { + return list2 + } + + if list2 == nil { + return list1 + } + + if list1.Val < list2.Val { + list1.Next = mergeTwoLists(list1.Next, list2) + return list1 + } else { + list2.Next = mergeTwoLists(list1, list2.Next) + return list2 + } +} diff --git a/merge-two-sorted-lists/pmjuu.py b/merge-two-sorted-lists/pmjuu.py new file mode 100644 index 000000000..2d9904c77 --- /dev/null +++ b/merge-two-sorted-lists/pmjuu.py @@ -0,0 +1,33 @@ +from typing import Optional + + +class ListNode: + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + head = ListNode() + current = head + + while list1 and list2: + if list1.val <= list2.val: + current.next = list1 + list1 = list1.next + else: + current.next = list2 + list2 = list2.next + + current = current.next + + current.next = list1 or list2 + + return head.next + +# 시간 복잡도: +# - 두 리스트의 모든 노드를 순회하며 병합하므로 O(n + m) => O(n) 으로 표현 +# 여기서 n은 list1의 길이, m은 list2의 길이. +# +# 공간 복잡도: +# - 기존 노드를 재사용하므로 O(1) diff --git a/merge-two-sorted-lists/river20s.java b/merge-two-sorted-lists/river20s.java new file mode 100644 index 000000000..68d9100f1 --- /dev/null +++ b/merge-two-sorted-lists/river20s.java @@ -0,0 +1,47 @@ +/** + * Definition for singly-linked list. + * public class ListNode { + * int val; + * ListNode next; + * ListNode() {} + * ListNode(int val) { this.val = val; } + * ListNode(int val, ListNode next) { this.val = val; this.next = next; } + * } + */ +class Solution { + /* + * [풀이] + * 1) 두 리스트가 이미 정렬된 상태 → 맨 앞에서부터 둘 중 더 작은 노드를 결과 리스트 rs에 이어 붙인다. + * 2) 하나의 리스트가 끝날 때까지 반복한다. → 남은 리스트의 노드는 그대로 rs에 붙인다. + * 3) rs 헤드 노드의 next부터 반환한다. + * [T.C] + * 각 노드를 한 번씩 비교 → 연결(또는 연결만)하므로, + * T.C = O(n+m) (n = list1의 길이, m = list2의 길이) + * [S.C] + * list1과 list2의 노드를 다시 연결하는 것이므로 추가적인 공간은 거의 필요하지 않으므로(rs의 head 정도?), + * S.C = O(1) + */ + + public ListNode mergeTwoLists(ListNode list1, ListNode list2) { + ListNode rs = new ListNode(0); + ListNode rsNext = rs; + // step 1 + while (list1 != null && list2 != null) { + if (list1.val <= list2.val) { + rsNext.next = list1; + list1 = list1.next; + } + else { + rsNext.next = list2; + list2 = list2.next; + } + rsNext = rsNext.next; + } + // step 2 + if (list1 != null) rsNext.next = list1; + if (list2 != null) rsNext.next = list2; + // step3 + return rs.next; + } +} + diff --git a/merge-two-sorted-lists/rivkode.py b/merge-two-sorted-lists/rivkode.py new file mode 100644 index 000000000..6cff0521e --- /dev/null +++ b/merge-two-sorted-lists/rivkode.py @@ -0,0 +1,53 @@ +class ListNode(object): + def __init__(self, val=0, next=None): + self.val = val + self.next = next + +class Solution(object): + def mergeTwoLists(self, list1, list2): + """ + :type list1: Optional[ListNode] + :type list2: Optional[ListNode] + :rtype: Optional[ListNode] + """ + # 더미 노드 생성 + dummy = ListNode(-1) + current = dummy + + # 두 리스트를 순회하며 병합 + while list1 and list2: + if list1.val <= list2.val: + current.next = list1 + list1 = list1.next + else: + current.next = list2 + list2 = list2.next + current = current.next + + # 남아 있는 노드 처리 + if list1: + current.next = list1 + elif list2: + current.next = list2 + + # 더미 노드 다음부터 시작 + return dummy.next + +if __name__ == "__main__": + solution = Solution() + + # test case + list1 = ListNode(1, ListNode(2, ListNode(4, ))) + list2 = ListNode(1, ListNode(3, ListNode(4, ))) + + result = solution.mergeTwoLists(list1, list2) + + while result is not None: + print(result.val) + result = result.next + + + + + + diff --git a/merge-two-sorted-lists/sungjinwi.py b/merge-two-sorted-lists/sungjinwi.py new file mode 100644 index 000000000..236bea895 --- /dev/null +++ b/merge-two-sorted-lists/sungjinwi.py @@ -0,0 +1,33 @@ +""" + 기억할 키워드 + dummy.next 리턴해서 건너뜀 + list1 list2 둘 중 하나가 None이 되면 while문 끝내고 나머지 next로 이어줌 + + list1의 길이 M, list2의 길이N + + TC : O(M + N) + + SC : O(1) + + 추가적인 풀이 : 알고달레에서 재귀방법 확인 +""" +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + dummy = ListNode() + node = dummy + + while list1 and list2 : + if list1.val < list2.val : + node.next = list1 + list1 = list1.next + else : + node.next = list2 + list2 = list2.next + node = node.next + node.next = list1 or list2 + return dummy.next diff --git a/merge-two-sorted-lists/taewanseoul.ts b/merge-two-sorted-lists/taewanseoul.ts new file mode 100644 index 000000000..ece78b220 --- /dev/null +++ b/merge-two-sorted-lists/taewanseoul.ts @@ -0,0 +1,52 @@ +/** + * 21. Merge Two Sorted Lists + * You are given the heads of two sorted linked lists list1 and list2. + * Merge the two lists into one sorted list. The list should be made by splicing together the nodes of the first two lists. + * Return the head of the merged linked list. + * + * https://leetcode.com/problems/merge-two-sorted-lists/description/ + */ + +/** + * Definition for singly-linked list. + * class ListNode { + * val: number + * next: ListNode | null + * constructor(val?: number, next?: ListNode | null) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + * } + */ + +// O(n + m) time +// O(n + m) space +function mergeTwoLists( + list1: ListNode | null, + list2: ListNode | null +): ListNode | null { + if (!list1) return list2; + if (!list2) return list1; + + let mergedListNode: ListNode; + const val1 = list1.val; + const val2 = list2.val; + if (val1 > val2) { + mergedListNode = new ListNode(val2); + mergedListNode.next = mergeTwoLists(list1, list2.next); + } else { + mergedListNode = new ListNode(val1); + mergedListNode.next = mergeTwoLists(list1.next, list2); + } + + return mergedListNode; +} + +class ListNode { + val: number; + next: ListNode | null; + constructor(val?: number, next?: ListNode | null) { + this.val = val === undefined ? 0 : val; + this.next = next === undefined ? null : next; + } +} diff --git a/merge-two-sorted-lists/thispath98.py b/merge-two-sorted-lists/thispath98.py new file mode 100644 index 000000000..9848d6758 --- /dev/null +++ b/merge-two-sorted-lists/thispath98.py @@ -0,0 +1,86 @@ +# Definition for singly-linked list. +# class ListNode: +# def __init__(self, val=0, next=None): +# self.val = val +# self.next = next +class Solution: + def mergeTwoListsList(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + """ + Intuition: + 두 리스트의 원소를 각각 비교하면서 한번씩 스캔한다. + 결과적으로 한번씩만 스캔하면 정렬할 수 있다. + + Time Complexity: + O(N): + 두개의 리스트를 1번 순회하며 답을 찾으므로, + O(N)의 시간복잡도가 소요된다. + + Space Complexity: + O(N): + sorted_list에 정렬된 배열을 저장하므로, + O(N)의 공간복잡도가 소요된다. + """ + sorted_list = [] + while list1 is not None and list2 is not None: + if list1.val < list2.val: + sorted_list.append(list1.val) + list1 = list1.next + else: + sorted_list.append(list2.val) + list2 = list2.next + + while list1 is not None: + sorted_list.append(list1.val) + list1 = list1.next + while list2 is not None: + sorted_list.append(list2.val) + list2 = list2.next + + sorted_node = None + while sorted_list: + val = sorted_list.pop() + sorted_node = ListNode(val, sorted_node) + + return sorted_node + + def mergeTwoListsNode(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: + """ + Intuition: + 파이썬 리스트를 사용하지 않고 + 주어진 ListNode로부터 바로 시작한다. + + Time Complexity: + O(N): + 두개의 리스트를 1번 순회하며 답을 찾으므로, + O(N)의 시간복잡도가 소요된다. + + Space Complexity: + O(1): + ListNode를 바로 사용하므로 + 상수 만큼의 O(1)의 공간복잡도가 소요된다. + + Key takeaway: + 링크드 리스트를 오랜만에 접하니 잘 풀지 못했던 것 같다. + 전통적인 자료구조를 OOP 관점으로 고민해보자. + """ + sorted_node = ListNode() + current_node = sorted_node + + while True: + if list1 is None: + current_node.next = list2 + break + elif list2 is None: + current_node.next = list1 + break + + if list1.val < list2.val: + current_node.next = ListNode(list1.val) + current_node = current_node.next + list1 = list1.next + else: + current_node.next = ListNode(list2.val) + current_node = current_node.next + list2 = list2.next + + return sorted_node.next diff --git a/merge-two-sorted-lists/yoonthecoder.js b/merge-two-sorted-lists/yoonthecoder.js new file mode 100644 index 000000000..7e63fa58c --- /dev/null +++ b/merge-two-sorted-lists/yoonthecoder.js @@ -0,0 +1,25 @@ +var mergeTwoLists = function (list1, list2) { + // create a placeholder node and use it as a starting point + let placeholder = { val: -1, next: null }; + let current = placeholder; + + // loop through the lists until one of them is fully traversed + while (list1 !== null && list2 !== null) { + if (list1.val <= list2.val) { + // connect the element of list1 with the current node + current.next = list1; + // move list1 to its next node + list1 = list1.next; + } else { + current.next = list2; + list2 = list2.next; + } + // move the current pointer to the newly added node + current = current.next; + } + current.next = list1 !== null ? list1 : list2; + return placeholder.next; +}; + +// Time Complexity: O(n+m); +// Space Complexity: O(1) 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/missing-number/GangBean.java b/missing-number/GangBean.java new file mode 100644 index 000000000..bf19c65d8 --- /dev/null +++ b/missing-number/GangBean.java @@ -0,0 +1,19 @@ +class Solution { + public int missingNumber(int[] nums) { + /** + 1. understanding + - array nums, n distinct numbers in range [0, n] + - find missing number + 2. strategy + - you can calculate the sum of range [0, n]: n(n+1)/2 ... (1) + - and the sum of nums ... (2) + - and then extract (2) from (1) = (missing value) what we want. + 3. complexity + - time: O(N), N is the length of nums + - space: O(1) + */ + int N = nums.length; + return N*(N+1)/2 - Arrays.stream(nums).sum(); + } +} + diff --git a/missing-number/Gotprgmer.java b/missing-number/Gotprgmer.java new file mode 100644 index 000000000..c7b1736d2 --- /dev/null +++ b/missing-number/Gotprgmer.java @@ -0,0 +1,18 @@ +// 단순하게 정렬해서 일치하지 않으면 출력하고 리스트를 벗어나면 그대로 checkNum을 출력하는 방식 +// 시간복잡도 : O(NlogN) +// 공간복잡도 : O(1) + +class SolutionGotprgmer { + public int missingNumber(int[] nums) { + Arrays.sort(nums); + int checkNum = 0; + for(int i=0;i sum + num, 0); + + return expectedSum - actualSum; +}; + diff --git a/missing-number/HodaeSsi.py b/missing-number/HodaeSsi.py new file mode 100644 index 000000000..e289dec5f --- /dev/null +++ b/missing-number/HodaeSsi.py @@ -0,0 +1,9 @@ +class Solution: + def missingNumber(self, nums: List[int]) -> int: + n = len(nums) + expected_sum = (n ** 2 + n) // 2 + + actual_sum = sum(nums) + + return expected_sum - actual_sum + diff --git a/missing-number/Jay-Mo-99.py b/missing-number/Jay-Mo-99.py new file mode 100644 index 000000000..4dd67c175 --- /dev/null +++ b/missing-number/Jay-Mo-99.py @@ -0,0 +1,42 @@ + #해석 + #nums가 0에서 len(nums) 까지의 숫자를 포함하나 확인하고, 없다면 해당 숫자를 return한다. + #nums를 오름차순 정렬한다 + #nums가 조건을 만족하면, nums[i]는 인덱스 i와 동일해야한다. (e.g nums[0] =0, nums[1]=1) + #배열의 마지막 요소(nums(len(num-1))) 이 len(nums)와 동일하지 않으면 nums은 0~len(nums) 까지의 숫자를 가진다는 조건을 만족 X -> 누락된 len(nums)를 return한다. + #for loop 로 각 숫자가 인덱스와 일치 여부 확인 + #인덱스와 값이 일치하지 않는 nums[i]의 인덱스를 return한다. + + + #Big O + #N: 매개변수 n의 크기(계단 갯수) + + #Time Complexity: O(nlog(n)) = O(nlog(n))+O(1)+O(n) + #- n: nums배열의 길이 + #- sort(): Timsort방식이기에 O(nlog(n)) + #-if(nums[len(nums)-1] != len(nums)): 단일 조건 비교는 O(1) + #for loop: nums의 길이에 비례하므로 O(n) + + + #Space Complexity: O(1) + #- sort(): nums.sort()는 제자리 정렬이기에 추가 공간 필요치 않으므로 O(1) + +class Solution(object): + def missingNumber(self, nums): + """ + :type nums: List[int] + :rtype: int + """ + # Sort the list + nums.sort() + # If the last element of nums doesn't align with the numbers of element in nums, return len(nums) + # For example, nums[0,1] so range is [0:2] but there's no last element of 2 so return 2(len(nums)) + if(nums[len(nums)-1] != len(nums)): + return len(nums) + #If each element doesn't match with the number of [0:len(nums)], return the i(index) + for i in range(len(nums)): + if nums[i] != i: + print(nums[i],i) + return i + + + diff --git a/missing-number/Jeehay28.js b/missing-number/Jeehay28.js new file mode 100644 index 000000000..a47a59fe7 --- /dev/null +++ b/missing-number/Jeehay28.js @@ -0,0 +1,68 @@ +/** + * @param {number[]} nums + * @return {number} + */ + +// *** Guided approach 2: bitwise operations and avoids potential overflow issues with very large sums +// XOR method +// Time complexity: O(n)(two loops: one for numbers 0 to n and one for array elements) +// Space complexity: O(1) + +var missingNumber = function (nums) { + // XOR with itself results in 0 : a xor a = 0 + // XOR with 0 results in the number itself : a xor 0 = a + // XOR is commutative and associative + + const n = nums.length; + + let xor = 0; + + for (let i = 0; i <= n; i++) { + xor ^= i; + } + + for (any of nums) { + xor ^= any; + } + + return xor; +}; + +// *** Guided approach 1: simplicity and clarity +// Gauss' Formula (Sum of First n Numbers): n*(n+1) / 2 +// Time complexity: O(n) +// Space complexity: O(1) +// var missingNumber = function (nums) { +// const n = nums.length; +// const expectedSum = (n * (n + 1)) / 2; +// const actualSum = nums.reduce((acc, cur) => acc + cur, 0); // O(n) + +// const missingNum = expectedSum - actualSum; + +// return missingNum; +// }; + +// *** My own approach +// Time complexity: O(n^2) +// Space complexity: O(n) +// var missingNumber = function (nums) { + +// let distinctNums = new Set([]); + +// for (any of nums) { +// if (distinctNums.has(any)) { +// return +// } else { +// distinctNums.add(any) +// } +// } + +// const n = distinctNums.size; + +// for (let i = 0; i <= n; i++) { +// if (!nums.includes(i)) { +// return i; +// } +// } + +// }; diff --git a/missing-number/KwonNayeon.py b/missing-number/KwonNayeon.py new file mode 100644 index 000000000..2a9820a9f --- /dev/null +++ b/missing-number/KwonNayeon.py @@ -0,0 +1,28 @@ +""" +Constraints: + 1. n equals the length of array nums + 2. n is between 1 and 10^4 inclusive + 3. Each element nums[i] is between 0 and n inclusive + 4. All numbers in nums are unique (no duplicates) + +Time Complexity: O(nlogn) + - 정렬에 nlogn, 순회에 n이 필요하므로 전체적으로 O(nlogn) +Space Complexity: O(1) + - 추가 공간을 사용하지 않고 입력 배열만 사용 + +풀이 방법: + 1. 배열을 정렬하여 0부터 n까지 순서대로 있어야 할 위치에 없는 숫자를 찾음 + 2. 인덱스와 해당 위치의 값을 비교하여 다르다면 그 인덱스가 missing number + 3. 모든 인덱스를 확인했는데도 없다면 n이 missing number +""" + +class Solution: + def missingNumber(self, nums: List[int]) -> int: + nums.sort() + + for i in range(len(nums)): + if nums[i] != i: + return i + + return len(nums) + diff --git a/missing-number/YeomChaeeun.ts b/missing-number/YeomChaeeun.ts new file mode 100644 index 000000000..f5067f4a7 --- /dev/null +++ b/missing-number/YeomChaeeun.ts @@ -0,0 +1,20 @@ +/** + * 주어진 배열의 중간에 없는 숫자 찾기 + * 알고리즘 복잡도 + * - 시간 복잡도: O(nlogn) + * - 공간 복잡도: O(1) + * @param nums + */ +function missingNumber(nums: number[]): number { + if(nums.length === 1) { + return nums[0] === 0 ? 1 : 0 + } + + nums.sort((a, b) => a - b) + + for(let i = 0; i < nums.length; i++) { + if(nums[0] !== 0) return 0 + if(nums[i] + 1 !== nums[i + 1]) + return nums[i] + 1 + } +} diff --git a/missing-number/Yjason-K.ts b/missing-number/Yjason-K.ts new file mode 100644 index 000000000..83501be6e --- /dev/null +++ b/missing-number/Yjason-K.ts @@ -0,0 +1,29 @@ +/** + * 0부터 n까지의 숫자 중 배열에서 누락된 숫자를 찾는 함수 + * @param {number[]} nums - 0부터 n까지의 숫자가 포함된 배열 (순서는 무작위이며 일부 숫자가 누락될 수 있음) + * @returns {number} - 배열에서 누락된 숫자 + * + * 시간 복잡도: O(n) + * - Set은 Hash Table로 구현되어 has 메서드가 Array.includes 메서드보다 유리 + * - Set을 생성하는 데 O(n) 시간이 소요 + * - 배열 길이만큼 반복문을 돌면서 Set의 존재 여부를 확인하는 데 O(1) * n = O(n) + * - 결과적으로 O(n) + O(n) = O(n) + * + * 공간 복잡도: O(n) + * - Set 자료구조를 사용하여 입력 배열의 모든 요소를 저장하므로 O(n)의 추가 메모리 사용 + */ +function missingNumber(nums: number[]): number { + // 배열의 숫자를 모두 Set에 추가하여 중복 제거 + const distinctSet = new Set([...nums]); + + // 0부터 n까지의 숫자 중에서 누락된 숫자를 탐색 + for (let i = 0; i < nums.length; i++) { + // Set에 i가 존재하지 않으면 i가 누락된 숫자 + if (!distinctSet.has(i)) { + return i; + } + } + + // 모든 숫자가 Set에 존재하면 n이 누락된 숫자 + return nums.length; +} diff --git a/missing-number/anniemon.js b/missing-number/anniemon.js new file mode 100644 index 000000000..5700e6746 --- /dev/null +++ b/missing-number/anniemon.js @@ -0,0 +1,17 @@ +/** + * 시간 복잡도: nums.length + 1만큼 순회하므로 O(n) + * 공간 복잡도: totalSum, numsSum 변수를 사용하므로 O(1) + */ +/** + * @param {number[]} nums + * @return {number} + */ +var missingNumber = function(nums) { + const n = nums.length; + const totalSum = n * (n + 1) / 2; + let numsSum = 0; + for(const n of nums) { + numsSum += n; + } + return totalSum - numsSum; +}; diff --git a/missing-number/choidabom.ts b/missing-number/choidabom.ts new file mode 100644 index 000000000..c72ebd61e --- /dev/null +++ b/missing-number/choidabom.ts @@ -0,0 +1,40 @@ +/** + * Runtime: 19ms, Memory: 52.48MB + * + * 접근 + * 직관적으로 생각했을 때, 0부터 n까지의 숫자 중에서 없는 숫자를 찾아야 한다. + * 완전 탐색으로 정렬한 배열에서 순서대로 비교하면서 없는 숫자를 찾을 수 있다. + * Time Complexity: O(nlogn) + * Space Complexity: O(n) + * + * Follow up: Could you implement a solution using only O(1) extra space complexity and O(n) runtime complexity? + */ + +function missingNumber(nums: number[]): number { + const numsLen = nums.length; + const sortedNums = nums.sort((a, b) => a - b); // 오름차순 정렬 + + for (let i = 0; i <= numsLen; i++) { + if (i !== sortedNums[i]) { + return i; + } + } +} + +/** + * Runtime: 1ms, Memory: 51.96MB + * + * 접근 + * Follow up에 대한 해결 방법 + * 0부터 n까지의 숫자의 합을 구한 뒤, 주어진 배열의 합을 빼면 없는 숫자를 찾을 수 있다. + * Time Complexity: O(n) + * Space Complexity: O(1) + */ + +function missingNumber(nums: number[]): number { + const size = nums.length; + const sum = (size * (size + 1)) / 2; + const accurate = nums.reduce((sum, num) => sum + num, 0); + + return sum - accurate; +} diff --git a/missing-number/donghyeon95.java b/missing-number/donghyeon95.java new file mode 100644 index 000000000..03985af52 --- /dev/null +++ b/missing-number/donghyeon95.java @@ -0,0 +1,19 @@ +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +// O(N) +class Solution { + public int missingNumber(int[] nums) { + Set numSet = Arrays.stream(nums).boxed().collect(Collectors.toSet()); + + for (int i=0; i int: + nums.sort() + n = len(nums) + + for i in range(n): + if nums[i] != i: + return i + return n + + ''' + B. XOR Approach + ''' + def missingNumberXOR(self, nums: List[int]) -> int: + n = len(nums) + xor_nums = 0 + + for i in range(n + 1): + if i < n: + xor_nums ^= nums[i] + xor_nums ^= i + + return xor_nums diff --git a/missing-number/ekgns33.java b/missing-number/ekgns33.java new file mode 100644 index 000000000..1cfcb2a5b --- /dev/null +++ b/missing-number/ekgns33.java @@ -0,0 +1,35 @@ +/** + input : integer of array + output : return the only missing number + constraints: + 1) is there any duplicate? + no. + 2) range of element + [0, n] + + solution 1) + iterate through the array + save to hash set + iterate i from 0 to n + check if exists + + tc : O(n) sc : O(n) + + solution 2) + iterate throught the array + add all the elements + get sum of integer sequence 0 .. n with. n(n+1)/2 + get subtraction + tc : O(n) sc : O(1) + */ +class Solution { + public int missingNumber(int[] nums) { + int sum = 0; + int n = nums.length; + for(int num : nums) { + sum += num; + } + int totalSum = (n+1) * n / 2; + return totalSum - sum; + } +} diff --git a/missing-number/forest000014.java b/missing-number/forest000014.java new file mode 100644 index 000000000..ddac2e98f --- /dev/null +++ b/missing-number/forest000014.java @@ -0,0 +1,16 @@ +/* +시간 복잡도: O(n) +공간 복잡도: O(1) + +1 ~ n의 합이 n * (n + 1) / 2 라는 점을 활용 +*/ +class Solution { + public int missingNumber(int[] nums) { + int n = nums.length; + int ans = n * (n + 1) / 2; + for (int i = 0; i < n; i++) { + ans -= nums[i]; + } + return ans; + } +} diff --git a/missing-number/gmlwls96.kt b/missing-number/gmlwls96.kt new file mode 100644 index 000000000..cafe84bbf --- /dev/null +++ b/missing-number/gmlwls96.kt @@ -0,0 +1,13 @@ +class Solution { + fun missingNumber(nums: IntArray): Int { + nums.sort() + var num = nums[0] + for (i in 1 until nums.size) { + if (nums[i] != (num + 1)) { + return num + 1 + } + num = nums[i] + } + return num + 1 + } +} diff --git a/missing-number/gwbaik9717.js b/missing-number/gwbaik9717.js new file mode 100644 index 000000000..5df34e02c --- /dev/null +++ b/missing-number/gwbaik9717.js @@ -0,0 +1,15 @@ +// Time complexity: O(n) +// Space complexity: O(1) + +/** + * @param {number[]} nums + * @return {number} + */ +var missingNumber = function (nums) { + const n = nums.length; + const target = (n * (n + 1)) / 2; + + const sum = nums.reduce((a, c) => a + c, 0); + + return target - sum; +}; diff --git a/missing-number/higeuni.js b/missing-number/higeuni.js new file mode 100644 index 000000000..2a25ac51a --- /dev/null +++ b/missing-number/higeuni.js @@ -0,0 +1,15 @@ +/** + * @param {number[]} nums + * @return {number} + * + * complexity + * time: O(n) + * space: O(1) + */ + +var missingNumber = function(nums) { + const sumOfNums = nums.reduce((acc, curr) => acc + curr, 0); + const sumOfTotalNumbers = (nums.length * (nums.length + 1)) / 2; + return sumOfTotalNumbers - sumOfNums; +}; + diff --git a/missing-number/imsosleepy.java b/missing-number/imsosleepy.java new file mode 100644 index 000000000..6bbfe2792 --- /dev/null +++ b/missing-number/imsosleepy.java @@ -0,0 +1,34 @@ +// GPT의 도움을 받은 결과, 수학적으로 접근하면 된다. +// 모든 값은 유니크하고 nums 배열 사이즈인 n을 지켜주기 때문에 가능한 결과 +class Solution { + public int missingNumber(int[] nums) { + int n = nums.length; + int expected = n * (n + 1) / 2; + int actual = 0; + for (int num : nums) { + actual += num; + } + return expected - actual; + } +} + +// 시간복잡도는 O(N)으로 떨어진다. +// 공간복잡도가 nums 배열 사이즈에 종속되서 O(N)이다. +// Accepted가 되지만, 다른 방법을 찾아봐야함 +class Solution { + public int missingNumber(int[] nums) { + boolean[] existCheck = new boolean[nums.length + 1]; + + for (int num : nums) { + existCheck[num] = true; + } + + for (int i = 0; i < existCheck.length; i++) { + if (!existCheck[i]) { + return i; + } + } + + return 0; + } +} diff --git a/missing-number/jeldo.py b/missing-number/jeldo.py new file mode 100644 index 000000000..67976636b --- /dev/null +++ b/missing-number/jeldo.py @@ -0,0 +1,5 @@ +class Solution: + # O(n), n = len(nums) + def missingNumber(self, nums: List[int]) -> int: + n = len(nums) + return n*(n+1)//2 - sum(nums) diff --git a/missing-number/jinah92.py b/missing-number/jinah92.py new file mode 100644 index 000000000..357bb8070 --- /dev/null +++ b/missing-number/jinah92.py @@ -0,0 +1,10 @@ +# O(n) times, O(n) spaces +class Solution: + def missingNumber(self, nums: List[int]) -> int: + nums_keys = set(nums) + + for i in range(len(nums)): + if not i in nums_keys: + return i + + return len(nums) diff --git a/missing-number/limlimjo.js b/missing-number/limlimjo.js new file mode 100644 index 000000000..98538781a --- /dev/null +++ b/missing-number/limlimjo.js @@ -0,0 +1,17 @@ +/** + * @param {number[]} nums + * @return {number} + */ +var missingNumber = function (nums) { + // 1. nums 정렬 + nums.sort((a, b) => a - b); + // 2. for문 돌며 빠진 숫자 찾기 + for (let i = 0; i <= nums.length; i++) { + if (nums[i] !== i) { + return i; + } + } +}; + +// 시간 복잡도: O(nlogn) +// 공간 복잡도: O(1) diff --git a/missing-number/mike2ox.ts b/missing-number/mike2ox.ts new file mode 100644 index 000000000..57a2b29dd --- /dev/null +++ b/missing-number/mike2ox.ts @@ -0,0 +1,15 @@ +/** + * source: https://leetcode.com/problems/missing-number/ + * 풀이방법: 0부터 n까지의 합에서 주어진 배열의 합을 빼면 빠진 숫자를 구할 수 있음 + * + * 시간복잡도: O(n) (n: nums의 길이) + * 공간복잡도: O(1) (상수 공간만 사용) + */ + +function missingNumber(nums: number[]): number { + const n = nums.length; + let expectedSum = (n * (n + 1)) / 2; // 0부터 n까지의 합 공식 + let realSum = nums.reduce((sum, cur) => sum + cur, 0); + + return expectedSum - realSum; +} diff --git a/missing-number/minji-go.java b/missing-number/minji-go.java new file mode 100644 index 000000000..61cc2894f --- /dev/null +++ b/missing-number/minji-go.java @@ -0,0 +1,19 @@ +/* + Problem: https://leetcode.com/problems/missing-number/ + Description: return the only number in the range that is missing from the array. + Concept: Array, Hash Table, Math, Binary Search, Bit Manipulation, Sorting + Time Complexity: O(N), Runtime 0ms + Space Complexity: O(1), Memory 45.71MB +*/ +class Solution { + public int missingNumber(int[] nums) { + int n = nums.length; + int missing = n; + + for(int i=0; i int: + n = len(nums) + # 0부터 n까지의 숫자의 합을 수학적 합 공식을 사용해 계산 + total_sum = n * (n + 1) // 2 + + return total_sum - sum(nums) + +# 시간 복잡도 O(n) +# 공간 복잡도 O(1) diff --git a/missing-number/river20s.java b/missing-number/river20s.java new file mode 100644 index 000000000..488e76e9e --- /dev/null +++ b/missing-number/river20s.java @@ -0,0 +1,28 @@ +import java.util.Arrays; +class Solution { + /* [풀이] + * 1) 배열 nums을 오름차순 정렬한다. + * 2) 배열 요소의 인덱스와 값을 비교한다. + * 2-1) 인덱스와 값이 같다면 해당 값은 배열에 있다. + * 2-2) 인덱스와 값이 다르다면, 해당 값은 배열에 없다. + * 3) 다른 값의 인덱스를 반환한다. + * [T.C] + * 내장된 Arrays.sort()를 사용하면 시간 복잡도는 O(n log n)이 된다. + * [S.C] + * 최악의 경우 Arrays.sort()는 추가적으로 O(n) 만큼의 공간을 사용한다. + */ + + public int missingNumber(int[] nums) { + // nums 오름차순 정렬 + Arrays.sort(nums); + // 인덱스와 요소 비교 + for (int i = 0; i < nums.length; i++) { + if (nums[i] != i) { + return i; + } + } + // 배열에 없는 값 반환 + return nums.length + } +} + diff --git a/missing-number/rivkode.py b/missing-number/rivkode.py new file mode 100644 index 000000000..43e1ac29d --- /dev/null +++ b/missing-number/rivkode.py @@ -0,0 +1,38 @@ +class Solution(object): + # 시간복잡도 nlog(n) sort + # 공간복잡도 n 정렬시 + def missingNumber_1(self, nums): + """ + :type nums: List[int] + :rtype: int + """ + sort_nums = sorted(nums) + + v = 0 + for i in sort_nums: + if v != i: + return v + else: + v += 1 + + return v + + # hash를 사용 + # 모든 숫자를 dict에 입력 + # for문을 돌면서 해당 hash가 dict에 존재하는지 체크 + # 시간복잡도 n - for문 + # 공간복잡도 n - dict 생성 + def missingNumber(self, nums): + hash_keys = dict() + for i in range(len(nums) + 1): + hash_keys[i] = 0 + for i in nums: + hash_keys[i] = 1 + + for i in range(len(nums) + 1): + if hash_keys[i] != 1: + return i + + return 0 + + diff --git a/missing-number/sungjinwi.py b/missing-number/sungjinwi.py new file mode 100644 index 000000000..dd8ebcce8 --- /dev/null +++ b/missing-number/sungjinwi.py @@ -0,0 +1,16 @@ +""" + 풀이 : 비지 않을경우 합 nSum을 구하고 list를 돌면서 빼고 남은 것이 답 + + + TC : O(N) + sum구할 떄 O(N) + for문 O(N) = O(2N)\ + + SC : O(1) +""" + +class Solution: + def missingNumber(self, nums: List[int]) -> int: + nSum = sum(range(0,len(nums) + 1)) + for num in nums : + nSum -=num + return nSum diff --git a/missing-number/taewanseoul.ts b/missing-number/taewanseoul.ts new file mode 100644 index 000000000..8a69b91d1 --- /dev/null +++ b/missing-number/taewanseoul.ts @@ -0,0 +1,20 @@ +/** + * 268. Missing Number + * Given an array nums containing n distinct numbers in the range [0, n], return the only number in the range that is missing from the array. + * + * https://leetcode.com/problems/missing-number/description/ + */ + +// O(n) time +// O(1) space +function missingNumber(nums: number[]): number { + const n = nums.length; + let total = (n * (n + 1)) / 2; + + let sum = 0; + for (let i = 0; i < n; i++) { + sum += nums[i]; + } + + return total - sum; +} diff --git a/missing-number/thispath98.py b/missing-number/thispath98.py new file mode 100644 index 000000000..2719095ee --- /dev/null +++ b/missing-number/thispath98.py @@ -0,0 +1,23 @@ +class Solution: + def missingNumber(self, nums: List[int]) -> int: + """ + Intuition: + 주어진 리스트의 개수를 얻어 범위를 구한다. + 이후 세트를 이용해서 범위 내의 정수가 + 세트 안에 없으면 그 수를 리턴한다. + + Time Complexity: + O(N): + 세트(해시)는 접근하는 데에 상수의 시간이 걸리므로 + 최대 N + 1번의 접근을 하므로 + O(N)의 시간복잡도가 소요된다. + + Space Complexity: + O(N): + 리스트를 해시로 변환하여 저장하고 있으므로 + O(N)의 공간복잡도가 소요된다. + """ + num_set = set(nums) + for i in range(len(nums) + 1): + if i not in num_set: + return i diff --git a/missing-number/yoonthecoder.js b/missing-number/yoonthecoder.js new file mode 100644 index 000000000..6ce3190ce --- /dev/null +++ b/missing-number/yoonthecoder.js @@ -0,0 +1,14 @@ +var missingNumber = function (nums) { + // store all the elemenst from nums in a Set + const set = new Set(nums); + + // check the missing number by iterating through the index + for (i = 0; i <= nums.length; i++) { + if (!set.has(i)) { + return i; + } + } +}; + +// Time complexity: O(n); +// Space complexity: O(n); 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/palindromic-substrings/GangBean.java b/palindromic-substrings/GangBean.java index ebb22ac35..d690efd01 100644 --- a/palindromic-substrings/GangBean.java +++ b/palindromic-substrings/GangBean.java @@ -1,8 +1,15 @@ class Solution { public int countSubstrings(String s) { /** - 각 문자를 중간으로 갖는 palindrome 여부 체크 - + 두개의 문자를 중간으로 갖는 palindrome 여부 체크 + 1. understanding + - find the number of palindromic substrings + 2. strategy + - iterate over each character, count below substrings + - First, start with same position, move left and right directions each, until two charactes are not same. + - Second, start with i and i + 1 position, move left and right directions until two chracters are not same. + 3. complexity + - time: O(N^2) + - space: O(1) */ int count = 0; int length = s.length(); @@ -27,3 +34,4 @@ public int countSubstrings(String s) { return count; // O(N^2) } } + diff --git a/palindromic-substrings/HerrineKim.js b/palindromic-substrings/HerrineKim.js new file mode 100644 index 000000000..b0b563909 --- /dev/null +++ b/palindromic-substrings/HerrineKim.js @@ -0,0 +1,25 @@ +// 시간 복잡도: O(n^2) +// 공간 복잡도: O(1) + +/** + * @param {string} s + * @return {number} + */ +var countSubstrings = function(s) { + let count = 0; + + const countPalindrome = (left, right) => { + while (left >= 0 && right < s.length && s[left] === s[right]) { + count++; + left--; + right++; + } + }; + + for (let i = 0; i < s.length; i++) { + countPalindrome(i, i); + countPalindrome(i, i + 1); + } + + return count; +}; diff --git a/palindromic-substrings/HodaeSsi.py b/palindromic-substrings/HodaeSsi.py new file mode 100644 index 000000000..29088685f --- /dev/null +++ b/palindromic-substrings/HodaeSsi.py @@ -0,0 +1,19 @@ +# space complexity: O(n^2) +# time complexity: O(n^2) (*exactly, n^2 / 2) +class Solution: + def countSubstrings(self, s: str) -> int: + dp = [[False] * len(s) for _ in range(len(s))] # dp[i][j] = True if s[i:j+1] is a palindrome + for i in range(len(s)): + for j in range(i, -1, -1): + if i == j: + dp[j][i] = True + continue + if i - j == 1: + dp[j][i] = s[i] == s[j] + continue + if s[i] == s[j]: + if dp[j+1][i-1]: + dp[j][i] = True + + return sum([sum(row) for row in dp]) + diff --git a/palindromic-substrings/Jeehay28.js b/palindromic-substrings/Jeehay28.js new file mode 100644 index 000000000..a28fb5d0c --- /dev/null +++ b/palindromic-substrings/Jeehay28.js @@ -0,0 +1,37 @@ +/** + * @param {string} s + * @return {number} + */ + +// TC : O(n^2) +// SC : O(1) + +var countSubstrings = function (s) { + // For each character in the string, treat it as the center of a potential palindrome. + + // 'Count Palindromic Substrings' helper function + const countPS = (left, right) => { + let cnt = 0; + while (left >= 0 && right < s.length && s[left] === s[right]) { + cnt += 1; + left -= 1; + right += 1; + } + return cnt; + }; + + let totCnt = 0; + + for (let i = 0; i < s.length; i++) { + // left === right : 1 center point, odd-length palindromic + totCnt += countPS(i, i); + + // left !== right : 2 center points, even-length palindromic + totCnt += countPS(i, i + 1); + } + + return totCnt; +}; + + + diff --git a/palindromic-substrings/Yjason-K.ts b/palindromic-substrings/Yjason-K.ts new file mode 100644 index 000000000..9405bff4f --- /dev/null +++ b/palindromic-substrings/Yjason-K.ts @@ -0,0 +1,48 @@ +/** + * 문자열에서 substring 중 panlindrome인 경우의 수를 구하는 함수 + * @param {string} s - 입력 문자열 + * @returns {number} - s문자열에서 찾을수 있는 panlindrome substring의 개수 + * + * 시간 복잡도: O(n^2) (n: 문자열 길이) + * - 한 번의 외부 루프: 부분 문자열 길이 (\(subLen\)) - O(n) + * - 내부 루프: 시작 인덱스 (\(start\)) - O(n) + * - 따라서, 총 복잡도는 O(n^2) + * + * 공간 복잡도: O(n^2) + * - DP 배열 dp[i][j]는 \(n^2\) 크기를 가지며 문자열의 모든 시작과 끝 조합에 대한 정보를 저장합니다. + */ +function countSubstrings(s: string): number { + const n = s.length; // 문자열 길이 + let result = 0; + + // DP 배열 생성 (dp[i][j] 는 s[i] ~ s[j] 까지가 회문인지 여부를 저장) + const dp: boolean[][] = Array.from({ length: n }, () => Array(n).fill(false)); + + // 1글자 경우 + for (let i = 0; i < n; i++) { + dp[i][i] = true; + result++; + } + + // 2글자 경우 + for (let i = 0; i < n - 1; i++) { + if (s[i] === s[i + 1]) { + dp[i][i + 1] = true; + result++; + } + } + + // 3글자 이상인 경우 + for (let subLen = 3; subLen <= n; subLen++) { + for (let start = 0; start <= n - subLen; start++) { + const end = start + subLen - 1; + // 양 끝 문자가 같고, 내부 부분 문자열이 회문이면 true + if (s[start] === s[end] && dp[start + 1][end - 1]) { + dp[start][end] = true; + result++; + } + } + } + + return result; +} diff --git a/palindromic-substrings/dusunax.py b/palindromic-substrings/dusunax.py new file mode 100644 index 000000000..384960667 --- /dev/null +++ b/palindromic-substrings/dusunax.py @@ -0,0 +1,78 @@ +''' +# 647. Palindromic Substrings + +A. use dynamic programming table to store the palindrome status. +B. use two pointers to expand around the center. + +## Time and Space Complexity + +### A. Dynamic Programming Table +``` +TC: O(n^2) +SC: O(n^2) +``` + +#### TC is O(n^2): +- filling DP table by iterating through all substrings. +- each cell (i, j) checks if a substring is a palindrome & counting the cases = O(n^2) + +#### SC is O(n^2): +- storing the n x n dp table. = O(n^2) + +### B. Expand Around Center +``` +TC: O(n^2) +SC: O(1) +``` + +#### TC is O(n^2): +- for each char, expand outwards to check for palindromes. = O(n^2) + +#### SC is O(1): +- no additional data structures are used. `count` is a single integer. = O(1) +''' +class Solution: + def countSubstringsDPTable(self, s: str) -> int: + ''' + A. Dynamic Programming Table + ''' + n = len(s) + dp = [[False] * n for _ in range(n)] # List comprehension. = SC: O(n^2) + count = 0 + + for i in range(n): # TC: O(n) + dp[i][i] = True + count += 1 + + for i in range(n - 1): + if s[i] == s[i + 1]: + dp[i][i + 1] = True + count += 1 + + for s_len in range(3, n + 1): # TC: O(n) + for i in range(n - s_len + 1): # TC: O(n) + j = i + s_len - 1 + + if s[i] == s[j] and dp[i + 1][j - 1]: + dp[i][j] = True + count += 1 + + return count + def countSubstrings(self, s: str) -> int: + ''' + B. Expand Around Center + ''' + count = 0 + + def expand(left, right): + nonlocal count + while left >= 0 and right < len(s) and s[left] == s[right]: # TC: O(n) + count += 1 + left -= 1 + right += 1 + + for i in range(len(s)): # TC: O(n) + expand(i, i) + expand(i, i + 1) + + return count diff --git a/palindromic-substrings/ekgns33.java b/palindromic-substrings/ekgns33.java new file mode 100644 index 000000000..5fa4dcdfa --- /dev/null +++ b/palindromic-substrings/ekgns33.java @@ -0,0 +1,23 @@ +/* +input : string s +output: the number of palindromic substrings of given string +tc : O(n^2) sc : o(n^2) when n is the length of string s + +optimize? +maybe nope.. atleast n^2 to select i and j + + */ +class Solution { + public int countSubstrings(String s) { + int n = s.length(); + int cnt = 0; + boolean[][] dp = new boolean[n][n]; + for(int i = n-1; i >= 0; i--) { + for(int j = i; j < n; j++) { + dp[i][j] = s.charAt(i) == s.charAt(j) && (j-i +1 < 3 || dp[i+1][j-1]); + if(dp[i][j]) cnt++; + } + } + return cnt; + } +} diff --git a/palindromic-substrings/forest000014.java b/palindromic-substrings/forest000014.java new file mode 100644 index 000000000..42e69387d --- /dev/null +++ b/palindromic-substrings/forest000014.java @@ -0,0 +1,38 @@ +/* +time complexity: O(n^2) +space complexity: O(1) + +모든 가능한 조합(O(n^2))에 대해 palindrome 여부를 검사(O(n))하는 brute force는 O(n^3). +i번째 문자에서 시작하여, 앞/뒤로 한 글자씩 늘려가면서 탐색하되, 한번이라도 앞/뒤 글자가 서로 다르다면 그 이후는 탐색하지 않을 수 있음. 이 경우는 O(n^2). +*/ +class Solution { + public int countSubstrings(String s) { + int ans = 0; + for (int i = 0; i < s.length(); i++) { + int head = i, tail = i; + while (head >= 0 && tail < s.length()) { + if (s.charAt(head) == s.charAt(tail)) { + ans++; + } else { + break; + } + head--; + tail++; + } + + head = i; + tail = i + 1; + while (head >= 0 && tail < s.length()) { + if (s.charAt(head) == s.charAt(tail)) { + ans++; + } else { + break; + } + head--; + tail++; + } + } + + return ans; + } +} diff --git a/palindromic-substrings/gmlwls96.kt b/palindromic-substrings/gmlwls96.kt new file mode 100644 index 000000000..50432be73 --- /dev/null +++ b/palindromic-substrings/gmlwls96.kt @@ -0,0 +1,28 @@ +class Solution { + // 알고리즘 : brute-force + /** 풀이 + * 1. 모든 substring을 전부 뽑아낸다. + * 2. 해당 substring이 palindrome인지 체크한다. + */ + // 시간 : O(n^3) + fun countSubstrings(s: String): Int { + var count = 0 + for (len in 1..s.length) { // 길이 + for (i in 0..(s.length - len)) { // i : sub string start index. + if (checkPalindrome(s.substring(i, i + len))) { + count++ + } + } + } + return count + } + + private fun checkPalindrome(subStr: String): Boolean { + return if (subStr.length == 1) { + true + } else { + val reverse = subStr.reversed() + subStr == reverse + } + } +} diff --git a/palindromic-substrings/gwbaik9717.js b/palindromic-substrings/gwbaik9717.js new file mode 100644 index 000000000..69e042bd4 --- /dev/null +++ b/palindromic-substrings/gwbaik9717.js @@ -0,0 +1,40 @@ +// Time complexity: O(n^2) +// Space complexity: O(n^2) + +/** + * @param {string} s + * @return {number} + */ +var countSubstrings = function (s) { + const n = s.length; + const dp = Array.from({ length: n }, () => + Array.from({ length: n }, () => false) + ); + let answer = 0; + + for (let end = 0; end < n; end++) { + for (let start = end; start >= 0; start--) { + if (start === end) { + dp[start][end] = true; + answer++; + continue; + } + + if (start + 1 === end) { + if (s[start] === s[end]) { + dp[start][end] = true; + answer++; + } + continue; + } + + if (s[start] === s[end] && dp[start + 1][end - 1]) { + dp[start][end] = true; + answer++; + continue; + } + } + } + + return answer; +}; diff --git a/palindromic-substrings/imsosleepy.java b/palindromic-substrings/imsosleepy.java new file mode 100644 index 000000000..a645b1cc1 --- /dev/null +++ b/palindromic-substrings/imsosleepy.java @@ -0,0 +1,31 @@ +// O(N^2) 이 나올 수밖에 없는 문제. 이런 문제의 특징은 N의 크기가 작다. +// 이번문제도 N의 크기가 1000으로 주어졌을때, 이차원 for문이 허용된다는걸 간접적으로 알아챌 수 있다. +// 이차원 배열 이므로 공간복잡도도 O(N^2) +class Solution { + public int countSubstrings(String s) { + int n = s.length(); + boolean[][] dp = new boolean[n][n]; + int count = 0; + + for (int i = 0; i < n; i++) { + dp[i][i] = true; + count++; + } + + for (int len = 2; len <= n; len++) { + for (int i = 0; i <= n - len; i++) { // 시작 위치 + int j = i + len - 1; // 끝 위치 + + // 양 끝 문자가 같고, 내부가 회문이거나 길이가 2인 경우 + if (s.charAt(i) == s.charAt(j)) { + if (len == 2 || dp[i + 1][j - 1]) { + dp[i][j] = true; + count++; + } + } + } + } + + return count; + } +} diff --git a/palindromic-substrings/jinah92.py b/palindromic-substrings/jinah92.py new file mode 100644 index 000000000..05e36bad3 --- /dev/null +++ b/palindromic-substrings/jinah92.py @@ -0,0 +1,39 @@ +# O(N^3) times, O(1) spaces +# 내부 while문의 관계가 외부 while문의 sub_str_len에 따라 반복횟수가 줄어드므로, 1+2+...N = N(N-1)/2 = O(N2) 시간 소요 +# 추라고 내부 while에서 sub_str_len에 따라 s가 인덱싱되므로 최대 O(N) 시간이 소요 +# 최종적으로 O(N^2 * N) = O(N^3)이 소요됨 +class Solution: + def countSubstrings(self, s: str) -> int: + sub_str_len = 1 + result = 0 + + while sub_str_len <= len(s): + start_idx = 0 + while start_idx + sub_str_len <= len(s): + sub_str = s[start_idx:start_idx+sub_str_len] + if sub_str == sub_str[::-1]: + result += 1 + start_idx += 1 + + sub_str_len += 1 + + + return result + +# DP 풀이 +# O(N^2) times, O(N^2) spaces +# start, end 지점을 순회하면서 이전 계산값을 재사용하여 회문을 파악 +class Solution2: + def countSubstrings(self, s: str) -> int: + dp = {} + + for end in range(len(s)): + for start in range(end, -1, -1): + if start == end: + dp[(start, end)] = True + elif start + 1 == end: + dp[(start, end)] = s[start] == s[end] + else: + dp[(start, end)] = s[start] == s[end] and dp[(start+1, end-1)] + + return list(dp.values()).count(True) diff --git a/palindromic-substrings/minji-go.java b/palindromic-substrings/minji-go.java new file mode 100644 index 000000000..cecce3457 --- /dev/null +++ b/palindromic-substrings/minji-go.java @@ -0,0 +1,27 @@ +/* + Problem: https://leetcode.com/problems/palindromic-substrings/ + Description: return the number of palindromic substrings in it. + Concept: Two Pointers, String, Dynamic Programming + Time Complexity: O(N²), Runtime 6ms + Space Complexity: O(1), Memory 41.62MB +*/ +class Solution { + public int countSubstrings(String s) { + int totalCount = 0; + for(int i=0; i=0 && right 모든 부분 문자열을 확인하는 방식 대신 좀 더 최적화한 방식으로 다시 풀어볼 것. + */ + public int countSubstrings(String s) { + int count = 0; + + for(int i = 0; i < s.length(); i++) { + for(int j = i; j < s.length(); j++) { + if(isPalindrom(s, i, j)) { + count++; + } + } + } + + return count; + } + + private boolean isPalindrom(String text, int left, int right) { + while(left < right) { + if(text.charAt(left) != text.charAt(right)) { + return false; + } + + left++; + right--; + } + + return true; + } +} diff --git a/palindromic-substrings/mmyeon.ts b/palindromic-substrings/mmyeon.ts new file mode 100644 index 000000000..bacf48a2b --- /dev/null +++ b/palindromic-substrings/mmyeon.ts @@ -0,0 +1,37 @@ +/** + * + * 접근 방법 : + * - 각 문자열에서 회문 조건 충족하는 경우 중심을 기준으로 확장해나가기 위해 투 포인터 사용 + * - 문자가 같고 범위 내에 있는 경우 확장해나가면서 횟수 업데이트 + * - 홀수 회문과 다르게 짝수 회문은 중심을 2문자에서 시작되어야 하니까 인덱스 별도 처리 + * + * 시간복잡도 : O(n^2) + * - 문자열 길이가 n일 때, for문에서 각 문자마다 최대 문자열 길이까지 비교하니까 O(n^2) + * + * 공간복잡도 : O(1) + * + */ + +function countPalindromes(s: string, left: number, right: number): number { + let count = 0; + + while (0 <= left && right < s.length && s[left] === s[right]) { + count++; + left--; + right++; + } + + return count; +} + +function countSubstrings(s: string): number { + let count = 0; + + for (let i = 0; i < s.length; i++) { + // 홀수 회문 카운트 + count += countPalindromes(s, i, i); + // 짝수 회문 카운트 + count += countPalindromes(s, i, i + 1); + } + return count; +} diff --git a/palindromic-substrings/neverlish.go b/palindromic-substrings/neverlish.go new file mode 100644 index 000000000..aafb974f8 --- /dev/null +++ b/palindromic-substrings/neverlish.go @@ -0,0 +1,40 @@ +// 시간복잡도: O(n^3) +// 공간복잡도: O(n^2) + +package main + +import "testing" + +func expect(input string, expected int, t *testing.T) { + result := countSubstrings(input) + if result != expected { + t.Errorf("Expected %d but got %d", expected, result) + } +} + +func TestCountSubstrings(t *testing.T) { + expect("abc", 3, t) + expect("dnncbwoneinoplypwgbwktmvkoimcooyiwirgbxlcttgteqthcvyoueyftiwgwwxvxvg", 77, t) +} + +func isPalindrome(s string) bool { + for i := 0; i < len(s)/2; i++ { + if s[i] != s[len(s)-1-i] { + return false + } + } + return true +} + +func countSubstrings(s string) int { + result := 0 + + for i := 0; i < len(s); i++ { + for j := i; j < len(s); j++ { + if isPalindrome(s[i:j+1]) { + result++ + } + } + } + return result +} diff --git a/palindromic-substrings/pmjuu.py b/palindromic-substrings/pmjuu.py new file mode 100644 index 000000000..41c4ddf8c --- /dev/null +++ b/palindromic-substrings/pmjuu.py @@ -0,0 +1,60 @@ +# Dynamic programming +class Solution: + def countSubstrings(self, s: str) -> int: + n = len(s) + dp = [[False] * n for _ in range(n)] # dp[i][j]는 s[i:j+1]이 팰린드롬인지 나타냄 + count = 0 + + for length in range(1, n + 1): # 부분 문자열 길이 + for i in range(n - length + 1): # 시작 인덱스 + j = i + length - 1 # 끝 인덱스 + + if length == 1: # 길이 1: 항상 팰린드롬 + dp[i][j] = True + elif length == 2: # 길이 2: 두 문자가 같으면 팰린드롬 + dp[i][j] = (s[i] == s[j]) + else: # 그 외의 경우: 양 끝이 같고 내부가 팰린드롬이면 참 + dp[i][j] = (s[i] == s[j] and dp[i + 1][j - 1]) + + if dp[i][j]: + count += 1 + + return count + + +# 시간 복잡도: +# - 이중 반복문으로 모든 부분 문자열을 확인하므로 O(n^2) +# - 각 확인은 O(1)이므로 최종적으로 O(n^2) + +# 공간 복잡도: +# - DP 테이블(dp)은 O(n^2)의 공간을 사용 +# - 추가 변수는 O(1)이므로 전체 공간 복잡도는 O(n^2) + + +# 투 포인터 방식 +class Solution: + def countSubstrings(self, s: str) -> int: + def expand_around_center(left: int, right: int) -> int: + count = 0 + # 좌우로 확장하며 팰린드롬인지 확인 + while left >= 0 and right < len(s) and s[left] == s[right]: + count += 1 + left -= 1 + right += 1 + return count + + total_count = 0 + for i in range(len(s)): + # 홀수 길이 팰린드롬 (중심이 문자 하나) + total_count += expand_around_center(i, i) + # 짝수 길이 팰린드롬 (중심이 문자 두 개) + total_count += expand_around_center(i, i + 1) + + return total_count + +# 시간 복잡도: +# - 각 문자에서 중심을 기준으로 확장하므로 최대 O(n) 확장 +# - 모든 문자에 대해 확장을 시도하므로 O(n^2) + +# 공간 복잡도: +# - 추가 공간 사용 없이 O(1) diff --git a/palindromic-substrings/thispath98.py b/palindromic-substrings/thispath98.py new file mode 100644 index 000000000..9e83ef41f --- /dev/null +++ b/palindromic-substrings/thispath98.py @@ -0,0 +1,130 @@ +class Solution: + def countSubstrings(self, s: str) -> int: + """ + Intuition: + 2중 루프를 돌면서 각 substring에 대해 + palindrome인지 아닌지 확인한다. + 한번 palindrome인지 확인했으면, set에 추가하여 + 중복 확인을 한다. + + Time Complexity: + O(N^2 x s.length): + 2중 루프는 N^2만큼 소요되고, + 각 루프에 palindrome을 체크하는 것은 + s.length만큼 소요된다. + + Space Complexity: + O(N^2): + palindrome이 모두 중복되지 않을 경우 set에 + s의 substring 개수만큼 저장한다. + 이는 대략 N^2이다. + """ + def is_palindrome(s): + return s == s[::-1] + + palindrome_set = set() + answer = 0 + for i in range(1, len(s) + 1): + for j in range(0, len(s) - i + 1): + substr = s[j: j + i] + if substr in palindrome_set or is_palindrome(substr): + palindrome_set.add(substr) + answer += 1 + return answer + + +class SolutionDPSet: + def countSubstrings(self, s: str) -> int: + """ + Intuition: + 위 solution에서 중복을 제거할 수 있는 방법은, + start : end 길이를 갖는 substring에서 + s[start] == s[end]이고, start + 1 : end - 1의 + substring이 palindrome이라면, 이 substring은 + palindrome이라고 볼 수 있다. + + Time Complexity: + O(N^2): + DP로 인해 palindrome을 찾는 함수가 대략 + 상수의 시간복잡도가 걸린다고 볼 수 있다. + 따라서 substring을 만드는 이중 루프에서의 + 시간복잡도가 걸릴 수 있다고 보면 된다. + + Space Complexity: + O(N^2): + dp set에 index set을 저장하는데, 최악의 경우 + index set은 N^2개만큼 저장될 수 있다. + + Key takeaway: + dp를 이용해서 푸는 방식에 대해 익숙해져야겠다. + + 의문점은 leetcode에서 bruteforce보다 시간이 더 소요되었다는 것이다. + 아마 list 크기를 초과할 경우에 append를 할 경우, + 리스트 크기를 2배만큼 늘리는 list doubling 방식이 + set에도 적용이 되어 느려진 것으로 보인다. + """ + dp = set() + + + def is_palindrome(start, end): + while start < end: + if s[start] != s[end]: + return False + if (start, end) in dp: + return True + start += 1 + end -= 1 + + return True + + + answer = 0 + for length in range(1, len(s) + 1): + for start in range(0, len(s) - length + 1): + end = start + length - 1 + if (start, end) in dp or is_palindrome(start, end): + dp.add((start, end)) + answer += 1 + return answer + + +class SolutionDPList: + def countSubstrings(self, s: str) -> int: + """ + Intuition: + DP solution에서 set로 저장하지 않고, + 이중 리스트로 저장하는 것으로 수정했다. + length = 2인 경우에는 start와 end만 동일하면 + palindrome으로 판단할 수 있어 조건을 추가했다. + + Time Complexity: + O(N^2): + DP로 인해 palindrome을 찾는 함수가 대략 + 상수의 시간복잡도가 걸린다고 볼 수 있다. + 따라서 substring을 만드는 이중 루프에서의 + 시간복잡도가 걸릴 수 있다고 보면 된다. + + Space Complexity: + O(N^2): + dp 리스트에 substring 이중 리스트를 저장하므로 + N^2개만큼 저장될 수 있다. + + Key takeaway: + 이 방법이 가장 빠르게 동작했다. + """ + dp = [[False for _ in s] for _ in s] + answer = 0 + + for i in range(len(s)): + dp[i][i] = True + answer += 1 + + for length in range(2, len(s) + 1): + for start in range(len(s) - length + 1): + end = start + length - 1 + if s[start] == s[end]: + if length == 2 or dp[start + 1][end - 1]: + dp[start][end] = True + answer += 1 + + return answer diff --git a/reverse-bits/Gotprgmer.java b/reverse-bits/Gotprgmer.java new file mode 100644 index 000000000..eaa269ab5 --- /dev/null +++ b/reverse-bits/Gotprgmer.java @@ -0,0 +1,13 @@ +// 처음 문제를 봤을때는 이해가 잘 가지 않았지만, +// 비트들을 뒤집으라는 설명으로 풀었던 것 같다. +// Integer.reverse() 메소드를 사용하여 풀었다. +// 지피티의 도움으로 Integer.reverse()를 사용하라는 힌트를 얻었다. +// 찾아보니 reverse(N)는 N을 2의 보수 비트로 바꾸고 그것을 뒤집는 방식이었다. +// 시간복잡도 : O(1) -> Integer가 32비트 고정이라서 O(1) +// 공간복잡도 : O(1) -> 32비트 고정 +public class Solution { + // you need treat n as an unsigned value + public int reverseBits(int n) { + return Integer.reverse(n); + } +} diff --git a/two-sum/Gotprgmer.java b/two-sum/Gotprgmer.java new file mode 100644 index 000000000..1f6ad5516 --- /dev/null +++ b/two-sum/Gotprgmer.java @@ -0,0 +1,54 @@ +// 배열을 정렬하여 투포인터로 접근하여 풀었습니다. +// 정렬된 배열의 인덱스를 찾기 위해 indexOf 메소드를 만들어서 사용했습니다. + +// 시간복잡도 : O(NlogN) -> 정렬을 위해 O(NlogN) + 투포인터로 O(N)이므로 O(NlogN) +// 공간복잡도 : O(N) -> 정렬을 위해 복사한 배열이 필요하므로 O(N) +class SolutionGotprgmer { + public int[] twoSum(int[] nums, int target) { + int[] original = new int[nums.length]; + + for(int i=0;i target){ + r -= 1; + } + else if(total < target){ + l += 1; + } + else{ + int[] ans = indexOf(lV,rV,original); + l = ans[0]; + r = ans[1]; + break; + } + } + return new int[] {l,r}; + } + + public int[] indexOf(int l,int r, int[] nums){ + int lIdx = -1; + int rIdx = -1; + for(int i = 0;i-1;i--){ + if(nums[i] == r){ + rIdx = i; + break; + } + } + return new int[] {lIdx,rIdx}; + } +} diff --git a/two-sum/yoonthecoder.js b/two-sum/yoonthecoder.js new file mode 100644 index 000000000..f37002549 --- /dev/null +++ b/two-sum/yoonthecoder.js @@ -0,0 +1,14 @@ +var twoSum = function (nums, target) { + const map = new Map(); + + for (let i = 0; i < nums.length; i++) { + const complement = target - nums[i]; + if (map.has(complement)) { + return [map.get(complement), i]; + } + map.set(nums[i], i); + } +}; + +// Time complexity: O(n) +// Space complexity: O(n) - hash map storage diff --git a/valid-anagram/Jay-Mo-99.py b/valid-anagram/Jay-Mo-99.py index e69de29bb..6aafc19cb 100644 --- a/valid-anagram/Jay-Mo-99.py +++ b/valid-anagram/Jay-Mo-99.py @@ -0,0 +1,32 @@ + #해석 + #문자열 s의 재배치로 문자열 t를 구성할 수 있으면 anagram으로 return true. + #s와 t를 list로 바꾸고 sort하여 오름차순으로 정렬시킨다, 둘이 같으면 같은 문자를 가진 문자열 리스트이므로 return true + + #Big O + #N: 주어진 문자열 s와 t의 길이(N) + + #Time Complexity: O(N) + #- 문자열을 list로 변환하는 작업: O(N) + #- 정렬작업 : NO(log N) + #- 리스트 비교 작업, s와 t의 각 문자가 서로 일치하는지 체크한다 : O(N) + #- 최종: O(N) + + #Space Complexity: O(N) + #- list s와 t는 주어진 문자열 s와 t에 기반하여 새로운 list 객체로 할당된다: O(N) + +class Solution(object): + def isAnagram(self, s, t): + """ + :type s: str + :type t: str + :rtype: bool + """ + s = list(s) #Convert string to list + t = list(t) + s.sort() #Sort the list + t.sort() + + return s == t #If the s and t are same, return true(anagram) + + + 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 +} diff --git a/word-search/GangBean.java b/word-search/GangBean.java new file mode 100644 index 000000000..5901ea044 --- /dev/null +++ b/word-search/GangBean.java @@ -0,0 +1,48 @@ +class Solution { + int[] dx = {0, 1, 0, -1}; + int[] dy = {1, 0, -1, 0}; + public boolean exist(char[][] board, String word) { + /** + 1. understanding + - check if word can be constructed from board, + - start in any block, moving only 4 direction, up, left, below, right + - can't use same block + 2. strategy + - backtracking and dfs + - iterate over each block, if first character matches, find words in depth first search algorithm + - each dfs, mark current block is visited, and find 4 or less possible directions, when any character matches with next character in word, then call dfs in that block recursively + 3. complexity + - time: O(M * N * L), where L is the length of word + - space: O(M * N) which marks if block of the indices is visited or not + */ + boolean[][] isVisited = new boolean[board.length][board[0].length]; + for (int y = 0; y < board.length; y++) { + for (int x = 0; x < board[0].length; x++) { + if (isWordExists(board, isVisited, word, y, x, 0)) return true; + } + } + return false; + } + + private boolean isWordExists(char[][] board, boolean[][] isVisited, String word, int y, int x, int idx) { + if (board[y][x] != word.charAt(idx)) return false; + if (idx == word.length() - 1) return true; + // boolean isExists = false; + isVisited[y][x] = true; + for (int dir = 0; dir < 4; dir++) { + int ny = y + dy[dir]; + int nx = x + dx[dir]; + if (0 <= ny && ny < board.length + && 0 <= nx && nx < board[0].length + && !isVisited[ny][nx] + && word.charAt(idx + 1) == board[ny][nx]) { + isVisited[ny][nx] = true; + if (isWordExists(board, isVisited, word, ny, nx, idx + 1)) return true; + isVisited[ny][nx] = false; + } + } + isVisited[y][x] = false; + return false; + } +} + diff --git a/word-search/HodaeSsi.py b/word-search/HodaeSsi.py new file mode 100644 index 000000000..dde94ac4d --- /dev/null +++ b/word-search/HodaeSsi.py @@ -0,0 +1,29 @@ +class Solution: + def dfs (self, board, word, visited, y, x, word_idx): + if word_idx == len(word): + return True + + if y < 0 or y >= len(board) or x < 0 or x >= len(board[0]) or visited[y][x] or board[y][x] != word[word_idx]: + return False + + visited[y][x] = True + for dy, dx in [(1, 0), (-1, 0), (0, -1), (0, 1)]: + if self.dfs(board, word, visited, y + dy, x + dx, word_idx + 1): + return True + visited[y][x] = False + return False + + def exist(self, board: List[List[str]], word: str) -> bool: + visited = [[False for _ in range(len(board[0]))] for _ in range(len(board))] + + # find fisrt letter in board + for y in range(len(board)): + for x in range(len(board[0])): + if board[y][x] == word[0]: + visited[y][x] = True + for dy, dx in [(1, 0), (-1, 0), (0, -1), (0, 1)]: + if self.dfs(board, word, visited, y + dy, x + dx, 1): + return True + visited[y][x] = False + return False + diff --git a/word-search/Jeehay28.js b/word-search/Jeehay28.js new file mode 100644 index 000000000..bdbe2bc97 --- /dev/null +++ b/word-search/Jeehay28.js @@ -0,0 +1,67 @@ +/** + * @param {character[][]} board + * @param {string} word + * @return {boolean} + */ + +// board(N * M), where N is the number of row and M is the number of columns +// L, the length of the word +// TC : O(N * M * 4^L) + +// recursion depth : the length of the word (L) +// each recursion call requires constant space. +// SC : O(L) + +var exist = function (board, word) { + let row = board.length; + let col = board[0].length; + + const dfs = (r, c, idx) => { + // search done + if (idx === word.length) { + return true; + } + + // row and column are out of range + if (r < 0 || r >= row || c < 0 || c >= col) { + return false; + } + + if (board[r][c] !== word[idx]) { + return false; + } + + // word[idx] === board[r][c] + // continue searching for word[idx + 1] in adjacent cells on the board + const temp = board[r][c]; + board[r][c] = "visited"; + + const arr = [ + [1, 0], // Move down + [-1, 0], // Move up + [0, 1], // Move right + [0, -1], // Move left + ]; + for (const [up, right] of arr) { + if (dfs(r + up, c + right, idx + 1)) { + return true; + } + } + + board[r][c] = temp; + return false; + }; + + for (let i = 0; i < row; i++) { + for (let j = 0; j < col; j++) { + if (dfs(i, j, 0)) { + return true; + } + } + } + + return false; +}; + + + diff --git a/word-search/KwonNayeon.py b/word-search/KwonNayeon.py new file mode 100644 index 000000000..39cc5fd2e --- /dev/null +++ b/word-search/KwonNayeon.py @@ -0,0 +1,51 @@ +""" +Constraints: + 1. m equals board length (number of rows) + 2. n equals board[i] length (number of columns) + 3. m and n are between 1 and 6 inclusive + 4. word length is between 1 and 15 inclusive + 5. board and word contain only lowercase and uppercase English letters + +Time Complexity: O(N * 3^L) + - N은 board의 모든 cell (m * n) + - L은 word의 길이 + - 각 cell에서 시작하여 word의 각 글자마다 세방향으로 탐색 (이미 방문한 방향 제외) + +Space Complexity: O(L) + - L은 word의 길이로, 재귀 호출 스택의 깊이 + +To Do: + - DFS와 백트래킹 개념 복습하기 + - 다른 스터디원분들의 답안 참조하여 다른 풀이방법 복습하기 +""" + +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + rows, cols = len(board), len(board[0]) + + def dfs(i, j, k): + if k == len(word): + return True + + if (i < 0 or i >= rows or + j < 0 or j >= cols or + board[i][j] != word[k]): + return False + + temp = board[i][j] + board[i][j] = '#' + + result = (dfs(i+1, j, k+1) or + dfs(i-1, j, k+1) or + dfs(i, j+1, k+1) or + dfs(i, j-1, k+1)) + + board[i][j] = temp + return result + + for i in range(rows): + for j in range(cols): + if board[i][j] == word[0]: + if dfs(i, j, 0): + return True + return False diff --git a/word-search/Yjason-K.ts b/word-search/Yjason-K.ts new file mode 100644 index 000000000..de11f48a3 --- /dev/null +++ b/word-search/Yjason-K.ts @@ -0,0 +1,70 @@ +/** + * board 에서 주어진 단어를 찾을 수 있는지 여부 확인 (boolean) + * @param {string[][]} board - 단어를 탐색할 2D board + * @param {string} word - 찾고자 하는 단어 + * @returns {boolean} - 단어가 격자에서 존재하면 true, 그렇지 않으면 false + * + * 시간 복잡도: O(N * M * 4^L) + * - N: board의 행 개수 + * - M: board의 열 개수 + * - L: word의 길이 + * + * 공간 복잡도: O(L) (재귀 호출 스택) + */ +function exist(board: string[][], word: string): boolean { + const rows = board.length; + const cols = board[0].length; + + // 방향 배열 (상, 하, 좌, 우) + const directions = [ + [0, -1], // 상 + [0, 1], // 하 + [-1, 0], // 좌 + [1, 0], // 우 + ]; + + /** + * DFS 탐색(깊이 우선 탐색)을 통해 단어를 찾는 함수 + * @param {number} x - 현재 x 좌표 (열) + * @param {number} y - 현재 y 좌표 (행) + * @param {number} index - 현재 탐색 중인 word의 문자 인덱스 + * @returns {boolean} - 현재 경로가 유효하면 true, 유효하지 않으면 false + */ + const dfs = (x: number, y: number, index: number): boolean => { + // 단어를 모두 찾았을 경우 + if (index === word.length) return true; + + // 범위를 벗어나거나 문자가 일치하지 않는 경우 + if (x < 0 || y < 0 || x >= cols || y >= rows || board[y][x] !== word[index]) { + return false; + } + + // 현재 위치 방문 처리 (임시 수정) + const temp = board[y][x]; + board[y][x] = "#"; + + // 상하좌우 탐색 + for (const [dx, dy] of directions) { + if (dfs(x + dx, y + dy, index + 1)) { + return true; + } + } + + // 백트래킹: 셀 값 복구 + board[y][x] = temp; + + return false; + }; + + // board에서 word 첫글자가 일치하는 경우 탐색 시작 + for (let y = 0; y < rows; y++) { + for (let x = 0; x < cols; x++) { + if (board[y][x] === word[0] && dfs(x, y, 0)) { + return true; + } + } + } + + return false; +} + diff --git a/word-search/choidabom.ts b/word-search/choidabom.ts new file mode 100644 index 000000000..0e18d9949 --- /dev/null +++ b/word-search/choidabom.ts @@ -0,0 +1,53 @@ +/** + * Runtime: 239ms, Memory: 51.98MB + * + * Time Complexity: O(rows * cols * 4^L) L: 단어 길이 + * Space Complexity: O(L) + * + */ +function exist(board: string[][], word: string): boolean { + const ROWS = board.length; + const COLUMNS = board[0].length; + + for (let r = 0; r < ROWS; r++) { + for (let c = 0; c < COLUMNS; c++) { + if (board[r][c] === word[0]) { + if (check(r, c, 0, board, word)) return true; + } + } + } + + return false; +} + +function check( + r: number, + c: number, + i: number, + board: string[][], + word: string +): boolean { + const ROWS = board.length; + const COLUMNS = board[0].length; + + if (i === word.length) { + return true; + } + + if (r < 0 || r >= ROWS || c < 0 || c >= COLUMNS || board[r][c] !== word[i]) { + return false; + } + + const temp = board[r][c]; + board[r][c] = "#"; + + const found = + check(r - 1, c, i + 1, board, word) || // 위 + check(r, c + 1, i + 1, board, word) || // 오른쪽 + check(r + 1, c, i + 1, board, word) || // 아래 + check(r, c - 1, i + 1, board, word); // 왼쪽 + + board[r][c] = temp; + + return found; +} diff --git a/word-search/dusunax.py b/word-search/dusunax.py new file mode 100644 index 000000000..fb3e1f855 --- /dev/null +++ b/word-search/dusunax.py @@ -0,0 +1,52 @@ +''' +# 79. Word Search + +use backtracking(DFS) to search for the word in the board. + +## Time and Space Complexity + +``` +TC: O(n * m * 4^L) +SC: O(L) +``` + +#### TC is O(n * m * 4^L): +- n is the number of rows in the board. +- m is the number of columns in the board. +- L is the length of the word. + - 4^L is the number of directions we can go at each step. (explores 4 branches recursively) + +#### SC is O(L): +- modifying the board in-place to mark visited cells. = O(L) +''' +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + rows = len(board) + cols = len(board[0]) + + def backtracking(i, j, word_index): # TC: O(4^L), SC: O(L) + if word_index == len(word): + return True + + if i < 0 or i >= rows or j < 0 or j >= cols or board[i][j] != word[word_index]: + return False + + temp = board[i][j] + board[i][j] = "." + + found = ( + backtracking(i + 1, j, word_index + 1) or + backtracking(i - 1, j, word_index + 1) or + backtracking(i, j + 1, word_index + 1) or + backtracking(i, j - 1, word_index + 1) + ) + board[i][j] = temp + + return found + + for row in range(rows): # TC: O(n * m) + for col in range(cols): + if backtracking(row, col, 0): + return True + + return False diff --git a/word-search/ekgns33.java b/word-search/ekgns33.java new file mode 100644 index 000000000..631b7b781 --- /dev/null +++ b/word-search/ekgns33.java @@ -0,0 +1,58 @@ +/* +input : m x n matrix and string word +output : return true if given word can be constructed + +solution 1) brute force +tc : O(n * 4^k) when n is the number of cells, k is the length of word +sc : O(k) call stack + */ +class Solution { + private int[][] directions = new int[][] {{-1,0}, {1,0}, {0,1}, {0,-1}}; + public boolean exist(char[][] board, String word) { + //edge case + int m = board.length; + int n = board[0].length; + + if(m * n < word.length()) return false; + + + //look for the starting letter and do dfs + for(int i = 0; i < m; i++) { + + for(int j = 0; j < n; j++) { + + if(board[i][j] == word.charAt(0)) { + //do dfs and get answer + board[i][j] = '0'; + boolean res = dfsHelper(board, word, i, j, 1); + board[i][j] = word.charAt(0); + if(res) return true; + } + } + } + + return false; + } + + public boolean dfsHelper(char[][] board, String word, int curR, int curC, int curP) { + + //endclause + if(curP == word.length()) return true; + + boolean ret = false; + + for(int[] direction : directions) { + int nextR = curR + direction[0]; + int nextC = curC + direction[1]; + + if(nextR < 0 || nextR >= board.length || nextC < 0 || nextC >= board[0].length) continue; + + if(board[nextR][nextC] == word.charAt(curP)) { + board[nextR][nextC] = '0'; + ret = ret || dfsHelper(board, word, nextR, nextC, curP + 1); + board[nextR][nextC] = word.charAt(curP); + } + } + return ret; + } +} diff --git a/word-search/forest000014.java b/word-search/forest000014.java new file mode 100644 index 000000000..f151e9fe8 --- /dev/null +++ b/word-search/forest000014.java @@ -0,0 +1,84 @@ +/* +Time Complexity: O(m * n * 4^(word.length)) +Space Complexity: O(m * n) +*/ + +class Solution { + boolean[][] visited; + int m, n; + int len; + int[] dr = {-1, 0, 1, 0}; // clockwise traversal + int[] dc = {0, 1, 0, -1}; + char board2[][]; + String word2; + + public boolean exist(char[][] board, String word) { + int[] cnt = new int[52]; + board2 = board; + word2 = word; + m = board.length; + n = board[0].length; + visited = new boolean[m][n]; + len = word.length(); + + // 1. for pruning, count characters in board and word respectively + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + cnt[charToInt(board[i][j])]++; + } + } + for (int i = 0; i < len; i++) { + int idx = charToInt(word.charAt(i)); + if (--cnt[idx] < 0) { + return false; + } + } + + // 2. DFS + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (board2[i][j] != word.charAt(0)) { + continue; + } + if (dfs(i, j, 1)) { + return true; + } + } + } + return false; + } + + private boolean dfs(int row, int col, int idx) { + if (idx == len) { // end of word + return true; + } + visited[row][col] = true; + + for (int i = 0; i < 4; i++) { + int nr = row + dr[i]; + int nc = col + dc[i]; + if (nr < 0 || nr >= m || nc < 0 || nc >= n) { // check boundary of the board + continue; + } + if (visited[nr][nc]) { // check visited + continue; + } + if (board2[nr][nc] == word2.charAt(idx)) { + if (dfs(nr, nc, idx + 1)) { + return true; + } + } + } + + visited[row][col] = false; + return false; + } + + private int charToInt(char ch) { + if (ch <= 'Z') { + return ch - 'A'; + } else { + return ch - 'a' + 26; + } + } +} diff --git a/word-search/gmlwls96.kt b/word-search/gmlwls96.kt new file mode 100644 index 000000000..18d13f7c9 --- /dev/null +++ b/word-search/gmlwls96.kt @@ -0,0 +1,67 @@ +class Solution { + + // 풀이 : dfs + // 시간 :O(m * n * 4^w), 공간 :O(m * n + w) + val movePos = arrayOf( + intArrayOf(-1, 0), + intArrayOf(0, -1), + intArrayOf(1, 0), + intArrayOf(0, 1) + ) + + fun exist(board: Array, word: String): Boolean { + for (y in board.indices) { + for (x in board[y].indices) { + if (existDfs( + board, + Array(board.size) { BooleanArray(board[it].size) }, + word, + "", + y, + x + ) + ) { + return true + } + } + } + return false + } + + private fun existDfs( + board: Array, + visit: Array, + findWord: String, + currentWord: String, + y: Int, + x: Int + ): Boolean { + if (findWord == currentWord) return true + val findChar = findWord[currentWord.length] + if (board[y][x] == findChar) { + val newWord = currentWord + board[y][x] + visit[y][x] = true + for (pos in movePos) { + val newY = y + pos[0] + val newX = x + pos[1] + if (newY >= 0 && newX >= 0 + && newY < board.size + && newX < board[newY].size + && !visit[newY][newX] + && existDfs( + board = board, + visit = visit, + findWord = findWord, + currentWord = newWord, + y = newY, + x = newX + ) + ) { + return true + } + } + visit[y][x] = false + } + return false + } +} diff --git a/word-search/gwbaik9717.js b/word-search/gwbaik9717.js new file mode 100644 index 000000000..2b1db0944 --- /dev/null +++ b/word-search/gwbaik9717.js @@ -0,0 +1,59 @@ +// h: height of the board, w: width of the board, n: length of the word +// Time complexity: O(h * w * 4**n) +// Space complexity: O(n) + +/** + * @param {character[][]} board + * @param {string} word + * @return {boolean} + */ +var exist = function (board, word) { + const n = word.length; + const h = board.length; + const w = board[0].length; + + const dy = [1, 0, -1, 0]; + const dx = [0, 1, 0, -1]; + + let answer = false; + + const dfs = (current, index) => { + if (index === n - 1) { + answer = true; + return; + } + + const [cy, cx] = current; + const value = board[cy][cx]; + board[cy][cx] = ""; + + for (let i = 0; i < dy.length; i++) { + const ny = cy + dy[i]; + const nx = cx + dx[i]; + const ni = index + 1; + + if ( + ny >= 0 && + ny < h && + nx >= 0 && + nx < w && + board[ny][nx] && + word[ni] === board[ny][nx] + ) { + dfs([ny, nx], ni); + } + } + + board[cy][cx] = value; + }; + + for (let i = 0; i < h; i++) { + for (let j = 0; j < w; j++) { + if (board[i][j] === word[0] && !answer) { + dfs([i, j], 0); + } + } + } + + return answer; +}; diff --git a/word-search/higeuni.js b/word-search/higeuni.js new file mode 100644 index 000000000..4e6fe19a2 --- /dev/null +++ b/word-search/higeuni.js @@ -0,0 +1,47 @@ +/** + * @param {character[][]} board + * @param {string} word + * @return {boolean} + * + * complexity + * time: O(n * m * 4^l) + * space: O(n * m) + */ + +var exist = function(board, word) { + word = word.split(''); + const dx = [-1, 1, 0, 0]; + const dy = [0, 0, 1, -1]; + const visited = Array.from({length: board.length}, () => Array(board[0].length).fill(false)); + + const dfs = (length, x, y) => { + if(word.length === length) { + return true; + } + for(let i = 0; i < 4; ++i) { + const nx = x + dx[i]; + const ny = y + dy[i]; + if(0 <= nx && nx < board[0].length && 0 <= ny && ny < board.length){ + if(board[ny][nx] === word[length] && !visited[ny][nx]) { + visited[ny][nx] = true; + if(dfs(length + 1, nx, ny)) return true; + visited[ny][nx] = false; + } + } + } + return false; + } + + for(let i = 0; i < board.length; ++i){ + for(let j = 0; j < board[0].length; ++j){ + if(board[i][j] === word[0]) { + visited[i][j] = true; + if(dfs(1, j, i)) return true; + visited[i][j] = false; + } + } + } + + return false; +}; + diff --git a/word-search/imsosleepy.java b/word-search/imsosleepy.java new file mode 100644 index 000000000..494d9797d --- /dev/null +++ b/word-search/imsosleepy.java @@ -0,0 +1,44 @@ +// 189ms가 나와서 다시 시도해볼 예정 +class Solution { + private int[] rowMove = {1, -1, 0, 0}; + private int[] colMove = {0, 0, 1, -1}; + + public boolean exist(char[][] board, String word) { + int rows = board.length; + int cols = board[0].length; + + for (int i = 0; i < rows; i++) { + for (int j = 0; j < cols; j++) { + if (dfs(board, word, i, j, 0)) { + return true; + } + } + } + return false; + } + + private boolean dfs(char[][] board, String word, int row, int col, int index) { + if (index == word.length()) { + return true; + } + + if (row < 0 || col < 0 || row >= board.length || col >= board[0].length || board[row][col] != word.charAt(index)) { + return false; + } + + char temp = board[row][col]; + board[row][col] = '#'; + + for (int i = 0; i < 4; i++) { + int newRow = row + rowMove[i]; + int newCol = col + colMove[i]; + if (dfs(board, word, newRow, newCol, index + 1)) { + return true; + } + } + + board[row][col] = temp; + + return false; + } +} diff --git a/word-search/jeldo.py b/word-search/jeldo.py new file mode 100644 index 000000000..06e04e833 --- /dev/null +++ b/word-search/jeldo.py @@ -0,0 +1,29 @@ +class Solution: + # O(m*n), m*n = board's width,heigh + def exist(self, board: List[List[str]], word: str) -> bool: + dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)] + result = False + + def dfs(i, j, visited, w): + nonlocal result + if (i, j) in visited: + return + if not (0 <= i < len(board)) or not (0 <= j < len(board[0])): + return + w += board[i][j] + if not (word[:len(w)] == w): + return + visited.add((i, j)) + if w == word: + result = True + return + for d in dirs: + dfs(i + d[0], j + d[1], visited, w) + visited.remove((i, j)) # backtracking + + for i in range(len(board)): + for j in range(len(board[0])): + dfs(i, j, set(), "") + if result: + return True + return False diff --git a/word-search/jinah92.py b/word-search/jinah92.py new file mode 100644 index 000000000..df3015a93 --- /dev/null +++ b/word-search/jinah92.py @@ -0,0 +1,24 @@ +# O(M*N*4^W) times, O(W) spaces +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + rows, cols = len(board), len(board[0]) + + directions = [(0, 1), (0, -1), (1, 0), (-1, 0)] + + def dfs(row, col, idx): + if idx == len(word): + return True + if not (0 <= row < rows and 0 <= col < cols): + return False + if board[row][col] != word[idx]: + return False + + temp_val = board[row][col] + board[row][col] = "" + + result = any(dfs(row+r, col+c, idx+1) for (r, c) in directions) + + board[row][col] = temp_val + return result + + return any(dfs(r, c, 0) for r in range(rows) for c in range(cols)) diff --git a/word-search/minji-go.java b/word-search/minji-go.java new file mode 100644 index 000000000..51ca4eed7 --- /dev/null +++ b/word-search/minji-go.java @@ -0,0 +1,47 @@ +/* + Problem: https://leetcode.com/problems/word-search/ + Description: return true if word exists in the grid + Concept: Array, String, Backtracking, Matrix + Time Complexity: O(MN4ᵀ), Runtime 147ms + Space Complexity: O(MN), Memory 42.11MB +*/ +class Solution { + public char[][] board; + public String word; + public boolean[][] visited; + public int n, m; + + public boolean exist(char[][] board, String word) { + this.board = board; + this.word = word; + this.m = board.length; + this.n = board[0].length; + this.visited = new boolean[m][n]; + + for(int i=0; im-1||nc>n-1||visited[nr][nc]) continue; + if(board[nr][nc]!=word.charAt(i)) continue; + if(wordExists(nr, nc, i+1)) return true; + } + visited[cr][cc] = false; + + return false; + } +} diff --git a/word-search/mintheon.java b/word-search/mintheon.java new file mode 100644 index 000000000..674d6789c --- /dev/null +++ b/word-search/mintheon.java @@ -0,0 +1,45 @@ +class Solution { + // BFS로 변환해서 다시 풀어볼 것 + char[][] board; + boolean[][] visited; + int[] moveX = {-1, 1, 0, 0}; + int[] moveY = {0, 0, -1, 1}; + int row, col; + + public boolean exist(char[][] board, String word) { + this.board = board; + this.row = board.length; + this.col = board[0].length; + this.visited = new boolean[row][col]; + + for(int y = 0; y < row; y++) { + for(int x = 0; x < col; x++) { + if(this.hasWord(y, x, word, 0)) { + return true; + } + } + } + + return false; + } + + private boolean hasWord(int y, int x, String word, int index) { + if(index >= word.length()) { + return true; + } + + if(x < 0 || x >= col || y < 0 || y >= row || visited[y][x] || board[y][x] != word.charAt(index)) { + return false; + } + + this.visited[y][x] = true; + for(int i = 0; i < 4; i++) { + if(this.hasWord(y + moveY[i], x + moveX[i], word, index + 1)) { + return true; + } + } + + this.visited[y][x] = false; + return false; + } +} diff --git a/word-search/mmyeon.ts b/word-search/mmyeon.ts new file mode 100644 index 000000000..f43907ae2 --- /dev/null +++ b/word-search/mmyeon.ts @@ -0,0 +1,60 @@ +/** + * + * 접근 방법 : + * 1. 행렬 순회하며 word와 첫 번째 문자가 같은지 체크 + * 2. 같으면 DFS(재귀)로 네 방향(상하좌우)을 탐색한다. + * - 현재 위치가 유효한지 체크 = 범위 안인가, 문자가 같은가 + * - 단어 다 찾아서 index가 단어 길이와 같은지 체크 + * 3. 이미 방문한 노드 제외하기 위해서 네 방향 체크하기 전에 방문 여부 표시하기 + * 4. 4방향으로 문자 체크하기 + * 5. 재귀 호출하는 동안 찾지 못한 경우 방문 여부 초기화하기 (backtracking) + * + * 시간복잡도 : O(N * M * 4^L) + * - L는 word의 길이, word 길이만큼 네 방향 체크하니까 O(4^L) + * 공간복잡도 : O(L) + * + * - L는 word의 길이, 찾으려는 단어 길이만큼 재귀 호출되니까 O(L) + * + */ + +function exist(board: string[][], word: string): boolean { + const rows = board.length; + const cols = board[0].length; + + const dfs = (x: number, y: number, index: number): boolean => { + // 종료조건 : 문자를 다 찾은 경우 + if (index === word.length) return true; + + // 범위를 벗어나거나 이미 방문했거나 문자가 다른 경우 + if (x < 0 || y < 0 || x >= rows || y >= cols || board[x][y] !== word[index]) + return false; + + // 방문 표시 + const temp = board[x][y]; + board[x][y] = "#"; + + // 4 방향 + const directions = [ + [1, 0], + [0, 1], + [-1, 0], + [0, -1], + ]; + + for (const [dx, dy] of directions) { + if (dfs(x + dx, y + dy, index + 1)) return true; + } + + // 백트래킹 + board[x][y] = temp; + return false; + }; + + for (let i = 0; i < rows; i++) { + for (let j = 0; j < cols; j++) { + if (word[0] === board[i][j] && dfs(i, j, 0)) return true; + } + } + + return false; +} diff --git a/word-search/neverlish.go b/word-search/neverlish.go new file mode 100644 index 000000000..38e852406 --- /dev/null +++ b/word-search/neverlish.go @@ -0,0 +1,81 @@ +// 시간복잡도: O(n^2 * 4^m) +// 공간복잡도: O(n * m) +// (n: board의 행의 개수, m: word의 길이) + +package main + +import "testing" + +func TestExist(t *testing.T) { + board := [][]byte{ + {'A', 'B', 'C', 'E'}, + {'S', 'F', 'C', 'S'}, + {'A', 'D', 'E', 'E'}, + } + + if !exist(board, "ABCCED") { + t.Error("Test case 0 failed") + } + + if !exist(board, "SEE") { + t.Error("Test case 1 failed") + } + + if exist(board, "ABCB") { + t.Error("Test case 2 failed") + } +} + +func backtrack(board [][]byte, histories [][]bool, n_row int, n_col int, word string, idx int) bool { + if idx == len(word) { + return true + } + + if n_row < 0 || n_row >= len(board) { + return false + } + + if n_col < 0 || n_col >= len(board[0]) { + return false + } + + if board[n_row][n_col] != word[idx] || histories[n_row][n_col] { + return false + } + + steps := [][]int{{0, 1}, {0, -1}, {1, 0}, {-1, 0}} + + histories[n_row][n_col] = true + + for _, step := range steps { + if backtrack(board, histories, n_row+step[0], n_col+step[1], word, idx+1) { + return true + } + } + + histories[n_row][n_col] = false + return false +} + +func exist(board [][]byte, word string) bool { + n_rows := len(board) + n_cols := len(board[0]) + + histories := make([][]bool, n_rows) + + for n_row := 0; n_row < n_rows; n_row++ { + for n_col := 0; n_col < n_cols; n_col++ { + if board[n_row][n_col] == word[0] { + for k := 0; k < n_rows; k++ { + histories[k] = make([]bool, n_cols) + } + + if backtrack(board, histories, n_row, n_col, word, 0) { + return true + } + } + } + } + + return false +} diff --git a/word-search/pmjuu.py b/word-search/pmjuu.py new file mode 100644 index 000000000..a119c032a --- /dev/null +++ b/word-search/pmjuu.py @@ -0,0 +1,106 @@ +from typing import List + + +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + n, m, word_length = len(board), len(board[0]), len(word) + + def search(row, col, word_idx, visited): + # 경계 체크 + if not (0 <= row < n and 0 <= col < m): + return False + # 이미 방문했거나, 문자가 일치하지 않는 경우 + if (row, col) in visited or board[row][col] != word[word_idx]: + return False + + # 모든 문자를 찾은 경우 + if word_idx == word_length - 1: + return True + + # 현재 셀을 방문한 것으로 표시 + visited.add((row, col)) + + # 인접한 셀 확인 + found = ( + search(row - 1, col, word_idx + 1, visited) or + search(row + 1, col, word_idx + 1, visited) or + search(row, col - 1, word_idx + 1, visited) or + search(row, col + 1, word_idx + 1, visited) + ) + # 현재 셀 방문 해제 (백트래킹) + visited.remove((row, col)) + + return found + + # 모든 셀에서 탐색 시작 + for row in range(n): + for col in range(m): + if board[row][col] == word[0]: + if search(row, col, 0, set()): + return True + + return False + +# 풀이 1: 방문 기록을 Set으로 관리하는 방식 +# 시간 복잡도: +# - 각 셀에서 DFS를 시작하며, 각 DFS는 최대 네 방향으로 이동하며 word의 길이만큼 재귀 호출을 진행함. +# - 최악의 경우 O(n * 4^k), 여기서 n은 전체 셀의 개수, k는 word의 길이. +# 공간 복잡도: +# - visited Set 사용: O(k), 여기서 k는 word의 길이. +# - 재귀 호출 스택: O(k), word의 길이만큼 재귀 호출이 쌓임. +# => 총 공간 복잡도: O(k) + + +class Solution: + def exist(self, board: list[list[str]], word: str) -> bool: + n, m = len(board), len(board[0]) + word_length = len(word) + + # 조기 종료: board에 word를 구성할 충분한 문자가 있는지 확인 + from collections import Counter + board_counter = Counter(char for row in board for char in row) + word_counter = Counter(word) + if any(word_counter[char] > board_counter[char] for char in word_counter): + return False + + def search(row, col, idx): + # 기본 조건: 모든 문자가 일치한 경우 + if idx == word_length: + return True + + # 경계 조건 및 문자 일치 여부 확인 + if row < 0 or row >= n or col < 0 or col >= m or board[row][col] != word[idx]: + return False + + # 현재 셀을 방문한 것으로 임시 표시 + temp = board[row][col] + board[row][col] = "#" + + # 모든 방향 탐색 + found = ( + search(row - 1, col, idx + 1) or + search(row + 1, col, idx + 1) or + search(row, col - 1, idx + 1) or + search(row, col + 1, idx + 1) + ) + + # 탐색 후 셀 복원 + board[row][col] = temp + return found + + # 첫 번째 문자와 일치하는 모든 셀에서 DFS 시작 + for i in range(n): + for j in range(m): + if board[i][j] == word[0] and search(i, j, 0): + return True + + return False + +# 풀이 2: Board를 직접 수정해 방문 기록 관리 +# 시간 복잡도: +# - 각 셀에서 DFS를 시작하며, 최대 네 방향으로 이동하며 word의 길이만큼 재귀 호출을 진행함. +# - 최악의 경우 O(n * 4^k), 여기서 n은 전체 셀의 개수, k는 word의 길이. +# 공간 복잡도: +# - 추가 공간 사용 없이 Board를 직접 수정: O(1). +# - 재귀 호출 스택: O(k), word의 길이만큼 재귀 호출이 쌓임. +# => 총 공간 복잡도: O(k) diff --git a/word-search/sungjinwi.py b/word-search/sungjinwi.py new file mode 100644 index 000000000..5acc94b8b --- /dev/null +++ b/word-search/sungjinwi.py @@ -0,0 +1,47 @@ +""" + 풀이 : + 상하좌우 이동한 좌표가 board범위 벗어나면 False + board[m][n]이 word[idx]와 불일치하면 False + 이미 방문했을경우 False + 단어가 완성되면 True + 상하좌우 한칸 이동한칸에 대해 재귀적 호출 + 상하좌우 중 True 있으면 True 없으면 False + + TC : O(M * N * 4 ^ W) + board의 크기에 비례 -> M * N + 단어의 길이 만큼 상하좌우 재귀 호출 -> 4 ^ W + + SC : O(M * N + W) + set의 메모리는 board 크기에 비례 -> M * N + 함수 호출 스택은 단어 길이에 비례 -> W +""" + +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + visit = set() + + def dfs(m: int, n: int, idx: int) -> bool: + if not (0 <= m < row and 0 <= n < col): + return False + if not board[m][n] == word[idx]: + return False + if (m, n) in visit: + return False + if idx == len(word) - 1: + return True + + visit.add((m, n)) + for (r, c) in [(1, 0), (-1, 0), (0, 1), (0, -1)] : + if(dfs(m + r, n + c, idx + 1)) : + return True + visit.remove((m, n)) + return False + + row = len(board) + col = len(board[0]) + + for m in range(row): + for n in range(col): + if dfs(m, n, 0): + return True + return False diff --git a/word-search/taewanseoul.ts b/word-search/taewanseoul.ts new file mode 100644 index 000000000..6679d0ad8 --- /dev/null +++ b/word-search/taewanseoul.ts @@ -0,0 +1,53 @@ +/** + * 268. Missing Number + * Given an m x n grid of characters board and a string word, return true if word exists in the grid. + * The word can be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once. + * + * https://leetcode.com/problems/word-search/description/ + */ + +// O(4^n) time +// O(n * m) space +function exist(board: string[][], word: string): boolean { + let result = false; + const num_rows = board.length; + const num_cols = board[0].length; + + function checkNeighbors( + board: (string | null)[][], + word: string, + row: number, + col: number, + startIndex: number + ) { + const num_rows = board.length; + const num_cols = board[0].length; + + if (row < 0 || row >= num_rows || col < 0 || col >= num_cols) return; + + if (board[row][col] !== word[startIndex]) return; + + if (startIndex === word.length - 1) { + result = true; + return; + } + + board[row][col] = null; + checkNeighbors(board, word, row + 1, col, startIndex + 1); + checkNeighbors(board, word, row - 1, col, startIndex + 1); + checkNeighbors(board, word, row, col + 1, startIndex + 1); + checkNeighbors(board, word, row, col - 1, startIndex + 1); + board[row][col] = word[startIndex]; + } + + for (let i = 0; i < num_rows; i++) { + for (let j = 0; j < num_cols; j++) { + if (board[i][j] === word[0]) { + checkNeighbors(board, word, i, j, 0); + if (result) return result; + } + } + } + + return result; +} diff --git a/word-search/thispath98.py b/word-search/thispath98.py new file mode 100644 index 000000000..80c791ab3 --- /dev/null +++ b/word-search/thispath98.py @@ -0,0 +1,48 @@ +class Solution: + def exist(self, board: List[List[str]], word: str) -> bool: + """ + Intuition: + 보드를 돌면서 dfs를 수행한다. + dfs는 상하좌우를 돌면서 word의 index와 + board의 word가 동일한지 확인한다. + + Time Complexity: + O(M x N + w.length^4): + M x N 크기의 배열을 돌면서, + 각 칸마다 상하좌우 4번씩 확인한다. + 최대 word length번만큼 반복한다. + + Space Complexity: + O(M x N + w.length): + M x N 크기의 visited 배열을 초기화하고, + dfs의 호출 스택은 word length만큼 반복한다. + """ + visited = [[False for _ in board[0]] for _ in board] + + def dfs(y, x, index): + if index == len(word): + return True + if not (0 <= y < len(board) and 0 <= x < len(board[0])): + return False + if visited[y][x]: + return False + if word[index] != board[y][x]: + return False + + visited[y][x] = True + for dy, dx in [[-1, 0], [1, 0], [0, -1], [0, 1]]: + ny = y + dy + nx = x + dx + + if dfs(ny, nx, index + 1): + return True + + visited[y][x] = False + return False + + for i in range(len(board)): + for j in range(len(board[0])): + if dfs(i, j, 0): + return True + + return False