diff --git a/contains-duplicate/changhoon-sung.cpp b/contains-duplicate/changhoon-sung.cpp new file mode 100644 index 000000000..77a851fc8 --- /dev/null +++ b/contains-duplicate/changhoon-sung.cpp @@ -0,0 +1,33 @@ +/* +contains-duplicate +요구사항: 주어진 입력에서 중복된 원소가 존재하는지 여부를 참/거짓으로 반환. +접근 1: 가장 단순한 방법은 다른 vector input_list를 두고, 입력 반복마다 전체 스캔해서 중복 여부를 체크하는 것입니다. + input_list의 스캔 비용 O(N)을 모든 입력에 대해 반복하므로 총 시간 복잡도는 O(N^2)입니다. + 공간 복잡도는 입력과 같으므로 O(N)입니다. +접근 2: 원소의 존재 여부를 더 빠르게 체크하는 방법은 set을 사용하는 것입니다. + set은 중복 원소를 검출하는데 효과적인 자료구조로, 내부 구현에 따라 비용과 특성이 다릅니다. + - C++에서 ordered_{set, map}은 정렬을 유지하는 search tree를 사용하므로 쿼리와 삽입, 삭제 비용이 평균 O(log(N))입니다. + search tree의 구현에 따라 최악의 경우 쿼리 비용이 O(N)까지 증가할 수 있습니다. + - C++에서 unordered_{set, map}은 해시 및 버킷 구조를 사용합니다. 이 경우, 평균적인 쿼리, 삽입, 삭제 비용은 O(1)입니다. + 우리의 요구사항은 입력에 대한 별도의 정보를 저장할 필요가 없으므로 unordered_set으로 중복 검사를 수행하는 것이 비용 효율적입니다. + 최악의 경우 모든 입력을 확인해야 합니다. N개의 입력에 대해 각각 쿼리와 입력을 수행하므로, 총 시간 복잡도는 O(N), 공간 복잡도도 입력만큼인 O(N)입니다. +*/ + +#include +#include + +class Solution { + public: + bool containsDuplicate(std::vector& nums) { + std::unordered_set s; + + for (auto it = nums.begin(); it != nums.end(); it++) { + int x = *it; + if (s.find(x) != s.end()) + return true; + else + s.insert(x); + } + return false; + } +}; diff --git a/longest-consecutive-sequence/changhoon-sung.cpp b/longest-consecutive-sequence/changhoon-sung.cpp new file mode 100644 index 000000000..4bcbfb837 --- /dev/null +++ b/longest-consecutive-sequence/changhoon-sung.cpp @@ -0,0 +1,83 @@ +/* +longest-consecutive-sequence +요구사항: 주어진 비정렬 배열에서 가장 긴 연속 수열의 길이를 O(N)에 구하라. +접근 1: 가장 단순한 방법은 정렬 후 스캔하여 가장 긴 연속 구간을 찾는 것이다. + 정렬 비용 O(NlogN)에 스캔 O(N)으로 총 O(NlogN)이다. 비용 초과. +접근 2: 정렬하지 않고, 인접한 연속 원소 수열을 '하나의 덩어리'로 취급하기 위해 union-find할 수 있다. + 중복 원소는 무시한다. + 입력 x에 대해 x-1의 유니온을 찾는다. + 그러한 유니온이 존재하면 x를 추가한다. 그 이후 x+1 유니온이 존재하면 통합한다. + 그러한 유니온이 존재하지 않으면 x+1 유니온을 찾는다. + 그러한 유니온이 존재하면 x를 추가한다. + 인접한 앞 뒤 유니온이 존재하지 않으면 입력을 새로운 root union으로 초기화한다. + + 비용: + 경로 압축과 높이 최소화 최적화를 적용하면, find 비용은 O(α(N))으로 최대 입력 N=10^5에 대해서 한 자리 상수만큼 충분히 낮다. + 유니온의 통합 비용은 O(1)이다. + N개의 입력에 대해 O(1) 연산을 상수번 수행하므로 유니온 빌딩 시간 복잡도는 O(N)이다. + 사이즈 리스트를 스캔하여 가장 큰 유니온의 크기를 구한다. 이는 O(K) where K <= N이다. + 따라서 총 시간 복잡도는 O(N)이다. + 공간 복잡도도 입력 범위의 상수배이므로 O(N)이다. +*/ + +#include +#include + +#define max(a, b) ((a) > (b) ? (a) : (b)) + +class Solution { + public: + std::unordered_map parent; + std::unordered_map size; + + int find(int x) { + if (x == parent[x]) return x; + return parent[x] = find(parent[x]); // path compression + } + void union_sets(int x, int y) { + int px = find(x); + int py = find(y); + + if (px == py) return; + + // min height optimization + if (size[px] > size[py]) { + size[px] += size[py]; + parent[py] = px; + } else { + size[py] += size[px]; + parent[px] = py; + } + } + int longestConsecutive(std::vector& nums) { + // build union + for (auto it = nums.begin(); it != nums.end(); it++) { + int x = *it; + + if (parent.find(x) != parent.end()) + continue; + + if (parent.find(x - 1) != parent.end()) { + parent[x] = find(x - 1); + size[parent[x]]++; + if (parent.find(x + 1) != parent.end()) { + union_sets(x, x + 1); + } + } else if (parent.find(x + 1) != parent.end()) { + parent[x] = find(x + 1); + size[parent[x]]++; + } else { + parent[x] = x; + size[x] = 1; + } + } + + // find largest set + int max_size = 0; + for(auto it = size.begin(); it != size.end(); it++) { + auto sz = it->second; + max_size = max(max_size, sz); + } + return max_size; + } +}; diff --git a/top-k-frequent-elements/changhoon-sung.cpp b/top-k-frequent-elements/changhoon-sung.cpp new file mode 100644 index 000000000..b0a066116 --- /dev/null +++ b/top-k-frequent-elements/changhoon-sung.cpp @@ -0,0 +1,54 @@ +/* +top-k-frequent-elements +요구사항: 주어진 입력에 대해 k번째 빈도를 가지는 원소를 모두 반환하라. +접근 1: 가장 단순한 접근법은 모든 원소에 대해 map으로 빈도를 세는 것입니다. + ordered_map에서 삽입 비용은 자료구조 길이가 K일 때 O(logK)으로, N회 삽입 연산으로 O(NlogK) 인데, + 이때 모든 원소가 유일한 경우 K=N이므로 최악의 경우 O(NlogN) 입니다. + 그리고 정렬된 맵에서 k 위치를 찾는 lower_bound 연산 비용은 O(logN)으로, 총 O(NlogN)입니다. + unordered_map은 삽입 비용이 O(1)이지만, N회 삽입 연산 이후, k를 찾기 위해 카운트를 정렬해야 한다는 점에서 + 보편적인 정렬 비용 O(Nlog(N))이 발생합니다. +접근 2: 총 비용이 O(NlogN)보다 낮아지려면 정렬 자료구조의 입력 비용이 O(logN)보다 낮거나, + 정렬 비용이 O(NlogN)보다 낮아야 합니다. + 한 가지 아이디어는 정렬 자료구조의 입력을 비용을 낮추는 필터 및 셋 기반 블랙리스트를 도입할 수 있다는 것입니다. + - k초과 원소 필터: k보다 높은 빈도를 가지는 원소를 자료구조에서 제거 및 블랙리스트 + - k도달 불가 원소 필터: 현재 조회중인 입력에 대해 남은 원소의 수가 모두 해당 원소더라도 k에 미치지 못 하는 경우 제거 및 블랙리스트 + 필터에 해당하는 원소가 M개일 때, 필터 비용은 해시셋 조회 O(1), 삽입 비용은 O(log(N-M))으로 + 모든 입력에 대한 총 비용은 O(Nlog(N-M))입니다. + 공간 복잡도는 최악의 경우에도 입력의 상수배이므로 O(N)입니다. +접근 3: 우리가 원하는 것이 k 빈도 딱 하나라는 것을 고려하면, 해당 빈도를 키로 원소를 반환받는 구조를 생각해볼 수 있습니다. + 아이디어는 원소-카운팅맵으로 전체 입력에 대해 비용 O(N)으로 카운팅하고, 이를 바탕으로 카운팅-원소 해시맵으로 <빈도, 원소셋>을 빌드하는겁니다. + 해당 빌드 비용은 최대 N개 원소에 대해 조회+삽입 O(1) 이므로 O(N)입니다. + 필요한 정보는 카운팅-원소 해시맵으로 O(1) 조회 및 O(N) 반환합니다. + 공간 복잡도는 최악의 경우에도 입력의 상수배이므로 O(N)입니다. +*/ + +#include +#include +#include + +class Solution { +public: + std::vector topKFrequent(std::vector& nums, int k) { + std::unordered_map elementCountMap; + std::unordered_map> countElementMap; + std::vector ans; + + for(auto it = nums.begin(); it != nums.end(); it++) { + int x = *it; + elementCountMap[x]++; + } + + for(auto it = elementCountMap.begin(); it != elementCountMap.end(); it++) { + int x = it->first; + int count = it->second; + countElementMap[count].insert(x); + } + + const std::unordered_set& kFreqSet = countElementMap[k]; // avoid copy + for(auto it = kFreqSet.begin(); it != kFreqSet.end(); it++) { + int x = *it; + ans.push_back(x); + } + return ans; + } +}; diff --git a/two-sum/changhoon-sung.cpp b/two-sum/changhoon-sung.cpp new file mode 100644 index 000000000..e0ab60b6e --- /dev/null +++ b/two-sum/changhoon-sung.cpp @@ -0,0 +1,33 @@ +/* +two-sum +요구사항: 주어진 배열에서 두 수의 합이 타겟을 만들 수 있는지 확인하고, 해당 원소를 반환하라. +접근 1: 가장 단순한 방법은 입력에 대해 모든 combination을 확인하는 것입니다. + 시간 복잡도는 입력 스캔 O(N)을 N개의 원소에 대해 수행하므로 O(N^2)입니다. + 공간 복잡도는 입력 레퍼런스를 그대로 사용하며, 추가로 저장 및 관리하는 자료구조가 없으므로 O(1)입니다. +접근 2: 한 가지 아이디어는 임의의 입력 X에 대해 합이 `target`이 되도록 하는 pair는 유일하게 결정된다는 것입니다. + 따라서, 합을 구하는 대신, 주어진 입력에 대한 페어 존재 유무를 검사하는 것으로 문제를 전환할 수 있습니다. + 수의 범위가 충분히 크므로, 배열 대신 해시 기반 unordered_set으로 페어 존재 여부 쿼리 및 삽입을 O(1)에 수행할 수 있습니다. + 최악의 경우, 모든 입력에 대해 unordered_set 쿼리 및 삽입을 수행하므로 총 시간 복잡도는 O(N), 공간 복잡도는 입력과 같으므로 O(N)입니다. +*/ + +#include +#include + +class Solution { +public: + std::vector twoSum(std::vector& nums, int target) { + std::unordered_set s; + + for(auto it = nums.begin(); it != nums.end(); it++) { + int x = *it; + if (s.find(target - x) != s.end()) { + return std::vector(x, target-x); + } else { + s.insert(x); + } + } + + // 문제의 조건에서 단 하나의 솔루션이 반드시 존재한다고 가정하므로, 이곳에 도달하지 않음. + throw std::runtime_error("No two sum solution found"); + } +};