Skip to content

Commit 6bb898f

Browse files
authored
Merge pull request #1679 from changhoon-sung/main
[changhoon-sung] WEEK 01 solutions
2 parents 384138b + 51b7b5c commit 6bb898f

File tree

4 files changed

+203
-0
lines changed

4 files changed

+203
-0
lines changed

contains-duplicate/changhoon-sung.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
contains-duplicate
3+
요구사항: 주어진 입력에서 중복된 원소가 존재하는지 여부를 참/거짓으로 반환.
4+
접근 1: 가장 단순한 방법은 다른 vector input_list를 두고, 입력 반복마다 전체 스캔해서 중복 여부를 체크하는 것입니다.
5+
input_list의 스캔 비용 O(N)을 모든 입력에 대해 반복하므로 총 시간 복잡도는 O(N^2)입니다.
6+
공간 복잡도는 입력과 같으므로 O(N)입니다.
7+
접근 2: 원소의 존재 여부를 더 빠르게 체크하는 방법은 set을 사용하는 것입니다.
8+
set은 중복 원소를 검출하는데 효과적인 자료구조로, 내부 구현에 따라 비용과 특성이 다릅니다.
9+
- C++에서 ordered_{set, map}은 정렬을 유지하는 search tree를 사용하므로 쿼리와 삽입, 삭제 비용이 평균 O(log(N))입니다.
10+
search tree의 구현에 따라 최악의 경우 쿼리 비용이 O(N)까지 증가할 수 있습니다.
11+
- C++에서 unordered_{set, map}은 해시 및 버킷 구조를 사용합니다. 이 경우, 평균적인 쿼리, 삽입, 삭제 비용은 O(1)입니다.
12+
우리의 요구사항은 입력에 대한 별도의 정보를 저장할 필요가 없으므로 unordered_set으로 중복 검사를 수행하는 것이 비용 효율적입니다.
13+
최악의 경우 모든 입력을 확인해야 합니다. N개의 입력에 대해 각각 쿼리와 입력을 수행하므로, 총 시간 복잡도는 O(N), 공간 복잡도도 입력만큼인 O(N)입니다.
14+
*/
15+
16+
#include <unordered_set>
17+
#include <vector>
18+
19+
class Solution {
20+
public:
21+
bool containsDuplicate(std::vector<int>& nums) {
22+
std::unordered_set<int> s;
23+
24+
for (auto it = nums.begin(); it != nums.end(); it++) {
25+
int x = *it;
26+
if (s.find(x) != s.end())
27+
return true;
28+
else
29+
s.insert(x);
30+
}
31+
return false;
32+
}
33+
};
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
longest-consecutive-sequence
3+
요구사항: 주어진 비정렬 배열에서 가장 긴 연속 수열의 길이를 O(N)에 구하라.
4+
접근 1: 가장 단순한 방법은 정렬 후 스캔하여 가장 긴 연속 구간을 찾는 것이다.
5+
정렬 비용 O(NlogN)에 스캔 O(N)으로 총 O(NlogN)이다. 비용 초과.
6+
접근 2: 정렬하지 않고, 인접한 연속 원소 수열을 '하나의 덩어리'로 취급하기 위해 union-find할 수 있다.
7+
중복 원소는 무시한다.
8+
입력 x에 대해 x-1의 유니온을 찾는다.
9+
그러한 유니온이 존재하면 x를 추가한다. 그 이후 x+1 유니온이 존재하면 통합한다.
10+
그러한 유니온이 존재하지 않으면 x+1 유니온을 찾는다.
11+
그러한 유니온이 존재하면 x를 추가한다.
12+
인접한 앞 뒤 유니온이 존재하지 않으면 입력을 새로운 root union으로 초기화한다.
13+
14+
비용:
15+
경로 압축과 높이 최소화 최적화를 적용하면, find 비용은 O(α(N))으로 최대 입력 N=10^5에 대해서 한 자리 상수만큼 충분히 낮다.
16+
유니온의 통합 비용은 O(1)이다.
17+
N개의 입력에 대해 O(1) 연산을 상수번 수행하므로 유니온 빌딩 시간 복잡도는 O(N)이다.
18+
사이즈 리스트를 스캔하여 가장 큰 유니온의 크기를 구한다. 이는 O(K) where K <= N이다.
19+
따라서 총 시간 복잡도는 O(N)이다.
20+
공간 복잡도도 입력 범위의 상수배이므로 O(N)이다.
21+
*/
22+
23+
#include <unordered_map>
24+
#include <vector>
25+
26+
#define max(a, b) ((a) > (b) ? (a) : (b))
27+
28+
class Solution {
29+
public:
30+
std::unordered_map<int, int> parent;
31+
std::unordered_map<int, int> size;
32+
33+
int find(int x) {
34+
if (x == parent[x]) return x;
35+
return parent[x] = find(parent[x]); // path compression
36+
}
37+
void union_sets(int x, int y) {
38+
int px = find(x);
39+
int py = find(y);
40+
41+
if (px == py) return;
42+
43+
// min height optimization
44+
if (size[px] > size[py]) {
45+
size[px] += size[py];
46+
parent[py] = px;
47+
} else {
48+
size[py] += size[px];
49+
parent[px] = py;
50+
}
51+
}
52+
int longestConsecutive(std::vector<int>& nums) {
53+
// build union
54+
for (auto it = nums.begin(); it != nums.end(); it++) {
55+
int x = *it;
56+
57+
if (parent.find(x) != parent.end())
58+
continue;
59+
60+
if (parent.find(x - 1) != parent.end()) {
61+
parent[x] = find(x - 1);
62+
size[parent[x]]++;
63+
if (parent.find(x + 1) != parent.end()) {
64+
union_sets(x, x + 1);
65+
}
66+
} else if (parent.find(x + 1) != parent.end()) {
67+
parent[x] = find(x + 1);
68+
size[parent[x]]++;
69+
} else {
70+
parent[x] = x;
71+
size[x] = 1;
72+
}
73+
}
74+
75+
// find largest set
76+
int max_size = 0;
77+
for(auto it = size.begin(); it != size.end(); it++) {
78+
auto sz = it->second;
79+
max_size = max(max_size, sz);
80+
}
81+
return max_size;
82+
}
83+
};
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
top-k-frequent-elements
3+
요구사항: 주어진 입력에 대해 k번째 빈도를 가지는 원소를 모두 반환하라.
4+
접근 1: 가장 단순한 접근법은 모든 원소에 대해 map으로 빈도를 세는 것입니다.
5+
ordered_map에서 삽입 비용은 자료구조 길이가 K일 때 O(logK)으로, N회 삽입 연산으로 O(NlogK) 인데,
6+
이때 모든 원소가 유일한 경우 K=N이므로 최악의 경우 O(NlogN) 입니다.
7+
그리고 정렬된 맵에서 k 위치를 찾는 lower_bound 연산 비용은 O(logN)으로, 총 O(NlogN)입니다.
8+
unordered_map은 삽입 비용이 O(1)이지만, N회 삽입 연산 이후, k를 찾기 위해 카운트를 정렬해야 한다는 점에서
9+
보편적인 정렬 비용 O(Nlog(N))이 발생합니다.
10+
접근 2: 총 비용이 O(NlogN)보다 낮아지려면 정렬 자료구조의 입력 비용이 O(logN)보다 낮거나,
11+
정렬 비용이 O(NlogN)보다 낮아야 합니다.
12+
한 가지 아이디어는 정렬 자료구조의 입력을 비용을 낮추는 필터 및 셋 기반 블랙리스트를 도입할 수 있다는 것입니다.
13+
- k초과 원소 필터: k보다 높은 빈도를 가지는 원소를 자료구조에서 제거 및 블랙리스트
14+
- k도달 불가 원소 필터: 현재 조회중인 입력에 대해 남은 원소의 수가 모두 해당 원소더라도 k에 미치지 못 하는 경우 제거 및 블랙리스트
15+
필터에 해당하는 원소가 M개일 때, 필터 비용은 해시셋 조회 O(1), 삽입 비용은 O(log(N-M))으로
16+
모든 입력에 대한 총 비용은 O(Nlog(N-M))입니다.
17+
공간 복잡도는 최악의 경우에도 입력의 상수배이므로 O(N)입니다.
18+
접근 3: 우리가 원하는 것이 k 빈도 딱 하나라는 것을 고려하면, 해당 빈도를 키로 원소를 반환받는 구조를 생각해볼 수 있습니다.
19+
아이디어는 원소-카운팅맵으로 전체 입력에 대해 비용 O(N)으로 카운팅하고, 이를 바탕으로 카운팅-원소 해시맵으로 <빈도, 원소셋>을 빌드하는겁니다.
20+
해당 빌드 비용은 최대 N개 원소에 대해 조회+삽입 O(1) 이므로 O(N)입니다.
21+
필요한 정보는 카운팅-원소 해시맵으로 O(1) 조회 및 O(N) 반환합니다.
22+
공간 복잡도는 최악의 경우에도 입력의 상수배이므로 O(N)입니다.
23+
*/
24+
25+
#include <vector>
26+
#include <unordered_map>
27+
#include <unordered_set>
28+
29+
class Solution {
30+
public:
31+
std::vector<int> topKFrequent(std::vector<int>& nums, int k) {
32+
std::unordered_map<int, int> elementCountMap;
33+
std::unordered_map<int, std::unordered_set<int>> countElementMap;
34+
std::vector<int> ans;
35+
36+
for(auto it = nums.begin(); it != nums.end(); it++) {
37+
int x = *it;
38+
elementCountMap[x]++;
39+
}
40+
41+
for(auto it = elementCountMap.begin(); it != elementCountMap.end(); it++) {
42+
int x = it->first;
43+
int count = it->second;
44+
countElementMap[count].insert(x);
45+
}
46+
47+
const std::unordered_set<int>& kFreqSet = countElementMap[k]; // avoid copy
48+
for(auto it = kFreqSet.begin(); it != kFreqSet.end(); it++) {
49+
int x = *it;
50+
ans.push_back(x);
51+
}
52+
return ans;
53+
}
54+
};

two-sum/changhoon-sung.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
two-sum
3+
요구사항: 주어진 배열에서 두 수의 합이 타겟을 만들 수 있는지 확인하고, 해당 원소를 반환하라.
4+
접근 1: 가장 단순한 방법은 입력에 대해 모든 combination을 확인하는 것입니다.
5+
시간 복잡도는 입력 스캔 O(N)을 N개의 원소에 대해 수행하므로 O(N^2)입니다.
6+
공간 복잡도는 입력 레퍼런스를 그대로 사용하며, 추가로 저장 및 관리하는 자료구조가 없으므로 O(1)입니다.
7+
접근 2: 한 가지 아이디어는 임의의 입력 X에 대해 합이 `target`이 되도록 하는 pair는 유일하게 결정된다는 것입니다.
8+
따라서, 합을 구하는 대신, 주어진 입력에 대한 페어 존재 유무를 검사하는 것으로 문제를 전환할 수 있습니다.
9+
수의 범위가 충분히 크므로, 배열 대신 해시 기반 unordered_set으로 페어 존재 여부 쿼리 및 삽입을 O(1)에 수행할 수 있습니다.
10+
최악의 경우, 모든 입력에 대해 unordered_set 쿼리 및 삽입을 수행하므로 총 시간 복잡도는 O(N), 공간 복잡도는 입력과 같으므로 O(N)입니다.
11+
*/
12+
13+
#include <vector>
14+
#include <unordered_set>
15+
16+
class Solution {
17+
public:
18+
std::vector<int> twoSum(std::vector<int>& nums, int target) {
19+
std::unordered_set<int> s;
20+
21+
for(auto it = nums.begin(); it != nums.end(); it++) {
22+
int x = *it;
23+
if (s.find(target - x) != s.end()) {
24+
return std::vector<int>(x, target-x);
25+
} else {
26+
s.insert(x);
27+
}
28+
}
29+
30+
// 문제의 조건에서 단 하나의 솔루션이 반드시 존재한다고 가정하므로, 이곳에 도달하지 않음.
31+
throw std::runtime_error("No two sum solution found");
32+
}
33+
};

0 commit comments

Comments
 (0)