給定二維平面整數點集輸出「最大點集」算法(今日頭條面試題)

引子

最近本身的獨立遊戲上線了,算是了卻了一樁心願。接下來仍是想找一份工做繼續幹,創業的事有緣再說。node

找工做以前,老是要瀏覽一些實戰題目,熱熱身嘛。經過搜索引擎,搜到了今日頭條的一道面試題。ios

題目

P爲給定的二維平面整數點集。定義 P 中某點x,若是x知足 P 中任意點都不在 x 的右上方區域內(橫縱座標都大於x),則稱其爲「最大的」。求出全部「最大的」點的集合。(全部點的橫座標和縱座標都不重複, 座標軸範圍在[0, 1e9) 內)面試

以下圖:實心點爲知足條件的點的集合。請實現代碼找到集合 P 中的全部 」最大「 點的集合並輸出。算法

輸入描述:數組

第一行輸入點集的個數 N, 接下來 N 行,每行兩個數字表明點的 X 軸和 Y 軸。數據結構

輸出描述:dom

輸出「最大的」 點集合, 按照 X 軸從小到大的方式輸出,每行兩個數字分別表明點的 X 軸和 Y軸。ide

輸入例子1:
5
1 2
5 3
4 6
7 5
9 0
輸出例子1:
4 6
7 5
9 0

 思路

1.暴力搜索法性能

先取一點,而後和其餘全部點比較,看看是否有點在其右上方,沒有則證實該點是「最大點」。重複檢測全部的點。顯而易見,算法的複雜度爲O(n2)測試

2.變治法(預排序)

由「最大點」的性質可知,對於每個「最大點」,若存在其餘點的y值大於該點y值,那麼其餘點x值必然小於該點的x值。

換言之,當某一點肯定它的x值高於全部y值大於它的點的x值,那麼該點就是「最大點」 。網上給出的答案基本上都是這個套路。

對於y有序的點集,只須要O(n)便可輸出「最大點」點集。通常基於比較的排序算法時間複雜度O(nlogn)。那麼,顯而易見,算法總體複雜度爲O(nlogn)。

3.減治法+變治法(過濾+預排序)

過濾很簡單,就是在集合中找出一個比較好的點,而後過濾掉其左下角的全部點。而後再採用方法2對過濾後的點集求解。

那麼這個集合中比較好的點,怎麼找,或者說哪一個點是比較好的點。顯而易見,越靠近點集右上角的點,左下角的面積就越大,越能夠過濾更多的點,故越好。

兒時學過,兩個數的和必定,那麼兩數差越小,乘積越大。簡單設計,該點x和y的和減去x和y差的絕對值越大,該點越好。

4.空間分割(優化的四叉樹)

由於以前對四叉樹有必定的瞭解,因此對於這個問題也想到能不能有四叉樹去處理。若是有同窗熟悉KD樹思路大體是同樣。 爲了優化將點集插入到四叉樹的時間,筆者使用預先開闢數組來表示四叉樹。對於500000個點,大概所需時間以下:

build tree cost: 167ms
105233923 999996852
398502327 999994996
837309014 999994263
899779160 999993102
980746971 999976098
990941053 999881685
991773349 999539486
996437667 999536388
999934209 999456481
999948454 989946068
999951793 933115039
999993165 920862637
999996248 471725091
search tree cost: 106ms

假設點集數量爲n,構建n次矩形查詢,第i次查詢範圍爲以第i個點爲左下角以數據最大範圍爲右上角的矩形,若該點右上方沒有點那麼該點爲「最大點」。若該點爲「最大點」,咱們還能夠以它爲右上角,以0點位左下角構建範圍查詢,過濾哪些不須要查詢的點。經過對比可知,查詢時間能夠接受,但構建時間確實長了一些。可能致使該方法尚未直接預排序好。整體時間複雜度應該也是O(nlogn)級別,大部分時間都用於構建了,構建時涉及到的內存操做比較多必然消耗更多時間。原本沒有使用預排序就能夠獲取結果,咱們的輸出結果能夠匹配輸入順序,牛客網的答案要求以x從小到大排序,故結果還要從新排序。。。若是結果集很是大,對結果集排序會消耗大量時間。很惋惜,四叉樹沒法經過測試,一是C#的輸入輸出會佔用大量時間,二是咱們還須要對結果集從新排序。但實驗證實了對於隨機500000點,使用四叉樹能夠和預排序有着差很少的時間複雜度。最後,還有交代一個重要問題:

int x=999999991;

float y = 1000000000f;

float z = x/y;

請問z值爲什麼?答案是1.0f,由於float的精度不足,沒法表示0.999999991,因此變成了1.0f,這個問題一度使個人四叉樹覺得小几率出現異常。找了半天才揪出來。

 

附上代碼,備忘。

using System.IO;
using System;
using System.Collections.Generic;
using System.Diagnostics;
class Program
{
    private static int maxData = 1000000000;
    private static List<MyPoint> dataPoints = new List<MyPoint>();
    private static Random rand = new Random();
    static void Main()
    {
      
        float time = 0;
        Stopwatch watch = new Stopwatch();
        watch.Start();
        
        Quadtree quad = new Quadtree(5, new Rectangle(0, 0, maxData, maxData));
      
        int count = int.Parse(Console.ReadLine());
    
        for (int i = 0; i < count; i++)
        {
            MyPoint temp;
            //string[] inputs = Console.ReadLine().Split(new char[] { ' ' });
            //temp.x = Convert.ToInt32(inputs[0]);
            //temp.y = Convert.ToInt32(inputs[1]);
            temp.x = rand.Next(maxData);
            temp.y = rand.Next(maxData);
            dataPoints.Add(temp);
            quad.Insert(temp);
        }
        time = watch.ElapsedMilliseconds - time;
        Console.WriteLine("build tree cost: " + time + "ms");
        List<MyPoint> result = new List<MyPoint>();
        Rectangle rect;
        rect.width = rect.height = maxData + 1;
       
        for (int i = 0; i < count; i++)
        {
           
            rect.x = dataPoints[i].x;
            rect.y = dataPoints[i].y;
            if (quad.retrieve(rect))
            {
                continue;
            }
            result.Add(dataPoints[i]);
           
        }
        //要以x軸y從小到大輸出,因此結果集須要排序
        result.Sort();
        for(int i=0;i< result.Count; i++)
        {
            Console.WriteLine( result[i]);
        }
        time = watch.ElapsedMilliseconds - time;
        Console.WriteLine("search tree cost: " + time + "ms");
        watch.Stop();
    }
}


public class Quadtree
{
    private class QuadtreeData
    {
        public int maxLevel;
        public double maxWidth;
        public double maxHeight;
        public Quadtree[] allNodes;
        public QuadtreeData(int maxLevel,float maxWidth,float maxHeight)
        {
            this.maxLevel = maxLevel;
            this.maxWidth = maxWidth;
            this.maxHeight = maxHeight;
            
            int maxNodes = 0;
            for (int i = 0; i <= maxLevel; i++)
            {
                maxNodes += (int)Math.Pow(4, i);
            }
            allNodes = new Quadtree[maxNodes];
           
        }
    }

    private int level;
    private int parent;
    private int count;
    private List<MyPoint> points;
    private Rectangle bounds;
    private Quadtree[] nodes;
    private QuadtreeData data;
    
    public Quadtree(int maxLevel,Rectangle bounds)
    {
        data = new QuadtreeData(maxLevel,bounds.width,bounds.height);
        this.bounds = bounds;
        level = 0;
        count = 0;
        parent = -1;
        nodes = new Quadtree[4];
        Init();
    }

    private void Init()
    {
        
        data.allNodes[0] = this;
       
        for (int i = 0; i < data.allNodes.Length; i++)
        {
            if (data.allNodes[i].level >= data.maxLevel)
                break;
            InitChildrenNew(i);
        }

    }
    private void InitChildrenNew(int parentIndex)
    {
       
        Rectangle bounds = data.allNodes[parentIndex].bounds;
        float subWidth = (bounds.getWidth() / 2);
        float subHeight = (bounds.getHeight() / 2);
        float x = bounds.getX();
        float y = bounds.getY();
        
        int nextLevel =  data.allNodes[parentIndex].level + 1;
        byte[,] offset =new byte[,]{{0,0},{1,0},{0,1},{1,1}};
        for (int i = 0; i < 4; i++)
        {
            Rectangle rect = new Rectangle(x,y,subWidth,subHeight);
            
            rect.x += offset[i,0]*subWidth;
            rect.y += offset[i,1]*subHeight;
            
            int childIndex = GetPointIndexByLevel(rect.getCenter(), nextLevel);
            if (childIndex < data.allNodes.Length)
            {
                data.allNodes[childIndex] = new Quadtree(nextLevel, rect, data);
                data.allNodes[childIndex].parent = parentIndex;
                data.allNodes[parentIndex].nodes[i] = data.allNodes[childIndex];
                //Console.WriteLine("p:"+parentIndex+",c:"+childIndex+",size:"+ rect.width);
            }



        }
    }
  
    
    private Quadtree(int pLevel, Rectangle pBounds , QuadtreeData pData)
    {
        level = pLevel;
        bounds = pBounds;
        nodes = new Quadtree[4];
        count = 0;
        data = pData;
    }
  
   

    public int GetPointIndexByLevel(MyPoint point, int targetLevel)
    {
       
        int[] indexByLevel={0,1,5,21,85,341,1365,5461,21845};
        int startIndex =indexByLevel[targetLevel] ;
        
        int cc = (int)Math.Pow(2, targetLevel);
        
        //if(point.x >= data.maxWidth || point.y >=data.maxHeight)
        //{
        //    Console.WriteLine("error point:"+point);
        //    Console.WriteLine("data:"+data.maxWidth+","+data.maxHeight);
        //}
        int locationX = (int)(point.x / data.maxWidth * cc);
        int locationY = (int)(point.y / data.maxHeight * cc);
        int idx = startIndex + locationY * cc + locationX;
        
        return idx;
    }
    /*
    * Insert the object into the quadtree. If the node
    * exceeds the capacity, it will split and add all
    * objects to their corresponding nodes.
    */
    public void Insert(MyPoint point)
    {

        int idx = GetPointIndexByLevel(point, data.maxLevel);

        var nodeToAdd = data.allNodes[idx];
       
        if (nodeToAdd != null)
        {
            
            if (nodeToAdd.points == null)
                   nodeToAdd.points = new List<MyPoint>();
            
            nodeToAdd.points.Add(point);
            nodeToAdd.AddCount();
            
        }
        
    }
    private void AddCount()
    {
        if(parent >=0 )
        {
             var nodeParent = data.allNodes[parent];
             nodeParent.AddCount();
        }
        count++;
    }

    /*
    * Return all objects that could collide with the given object
    */
    public bool retrieve(Rectangle pRect)
    {
       
        if(count > 0 && pRect.Contains(bounds))
        {
            return true;
        }
          
        if(count > 0 && bounds.Intersects(pRect))
        {
            
            if (points != null)
            {
              
                for (int i = 0; i < points.Count; i++)
                {
                  
                    if (pRect.Contains(points[i]))
                    {
                        return true;
                    }

                }
            }

            else if (level < data.maxLevel)
            {
               
                if (nodes[3] != null && nodes[3].retrieve(pRect)) return true;
                if (nodes[2] != null && nodes[2].retrieve(pRect)) return true;
                if (nodes[1] != null && nodes[1].retrieve(pRect)) return true;
                if (nodes[0] != null && nodes[0].retrieve(pRect)) return true;
            }

        }

        return false;

    }

   
}

public struct MyPoint : IComparable<MyPoint>
{
    public int x;
    public int y;
    public MyPoint(int x = 0, int y = 0)
    {
        this.x = x;
        this.y = y;
    }

    public override string ToString()
    {
        return  x + " " + y;
    }
    public int CompareTo(MyPoint other)
    {
        if(x == other.x)
          return 0;
        else if(x > other.x)
          return 1;
        else if( x < other.x)
          return -1;
         
         return -1;
    }
}
public struct Rectangle
{
    public float x;
    public float y;
    public float height;
    public float width;
    public Rectangle(float x = 0, float y = 0, float width = 0, float height = 0)
    {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;
    }
    public float getX() { return x; }
    public float getY() { return y; }
    public float getHeight() { return height; }
    public float getWidth() { return width; }
    public MyPoint getCenter() { return new MyPoint((int)(x + width / 2), (int)(y + height / 2)); }
    public bool Intersects(Rectangle Rect)
    {
        return (!(y > Rect.y + Rect.height ||
                   y + height < Rect.y ||
                   x + width < Rect.x ||
                   x > Rect.x + Rect.width));
    }
    public bool Contains(MyPoint point)
    {
        return (x < point.x && x + width >= point.x &&
                y < point.y && y + height >= point.y);
    }
    
    public bool Contains(Rectangle other)
    {
        return Contains(new MyPoint((int)other.x,(int)other.y)) 
        && Contains(new MyPoint((int)(other.x+other.width),(int)(other.y+other.height)));
    }

    public override string ToString()
    {
        return "Rect:" + x + "," + y + "," + width;
    }
}

 

 

過濾與直接預排序對比實現

#include<iostream>
#include<algorithm>
#include<vector>
#include <cstdlib>
#include <ctime>
using namespace std;
struct point{     //定義結構體
    int x,y;
};
bool cmp(point a,point b){  //自定義排序方法
    return a.y==b.y?a.x>b.x:a.y<b.y;  //y升序,x降序
}
int main(){
    clock_t start,finish;
    double totaltime;
    std::srand(std::time(nullptr)); // use current time as seed for random generator
    int count;  
    cout<<"輸入點的個數和點:" ;
    cin>>count;
cout<<"輸入總點數爲:"<<count<<endl; vector
<point> p; //容器用來裝平面上的點 for(int i=0;i<count;i++){ point temp; temp.x = std::rand()% 100000000; temp.y = std::rand()% 100000000; p.push_back(temp); //爲了方便對比性能,咱們隨機插入大量點 }

cout<<"------------------過濾後再使用預排序:------------------------------"<<endl; start
= clock(); vector<point> filter;//定義過濾容器 vector<point> res; //定義結果容器 int curMaxRank = 0; int curMaxIndex = 0; for(int i=0;i<count;i++){ int temp =p[i].x+p[i].y-std::abs(p[i].x-p[i].y); if(temp > curMaxRank) { curMaxRank = temp; curMaxIndex = i; } } for(int i=0;i<count;i++) { if(p[i].x >= p[curMaxIndex].x || p[i].y>= p[curMaxIndex].y) { filter.push_back(p[i]); } } sort(filter.begin(),filter.end(),cmp); res.push_back(filter[filter.size()-1]); //左上角的那個點,必定符合條件 int maxx=filter[filter.size()-1].x; for(int i=filter.size()-2;i>=0;i--){ //y從大到小,若i點x值大於全部比其y值大的點的x值,那麼i點爲「最大點」。 if(filter[i].x>maxx){ res.push_back(filter[i]); maxx=filter[i].x; } } finish = clock(); cout<<"過濾後點數量:"<<filter.size()<<endl; cout<<"符合條件的點數量:"<<res.size()<<endl; for(int i=0;i<res.size();i++){ printf("%d %d\n", res[i].x, res[i].y); } totaltime=(double)(finish-start)/CLOCKS_PER_SEC; cout<<"\n此程序的運行時間爲"<<totaltime<<"秒!"<<endl; cout<<"------------------直接使用預排序:------------------------------"<<endl; start = clock(); sort(p.begin(),p.end(),cmp); res.clear(); res.push_back(p[p.size()-1]); //左上角的那個點,必定符合條件 int maxX=p[p.size()-1].x; for(int i=p.size()-2;i>=0;i--){ //y從大到小,若i點x值大於全部比其y值大的點的x值,那麼i點爲「最大點」。 if(p[i].x>maxX){ res.push_back(p[i]); maxX=p[i].x; } } finish = clock(); cout<<"符合條件的點數量:"<<res.size()<<endl; for(int i=0;i<res.size();i++){ printf("%d %d\n", res[i].x, res[i].y); } totaltime=(double)(finish-start)/CLOCKS_PER_SEC; cout<<"\n此程序的運行時間爲"<<totaltime<<"秒!"<<endl; return 0; }
輸入點的個數和點:......輸入總點數爲:500000
------------------過濾後再使用預排序:------------------------------
過濾後點數量:648
符合條件的點數量:16
15480205 99999697
17427518 99999676
78059606 99999351
80881235 99998746
91608165 99997683
95825638 99996289
99690315 99993155
99874266 99991089
99884382 99978546
99908259 99961095
99942330 99858670
99963997 99157830
99975627 97385053
99996564 95654979
99998236 95378376
99999527 66461920

此程序的運行時間爲0.013037秒!
------------------直接使用預排序:------------------------------
符合條件的點數量:16
15480205 99999697
17427518 99999676
78059606 99999351
80881235 99998746
91608165 99997683
95825638 99996289
99690315 99993155
99874266 99991089
99884382 99978546
99908259 99961095
99942330 99858670
99963997 99157830
99975627 97385053
99996564 95654979
99998236 95378376
99999527 66461920

此程序的運行時間爲0.288308秒!

下面的代碼能夠經過牛客網測試

#include<iostream>
#include<algorithm>
#include<vector>
#include <cstdlib>

using namespace std;
struct point{     //定義結構體
    int x,y;
};
bool cmp(point a,point b){  //自定義排序方法
    return a.y==b.y?a.x>b.x:a.y<b.y;  //y升序,x降序
}
point p[500001];
point filter[500001];
int main(){
    
    int count;  
   
    scanf("%d",&count);
   
    
    for(int i = 0; i < count; i++)
    {
       scanf("%d%d", &p[i].x, &p[i].y);
    }

  
  
    int curMaxRank = 0;
    int curMaxIndex = 0;
    for(int i=0;i<count;i++){
        int temp =p[i].x+p[i].y-std::abs(p[i].x-p[i].y);
        if(temp > curMaxRank)
        {
            curMaxRank = temp;
            curMaxIndex = i;
        }
    }
    int fCount =0 ;
    for(int i=0;i<count;i++)
    {
        if(p[i].x >= p[curMaxIndex].x || p[i].y>= p[curMaxIndex].y)
        {
            filter[fCount++]=p[i];
        }
    }
    
    sort(filter,filter+fCount,cmp);
    
    int maxx=-1;
    for(int i=fCount-1;i>=0;i--){  //y從大到小,若i點x值大於全部比其y值大的點的x值,那麼i點爲「最大點」。
        if(filter[i].x>maxx){
           
            printf("%d %d\n", filter[i].x, filter[i].y);
            maxx=filter[i].x;
        }
    }
   
   
    return 0;
}

經過時間再200-300ms左右,和直接預排序的時間幾乎相同,那麼應該是牛客網的測試用例都比較特別,過濾起不到大的效果或者是輸入輸出佔用了大部分時間,不管怎麼優化算法都沒辦法減小打印時間。。。不過不要緊,對於隨機用例咱們能夠確定該算法會更加優秀。

總結與展望

對於隨機點進行大量測試,發現存在筆者給出的過濾方法,平都可以過濾99.9%的點。也就是說過濾後所剩點m的數量爲原始點集n數量的千分之一。

使用過濾的額外好處是,咱們只須要開闢千分之一的內存,而後就能夠不改變原有點集的順序,也就是說若是題目還有不改變原有點集的要求,依然能夠知足 。

過濾付出的時間代價是線性的。那麼算法的總體複雜度爲O(n+ mlogm),而通常m值爲n的千分之一。那麼算法的平均複雜度爲O(n),空間複雜度O(m)。經過上述代碼實際對比,性能提升了大約20倍左右。使用O(m)空間,能夠確保不改變原有點集的順序。 可不能夠繼續優化,能夠能夠,優化永無止境,只要別輕易放棄思考。

本文更新了使用四叉樹的數據結構來求解問題,對於隨機測試點也有不錯的性能。

 

若是對你有所幫助,點個讚唄~  原創文章,請勿轉載,謝謝

相關文章
相關標籤/搜索