diff --git a/container-with-most-water/forest000014.java b/container-with-most-water/forest000014.java new file mode 100644 index 000000000..232d3c2cd --- /dev/null +++ b/container-with-most-water/forest000014.java @@ -0,0 +1,115 @@ +/* +solution 1. brute force +Time Complexity: O(n^2) +Space Complexity: O(1) +모든 선분 i에 대해, 가장 많은 물을 담을 수 있는 반대쪽 선분 j를 찾는다. + + +solution 2. PQ +Time Complexity: O(nlogn) +- 정렬 : O(nlogn) +- i번째 선분에 대해 (자신보다 크거나 같은) 가장 멀리있는 선분 탐색(O(logn)) * n = O(nlogn) +Space Complexity: O(n) + +(사고의 흐름을 적기 위해 편의상 반말로 적었습니다... ^^) +brute force 에서 불필요한 탐색을 줄여보자. +일단 어떤 선분 i가 주어졌다고 가정하자. i를 한쪽 벽으로 해서 가장 많은 물을 담는 방법을 찾으려면, 반드시 나머지 모든 선분을 탐색해야만 할까? + +(1) 자신보다 작은 선분은 탐색하지 않는다. + +자신보다 큰 선분만을 탐색해도 충분하다. +왜냐하면, 설령 자신보다 더 작은 선분 중에 정답이 있었다고 하더라도, 그 선분을 기준으로 탐색할 때 정답에 해당하는 쌍을 확인하게 될 것이기 때문이다. + +(2) 자신보다 크거나 같은 선분만을 탐색 대상으로 삼는다면, 가장 멀리있는 선분만 확인하면 된다. + +탐색 대상이 자신보다 크거나 같은 선분들이라면, 어차피 담을 수 있는 물의 높이는 자신의 높이로 일정하다. +따라서, 멀리있는 선분일수록 더 많은 물을 담게 된다. +즉, "자신보다 크거나 같으면서", "가장 멀리 있는(오른쪽이든 왼쪽이든)" 선분만을 후보로 삼아 확인하면 충분하다. +(그 외의 나머지, 즉 자신보다 크거나 같으면서, 가장 멀리있지 않은 나머지 선분들은, 자연스럽게 탐색하지 않을 수 있다.) + +정리하자면, 주어진 선분 i에 대해서, 단 2번(오른쪽, 왼쪽)의 탐색만 하면 충분하다. + +(3) 내림차순 정렬과 2개의 PQ를 아래처럼 활용하면, 위 탐색을 O(logn) 시간에 수행할 수 있다. +PQ는 각각 x 좌표 기준 max heap(가장 오른쪽의 선분을 찾기 위함), x 좌표 기준 min heap(가장 왼쪽의 선분을 찾기 위함)을 사용한다. +선분들을 길이 내림차순으로 정렬해놓고, 하나씩 순회하면서 (say, i번째 선분), 아래 과정을 반복한다. +- 자신보다 크거나 같으면서 가장 오른쪽으로 멀리 있는 선분의 위치를 찾아서(= 현재 PQ(max heap)의 root), 최대 물의 양을 계산한다. +- 자신보다 크거나 같으면서 가장 왼쪽으로 멀리 있는 선분의 위치를 찾아서(= 현재 PQ(min heap)의 root), 최대 물의 양을 계산한다. +- i번째 선분을 PQ 2개에 각각 넣는다. + + +solution 3. two pointers +(AlgoDale 풀이를 참고함) + +Time Complexity: O(n) +Space Complexity: O(1) + +2 포인터를 활용하면, PQ도 없이 시간 복잡도를 O(n)으로 줄일 수 있었다. +단순히 "큰 쪽을 줄이기보다는, 작은 쪽을 줄이는 게 유리하겠지" 정도의 greedy한 논리는 충분하지 않은 것 같고, 더 명확한 근거가 있을 것 같은데 시간 관계상 고민해보지는 못했다. + +To-Do : 풀이가 대강 이해는 되었지만, 이게 왜 되는지, 엄밀하게 이해하기 위해 PQ를 사용했던 논리를 좀 더 발전시켜볼 필요가 있다. + +*/ +class Solution { + public int maxArea(int[] height) { + int i = 0, j = height.length - 1; + + int ans = 0; + while (i < j) { + int area = (j - i) * Math.min(height[i], height[j]); + if (area > ans) { + ans = area; + } + + if (height[i] <= height[j]) { + i++; + } else { + j--; + } + } + + return ans; + } + + ////// 아래는 solution 2 + private static class Tuple { + private int x; + private int h; + + Tuple(int x, int h) { + this.x = x; + this.h = h; + } + } + + public int maxArea2(int[] height) { + List tuples = IntStream.range(0, height.length) + .mapToObj(i -> new Tuple(i, height[i])) + .collect(Collectors.toList()); + Collections.sort(tuples, (a, b) -> b.h - a.h); + + PriorityQueue minPq = new PriorityQueue<>((a, b) -> a.x - b.x); + PriorityQueue maxPq = new PriorityQueue<>((a, b) -> b.x - a.x); + + int ans = 0; + for (int i = 0; i < height.length; i++) { + minPq.add(tuples.get(i)); + maxPq.add(tuples.get(i)); + + Tuple curr = tuples.get(i); + + Tuple left = minPq.peek(); + int leftArea = (curr.x - left.x) * curr.h; + if (leftArea > ans) { + ans = leftArea; + } + + Tuple right = maxPq.peek(); + int rightArea = (right.x - curr.x) * curr.h; + if (rightArea > ans) { + ans = rightArea; + } + } + + return ans; + } +} diff --git a/design-add-and-search-words-data-structure/forest000014.java b/design-add-and-search-words-data-structure/forest000014.java new file mode 100644 index 000000000..3d2ca1fa4 --- /dev/null +++ b/design-add-and-search-words-data-structure/forest000014.java @@ -0,0 +1,78 @@ +/* +Time Complexity: +- add: O(w) +- search: O(26^2 * w) = O(w) +Space Complexity: O(w) + +Trie를 활용하되, '.'의 탐색이 필요한 경우에는 for문을 사용한다. + +*/ +class WordDictionary { + class Node { + public char ch; + public boolean ends; + public Map children; + + Node() { + this.children = new HashMap<>(); + } + + Node(char ch) { + this.ch = ch; + this.children = new HashMap<>(); + } + } + + Node root; + + public WordDictionary() { + this.root = new Node(); + } + + public void addWord(String word) { + Node curr = this.root; + + for (int i = 0; i < word.length(); i++) { + char ch = word.charAt(i); + if (!curr.children.containsKey(ch)) { + curr.children.put(ch, new Node(ch)); + } + + curr = curr.children.get(ch); + } + + curr.ends = true; + } + + public boolean search(String word) { + return searchChar(word, 0, this.root); + } + + private boolean searchChar(String word, int idx, Node curr) { + if (curr == null) { + return false; + } else if (idx == word.length()) { + return curr.ends; + } + + char ch = word.charAt(idx); + + if (ch == '.') { + for (Character key : curr.children.keySet()) { + if (searchChar(word, idx + 1, curr.children.get(key))) { + return true; + } + } + return false; + } else { + return searchChar(word, idx + 1, curr.children.get(ch)); + } + } +} + +/** + * Your WordDictionary object will be instantiated and called as such: + * WordDictionary obj = new WordDictionary(); + * obj.addWord(word); + * boolean param_2 = obj.search(word); + */ diff --git a/longest-increasing-subsequence/forest000014.java b/longest-increasing-subsequence/forest000014.java new file mode 100644 index 000000000..9780ad5e5 --- /dev/null +++ b/longest-increasing-subsequence/forest000014.java @@ -0,0 +1,85 @@ +/* +Time Complexity: O(nlogn) +Space Complexity: O(nlogn) +*/ +class Solution { + + int[] tree; + int L = 1; + + public int lengthOfLIS(int[] nums) { + init(nums); + ArrayList tuples = new ArrayList<>(); + + for (int i = 0; i < nums.length; i++) { + tuples.add(new Tuple(i, nums[i])); + } + + Collections.sort(tuples, (a, b) -> { + if (a.val == b.val) { + return b.ref - a.ref; // 2순위 : ref 내림차순 + } else { + return a.val - b.val; // 1순위 : val 오름차순 + } + }); + + int ans = 0; + for (int i = 0; i < nums.length; i++) { + int curr = getMax(0, tuples.get(i).ref - 1) + 1; + ans = Math.max(ans, curr); + insert(tuples.get(i).ref, curr); + } + + return ans; + } + + public class Tuple { + public int ref; + public int val; + + public Tuple(int ref, int val) { + this.ref = ref; + this.val = val; + } + } + + public void init(int[] nums) { + while (L < nums.length) { + L *= 2; + } + + tree = new int[L * 2]; + + for (int i = 1; i < L * 2; i++) { + tree[i] = 0; + } + } + + public void insert(int idx, int v) { + int i = idx + L; + tree[i] = v; + i /= 2; + while (i >= 1) { + tree[i] = Math.max(tree[i * 2], tree[i * 2 + 1]); + i /= 2; + } + } + + public int getMax(int l, int r) { + int i = l + L; + int j = r + L; + int ret = 0; + while (i <= j) { + if (i % 2 == 1) { + ret = Math.max(ret, tree[i++]); + } + if (j % 2 == 0) { + ret = Math.max(ret, tree[j--]); + } + i /= 2; + j /= 2; + } + + return ret; + } +} diff --git a/spiral-matrix/forest000014.java b/spiral-matrix/forest000014.java new file mode 100644 index 000000000..f21494639 --- /dev/null +++ b/spiral-matrix/forest000014.java @@ -0,0 +1,35 @@ +/* +Time Complexity: O(m * n) +Space Complexity: O(1) + +현재 위치를 r, c라는 변수를 사용해서 나타내고, 이 r, c를 직접 제어하는 방식으로 행렬을 순회한다. +*/ +class Solution { + public List spiralOrder(int[][] matrix) { + List ans = new ArrayList<>(); + + int m = matrix.length, n = matrix[0].length; + int r = 0, c = 0; + int[] dr = {0, 1, 0, -1}; + int[] dc = {1, 0, -1, 0}; + int d = 0; + final int VISITED = -999; + + while (ans.size() < m * n) { + ans.add(matrix[r][c]); + matrix[r][c] = VISITED; + + int nr = r + dr[d]; + int nc = c + dc[d]; + if (nr < 0 || nr >= m || nc < 0 || nc >= n || matrix[nr][nc] == VISITED) { + d = (d + 1) % 4; + nr = r + dr[d]; + nc = c + dc[d]; + } + r = nr; + c = nc; + } + + return ans; + } +} diff --git a/valid-parentheses/forest000014.java b/valid-parentheses/forest000014.java new file mode 100644 index 000000000..f0118d8c1 --- /dev/null +++ b/valid-parentheses/forest000014.java @@ -0,0 +1,37 @@ +/* +Time Complexity: O(n) +Space Complexity: O(n) + +유효한 문자열이라면, 인접한 열고 닫는 괄호 쌍을 하나씩 캔슬시켰을 때, 빈 문자열만 남게 된다. +매치되는 쌍이 서로 떨어져있을 수 있기 때문에, 그 안의 유효한 쌍들을 미리 모두 캔슬시킨 뒤에 판단해야 매칭 여부를 쉽게 판단할 수 있는데, 이 과정을 스택을 이용해 구현할 수 있다. +*/ +class Solution { + public boolean isValid(String s) { + Stack st = new Stack<>(); + + if (s.length() % 2 == 1) { + return false; + } + + for (char ch : s.toCharArray()) { + if (ch == '(' || ch == '{' || ch == '[') { + st.push(ch); + } else { + if (st.empty()) + return false; + if (ch == ')') { + if (st.peek() != '(') + return false; + } else if (ch == '}') { + if (st.peek() != '{') + return false; + } else { + if (st.peek() != '[') + return false; + } + st.pop(); + } + } + return st.empty(); + } +}