diff --git a/clone-graph/flynn.cpp b/clone-graph/flynn.cpp new file mode 100644 index 000000000..39aa2df6f --- /dev/null +++ b/clone-graph/flynn.cpp @@ -0,0 +1,67 @@ +/** + * 풀이 + * - BFS와 해시맵을 사용하여 풀이합니다 + * + * Big O + * - N: 주어진 노드의 개수 + * - E: 주어진 노드의 간선의 개수 + * + * - Time complexity: O(E) + * - 한 Node에서 다른 Node로 향하는 모든 edge를 두번씩 탐색합니다 (두 방향으로 연결되어 있기 때문) + * - Space complexity: O(N) + * - 해시맵에 최대 N개의 노드를 저장합니다 + */ + +/* +// Definition for a Node. +class Node { +public: + int val; + vector<Node*> neighbors; + Node() { + val = 0; + neighbors = vector<Node*>(); + } + Node(int _val) { + val = _val; + neighbors = vector<Node*>(); + } + Node(int _val, vector<Node*> _neighbors) { + val = _val; + neighbors = _neighbors; + } +}; +*/ + +class Solution { +public: + Node* cloneGraph(Node* node) { + if (node == nullptr) return nullptr; + + unordered_map<Node*, Node*> node_map; + node_map[node] = new Node(node->val); + + queue<Node*> q; + q.push(node); + + while (!q.empty()) { + Node* p = q.front(); + q.pop(); + + for (Node* neighbor : p->neighbors) { + // 방문한 적이 없는 노드일 경우 + if (node_map.find(neighbor) == node_map.end()) { + // node_map에 새로운 노드를 복제하여 추가 + node_map[neighbor] = new Node(neighbor->val); + + // 큐에 추가 + q.push(neighbor); + } + + node_map[p]->neighbors.push_back(node_map[neighbor]); + } + } + + return node_map[node]; + } +}; diff --git a/longest-common-subsequence/flynn.cpp b/longest-common-subsequence/flynn.cpp new file mode 100644 index 000000000..134f21156 --- /dev/null +++ b/longest-common-subsequence/flynn.cpp @@ -0,0 +1,77 @@ +/** + * 풀이 1 + * - 2차원 DP를 사용하여 풀이합니다 + * DP[i][j]: text1의 i번째 문자까지와 text2의 j번째 문자까지 비교했을 때, 가장 긴 공통 부분 문자열의 길이 + * 즉, text1[0 .. i - 1]와 text2[0 .. j - 1]의 가장 긴 공통 부분 문자열의 길이 + * DP[i][j] = if text1[i - 1] == text2[j - 1] then DP[i - 1][j - 1] + 1 + * else max(DP[i - 1][j], DP[i][j - 1]) + * - 풀이 2로 공간복잡도를 줄일 수 있습니다 + * + * Big O + * - M: text1의 길이 + * - N: text2의 길이 + * + * - Time complexity: O(N * M) + * - Space complexity: O(N * M) + */ + +class Solution { +public: + int longestCommonSubsequence(string text1, string text2) { + size_t m = text1.size(); + size_t n = text2.size(); + + vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0)); + + for (int i = 1; i <= m; ++i) { + for (int j = 1; j <= n; ++j) { + if (text1[i - 1] == text2[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1; + else dp[i][j] = max(dp[i][j - 1], dp[i - 1][j]); + } + } + + return dp[m][n]; + } +}; + +/** + * 풀이 2 + * - 풀이 1의 DP 전개 과정을 보면 우리한테는 DP 배열 두 행만 필요하다는 걸 알 수 있습니다 + * + * Big O + * - M: text1의 길이 + * - N: text2의 길이 + * + * - M >= N이 되도록 고릅니다 + * + * - Time complexity: O(N * M) + * - Space complexity: O(N) + */ + +class Solution { +public: + int longestCommonSubsequence(string text1, string text2) { + size_t m = text1.size(); + size_t n = text2.size(); + + if (m < n) return longestCommonSubsequence(text2, text1); + + vector<int> dp1(n + 1, 0); + vector<int> dp2(n + 1, 0); + + for (int i = 1; i <= m; ++i) { + for (int j = 1; j <= n; ++j) { + if (text1[i - 1] == text2[j - 1]) dp2[j] = dp1[j - 1] + 1; + else dp2[j] = max(dp1[j], dp2[j - 1]); + } + + if (i == m) break; + + dp1.swap(dp2); + dp2.clear(); + dp2.resize(n + 1, 0); + } + + return dp2[n]; + } +}; diff --git a/longest-repeating-character-replacement/flynn.cpp b/longest-repeating-character-replacement/flynn.cpp new file mode 100644 index 000000000..7a11ac80c --- /dev/null +++ b/longest-repeating-character-replacement/flynn.cpp @@ -0,0 +1,73 @@ +/** + * 풀이 + * - 주어진 s로 만들 수 있는 가장 긴 valid substring의 길이를 찾는 문제입니다 + * - valid substring: 최대 k개의 문자를 바꿔서, 모든 문자가 같게 만들 수 있는 substring + * + * - 두 단계로 나누어 풀이에 대해 생각할 수 있습니다 + * + * - 1. 특정 길이의 valid substring을 만들 수 있는지 확인 + * - 함수 bool can_make_valid_substring(string const s, int substr_length, int k) + * - 특정 길이의 substring에 대해서, 등장 빈도가 가장 높은 문자의 빈도수를 저장합니다 (max_freq) + * 만약 해당 substring이 valid substring이라면, max_freq + k >= substr_length 이어야 합니다 + * + * - 2. 최대 길이의 valid substring을 찾는다 + * - 이진탐색을 통해 최대 길이를 찾는다 + * - 함수 int characterReplacement(string s, int k) + * - 주어진 문자열 s로 만들 수 있는 substring의 길이는 1이상 s.size() 이하입니다 + * 이진 탐색의 범위를 위에서 언급한 대로 설정하고, 현재 탐색하려는 길이 (mid)에 대해서 + * can_make_valid_substring 함수를 호출하여 현재 길이로 valid substring을 만들 수 있는지 확인합니다 + * 이진 탐색 알고리즘의 전개 및 결과에 대한 설명은 https://github.com/DaleStudy/leetcode-study/discussions/332를 참고해주세요 :) + * + * Big O + * - N: 주어진 문자열 s의 길이 + * - K: 주어진 정수 k + * + * - Time complexity: O(N * logN) + * - Space complexity: O(1) + */ + +class Solution { +public: + bool can_make_valid_substring(string const s, int substr_length, int k) { + // 문자의 빈도수를 저장하는 배열입니다 + array<int, 26> freq; + freq.fill(0); + + // 최대 빈도수를 저장하는 변수입니다 + int max_freq = 0; + + int window_start = 0; + + for (int window_end = 0; window_end < s.size(); ++window_end) { + ++freq[s[window_end] - 'A']; + + int curr_size = window_end - window_start + 1; + if (curr_size > substr_length) { + --freq[s[window_start] - 'A']; + ++window_start; + } + + max_freq = max(max_freq, freq[s[window_end] - 'A']); + if (max_freq + k >= substr_length) return true; + } + + return false; + } + + int characterReplacement(string s, int k) { + int lo = 1; + int hi = s.size() + 1; + while (lo < hi) { + int mid = lo + (hi - lo) / 2; + + if (can_make_valid_substring(s, mid, k)) lo = mid + 1; + else hi = mid; + } + + // 이진탐색이 종료되면 lo는 최대 길이보다 1 큰 값이 된다. + // EX: hi lo + // T T T T F F F F + // 따라서 최대 길이는 lo - 1이 된다 + return lo - 1; + } +}; diff --git a/merge-two-sorted-lists/flynn.cpp b/merge-two-sorted-lists/flynn.cpp new file mode 100644 index 000000000..f9dbe7a9f --- /dev/null +++ b/merge-two-sorted-lists/flynn.cpp @@ -0,0 +1,48 @@ +/** + * 풀이 + * - 주어진 두 링크드리스트의 각 node를 비교하며 반환할 새 링크드리스트에 추가해줍니다 + * + * Big O + * - N: 주어진 두 링크드리스트 list1, list2의 노드 개수의 총합 + * + * - Time complexity: O(N) + * - Space complexity: O(1) + * - 반환하는 링크드리스트를 복잡도에 포함시키지 않을 시, 공간복잡도는 N에 상관 없이 일정합니다 + */ + +/** + * Definition for singly-linked list. + * struct ListNode { + * int val; + * ListNode *next; + * ListNode() : val(0), next(nullptr) {} + * ListNode(int x) : val(x), next(nullptr) {} + * ListNode(int x, ListNode *next) : val(x), next(next) {} + * }; + */ +class Solution { +public: + ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) { + ListNode* head = new ListNode(); + ListNode* node = head; + + ListNode* p = list1; + ListNode* q = list2; + + while (p != nullptr && q != nullptr) { + if (p->val < q->val) { + node->next = p; + p = p->next; + } else { + node->next = q; + q = q->next; + } + node = node->next; + } + + if (p != nullptr) node->next = p; + if (q != nullptr) node->next = q; + + return head->next; + } +}; diff --git a/sum-of-two-integers/flynn.cpp b/sum-of-two-integers/flynn.cpp new file mode 100644 index 000000000..5d9ddea7e --- /dev/null +++ b/sum-of-two-integers/flynn.cpp @@ -0,0 +1,54 @@ +/** + * 풀이 + * - 두 정수를 한 bit씩 더하는 방식으로 풀이합니다 + * - 두 정수에 대해 이진 덧셈을 진행할 때, 해당 자리수의 bit 두 개와 carry를 비교하여 새로운 carry와 해당 자리수의 덧셈 결과를 얻을 수 있습니다 -> adder 함수 참고 + * - 각 비트에 대해 adder 함수를 호출하여 덧셈을 진행합니다 + * - res의 특정 자리에 덧셈 결과를 넣어주는 것이 까다로웠는데, position이라는 일종의 bitmask를 사용하여 해결할 수 있었습니다 + * - 저는 Nand2Tetris 라는 책/강의를 보면서 이 전에 bitwise 산술 연산기를 구현한 적이 있었는데, 그 경험이 큰 도움이 되었습니다 + * 궁금하신 분들께 coursera 강의 링크를 첨부합니다 (무료) (https://www.coursera.org/learn/build-a-computer) (2강에 나옴) + * + * Big O + * - N: a와 b 중 큰 수의 비트 수 <= 32 (c++ 기준) + * + * - Time complexity: O(N <= 32) = O(1) + * - Space complexity: O(1) + */ + +class Solution { +public: + // returns {carry, result} + // carry와 result를 아래와 같은 bool 연산으로 표현할 수 있다는 사실은 + // x, y, c에 대하여 벤 다이어그램을 그려보면 쉽게 파악할 수 있습니다 + pair<bool, bool> adder(bool x, bool y, bool c) { + return {(x & y) | (x & c) | (y & c), x ^ y ^ c}; + } + + int getSum(int a, int b) { + bool carry = 0; + unsigned int res = 0; + unsigned int position = 1; + + // 32 비트 정수 범위 내에서 덧셈을 진행합니다 + // 32 비트 모두 덧셈을 진행했거나, 더 더할 비트가 없다면 루프를 종료합니다 + while (position && (a || b || carry)) { + bool lsb_a = a & 1; + a >>= 1; + + bool lsb_b = b & 1; + b >>= 1; + + auto [new_carry, new_res] = adder(lsb_a, lsb_b, carry); + + carry = new_carry; + if (new_res) res |= position; + + // position이 unsigned int (32비트)이므로 + // bitwise left shift 연산을 32번 수행하면 0이 됨 + // 1000 0000 0000 0000 0000 0000 0000 0000 => 0000 0000 0000 0000 0000 0000 0000 0000 + // position이 0이 되면 32비트 모두 덧셈을 완료했다는 뜻이므로 loop를 종료함 + position <<= 1; + } + + return (int) res; + } +};