diff --git a/longest-increasing-subsequence/forest000014.java b/longest-increasing-subsequence/forest000014.java index f44ccdbe6..9d6c1c446 100644 --- a/longest-increasing-subsequence/forest000014.java +++ b/longest-increasing-subsequence/forest000014.java @@ -11,20 +11,20 @@ # solution 이 문제는 DP로 접근했습니다. -LIS(1, x)를 범위 [1, x] 내의 LIS(단, nums[x]를 반드시 포함)의 길이라고 정의하겠습니다. - (1) -1 <= j < i 인 모든 j에 한 LIS(1, j)를 알고 있다면, LIS(1, i)는 아래와 같이 구할 수 있습니다. -LIS(1, i) = max(LIS(1, j)) (단, j는 1 <= j < i 이고, nums[j] < nums[i]) - (2) +LIS(0, x)를 범위 [0, x] 내의 LIS(단, nums[x]를 반드시 포함)의 길이라고 정의하겠습니다. - (1) +1 <= j < i 인 모든 j에 한 LIS(1, j)를 알고 있다면, LIS(0, i)는 아래와 같이 구할 수 있습니다. +LIS(0, i) = max(LIS(0, j)) (단, j는 0 <= j < i 이고, nums[j] < nums[i]) - (2) -max(LIS(1, j))를 구할 때, 모든 j에 대해 탐색한다면, 전체 시간 복잡도는 O(n^2)가 되기 때문에, 시간 복잡도를 줄일 필요가 있습니다. +max(LIS(0, j))를 구할 때, 모든 j에 대해 탐색한다면, 전체 시간 복잡도는 O(n^2)가 되기 때문에, 시간 복잡도를 줄일 필요가 있습니다. 이 탐색 과정을 줄이기 위해, 아래의 사고 과정을 거쳤습니다. 어떤 범위 내의 가장 큰 값을 O(logn) 시간에 구하기 위한 자료구조로, 인덱스 트리(혹은 세그먼트 트리)를 사용합니다. -(이 인덱스 트리의 x번째 leaf 노드에는 LIS(1, x) 값을 저장하고, internal 노드에는 자식 노드들 중 가장 큰 값을 저장합니다.) +(이 인덱스 트리의 x번째 leaf 노드에는 LIS(0, x) 값을 저장하고, internal 노드에는 자식 노드들 중 가장 큰 값을 저장합니다.) 다만, 단순히 해당 범위 내의 가장 큰 값을 구하는 것만으로는 부족하고, nums[j] < nums[i]인 j만을 후보로 삼아야 할 텐데요, -그러기 위해서, 인덱스 트리에 모든 leaf 노드를 미리 삽입해두는 것이 아니라 아래처럼 순차적으로 max(LIS(1, i))의 계산과 삽입을 번갈아 수행합니다. -nums[i]의 크기가 작은 것부터 순서대로, "max(LIS(1, j))를 계산하고, leaf를 하나 삽입"하는 과정을 반복합니다. -nums[i]보다 더 큰 값은 아직 인덱스 트리에 삽입되지 않은 상태이기 때문에, 인덱스 트리에서 구간 [1, i-1]의 최대값을 조회하면 nums[j] < num[i]인 j에 대해서만 최대값을 찾게 되므로, +그러기 위해서, 인덱스 트리에 모든 leaf 노드를 미리 삽입해두는 것이 아니라 아래처럼 순차적으로 max(LIS(0, i))의 계산과 삽입을 번갈아 수행합니다. +nums[i]의 크기가 작은 것부터 순서대로, "max(LIS(0, j))를 계산하고, leaf를 하나 삽입"하는 과정을 반복합니다. +nums[i]보다 더 큰 값은 아직 인덱스 트리에 삽입되지 않은 상태이기 때문에, 인덱스 트리에서 구간 [0, i-1]의 최대값을 조회하면 nums[j] < num[i]인 j에 대해서만 최대값을 찾게 되므로, (2)번 과정을 O(logn) 시간에 구할 수 있습니다. 따라서 전체 시간 복잡도는 O(nlogn)이 됩니다. */ diff --git a/longest-substring-without-repeating-characters/forest000014.java b/longest-substring-without-repeating-characters/forest000014.java new file mode 100644 index 000000000..355804087 --- /dev/null +++ b/longest-substring-without-repeating-characters/forest000014.java @@ -0,0 +1,39 @@ +/* +Time Complexity: O(n) +Space Complexity: O(c) +(c는 사용되는 모든 character의 가짓수) + +solution (two pointers) + +begin, end 포인터를 각각 1에 두고 시작한다. +end 포인터를 1씩 증가하면서 탐색하다가, 현재 window 내에 이미 존재하는 문자가 또 추가된다면, 그 문자가 window에서 사라질 때까지 begin을 증가시킨다. + +(1) end++을 하는 도중의 모든 end에 대해서는, 또 다른 begin을 찾을 필요성은 없는가? + - 현재 begin보다 더 왼쪽의 begin : 현재의 begin은, window 내에 중복 문자가 없게끔 하는 leftmost 인덱스이다. 따라서, 더 작은 begin은 중복이 있을 것이므로, 탐색할 필요가 없다. + - 현재 begin보다 더 오른쪽의 begin : 더 짧은 길이는 탐색할 필요가 없다. + +(2) begin++을 하는 도중의 모든 begin에 대해서는, 또 다른 end를 찾을 필요성은 없는가? + - 현재 end보다 더 왼쪽의 end : 더 짧은 길이는 탐색할 필요가 없다. + - 현재 end보다 더 오른쪽의 end : 중복된 문자가 있는 구간은 LSWRC가 될 수 없으므로, 탐색할 필요가 없다. +*/ +class Solution { + public int lengthOfLongestSubstring(String s) { + Set set = new HashSet<>(); + int begin = 0, end = 0; + + int ans = 0; + while (end < s.length()) { + if (set.contains(s.charAt(end))) { + while (begin < end && s.charAt(begin) != s.charAt(end)) { + set.remove(s.charAt(begin++)); + } + set.remove(s.charAt(begin++)); + } else { + set.add(s.charAt(end++)); + ans = Math.max(ans, end - begin); + } + } + + return ans; + } +} diff --git a/number-of-islands/forest000014.java b/number-of-islands/forest000014.java new file mode 100644 index 000000000..db9dbafd1 --- /dev/null +++ b/number-of-islands/forest000014.java @@ -0,0 +1,40 @@ +/* +# Time Complexity: O(m * n) +모든 격자를 최대 2번씩(2중 for loop, dfs 호출) 방문 + +# Space Complexity: O(m * n) +최악의 경우, 모든 격자가 '1'인 경우에 m * n회 dfs() 재귀 호출이 이뤄진다. 각 콜 스택에서의 파라미터와 지역변수가 상수개 필요하므로, O(m * n) +*/ +class Solution { + public int numIslands(char[][] grid) { + int m = grid.length; + int n = grid[0].length; + int[] dr = {-1, 0, 1, 0}; + int[] dc = {0, 1, 0, -1}; + int ans = 0; + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (grid[i][j] != '1') { + continue; + } + dfs(grid, i, j, dr, dc); + ans++; + } + } + return ans; + } + + private void dfs(char[][] grid, int r, int c, int[] dr, int[] dc) { + grid[r][c] = '2'; // mark as visited + + for (int i = 0; i < 4; i++) { + int nr = r + dr[i]; + int nc = c + dc[i]; + if (nr < 0 || nr >= grid.length || nc < 0 || nc >= grid[0].length + || grid[nr][nc] != '1') { + continue; + } + dfs(grid, nr, nc, dr, dc); + } + } +} diff --git a/reverse-linked-list/forest000014.java b/reverse-linked-list/forest000014.java new file mode 100644 index 000000000..5f308f285 --- /dev/null +++ b/reverse-linked-list/forest000014.java @@ -0,0 +1,37 @@ +/* +Time Complexity: O(n) +Space Complexity: O(1) + +head에서부터 하나씩 next를 탐색하면서, 연결을 반대로 맺어준다. +*/ + +/** + * 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 reverseList(ListNode head) { + if (head == null) { + return null; + } + + ListNode curr = head; + ListNode next = head.next; + + head.next = null; // 마지막 노드가 될 노드의 next는 null로 세팅 + while (next != null) { + ListNode nnext = next.next; // 연결을 끊기 전에, 그 다음 노드를 미리 nnext로 참조해둔다. + next.next = curr; // 연결을 반대로 맺어준다. + curr = next; + next = nnext; + } + + return curr; + } +} diff --git a/set-matrix-zeroes/forest000014.java b/set-matrix-zeroes/forest000014.java new file mode 100644 index 000000000..5971e93de --- /dev/null +++ b/set-matrix-zeroes/forest000014.java @@ -0,0 +1,121 @@ +/* +solution 1. 재귀 호출 +Time Complexity: O(m * n * (m + n)) +Space Complexity: O(m * n) +처음에는 공간 복잡도를 O(1)이라고 생각했으나, 검색해보니 함수 호출 스택도 공간 복잡도 계산에 포함시켜야만 한다. 따라서 이 방법은 공간 복잡도 제한을 만족시키지 못한다. +(참고 : https://en.wikipedia.org/wiki/In-place_algorithm), + + +solution 2. bit manipulation + +long 변수를 선언해서, 각 bit에 x번째 row(혹은 col)를 0으로 바꿀지 여부를 기록한다. +(m + n) / 64 개의 변수를 써서 가능하긴 하지만, 64라는 factor가 다소 클 뿐, 결국 공간 복잡도는 O(m + n). + + +solution 3. matrix 내에 안 쓰이는 값 찾기 (probabilistic 접근) +int 범위 내에서, 쓰이는 값보다는 안 쓰이는 값의 갯수가 압도적으로 많다.(1 - (200 * 200 / 2^32) = 0.99999+) + +matrix 내에 안 쓰이는 수를 찾을 때까지 int 범위 내의 랜덤하게 뽑는 행위를 10번만 반복해도, +O(m * n) 시간에 상당히 높은 확률로 안 쓰이는 값을 찾을 수 있다. +(10번 이내에 찾지 못할 확률은 10^(-50) 정도.) +이렇게 찾은 값을 x라고 하자. matrix의 모든 원소를 순회하며, 0인 원소가 있다면 같은 행/열에 존재하는 모든 원소(또다른 0은 제외)를 x로 바꾼 뒤에, 마지막에 한번에 모든 x를 0으로 바꾸는 식으로 풀 수 있다. + +그러나 이 접근법의 확률은 문제의 제한 조건 m, n 범위 하에서 계산한 것이라는 한계가 있다. +m, n이 꽤나 커진다면 랜덤 추출로 안 쓰이는 값을 찾을 확률이 낮아지고, 극단적으로 m * n 이 2^32 이상이 되면, 쓸 수 없는 방법이기도 하다. + + +solution 4. in-place marking +(AlgoDale 풀이를 참고함) +Time Complexity: O(m * n) +Space Complexity: O(1) + +*/ +class Solution { + + // solution 4. in-place marking + public void setZeroes(int[][] matrix) { + int m = matrix.length; + int n = matrix[0].length; + + boolean should0thColumnBeZero = false; + for (int i = 0; i < m; i++) { + if (matrix[i][0] == 0) { + should0thColumnBeZero = true; + } + } + + for (int i = 0; i < m; i++) { + for (int j = 1; j < n; j++) { + if (matrix[i][j] == 0) { + matrix[i][0] = matrix[0][j] = 0; + } + } + } + + for (int i = 1; i < m; i++) { + if (matrix[i][0] == 0) { + for (int j = 1; j < n; j++) { + matrix[i][j] = 0; + } + } + } + for (int i = 1; i < n; i++) { + if (matrix[0][i] == 0) { + for (int j = 0; j < m; j++) { + matrix[j][i] = 0; + } + } + } + if (matrix[0][0] == 0) { + for (int i = 0; i < n; i++) { + matrix[0][i] = 0; + } + } + if (should0thColumnBeZero) { + for (int i = 0; i < m; i++) { + matrix[i][0] = 0; + } + } + } + + /* solution 1. 재귀 호출 + public void setZeroes(int[][] matrix) { + dfs(matrix, 0, 0); + } + + public void dfs(int[][] matrix, int sr, int sc) { + int m = matrix.length; + int n = matrix[0].length; + for (int r = sr; r < m; r++) { + boolean found = false; + for (int c = (r == sr) ? sc : 0; c < n; c++) { + if (matrix[r][c] != 0) { + continue; + } + + int nr = (c == n) ? (r + 1) : r; + int nc = (c == n) ? 0 : c + 1; + dfs(matrix, nr, nc); + setRowAndColumnZeroes(matrix, r, c); + + found = true; + break; + } + if (found) { + break; + } + } + } + + public void setRowAndColumnZeroes(int[][] matrix, int r, int c) { + int m = matrix.length; + int n = matrix[0].length; + for (int i = 0; i < n; i++) { + matrix[r][i] = 0; + } + for (int i = 0; i < m; i++) { + matrix[i][c] = 0; + } + } + */ +} diff --git a/unique-paths/forest000014.java b/unique-paths/forest000014.java new file mode 100644 index 000000000..b6ae4616d --- /dev/null +++ b/unique-paths/forest000014.java @@ -0,0 +1,23 @@ +/* +Time Complexity: O(m * n) +Space Complexity: O(n) +*/ +class Solution { + public int uniquePaths(int m, int n) { + int[] dp = new int[n]; + + for (int i = 0; i < n; i++) { + dp[i] = 1; + } + + for (int i = 1; i < m; i++) { + int prev = dp[0]; + for (int j = 1; j < n; j++) { + dp[j] += prev; + prev = dp[j]; + } + } + + return dp[n - 1]; + } +}