2017頭條筆試題:二維點集中找出右上角沒有點的點並按x坐標從小到大打印坐標
PS:這篇是之前本來就想發的但是一直沒時間寫,加上今天做了京東的題,結果代碼名就命名為jingdong了……懶得改代碼名重新跑一遍結果了=。=
暴力法去做就是遍歷每個點,判斷它是不是“最大點”。判斷過程則是又遍歷一遍,看看是否存在其他點在它右上方,若存在則不是最大點。O(N^2)
但是這樣就會有很多不必要的計算,舉個例子(這裏暫且當坐標都是int),若存在一個最大點(x0,y0),那麽所有在它左下角的點都不用考慮了。
另外,對於(x0,y0),只需要查找在它右邊(x>x0)的點是否在它上面。如果預先將點根據x坐標排序,那麽判斷過程就從for i in [0, n)變成了for i in [x0, n),但這並沒有本質的提升,還是O(N^2)。——這也暗示了點集中最右邊的點必然是最大點。
然後再註意,如果右邊的點存在一個點滿足y>y0,那麽判斷就會返回false了;若不存在則判斷返回true。
關鍵就是記錄右邊的點的ymax,不必每次都遍歷一遍重復計算ymax。
到了這一步後就可以寫代碼了,註意,由於輸入並不是有序的,必須得經過預處理(按照x排序),坐標軸範圍是0~1e9的話,用位圖法排序(參考編程珠璣)時間復雜度還是1e9的數量級,原題數據量不超過100000,位圖法排序並不比快排快。
#include <algorithm> #include <limits.h> using namespace std; struct Point { int x, y; };// a[n]為從輸入中讀取得到的點集數組 void solution(Point* a, int n) { std::sort(a, a + n, [](Point& p1, Point& p2) { return p1.x < p2.x; }); int ymax = INT_MIN; // 記錄從右往左遍歷過程中y的最大值 for (int i = n - 1; i >= 0; i--) { if (a[i].y > ymax) // 此時a[i].y大於或等於右邊所有點的最大y坐標, a[i]為最大點 { printf("%d %d\n", a[i].x, a[i].y); ymax = a[i].y; // 更新最大y坐標 } } }
上述代碼的成功有個前提,也是題目裏的限制:【所有點的橫縱坐標都不重復】
這點必須註意!如果沒有這個限制,比如y坐標不重復,比如對點集(1,1) (2,3) (3,3) (4,2),按照上面的做法只會得出(3,3)和(4,2)兩個最大點,但是(2,3)其實也是最大點。
錯誤的地方在於判斷語句:a[i].y > ymax,如果該點的y坐標與ymax相等,那麽該點也是最大點.。
因此判斷語句要改成
if (a[i].y >= ymax)
如果連x坐標不重復這個限制都沒有的話,那就更復雜,比如序列經過sort排序後為(1,3)(1,4)(2,2),(2,2)和(1,4)被確認為最大點,但是判斷(1,3)時,由於當前ymax已經被更新為4了,(1,3)不會被當成最大點。
這樣一來,邏輯就變成了【對所有滿足x=x0的點,其中y>=ymax的點都是最大點】,關鍵是要找出滿足x=x0的點集區間。
在沒有【所有的橫縱坐標都不重復】這個限制下的完整代碼與簡單測試結果如下
#include <iostream> #include <vector> #include <algorithm> #include <limits.h> using namespace std; struct Point { int x, y; Point(int x_, int y_) : x(x_), y(y_) { } bool operator<(const Point& rhs) const { return x < rhs.x; } }; void solution(Point* a, int n) { vector<Point> res; res.reserve(n); // 註釋掉排序預處理的代碼, 輸入排序後的結果進行測試 // 因為快排不是穩定排序, 可能打亂x相同的若幹點之間的相對順序 // sort(a, a + n); int ymax = INT_MIN; // 記錄從右往左遍歷過程中y的最大值 Point* low = &a[n - 1]; Point* high = &a[n]; while (low >= a) { // 尋找x坐標相同的左閉右開區間 while (low > a) // 保證low-1在a[n]中 { auto it = low - 1; if (it->x == low->x) low = it; else break; } // 左閉右開區間[low, high)的點的x坐標相同 // 區間內所有y不小於ymax的點均為最大點 int temp = ymax; for (auto it = low; it != high; ++it) { if (it->y >= ymax) { res.emplace_back(*it); if (it->y > temp) // 同時求出[low, a+n)區間的最大y坐標 temp = it->y; } } ymax = temp; high = low; low = high - 1; } // 按照x坐標從小到大輸出結果 std::sort(res.begin(), res.end()); for (auto& pt : res) cout << pt.x << " " << pt.y << endl; } int main() { // 讀取輸入 int n; // 點數 cin >> n; vector<Point> v; v.reserve(n); for (int i = 0; i < n; i++) { int x,y; cin >> x >> y; v.emplace_back(x, y); } // 計算並輸出結果 solution(&v[0], v.size()); return 0; }
測試用例data1是針對特殊情況(x/y坐標有重復的)的用例,data2是題目的示意圖的用例
[email protected]:~/Algorithms/2017$ g++ jingdong.cpp -std=c++11 [email protected]:~/Algorithms/2017$ cat data1 4 1 2 1 3 1 4 2 3 [email protected]:~/Algorithms/2017$ cat data1 | ./a.out 1 3 1 4 2 3 [email protected]:~/Algorithms/2017$ cat data2 9 1 10 2 3 3 8 4 4 5 6 5 3 6 9 7 7 8 5 [email protected]:~/Algorithms/2017$ cat data2 | ./a.out 1 10 6 9 7 7 8 5
如果是在題目限制下的算法,對data1的結果如下(漏掉了(1,3)這個點)
[email protected]:~/Algorithms/2017$ g++ jingdong_old.cpp -std=c++11 [email protected]:~/Algorithms/2017$ cat data1 | ./a.out 1 4 2 3
2017頭條筆試題:二維點集中找出右上角沒有點的點並按x坐標從小到大打印坐標