From 6436e261ad867e90b3542d1186b00a82a27593fd Mon Sep 17 00:00:00 2001 From: forest000014 Date: Tue, 28 Jan 2025 20:27:23 +0900 Subject: [PATCH 1/7] DaleStudy#244 Longest Repeating Character Replacement --- .../forest000014.java | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 longest-repeating-character-replacement/forest000014.java diff --git a/longest-repeating-character-replacement/forest000014.java b/longest-repeating-character-replacement/forest000014.java new file mode 100644 index 000000000..0dcf21aaa --- /dev/null +++ b/longest-repeating-character-replacement/forest000014.java @@ -0,0 +1,50 @@ +/* +Time Complexity: O(n) +Space Complexity: O(1) + +투 포인터 방식으로 접근했습니다. +현재 상태에서, 두 포인터 사이의 가장 많은 문자를 제외한 나머지 문자의 개수가 k개 이하면 right를 오른쪽으로 1칸, +k개 초과면 left를 오른쪽으로 1칸씩 이동합니다. + +다만, 개인적으로 에너지가 고갈된 상태에서 풀다보니, +현재 상태에서 가장 많은 문자를 카운트하는 방식을 counts[26] 배열을 순회하는 식으로 단순하게 짰습니다. +PQ를 사용하면 조금 더 시간이 개선될 것 같습니다. +*/ +class Solution { + public int characterReplacement(String s, int k) { + int[] counts = new int[26]; + + int l = 0; + int r = 0; + int mostCount = 1; + + counts[s.charAt(0) - 'A'] = 1; + int ans = 0; + + while (r < s.length()) { + mostCount = 0; + for (int i = 0; i < 26; i++) { + if (counts[i] > mostCount) { + mostCount = counts[i]; + } + } + + if (r - l + 1 - mostCount <= k) { + if (r - l + 1 > ans) { + ans = r - l + 1; + } + + r++; + if (r == s.length()) { + break; + } + counts[s.charAt(r) - 'A']++; + } else { + counts[s.charAt(l) - 'A']--; + l++; + } + } + + return ans; + } +} From d57ce2abb01a730948453483f81dfb4e6d264c28 Mon Sep 17 00:00:00 2001 From: forest000014 Date: Tue, 28 Jan 2025 23:53:12 +0900 Subject: [PATCH 2/7] DaleStudy#232 Number of 1 Bits --- number-of-1-bits/forest000014.java | 88 ++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 number-of-1-bits/forest000014.java diff --git a/number-of-1-bits/forest000014.java b/number-of-1-bits/forest000014.java new file mode 100644 index 000000000..0a232ee93 --- /dev/null +++ b/number-of-1-bits/forest000014.java @@ -0,0 +1,88 @@ +/* +solution 1. bit operation +# Time Complexity: O(1) + - n의 사이즈는 32 bit로 일정 + - 2 * 32 회 연산 필요 +# Space Complexity: O(1) + +bit를 하나씩 센다. + + +solution 2. bit operation (advanced) +# Time Complexity: O(1) + - 2 * b 회 연산 필요 (b는 1인 bit의 개수) +# Space Complexity: O(1) + +n &= (n - 1) 연산을 통해, 마지막 bit를 한번에 하나씩 제거하면서 bit를 센다. +1인 bit의 개수만큼 연산을 하므로, 평균적으로 solution 1보다는 연산 횟수가 적다. + + +solution 3. 8-bit chunk lookup table +# Time Complexity: O(n) + - n / 8 회 연산 필요 +# Space Complexity: O(1) + - 2^8 사이즈의 int 배열 사용 + +이진수 00000000 ~ 11111111 에 대해서, 각 수에 bit 1이 몇 개 등장하는지 미리 lookup table에 저장해둔다. +그리고 n을 8 bit 단위로 잘라서, loopup table에서 조회하여 누적해준다. +연산 횟수가 n / 8로 줄어든다는 장점이 있으나, lookup table을 미리 계산하거나 런타임에 계산해야 하고, lookup table 사이즈만큼의 메모리를 더 사용해야 한다는 트레이드 오프가 있다. + + +solution 4. population count 알고리즘 +# Time Complexity: O(1) + - 5 회 연산 필요 +# Space Complexity: O(1) + +각 단계를 진행할 때마다, 2, 4, 8, 16, 32 bit chunk 안의 1 bit의 개수를 센다. + + +solution 5. 자바 내장 함수 Integer.bitCount() 사용 +# Time Complexity: O(1) +# Space Complexity: O(1) + +*/ +class Solution { + // solution 1 + // public int hammingWeight(int n) { + // int ans = 0; + // while (n > 0) { + // ans += (n & 1); + // n >>= 1; + // } + // return ans; + // } + + // solution 2 + // public int hammingWeight(int n) { + // int ans = 0; + // while (n > 0) { + // n &= (n - 1); // 최하위 1비트를 제거 + // ans++; + // } + // return ans; + // } + + // solution 3. + // lookup table (8-bit 단위로) + // 이 아이디어는 시간이 부족해서 구현하지 못했습니다. + + + // solution 4. + // population count 알고리즘 + // https://blog.naver.com/jinhan814/222540111549 + // http://shumin.co.kr/algorithm-hamming-weight-bit-count/ + public int hammingWeight(int n) { + n = (n >> 1 & 0x55555555) + (n & 0x55555555); + n = (n >> 2 & 0x33333333) + (n & 0x33333333); + n = (n >> 4 & 0x0F0F0F0F) + (n & 0x0F0F0F0F); + n = (n >> 8 & 0x00FF00FF) + (n & 0x00FF00FF); + n = (n >> 16 & 0x0000FFFF) + (n & 0x0000FFFF); + return n; + } + + // solution 5. + // 자바 내장 함수 사용 O(logn) + // public int hammingWeight(int n) { + // return Integer.bitCount(n); + // } +} From 532e7b50e30bcd64a9947c5cd750c508a4404dac Mon Sep 17 00:00:00 2001 From: forest000014 Date: Tue, 28 Jan 2025 23:56:20 +0900 Subject: [PATCH 3/7] DaleStudy#259 Clone Graph --- clone-graph/forest000014.java | 77 +++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 clone-graph/forest000014.java diff --git a/clone-graph/forest000014.java b/clone-graph/forest000014.java new file mode 100644 index 000000000..458f59ed3 --- /dev/null +++ b/clone-graph/forest000014.java @@ -0,0 +1,77 @@ +/* +# Time Complexity: O(n * e), where e is the maximum number of edges of a node + - 전체 node를 순회하면서, 그 이웃에 해당하는 복제본을 생성해서 복제본끼리 연결해준다. + - 단, 중복 방문을 막기 위해, 복제본이 이미 이웃 복제본을 가지고 있는지 확인한다. 이 과정에서 O(e)만큼의 List 순회를 한다. + +# Space Complexity: O(n) + - 전체 Node 만큼의 메모리가 필요하다. +(Space Complexity를 계산하기 애매한 측면이 있네요. 저는 지금까지 출력은 space complexity에 포함하지 않고 계산했었는데, 그 이유는 "어떤 알고리즘을 구현하든 출력은 동일하기 때문"인데요. 이 문제의 경우에 출력은 Node 하나이지만, 실제로는 Node 전체만큼의 메모리를 반드시 생성해야 한다는 특수성이 있습니다. 그래서 "어떻게 구현하든 동일하게 사용해야만 하는 메모리는 Space Complexity에서 배제한다" 라는 논리로만 보자면 O(1)일 것 같고, "출력을 제외한 메모리 사용은 Space Complexity에 포함한다" 라는 논리대로라면 O(n)인 것 같습니다.) + + +전체 노드를 DFS로 순회하면서 이웃 노드의 복제본을 생성하여 현재 노드의 복제본과 연결을 맺어줍니다. +다만, 중복 방문을 막기 위해, 복제본이 이미 이웃 복제본을 가지고 있는지 확인한다. +또한 순환 참조(cycle 구조)를 막기 위해서, 복제본 노드를 생성시 단순히 new 키워드를 사용하지 않고, 별도의 map을 통해 싱글톤으로 생성한다. (각 노드의 val은 distinct하다는 점을 이용) + + +// Definition for a Node. +class Node { + public int val; + public List neighbors; + public Node() { + val = 0; + neighbors = new ArrayList(); + } + public Node(int _val) { + val = _val; + neighbors = new ArrayList(); + } + public Node(int _val, ArrayList _neighbors) { + val = _val; + neighbors = _neighbors; + } +} +*/ + +class Solution { + + Map map = new HashMap<>(); + + public Node cloneGraph(Node node) { + if (node == null) { + return null; + } + + Node newNode = createNode(node.val); + dfs(node, newNode); + + return newNode; + } + + public Node createNode(int val) { + if (!map.containsKey(val)) { + map.put(val, new Node(val)); + } + return map.get(val); + } + + public void dfs(Node oldNode, Node newNode) { + List dfsList = new ArrayList<>(); + + for (Node oldNeighbor : oldNode.neighbors) { + boolean hasIt = false; + for (Node newNeighbor : newNode.neighbors) { + if (newNeighbor.val == oldNeighbor.val) { + hasIt = true; + break; + } + } + + if (!hasIt) { + Node newNeighbor = createNode(oldNeighbor.val); + newNode.neighbors.add(newNeighbor); + newNeighbor.neighbors.add(newNode); + dfs(oldNeighbor, newNeighbor); + } + } + } +} From eee706d8909307537564ba505750e0b65a4fea94 Mon Sep 17 00:00:00 2001 From: forest000014 Date: Wed, 29 Jan 2025 00:22:38 +0900 Subject: [PATCH 4/7] DaleStudy#274 Longest Common Subsequence --- longest-common-subsequence/forest000014.java | 51 ++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 longest-common-subsequence/forest000014.java diff --git a/longest-common-subsequence/forest000014.java b/longest-common-subsequence/forest000014.java new file mode 100644 index 000000000..674619fe7 --- /dev/null +++ b/longest-common-subsequence/forest000014.java @@ -0,0 +1,51 @@ +/* +# Time Complexity: O(m * n), where m = text1.length(), n = text2.length() +# Space Complexity: O(m * n) + +DP로 접근했습니다. +text1[0..i], text2[0..j]의 LCS의 길이를 lcs[i][j]라고 정의하겠습니다. (0 <= i < m, 0 <= j < n) +lcs[i - 1][j - 1], lcs[i - 1][j], lcs[i][j - 1]을 미리 구해두었다면, lcs[i][j]는 아래처럼 O(1)에 계산할 수 있습니다. + +1. text1[i] != text2[j]인 경우 +lcs[i - 1][j] 를 기준으로 생각해보면, text1[i] != text2[j]이기 때문에, text1[0..i-1]에 text1[i]를 추가한다 해도, LCS의 길이에는 변화가 없습니다. +마찬가지로 lcs[i][j - 1] 을 기준으로, text2[0..j-1]에 text2[j]를 추가해도, LCS의 길이에는 변화가 없습니다. +따라서 lcs[i][j] = max(lcs[i - 1][j], lcs[i][j - 1]) 로 구할 수 있습니다. + +2. text1[i] == text2[j]인 경우 +이 경우에는, lcs[i - 1][j - 1]에서 구한 LCS에 1글자가 추가되므로, lcs[i][j] = lcs[i - 1][j - 1] + 1 로 구할 수 있습니다. + +3. i = 0 혹은 j = 0인 경우의 예외 로직을 추가하면, LCS의 길이를 구하는 2차원 DP를 구현할 수 있습니다. + +*/ + +class Solution { + public int longestCommonSubsequence(String text1, String text2) { + int m = text1.length(); + int n = text2.length(); + int[][] lcs = new int[m][n]; + + for (int i = 0; i < m; i++) { + for (int j = 0; j < n; j++) { + if (text1.charAt(i) == text2.charAt(j)) { + if (i == 0 || j == 0) { + lcs[i][j] = 1; + } else { + lcs[i][j] = lcs[i - 1][j - 1] + 1; + } + } else { + if (i == 0 && j == 0) { + lcs[i][j] = 0; + } else if (i == 0) { + lcs[i][j] = lcs[i][j - 1]; + } else if (j == 0) { + lcs[i][j] = lcs[i - 1][j]; + } else { + lcs[i][j] = Math.max(lcs[i][j - 1], lcs[i - 1][j]); + } + } + } + } + + return lcs[m - 1][n - 1]; + } +} From fd60c6a9eef563c3d450effa55981a6a7a5ef8da Mon Sep 17 00:00:00 2001 From: forest000014 Date: Wed, 29 Jan 2025 01:06:41 +0900 Subject: [PATCH 5/7] DaleStudy#284 Sum of Two Integers --- sum-of-two-integers/forest000014.java | 109 ++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 sum-of-two-integers/forest000014.java diff --git a/sum-of-two-integers/forest000014.java b/sum-of-two-integers/forest000014.java new file mode 100644 index 000000000..d9359aac2 --- /dev/null +++ b/sum-of-two-integers/forest000014.java @@ -0,0 +1,109 @@ +/* +# Time Complexity: O(n) +# Space Complexity: O(1) + +음...... bit manipulation을 최대한 활용해서 했습니다. +이진법의 덧셈/뺄셈을 손으로 계산한다면, carry/borrow 개념을 활용해서 수행할 텐데, 이 과정을 그대로 코드로 옮겨보았습니다. +++은 increment operator이고, -는 unary minus operator로만 썼으니, 문제의 조건인 +, - operator를 쓰지 말라는 제약사항은 지켰다고 주장하고 싶습니다. +그런데 이렇게 지저분하게 구현하는 것은 출제자의 의도에 부합하지 않는 것 같네요. Dale님의 풀이를 보니 훨씬 간결하던데, 좀 더 공부해봐야 할 것 같습니다. +*/ + +class Solution { + public int getSum(int a, int b) { + if (a >= 0 && b >= 0) { + return sum(a, b); + } else if (a <= 0 && b <= 0) { + return -sum(-a, -b); + } else if (a < 0) { + if (-a >= b) { + return -subtract(-a, b); + } else { + return subtract(b, -a); + } + } else { + if (a >= -b) { + return subtract(a, -b); + } else { + return -subtract(-b, a); + } + } + } + + public int sum(int a, int b) { + int sum = 0; + int carry = 0; + int bit = 0; + int digit = 0; + + while (a > 0 || b > 0) { + if (((a & 1) & (b & 1) & (carry & 1)) == 1) { + carry = 1; + bit = 1; + } else if (((a & 1) | (b & 1) | (carry & 1)) == 1) { + if (((a & 1) ^ (b & 1) ^ (carry & 1)) == 0) { + carry = 1; + bit = 0; + } else { + carry = 0; + bit = 1; + } + } else { + carry = 0; + bit = 0; + } + + sum |= (bit << digit); + + a >>= 1; + b >>= 1; + digit++; + } + + if (carry == 1) { + sum |= (1 << digit); + } + + return sum; + } + + public int subtract(int a, int b) { + int sub = 0; + int borrow = 0; + int bit = 0; + int digit = 0; + + while (a > 0) { + if (borrow == 1) { + if ((a & 1) == (b & 1)) { + borrow = 1; + bit = 1; + } else if ((a & 1) == 1) { + borrow = 0; + bit = 0; + } else { + borrow = 1; + bit = 0; + } + } else { + if ((a & 1) == (b & 1)) { + borrow = 0; + bit = 0; + } else if ((a & 1) == 1) { + borrow = 0; + bit = 1; + } else { + borrow = 1; + bit = 1; + } + } + + sub |= (bit << digit); + + digit++; + a >>= 1; + b >>= 1; + } + + return sub; + } +} From d2d605463039982ebb4c8e584936493c34015dab Mon Sep 17 00:00:00 2001 From: forest000014 Date: Fri, 31 Jan 2025 23:25:13 +0900 Subject: [PATCH 6/7] =?UTF-8?q?DaleStudy#259=20Clone=20Graph=20-=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clone-graph/forest000014.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/clone-graph/forest000014.java b/clone-graph/forest000014.java index 458f59ed3..9cbc3fce1 100644 --- a/clone-graph/forest000014.java +++ b/clone-graph/forest000014.java @@ -55,8 +55,6 @@ public Node createNode(int val) { } public void dfs(Node oldNode, Node newNode) { - List dfsList = new ArrayList<>(); - for (Node oldNeighbor : oldNode.neighbors) { boolean hasIt = false; for (Node newNeighbor : newNode.neighbors) { From c61d74c919f28738cd05d90c1ee42372ee8d6f71 Mon Sep 17 00:00:00 2001 From: forest000014 Date: Fri, 31 Jan 2025 23:29:17 +0900 Subject: [PATCH 7/7] =?UTF-8?q?DaleStudy#259=20Clone=20Graph=20-=20?= =?UTF-8?q?=ED=94=BC=EB=93=9C=EB=B0=B1=20=EB=B0=98=EC=98=81=20(newNode=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EC=A0=9C=EA=B1=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- clone-graph/forest000014.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clone-graph/forest000014.java b/clone-graph/forest000014.java index 9cbc3fce1..60c0a2b25 100644 --- a/clone-graph/forest000014.java +++ b/clone-graph/forest000014.java @@ -42,7 +42,7 @@ public Node cloneGraph(Node node) { } Node newNode = createNode(node.val); - dfs(node, newNode); + dfs(node); return newNode; } @@ -54,7 +54,9 @@ public Node createNode(int val) { return map.get(val); } - public void dfs(Node oldNode, Node newNode) { + public void dfs(Node oldNode) { + Node newNode = map.get(oldNode.val); + for (Node oldNeighbor : oldNode.neighbors) { boolean hasIt = false; for (Node newNeighbor : newNode.neighbors) {