# Leetcode刷題學習筆記--Set/Multiset
## Introduction
ref : [Difference Between set, multiset, unordered_set, unordered_multiset in C++](https://www.geeksforgeeks.org/difference-between-set-multiset-unordered_set-unordered_multiset-in-cpp/)
| | 排序 | 允許重複資料 | 用途 |
| ------------------ | ------------------ | ------------ | --- |
| set | :heavy_check_mark: | | 排序不重複資料 |
| unordered_set | || 檢查資料單一性</br>當成hash table使用 |
| multiset | :heavy_check_mark: | :heavy_check_mark: | 排序重複資料 |
| unordered_multiset | |:heavy_check_mark:| |
+ set/multiset只能存放一個資料,不像map可以存key-value pair。
+ ==數字放入set會依小到大排序,字串放入set會依字典序排序。==
## Time Complexity
ref : [Data Structures: MULTI_SET vs SET vs UNORDERED_SET](https://medium.com/geekculture/data-structures-multi-set-vs-set-vs-unordered-set-bcc4f6609ac9)
| Container | insert() | erase() | find() | Implement|
| ------------------ | -------- | ------- | ------ | --- |
| set | $O(logN)$| $O(logN)$|$O(logN)$|Binary Search Tree|
| unordered_set | $O(1)$ | $O(1)$ |$O(1)$ |Hash Table|
| multiset | $O(logN)$| $O(logN)$|$O(logN)$|Red Black Tree|
| unordered_multiset | $O(1)$ | $O(1)$ |$O(1)$ |Hash Table|
## Code snippet
### 檢查資料是否重複
把每筆資料轉換成單一的識別碼,從而檢查是否重複。可以使用set。
```cpp=
vector<int> nums;
unordered_set<int> s(nums.begin(), nums.end());
// check if num has alreay existed in set
if(s.count(num)) {...}
```
### 只能insert和erase不能修改資料
```cpp=
unordered_set<int> s;
s.insert(1);
s.insert(2);
s.erase(1);
```
+ multiset/unordered_multiset 的資料刪除
因為multiset和unordered_multiset可以允許重複的資料,
使用erase或刪除相同的資料,所以必須使用find。
```cpp=
multiset<int> ms;
ms.erase(ms.find(num));
```
### 如果是插入到set或是multiset,可以知道插入的位置。
視情況如果想知道insert的位置。
```cpp=
set<int> s;
int nums = 5;
auto it = s.insert(nums); // get the iterator of inserted number
int idx = it - s.begin();
```
### Traversal the set
使用iterator來存取所有的item,或是使用for loop來存取所有的資料。
ref : [Different ways to iterate over a set in C++](https://www.geeksforgeeks.org/different-ways-to-iterate-over-a-set-in-c/)
```cpp=
set<int> s;
// iterator為指向set中的element,可視為pointer,必須使用*來取值。
for(auto it = s.begin(); it != s.end(); ++it)
cout << *it << endl;
// for loop 直接就會拿到element的reference
for(auto& n : s)
cout << n << endl;
// 必須使用const來定義輸入argument,因為set內的element也是const,
// 所以只能刪除不能修改。
// (start iterator, end iterator, funcion pointer)
auto print = [&](const int& x) {
cout << x << endl;
};
for_each(s.begin(), s.end(), print);
for_each(s,begin(), s.end(), [&](const int& x){
cout << x << endl;
});
```
### 改變Set排列順序
+ 預設Set排列,integer為從小到大,string為字典序排列。
+ 但是有些情況想要有integer從大到小的排列。
1. 使用iterator
```cpp=
for(auto it = s.rbegin(); it != r.end(); ++it) {
cout << *it << endl;
}
```
2. 使用compare
```cpp=
set<int, less<int>> s; // 從小到大
set<int, greater<int>> s; //從大到小
```
3. 反轉key
```cpp=
vector<int> nums;
set<int> s;
for(auto& n : nums)
s.insert(-1 * n);
```
### Sets of pairs in C++
ref : [Sets of pairs in C++](https://www.geeksforgeeks.org/sets-of-pairs-in-c/)
在Set中存放pairs。則會先依pairs first element排序,如果first element一樣就會用second element排序。
### Custom Comparator
ref : [Using custom std::set comparator](https://stackoverflow.com/questions/2620862/using-custom-stdset-comparator)
+ Modern C++11 Solution
```cpp=
auto cmp = [](int a, int b) {
return a < b; // 從小到大
};
set<int, decltype(cmp)> s(cmp);
```
+ function pointer
```cpp=
bool cmp(int a, int b) {
return a < b;
}
set<int, decltype(&cmp)> s(&cmp);
```
### [36. Valid Sudoku(Medium)](https://leetcode.com/problems/valid-sudoku/)
找出行,列,每個block是否有數字重複。
```cpp=
class Solution {
public:
bool isValidSudoku(vector<vector<char>>& board) {
unordered_set<string> s;
string t1, t2, t3;
for(int i = 0; i < 9; ++i) {
for(int j = 0; j < 9; ++j) {
if(board[i][j]=='.') continue;
t1 = to_string(i) + "r" + board[i][j];
t2 = to_string(j) + "c" + board[i][j];
t3 = to_string(i / 3) + to_string(j / 3)
+ "e" + board[i][j];
if(s.count(t1) || s.count(t2) || s.count(t3))
return false;
else {
s.insert(t1);
s.insert(t2);
s.insert(t3);
}
}
}
return true;
}
};
```
### [929. Unique Email Addresses(Easy)](https://leetcode.com/problems/unique-email-addresses/)
找出不同的email address,判斷規則參考題目。
> 使用unordered_set來過濾是否有重複的email address。**unordered_set<string> valid_mails;**
> 把email分成local name和domain name。對localname進行處理。把"+"以後的字串都刪除掉,然後把"."都拿掉。
完整程式碼如下:
```cpp=
int numUniqueEmails(vector<string>& emails) {
unordered_set<string> valid_mails;
for(auto& mail : emails) {
int pos = mail.find("@");
string localname = mail.substr(0, pos);
string domainname = mail.substr(pos + 1);
int p = localname.find("+");
string newname = localname.substr(0, p);
newname.erase(std::remove(newname.begin(), newname.end(), '.'), newname.end());
valid_mails.insert(newname + "@" + domainname);
}
return valid_mails.size();
}
```
### [220. Contains Duplicate III](https://leetcode.com/problems/contains-duplicate-iii/)
給你一個vector<int> nums, 找出index差小於等於k,且數字差小於等於t。
$abs(num[i] - nums[j]) <= t$ 且 $abs(i - j) <= k$。
> 1. 因為限制index差在k之內,所以使用slinding window,新增一個數nums[i]就刪除nums[i - k - 1]
> 2. 使用multiset來記錄數字,因為數字可能重複。
> 3. 因為題目是要差值小於t,所以insert之後的數字只需要跟前後去比較。
> 4. **刪除的時候必須使用 s.earse(s.find(val)); 避免刪除全部相同的key。**,因為可能會遇到windows中好幾個相同的數值。
```cpp=
bool containsNearbyAlmostDuplicate(vector<int>& nums, int k, int t) {
multiset<long> s;
for(int i = 0; i < nums.size(); ++i) {
// 使用find之後再erase避免刪除重複的key
if(i - k - 1 >= 0)
s.erase(s.find(nums[i - k - 1])); // O(logK + logK)
// insert之後取得在multiset之中的位置。
auto it = s.insert(nums[i]); // O(logK)
if(it != s.begin() && it - *prev(it) <= t) return true;
if(next(it) != s.end() && *next(it) - *it <= t) return true;
// time : O(N*3*logK) = O(NlogK),
// K : size of slinding window
// space : O(K)
}
return false;
}
```
###### tags: `leetcode` `刷題`