Skip to content

Commit 45f6981

Browse files
authored
Merge pull request #943 from forest000014/main
[Week 7] forest000014
2 parents 5ce2b69 + 233b460 commit 45f6981

File tree

6 files changed

+268
-8
lines changed

6 files changed

+268
-8
lines changed

longest-increasing-subsequence/forest000014.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,20 @@
1111
# solution
1212
이 문제는 DP로 접근했습니다.
1313
14-
LIS(1, x)를 범위 [1, x] 내의 LIS(단, nums[x]를 반드시 포함)의 길이라고 정의하겠습니다. - (1)
15-
1 <= j < i 인 모든 j에 한 LIS(1, j)를 알고 있다면, LIS(1, i)는 아래와 같이 구할 수 있습니다.
16-
LIS(1, i) = max(LIS(1, j)) (단, j는 1 <= j < i 이고, nums[j] < nums[i]) - (2)
14+
LIS(0, x)를 범위 [0, x] 내의 LIS(단, nums[x]를 반드시 포함)의 길이라고 정의하겠습니다. - (1)
15+
1 <= j < i 인 모든 j에 한 LIS(1, j)를 알고 있다면, LIS(0, i)는 아래와 같이 구할 수 있습니다.
16+
LIS(0, i) = max(LIS(0, j)) (단, j는 0 <= j < i 이고, nums[j] < nums[i]) - (2)
1717
18-
max(LIS(1, j))를 구할 때, 모든 j에 대해 탐색한다면, 전체 시간 복잡도는 O(n^2)가 되기 때문에, 시간 복잡도를 줄일 필요가 있습니다.
18+
max(LIS(0, j))를 구할 때, 모든 j에 대해 탐색한다면, 전체 시간 복잡도는 O(n^2)가 되기 때문에, 시간 복잡도를 줄일 필요가 있습니다.
1919
이 탐색 과정을 줄이기 위해, 아래의 사고 과정을 거쳤습니다.
2020
2121
어떤 범위 내의 가장 큰 값을 O(logn) 시간에 구하기 위한 자료구조로, 인덱스 트리(혹은 세그먼트 트리)를 사용합니다.
22-
(이 인덱스 트리의 x번째 leaf 노드에는 LIS(1, x) 값을 저장하고, internal 노드에는 자식 노드들 중 가장 큰 값을 저장합니다.)
22+
(이 인덱스 트리의 x번째 leaf 노드에는 LIS(0, x) 값을 저장하고, internal 노드에는 자식 노드들 중 가장 큰 값을 저장합니다.)
2323
2424
다만, 단순히 해당 범위 내의 가장 큰 값을 구하는 것만으로는 부족하고, nums[j] < nums[i]인 j만을 후보로 삼아야 할 텐데요,
25-
그러기 위해서, 인덱스 트리에 모든 leaf 노드를 미리 삽입해두는 것이 아니라 아래처럼 순차적으로 max(LIS(1, i))의 계산과 삽입을 번갈아 수행합니다.
26-
nums[i]의 크기가 작은 것부터 순서대로, "max(LIS(1, j))를 계산하고, leaf를 하나 삽입"하는 과정을 반복합니다.
27-
nums[i]보다 더 큰 값은 아직 인덱스 트리에 삽입되지 않은 상태이기 때문에, 인덱스 트리에서 구간 [1, i-1]의 최대값을 조회하면 nums[j] < num[i]인 j에 대해서만 최대값을 찾게 되므로,
25+
그러기 위해서, 인덱스 트리에 모든 leaf 노드를 미리 삽입해두는 것이 아니라 아래처럼 순차적으로 max(LIS(0, i))의 계산과 삽입을 번갈아 수행합니다.
26+
nums[i]의 크기가 작은 것부터 순서대로, "max(LIS(0, j))를 계산하고, leaf를 하나 삽입"하는 과정을 반복합니다.
27+
nums[i]보다 더 큰 값은 아직 인덱스 트리에 삽입되지 않은 상태이기 때문에, 인덱스 트리에서 구간 [0, i-1]의 최대값을 조회하면 nums[j] < num[i]인 j에 대해서만 최대값을 찾게 되므로,
2828
(2)번 과정을 O(logn) 시간에 구할 수 있습니다.
2929
따라서 전체 시간 복잡도는 O(nlogn)이 됩니다.
3030
*/
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Time Complexity: O(n)
3+
Space Complexity: O(c)
4+
(c는 사용되는 모든 character의 가짓수)
5+
6+
solution (two pointers)
7+
8+
begin, end 포인터를 각각 1에 두고 시작한다.
9+
end 포인터를 1씩 증가하면서 탐색하다가, 현재 window 내에 이미 존재하는 문자가 또 추가된다면, 그 문자가 window에서 사라질 때까지 begin을 증가시킨다.
10+
11+
(1) end++을 하는 도중의 모든 end에 대해서는, 또 다른 begin을 찾을 필요성은 없는가?
12+
- 현재 begin보다 더 왼쪽의 begin : 현재의 begin은, window 내에 중복 문자가 없게끔 하는 leftmost 인덱스이다. 따라서, 더 작은 begin은 중복이 있을 것이므로, 탐색할 필요가 없다.
13+
- 현재 begin보다 더 오른쪽의 begin : 더 짧은 길이는 탐색할 필요가 없다.
14+
15+
(2) begin++을 하는 도중의 모든 begin에 대해서는, 또 다른 end를 찾을 필요성은 없는가?
16+
- 현재 end보다 더 왼쪽의 end : 더 짧은 길이는 탐색할 필요가 없다.
17+
- 현재 end보다 더 오른쪽의 end : 중복된 문자가 있는 구간은 LSWRC가 될 수 없으므로, 탐색할 필요가 없다.
18+
*/
19+
class Solution {
20+
public int lengthOfLongestSubstring(String s) {
21+
Set<Character> set = new HashSet<>();
22+
int begin = 0, end = 0;
23+
24+
int ans = 0;
25+
while (end < s.length()) {
26+
if (set.contains(s.charAt(end))) {
27+
while (begin < end && s.charAt(begin) != s.charAt(end)) {
28+
set.remove(s.charAt(begin++));
29+
}
30+
set.remove(s.charAt(begin++));
31+
} else {
32+
set.add(s.charAt(end++));
33+
ans = Math.max(ans, end - begin);
34+
}
35+
}
36+
37+
return ans;
38+
}
39+
}

number-of-islands/forest000014.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
# Time Complexity: O(m * n)
3+
모든 격자를 최대 2번씩(2중 for loop, dfs 호출) 방문
4+
5+
# Space Complexity: O(m * n)
6+
최악의 경우, 모든 격자가 '1'인 경우에 m * n회 dfs() 재귀 호출이 이뤄진다. 각 콜 스택에서의 파라미터와 지역변수가 상수개 필요하므로, O(m * n)
7+
*/
8+
class Solution {
9+
public int numIslands(char[][] grid) {
10+
int m = grid.length;
11+
int n = grid[0].length;
12+
int[] dr = {-1, 0, 1, 0};
13+
int[] dc = {0, 1, 0, -1};
14+
int ans = 0;
15+
for (int i = 0; i < m; i++) {
16+
for (int j = 0; j < n; j++) {
17+
if (grid[i][j] != '1') {
18+
continue;
19+
}
20+
dfs(grid, i, j, dr, dc);
21+
ans++;
22+
}
23+
}
24+
return ans;
25+
}
26+
27+
private void dfs(char[][] grid, int r, int c, int[] dr, int[] dc) {
28+
grid[r][c] = '2'; // mark as visited
29+
30+
for (int i = 0; i < 4; i++) {
31+
int nr = r + dr[i];
32+
int nc = c + dc[i];
33+
if (nr < 0 || nr >= grid.length || nc < 0 || nc >= grid[0].length
34+
|| grid[nr][nc] != '1') {
35+
continue;
36+
}
37+
dfs(grid, nr, nc, dr, dc);
38+
}
39+
}
40+
}

reverse-linked-list/forest000014.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
Time Complexity: O(n)
3+
Space Complexity: O(1)
4+
5+
head에서부터 하나씩 next를 탐색하면서, 연결을 반대로 맺어준다.
6+
*/
7+
8+
/**
9+
* Definition for singly-linked list.
10+
* public class ListNode {
11+
* int val;
12+
* ListNode next;
13+
* ListNode() {}
14+
* ListNode(int val) { this.val = val; }
15+
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
16+
* }
17+
*/
18+
class Solution {
19+
public ListNode reverseList(ListNode head) {
20+
if (head == null) {
21+
return null;
22+
}
23+
24+
ListNode curr = head;
25+
ListNode next = head.next;
26+
27+
head.next = null; // 마지막 노드가 될 노드의 next는 null로 세팅
28+
while (next != null) {
29+
ListNode nnext = next.next; // 연결을 끊기 전에, 그 다음 노드를 미리 nnext로 참조해둔다.
30+
next.next = curr; // 연결을 반대로 맺어준다.
31+
curr = next;
32+
next = nnext;
33+
}
34+
35+
return curr;
36+
}
37+
}

set-matrix-zeroes/forest000014.java

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*
2+
solution 1. 재귀 호출
3+
Time Complexity: O(m * n * (m + n))
4+
Space Complexity: O(m * n)
5+
처음에는 공간 복잡도를 O(1)이라고 생각했으나, 검색해보니 함수 호출 스택도 공간 복잡도 계산에 포함시켜야만 한다. 따라서 이 방법은 공간 복잡도 제한을 만족시키지 못한다.
6+
(참고 : https://en.wikipedia.org/wiki/In-place_algorithm),
7+
8+
9+
solution 2. bit manipulation
10+
11+
long 변수를 선언해서, 각 bit에 x번째 row(혹은 col)를 0으로 바꿀지 여부를 기록한다.
12+
(m + n) / 64 개의 변수를 써서 가능하긴 하지만, 64라는 factor가 다소 클 뿐, 결국 공간 복잡도는 O(m + n).
13+
14+
15+
solution 3. matrix 내에 안 쓰이는 값 찾기 (probabilistic 접근)
16+
int 범위 내에서, 쓰이는 값보다는 안 쓰이는 값의 갯수가 압도적으로 많다.(1 - (200 * 200 / 2^32) = 0.99999+)
17+
18+
matrix 내에 안 쓰이는 수를 찾을 때까지 int 범위 내의 랜덤하게 뽑는 행위를 10번만 반복해도,
19+
O(m * n) 시간에 상당히 높은 확률로 안 쓰이는 값을 찾을 수 있다.
20+
(10번 이내에 찾지 못할 확률은 10^(-50) 정도.)
21+
이렇게 찾은 값을 x라고 하자. matrix의 모든 원소를 순회하며, 0인 원소가 있다면 같은 행/열에 존재하는 모든 원소(또다른 0은 제외)를 x로 바꾼 뒤에, 마지막에 한번에 모든 x를 0으로 바꾸는 식으로 풀 수 있다.
22+
23+
그러나 이 접근법의 확률은 문제의 제한 조건 m, n 범위 하에서 계산한 것이라는 한계가 있다.
24+
m, n이 꽤나 커진다면 랜덤 추출로 안 쓰이는 값을 찾을 확률이 낮아지고, 극단적으로 m * n 이 2^32 이상이 되면, 쓸 수 없는 방법이기도 하다.
25+
26+
27+
solution 4. in-place marking
28+
(AlgoDale 풀이를 참고함)
29+
Time Complexity: O(m * n)
30+
Space Complexity: O(1)
31+
32+
*/
33+
class Solution {
34+
35+
// solution 4. in-place marking
36+
public void setZeroes(int[][] matrix) {
37+
int m = matrix.length;
38+
int n = matrix[0].length;
39+
40+
boolean should0thColumnBeZero = false;
41+
for (int i = 0; i < m; i++) {
42+
if (matrix[i][0] == 0) {
43+
should0thColumnBeZero = true;
44+
}
45+
}
46+
47+
for (int i = 0; i < m; i++) {
48+
for (int j = 1; j < n; j++) {
49+
if (matrix[i][j] == 0) {
50+
matrix[i][0] = matrix[0][j] = 0;
51+
}
52+
}
53+
}
54+
55+
for (int i = 1; i < m; i++) {
56+
if (matrix[i][0] == 0) {
57+
for (int j = 1; j < n; j++) {
58+
matrix[i][j] = 0;
59+
}
60+
}
61+
}
62+
for (int i = 1; i < n; i++) {
63+
if (matrix[0][i] == 0) {
64+
for (int j = 0; j < m; j++) {
65+
matrix[j][i] = 0;
66+
}
67+
}
68+
}
69+
if (matrix[0][0] == 0) {
70+
for (int i = 0; i < n; i++) {
71+
matrix[0][i] = 0;
72+
}
73+
}
74+
if (should0thColumnBeZero) {
75+
for (int i = 0; i < m; i++) {
76+
matrix[i][0] = 0;
77+
}
78+
}
79+
}
80+
81+
/* solution 1. 재귀 호출
82+
public void setZeroes(int[][] matrix) {
83+
dfs(matrix, 0, 0);
84+
}
85+
86+
public void dfs(int[][] matrix, int sr, int sc) {
87+
int m = matrix.length;
88+
int n = matrix[0].length;
89+
for (int r = sr; r < m; r++) {
90+
boolean found = false;
91+
for (int c = (r == sr) ? sc : 0; c < n; c++) {
92+
if (matrix[r][c] != 0) {
93+
continue;
94+
}
95+
96+
int nr = (c == n) ? (r + 1) : r;
97+
int nc = (c == n) ? 0 : c + 1;
98+
dfs(matrix, nr, nc);
99+
setRowAndColumnZeroes(matrix, r, c);
100+
101+
found = true;
102+
break;
103+
}
104+
if (found) {
105+
break;
106+
}
107+
}
108+
}
109+
110+
public void setRowAndColumnZeroes(int[][] matrix, int r, int c) {
111+
int m = matrix.length;
112+
int n = matrix[0].length;
113+
for (int i = 0; i < n; i++) {
114+
matrix[r][i] = 0;
115+
}
116+
for (int i = 0; i < m; i++) {
117+
matrix[i][c] = 0;
118+
}
119+
}
120+
*/
121+
}

unique-paths/forest000014.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
Time Complexity: O(m * n)
3+
Space Complexity: O(n)
4+
*/
5+
class Solution {
6+
public int uniquePaths(int m, int n) {
7+
int[] dp = new int[n];
8+
9+
for (int i = 0; i < n; i++) {
10+
dp[i] = 1;
11+
}
12+
13+
for (int i = 1; i < m; i++) {
14+
int prev = dp[0];
15+
for (int j = 1; j < n; j++) {
16+
dp[j] += prev;
17+
prev = dp[j];
18+
}
19+
}
20+
21+
return dp[n - 1];
22+
}
23+
}

0 commit comments

Comments
 (0)