1. 程式人生 > >2017頭條筆試題:二維點集中找出右上角沒有點的點並按x坐標從小到大打印坐標

2017頭條筆試題:二維點集中找出右上角沒有點的點並按x坐標從小到大打印坐標

測試結果 ++ reserve using 如果 穩定 一個 lac 順序

技術分享

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坐標從小到大打印坐標