12|排序與去重
相關主題: C++ 基礎速查
只限 C++|CPU 時間上限 1 秒
讀入一堆整數(事先不知道有幾個),把重複的去掉,再由小到大輸出還剩下的不同數值,並輸出一共有幾個不同的數值。
這一題要練習 C++ 標準函式庫裡最常用的三樣東西:std::vector(會自己長大的陣列)、std::sort(排序)、std::unique 搭配 erase(去掉重複)。用 C 寫的話,要自己用 malloc/realloc 管理一塊會變大的陣列、用 qsort 加上自己寫的比較函式排序、再寫一個迴圈去重;C++ 幾行就做完了。
資料最多有 20 萬個。如果每讀一個數字,就在已經存下來的結果裡從頭找一遍「有沒有出現過」,最大的測試資料要比較幾十億次,會超過時間;先排序再去重只要 O(n log n)。
- 一些整數,一直讀到輸入結束 (EOF)。至少 1 個,最多 200000 個。
- 每個整數 x 都滿足 -10^9 ≤ x ≤ 10^9。
- 整數之間用空白分隔:可能是一個或多個空格、Tab 或換行,也可能有空行。每行有幾個整數不固定。
一共 2 行:
- 第 1 行:不同的數值有幾個。
- 第 2 行:這些不同的數值,由小到大排列,用一個空格分隔。
輸入:
5 3 -2 5 03 3 -28輸出:
5-2 0 3 5 8輸入共有 9 個整數,去掉重複後剩下 -2、0、3、5、8 這 5 個。
C++:
std::vector<int> v;加上v.push_back(x);:不用事先知道資料有幾筆,空間不夠時 vector 會自動擴充while (std::cin >> x):讀到 EOF 或讀不到整數時條件會變成 false,迴圈就結束;>>會自動跳過空格、Tab 和換行std::sort(v.begin(), v.end());(在<algorithm>):由小到大排序std::unique(v.begin(), v.end()):把相鄰的重複元素移掉,回傳「新結尾」的位置,但不會改變 vector 的大小,所以要搭配v.erase(新結尾, v.end())真正刪掉後面那段- 資料量大的時候,習慣在
main一開始加上std::ios::sync_with_stdio(false);和std::cin.tie(nullptr);,在很多編譯器上可以讓cin/cout快不少
對照 C:
- 讀到 EOF 的寫法是
while (scanf("%d", &x) == 1) - 會變大的陣列要自己用
malloc配置、滿了用realloc加倍,最後還要free - 排序用
qsort(a, n, sizeof(int), cmp),比較函式要接收const void*再自己轉型 - 去重要自己寫迴圈:排序後只保留「和前一個不一樣」的元素
提示 1:怎麼讀到輸入結束?
std::vector<int> v;int x;while (std::cin >> x) { v.push_back(x);}在終端機手動測試時,打完數字後按 Ctrl+Z 再按 Enter 代表輸入結束。
讀 200000 個整數時,建議在 main 的第一行加上:
std::ios::sync_with_stdio(false);std::cin.tie(nullptr);第一行讓 cin/cout 不必和 C 的 scanf/printf 保持同步(加了之後就不要混用兩種),第二行讓 cin 每次讀之前不用先清空 cout。
提示 2:為什麼要先排序才能用 std::unique?
std::unique 只會處理「連在一起的重複」,例如 3 3 5 3 只會變成 3 5 3。先排序之後,相同的數值一定會排在一起,std::unique 就能把所有重複都去掉。
另外要注意 std::unique 不會讓 vector 變短:它把要留下的元素往前搬,回傳新結尾的位置。後面那段要用 erase 刪掉:
auto new_end = std::unique(v.begin(), v.end());v.erase(new_end, v.end());提示 3:另一種做法:std::set
std::set<int>(在 <set>)是「自動排序、不允許重複」的容器。把每個數字 insert 進去,最後用範圍 for 迴圈走一遍,就已經是由小到大、沒有重複,而 s.size() 就是不同數值的個數。
兩種做法都可以通過。set 寫起來更短,但每次插入都要找位置,資料量大時通常比「vector + sort + unique」慢一些。有興趣的話兩種都寫寫看。