diff --git a/3sum/ICE0208.java b/3sum/ICE0208.java new file mode 100644 index 0000000000..cd33e8ec02 --- /dev/null +++ b/3sum/ICE0208.java @@ -0,0 +1,54 @@ +import java.util.*; + +class Solution { + public List> threeSum(int[] nums) { + /* + * 세 수의 합이 0이 되는 조합을 찾는다. + * + * nums[i]를 하나 고정하면, + * 나머지 두 수를 찾는 Two Sum 문제로 바꿀 수 있다. + * + * seen에는 현재 i 기준으로 지나온 값들을 저장한다. + * target이 seen에 있으면 nums[i] + target + nums[j] = 0 이다. + * + * + * 시간 복잡도: O(n^2) + * 공간 복잡도: O(n) + */ + + Arrays.sort(nums); + + List> answer = new ArrayList<>(); + + for (int i = 0; i < nums.length; i++) { + // 같은 기준값은 한 번만 사용 + if (i > 0 && nums[i] == nums[i - 1]) { + continue; + } + + // 기준값이 양수면 합이 0이 될 수 없음 + if (nums[i] > 0) { + break; + } + + Set seen = new HashSet<>(); + + for (int j = i + 1; j < nums.length; j++) { + int target = -nums[i] - nums[j]; + + if (seen.contains(target)) { + answer.add(Arrays.asList(nums[i], target, nums[j])); + + // 같은 nums[j]는 중복 조합을 만들 수 있으므로 스킵 + while (j + 1 < nums.length && nums[j] == nums[j + 1]) { + j++; + } + } + + seen.add(nums[j]); + } + } + + return answer; + } +} diff --git a/climbing-stairs/ICE0208.java b/climbing-stairs/ICE0208.java new file mode 100644 index 0000000000..fb9912e262 --- /dev/null +++ b/climbing-stairs/ICE0208.java @@ -0,0 +1,35 @@ +class Solution { + public int climbStairs(int n) { + /* + * i번째 계단에 도착하는 경우는 두 가지다. + * + * 1. i - 1번째 계단에서 한 칸 올라오는 경우 + * 2. i - 2번째 계단에서 두 칸 올라오는 경우 + * + * dp[i]를 i번째 계단에 도착하는 방법의 수라고 하면, + * dp[i] = dp[i - 1] + dp[i - 2] 이다. (i >= 3) + * + * 이때 dp[i]를 구하기 위해 필요한 값은 직전 값과 전전 값뿐이다. + * 따라서 dp 배열 전체를 만들지 않고 prev1, prev2 두 변수만 사용해 갱신한다. + * + * 시간 복잡도: O(n) + * 공간 복잡도: O(1) + */ + + if (n <= 2) { + // 1번째 계단은 1가지, 2번째 계단은 2가지 방법으로 오를 수 있다. + return n; + } + + int prev2 = 1; // dp[1] + int prev1 = 2; // dp[2] + + for (int i = 3; i <= n; i++) { + int curr = prev1 + prev2; // dp[i] + prev2 = prev1; + prev1 = curr; + } + + return prev1; + } +} diff --git a/product-of-array-except-self/ICE0208.java b/product-of-array-except-self/ICE0208.java new file mode 100644 index 0000000000..3383e36703 --- /dev/null +++ b/product-of-array-except-self/ICE0208.java @@ -0,0 +1,38 @@ +class Solution { + public int[] productExceptSelf(int[] nums) { + /* + * 자기 자신을 제외한 모든 요소의 곱 + * = 왼쪽 요소들의 곱 * 오른쪽 요소들의 곱 + * + * 왼쪽에서 오른쪽으로 순회하면서 누적 곱을 구하면 + * 각 요소 기준 왼쪽 요소들의 곱을 구할 수 있다. + * + * 반대로 오른쪽에서 왼쪽으로 순회하면서 누적 곱을 구하면 + * 각 요소 기준 오른쪽 요소들의 곱을 구할 수 있다. + * + * 왼쪽 요소들의 곱과 오른쪽 요소들의 곱을 곱하면 정답이 된다. + * + * 시간 복잡도: O(n) + * 공간 복잡도: O(1) + * 단, 반환 배열 result는 제외한다. + */ + int n = nums.length; + int[] result = new int[n]; + + // 각 위치 기준 왼쪽 요소들의 곱을 result에 저장 + int prefixProduct = 1; + for (int i = 0; i < n; i++) { + result[i] = prefixProduct; + prefixProduct *= nums[i]; + } + + // 각 위치 기준 오른쪽 요소들의 곱을 result에 반영 + int suffixProduct = 1; + for (int i = n - 1; i >= 0; i--) { + result[i] *= suffixProduct; + suffixProduct *= nums[i]; + } + + return result; + } +} diff --git a/valid-anagram/ICE0208.java b/valid-anagram/ICE0208.java new file mode 100644 index 0000000000..5e9aa5800b --- /dev/null +++ b/valid-anagram/ICE0208.java @@ -0,0 +1,25 @@ +import java.util.Arrays; + +class Solution { + /** + * n = s.length(), m = t.length() + * 각 문자열에서 알파벳별 등장 횟수를 센 뒤 비교한다. + * 시간 복잡도: O(n + m) + * 공간 복잡도: O(1) - 알파벳 소문자 26개만 사용하기 때문 + */ + public boolean isAnagram(String s, String t) { + // 0: a, 1: b, ... , 25: z + int[] sCount = new int[26]; + int[] tCount = new int[26]; + + for (int i = 0; i < s.length(); i++) { + sCount[s.charAt(i) - 'a']++; + } + + for (int i = 0; i < t.length(); i++) { + tCount[t.charAt(i) - 'a']++; + } + + return Arrays.equals(sCount, tCount); + } +} diff --git a/valid-anagram/ICE0208.py b/valid-anagram/ICE0208.py new file mode 100644 index 0000000000..9bb9947498 --- /dev/null +++ b/valid-anagram/ICE0208.py @@ -0,0 +1,28 @@ +from collections import Counter + + +class Solution_2: + def isAnagram(self, s: str, t: str) -> bool: + """ + n = len(s), m = len(t) + + 두 문자열의 각 문자 개수를 센 뒤 비교한다. + + 시간 복잡도: O(n + m) + 공간 복잡도: O(1) + - 입력 문자가 알파벳 소문자 26개로 제한되기 때문 + """ + return Counter(s) == Counter(t) + + +class Solution_01: + def isAnagram(self, s: str, t: str) -> bool: + """ + n = len(s), m = len(t) + + 두 문자열을 각각 정렬한 뒤 비교한다. + + 시간 복잡도: O(n log n + m log m) + 공간 복잡도: O(n + m) + """ + return sorted(s) == sorted(t) diff --git a/validate-binary-search-tree/ICE0208.java b/validate-binary-search-tree/ICE0208.java new file mode 100644 index 0000000000..9e73494fc4 --- /dev/null +++ b/validate-binary-search-tree/ICE0208.java @@ -0,0 +1,37 @@ +class Solution { + /* + * 현재 노드가 허용된 범위 안에 있는지 확인한다. + * + * 왼쪽 자식은 현재 노드보다 작아야 하므로, + * upperBound를 현재 노드 값으로 줄인다. + * + * 오른쪽 자식은 현재 노드보다 커야 하므로, + * lowerBound를 현재 노드 값으로 올린다. + * + * 시간 복잡도: O(n) + * - n은 전체 노드의 개수 + * - 모든 노드를 한 번씩 검사한다. + * + * 공간 복잡도: O(h) + * - h는 트리의 높이 + * - 재귀 호출 스택이 현재 탐색 중인 경로만큼 쌓인다. + * - 균형 잡힌 트리라면 O(log n) / 한쪽으로 치우친 트리라면 O(n) + */ + private boolean isValidWithinRange(TreeNode node, long lowerBound, long upperBound) { + if (node == null) { + return true; + } + + // BST는 중복 값을 허용하지 않으므로 경계값과 같아도 false다. + if (node.val <= lowerBound || node.val >= upperBound) { + return false; + } + + return isValidWithinRange(node.left, lowerBound, node.val) + && isValidWithinRange(node.right, node.val, upperBound); + } + + public boolean isValidBST(TreeNode root) { + return isValidWithinRange(root, Long.MIN_VALUE, Long.MAX_VALUE); + } +}