Skip to content

Commit 111feeb

Browse files
authored
Merge pull request #968 from forest000014/main
[forest000014] Week 8
2 parents 5714288 + c61d74c commit 111feeb

File tree

5 files changed

+375
-0
lines changed

5 files changed

+375
-0
lines changed

clone-graph/forest000014.java

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
# Time Complexity: O(n * e), where e is the maximum number of edges of a node
3+
- 전체 node를 순회하면서, 그 이웃에 해당하는 복제본을 생성해서 복제본끼리 연결해준다.
4+
- 단, 중복 방문을 막기 위해, 복제본이 이미 이웃 복제본을 가지고 있는지 확인한다. 이 과정에서 O(e)만큼의 List 순회를 한다.
5+
6+
# Space Complexity: O(n)
7+
- 전체 Node 만큼의 메모리가 필요하다.
8+
(Space Complexity를 계산하기 애매한 측면이 있네요. 저는 지금까지 출력은 space complexity에 포함하지 않고 계산했었는데, 그 이유는 "어떤 알고리즘을 구현하든 출력은 동일하기 때문"인데요. 이 문제의 경우에 출력은 Node 하나이지만, 실제로는 Node 전체만큼의 메모리를 반드시 생성해야 한다는 특수성이 있습니다. 그래서 "어떻게 구현하든 동일하게 사용해야만 하는 메모리는 Space Complexity에서 배제한다" 라는 논리로만 보자면 O(1)일 것 같고, "출력을 제외한 메모리 사용은 Space Complexity에 포함한다" 라는 논리대로라면 O(n)인 것 같습니다.)
9+
10+
11+
전체 노드를 DFS로 순회하면서 이웃 노드의 복제본을 생성하여 현재 노드의 복제본과 연결을 맺어줍니다.
12+
다만, 중복 방문을 막기 위해, 복제본이 이미 이웃 복제본을 가지고 있는지 확인한다.
13+
또한 순환 참조(cycle 구조)를 막기 위해서, 복제본 노드를 생성시 단순히 new 키워드를 사용하지 않고, 별도의 map을 통해 싱글톤으로 생성한다. (각 노드의 val은 distinct하다는 점을 이용)
14+
15+
16+
// Definition for a Node.
17+
class Node {
18+
public int val;
19+
public List<Node> neighbors;
20+
public Node() {
21+
val = 0;
22+
neighbors = new ArrayList<Node>();
23+
}
24+
public Node(int _val) {
25+
val = _val;
26+
neighbors = new ArrayList<Node>();
27+
}
28+
public Node(int _val, ArrayList<Node> _neighbors) {
29+
val = _val;
30+
neighbors = _neighbors;
31+
}
32+
}
33+
*/
34+
35+
class Solution {
36+
37+
Map<Integer, Node> map = new HashMap<>();
38+
39+
public Node cloneGraph(Node node) {
40+
if (node == null) {
41+
return null;
42+
}
43+
44+
Node newNode = createNode(node.val);
45+
dfs(node);
46+
47+
return newNode;
48+
}
49+
50+
public Node createNode(int val) {
51+
if (!map.containsKey(val)) {
52+
map.put(val, new Node(val));
53+
}
54+
return map.get(val);
55+
}
56+
57+
public void dfs(Node oldNode) {
58+
Node newNode = map.get(oldNode.val);
59+
60+
for (Node oldNeighbor : oldNode.neighbors) {
61+
boolean hasIt = false;
62+
for (Node newNeighbor : newNode.neighbors) {
63+
if (newNeighbor.val == oldNeighbor.val) {
64+
hasIt = true;
65+
break;
66+
}
67+
}
68+
69+
if (!hasIt) {
70+
Node newNeighbor = createNode(oldNeighbor.val);
71+
newNode.neighbors.add(newNeighbor);
72+
newNeighbor.neighbors.add(newNode);
73+
dfs(oldNeighbor, newNeighbor);
74+
}
75+
}
76+
}
77+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
/*
2+
# Time Complexity: O(m * n), where m = text1.length(), n = text2.length()
3+
# Space Complexity: O(m * n)
4+
5+
DP로 접근했습니다.
6+
text1[0..i], text2[0..j]의 LCS의 길이를 lcs[i][j]라고 정의하겠습니다. (0 <= i < m, 0 <= j < n)
7+
lcs[i - 1][j - 1], lcs[i - 1][j], lcs[i][j - 1]을 미리 구해두었다면, lcs[i][j]는 아래처럼 O(1)에 계산할 수 있습니다.
8+
9+
1. text1[i] != text2[j]인 경우
10+
lcs[i - 1][j] 를 기준으로 생각해보면, text1[i] != text2[j]이기 때문에, text1[0..i-1]에 text1[i]를 추가한다 해도, LCS의 길이에는 변화가 없습니다.
11+
마찬가지로 lcs[i][j - 1] 을 기준으로, text2[0..j-1]에 text2[j]를 추가해도, LCS의 길이에는 변화가 없습니다.
12+
따라서 lcs[i][j] = max(lcs[i - 1][j], lcs[i][j - 1]) 로 구할 수 있습니다.
13+
14+
2. text1[i] == text2[j]인 경우
15+
이 경우에는, lcs[i - 1][j - 1]에서 구한 LCS에 1글자가 추가되므로, lcs[i][j] = lcs[i - 1][j - 1] + 1 로 구할 수 있습니다.
16+
17+
3. i = 0 혹은 j = 0인 경우의 예외 로직을 추가하면, LCS의 길이를 구하는 2차원 DP를 구현할 수 있습니다.
18+
19+
*/
20+
21+
class Solution {
22+
public int longestCommonSubsequence(String text1, String text2) {
23+
int m = text1.length();
24+
int n = text2.length();
25+
int[][] lcs = new int[m][n];
26+
27+
for (int i = 0; i < m; i++) {
28+
for (int j = 0; j < n; j++) {
29+
if (text1.charAt(i) == text2.charAt(j)) {
30+
if (i == 0 || j == 0) {
31+
lcs[i][j] = 1;
32+
} else {
33+
lcs[i][j] = lcs[i - 1][j - 1] + 1;
34+
}
35+
} else {
36+
if (i == 0 && j == 0) {
37+
lcs[i][j] = 0;
38+
} else if (i == 0) {
39+
lcs[i][j] = lcs[i][j - 1];
40+
} else if (j == 0) {
41+
lcs[i][j] = lcs[i - 1][j];
42+
} else {
43+
lcs[i][j] = Math.max(lcs[i][j - 1], lcs[i - 1][j]);
44+
}
45+
}
46+
}
47+
}
48+
49+
return lcs[m - 1][n - 1];
50+
}
51+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
Time Complexity: O(n)
3+
Space Complexity: O(1)
4+
5+
투 포인터 방식으로 접근했습니다.
6+
현재 상태에서, 두 포인터 사이의 가장 많은 문자를 제외한 나머지 문자의 개수가 k개 이하면 right를 오른쪽으로 1칸,
7+
k개 초과면 left를 오른쪽으로 1칸씩 이동합니다.
8+
9+
다만, 개인적으로 에너지가 고갈된 상태에서 풀다보니,
10+
현재 상태에서 가장 많은 문자를 카운트하는 방식을 counts[26] 배열을 순회하는 식으로 단순하게 짰습니다.
11+
PQ를 사용하면 조금 더 시간이 개선될 것 같습니다.
12+
*/
13+
class Solution {
14+
public int characterReplacement(String s, int k) {
15+
int[] counts = new int[26];
16+
17+
int l = 0;
18+
int r = 0;
19+
int mostCount = 1;
20+
21+
counts[s.charAt(0) - 'A'] = 1;
22+
int ans = 0;
23+
24+
while (r < s.length()) {
25+
mostCount = 0;
26+
for (int i = 0; i < 26; i++) {
27+
if (counts[i] > mostCount) {
28+
mostCount = counts[i];
29+
}
30+
}
31+
32+
if (r - l + 1 - mostCount <= k) {
33+
if (r - l + 1 > ans) {
34+
ans = r - l + 1;
35+
}
36+
37+
r++;
38+
if (r == s.length()) {
39+
break;
40+
}
41+
counts[s.charAt(r) - 'A']++;
42+
} else {
43+
counts[s.charAt(l) - 'A']--;
44+
l++;
45+
}
46+
}
47+
48+
return ans;
49+
}
50+
}

number-of-1-bits/forest000014.java

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/*
2+
solution 1. bit operation
3+
# Time Complexity: O(1)
4+
- n의 사이즈는 32 bit로 일정
5+
- 2 * 32 회 연산 필요
6+
# Space Complexity: O(1)
7+
8+
bit를 하나씩 센다.
9+
10+
11+
solution 2. bit operation (advanced)
12+
# Time Complexity: O(1)
13+
- 2 * b 회 연산 필요 (b는 1인 bit의 개수)
14+
# Space Complexity: O(1)
15+
16+
n &= (n - 1) 연산을 통해, 마지막 bit를 한번에 하나씩 제거하면서 bit를 센다.
17+
1인 bit의 개수만큼 연산을 하므로, 평균적으로 solution 1보다는 연산 횟수가 적다.
18+
19+
20+
solution 3. 8-bit chunk lookup table
21+
# Time Complexity: O(n)
22+
- n / 8 회 연산 필요
23+
# Space Complexity: O(1)
24+
- 2^8 사이즈의 int 배열 사용
25+
26+
이진수 00000000 ~ 11111111 에 대해서, 각 수에 bit 1이 몇 개 등장하는지 미리 lookup table에 저장해둔다.
27+
그리고 n을 8 bit 단위로 잘라서, loopup table에서 조회하여 누적해준다.
28+
연산 횟수가 n / 8로 줄어든다는 장점이 있으나, lookup table을 미리 계산하거나 런타임에 계산해야 하고, lookup table 사이즈만큼의 메모리를 더 사용해야 한다는 트레이드 오프가 있다.
29+
30+
31+
solution 4. population count 알고리즘
32+
# Time Complexity: O(1)
33+
- 5 회 연산 필요
34+
# Space Complexity: O(1)
35+
36+
각 단계를 진행할 때마다, 2, 4, 8, 16, 32 bit chunk 안의 1 bit의 개수를 센다.
37+
38+
39+
solution 5. 자바 내장 함수 Integer.bitCount() 사용
40+
# Time Complexity: O(1)
41+
# Space Complexity: O(1)
42+
43+
*/
44+
class Solution {
45+
// solution 1
46+
// public int hammingWeight(int n) {
47+
// int ans = 0;
48+
// while (n > 0) {
49+
// ans += (n & 1);
50+
// n >>= 1;
51+
// }
52+
// return ans;
53+
// }
54+
55+
// solution 2
56+
// public int hammingWeight(int n) {
57+
// int ans = 0;
58+
// while (n > 0) {
59+
// n &= (n - 1); // 최하위 1비트를 제거
60+
// ans++;
61+
// }
62+
// return ans;
63+
// }
64+
65+
// solution 3.
66+
// lookup table (8-bit 단위로)
67+
// 이 아이디어는 시간이 부족해서 구현하지 못했습니다.
68+
69+
70+
// solution 4.
71+
// population count 알고리즘
72+
// https://blog.naver.com/jinhan814/222540111549
73+
// http://shumin.co.kr/algorithm-hamming-weight-bit-count/
74+
public int hammingWeight(int n) {
75+
n = (n >> 1 & 0x55555555) + (n & 0x55555555);
76+
n = (n >> 2 & 0x33333333) + (n & 0x33333333);
77+
n = (n >> 4 & 0x0F0F0F0F) + (n & 0x0F0F0F0F);
78+
n = (n >> 8 & 0x00FF00FF) + (n & 0x00FF00FF);
79+
n = (n >> 16 & 0x0000FFFF) + (n & 0x0000FFFF);
80+
return n;
81+
}
82+
83+
// solution 5.
84+
// 자바 내장 함수 사용 O(logn)
85+
// public int hammingWeight(int n) {
86+
// return Integer.bitCount(n);
87+
// }
88+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
# Time Complexity: O(n)
3+
# Space Complexity: O(1)
4+
5+
음...... bit manipulation을 최대한 활용해서 했습니다.
6+
이진법의 덧셈/뺄셈을 손으로 계산한다면, carry/borrow 개념을 활용해서 수행할 텐데, 이 과정을 그대로 코드로 옮겨보았습니다.
7+
++은 increment operator이고, -는 unary minus operator로만 썼으니, 문제의 조건인 +, - operator를 쓰지 말라는 제약사항은 지켰다고 주장하고 싶습니다.
8+
그런데 이렇게 지저분하게 구현하는 것은 출제자의 의도에 부합하지 않는 것 같네요. Dale님의 풀이를 보니 훨씬 간결하던데, 좀 더 공부해봐야 할 것 같습니다.
9+
*/
10+
11+
class Solution {
12+
public int getSum(int a, int b) {
13+
if (a >= 0 && b >= 0) {
14+
return sum(a, b);
15+
} else if (a <= 0 && b <= 0) {
16+
return -sum(-a, -b);
17+
} else if (a < 0) {
18+
if (-a >= b) {
19+
return -subtract(-a, b);
20+
} else {
21+
return subtract(b, -a);
22+
}
23+
} else {
24+
if (a >= -b) {
25+
return subtract(a, -b);
26+
} else {
27+
return -subtract(-b, a);
28+
}
29+
}
30+
}
31+
32+
public int sum(int a, int b) {
33+
int sum = 0;
34+
int carry = 0;
35+
int bit = 0;
36+
int digit = 0;
37+
38+
while (a > 0 || b > 0) {
39+
if (((a & 1) & (b & 1) & (carry & 1)) == 1) {
40+
carry = 1;
41+
bit = 1;
42+
} else if (((a & 1) | (b & 1) | (carry & 1)) == 1) {
43+
if (((a & 1) ^ (b & 1) ^ (carry & 1)) == 0) {
44+
carry = 1;
45+
bit = 0;
46+
} else {
47+
carry = 0;
48+
bit = 1;
49+
}
50+
} else {
51+
carry = 0;
52+
bit = 0;
53+
}
54+
55+
sum |= (bit << digit);
56+
57+
a >>= 1;
58+
b >>= 1;
59+
digit++;
60+
}
61+
62+
if (carry == 1) {
63+
sum |= (1 << digit);
64+
}
65+
66+
return sum;
67+
}
68+
69+
public int subtract(int a, int b) {
70+
int sub = 0;
71+
int borrow = 0;
72+
int bit = 0;
73+
int digit = 0;
74+
75+
while (a > 0) {
76+
if (borrow == 1) {
77+
if ((a & 1) == (b & 1)) {
78+
borrow = 1;
79+
bit = 1;
80+
} else if ((a & 1) == 1) {
81+
borrow = 0;
82+
bit = 0;
83+
} else {
84+
borrow = 1;
85+
bit = 0;
86+
}
87+
} else {
88+
if ((a & 1) == (b & 1)) {
89+
borrow = 0;
90+
bit = 0;
91+
} else if ((a & 1) == 1) {
92+
borrow = 0;
93+
bit = 1;
94+
} else {
95+
borrow = 1;
96+
bit = 1;
97+
}
98+
}
99+
100+
sub |= (bit << digit);
101+
102+
digit++;
103+
a >>= 1;
104+
b >>= 1;
105+
}
106+
107+
return sub;
108+
}
109+
}

0 commit comments

Comments
 (0)