• 求重叠区间个数,某书某题错例分析


    大意就是我淘钱买了一本题集,觉得书中有些地方作者太随意,例子错得不严谨,一度阻碍阅读。作为消费者不得不拿出来说一说。
    本文本着不迷信,实事求是精神。
    本文编排如下:
    1.引用书中原例
    2.主观分析例子有错
    3.代码运行验证其错
    4.修正例子代码
    5.另一个求值代码
    现在开始。

    下面是书中原例引用:

    求重叠区间个数
    给定多个可能重叠的区间,找出重叠区间的个数。区间定义如下:
    class Interval {
        int start; // 起点
        int end; // 止点
        Interval(int a, intb) {
        start=a;
        end=b;
        }
    }
    举例
    输入:[1,5], [10,15], [5,10], [20,30]
    输出:3
    思路
    假如输入的四个区间:[start1,end1], [start2,end2], [start3,end3], [start4,end4], 我们不区分区间点的类型,而是根据区间点的大小排序,得到start1-start2-start3-end1-end2-end3-start4-end4。然后扫描上面结果,当遇到起点时,重叠个数加一,并且记录重叠个数的最大值;否则当遇到止点时,重叠个数减一。
    首先,要定义区间的类,实现Comparable接口,含有起点与止点的值和类型,还要重写用于排序的compareTo函数。
    class Point implements Comparable<Point> {
        int value; // 数值
        int type;  // 点的类型, 0为起点, 1为止点
        Point(int v, int t) {
            value = v;
            type = t;
        }
        // 还需要实现compareTo函数,以便排序
        public int compareTo(Point p) {
            if(this.value == p.value) {
                return 0;
            } else if (this.value > p.value) {
                return 1;
            } else {
                return -1;
            }
        }
    }
    
    int getOverlappingCount(Interval[] A) {
        int max=0, count=1;
        if(A==null || A.length==0) return max;
        Point[] points = new point[A.length*2];
        for(int i=0; i<A.length; i++) { // 转为可排序的点
            points[2*i] = new Point(A[i].start, 0);
            points[2*i+1] = new Point(A[i].end, 1);
        }
        Collections.sort(points); // 排序
        for(int i=0; i<points.length; i++) {
            if(points[i].type==0) {
                count++;  // 起点
                max = Math.max(max, count);
            } else {
                count--;
            }
        }
        return max;
    }

    作者给出的思路,有两步,首先对区间进行区间点的排序, 以及之后对重叠的判定。


    作者给出对重叠的判定是这样:扫描排序后的结果,“当遇到起点时,重叠个数加一,并且记录重叠个数的最大值;否则当遇到止点时,重叠个数减一”。但是怎么想都应该是当遇到起点时,计数加一,表示进入区间;当遇到止点时,计数减一,表示离开区间。计数不为0时,再次遇到另一个起点,表明区间发生了重叠,这时重叠数加一。通过扮演分析,作者给出的判定方法,对于一个有序的每三个地重叠的的区间序列中,只能得到一个3的max值。


    此外作者给出的排序方法也有问题,根据作者的思路,他所希望的排序结果是,当边界发生重叠时,起点值一定是排在止点值的前面。对于作者的举例输入“[1,5], [10,15], [5,10], [20,30]”中,如果排序是稳定的话,"[5"一定不会被排在"5]"的前面。所以比较方法中缺少了一个类型优先的次级比较条件。


    并且作者为得出他举例的结果3,在getOverlappingCount函数中作弊将count初始值为1。对于区间点排序的结果,第一个点一定是起点类型,即使输入中只有一个区间,或输入的是一些不重叠的区间,作者的代码都返回2。虽然区间发生重叠最少是2个不可能1个区间自己发生重叠,但是作者初始count=1明显不符合它自身的意义。

    现在将书中的java代码转换成是c++代码,运行验证。

    struct Interval
    {
        int start;
        int end;
        Interval(int a, int b)
        {
            start = a;
            end = b;
        }
    };
    
    struct Point
    {
        int value;
        int type;
        bool operator< (Point& _R)
        {
            return value < _R.value;
        }
    };
    
    int getOverlappingCount(std::vector<Interval>& A)
    {
        int max = 0;
        int count = 1;
        std::vector<Point> points;
        points.resize(A.size() * 2);
        for(int i = 0; i < A.size(); ++i)
        {
            points[2*i].value = A[i].start;
            points[2*i].type = 0;
            points[2*i+1].value = A[i].end;
            points[2*i+1].type = 1;
        }
        std::sort(points.begin(), points.end());
        for(int i = 0; i < points.size(); ++i)
        {
            printf("%s%d%s, ", points[i].type ? "":"[", points[i].value, points[i].type ? "]":"");
            if(points[i].type == 0)
            {
                count++;
                max = std::max(max, count);
            }
            else
            {
                count--;
            }
        }
        printf("
    ");
        return max;
    }

    我拟定三个测试区间序列
    A: [1,5], [10,15], [5,10], [20,30] // 书中举例的输入,前三个区间边界重叠于5和10
    B: [1,4], [10,15], [5,9], [20,30] // A变形成无重叠的四个区间
    C: [1,4], [10,15], [5,9], [1,12], [20,30] // B中加一个区间重叠前三个区间

    并将区间点排序的结果打印,我转换过来的c++代码运行结果如下:

    A:
    sort: [1, 5], [5, [10, 10], 15], [20, 30]
    olap: 3
    B:
    sort: [1, 4], [5, 9], [10, 15], [20, 30]
    olap: 2
    C:
    sort: [1, [1, 4], [5, 9], [10, 12], 15], [20, 30]
    olap: 3

    从运行结果中可以看到,对于A序列作者因为作弊将count初始值为1得到了结果3。对于B序列,如我上面分析,它对并无重叠的区间序列,返回了2。对于C序列,有4个重叠区间,却只返回了3。

    下面的代码是修改后的版本,我对比较方法加入次级条件,以及改换重叠判定处理。

    int getOverlappingCount_revision(std::vector<Interval>& A)
    {
        int olap = 0;
        int count = 0;
        std::vector<Point> points;
        points.resize(A.size() * 2);
        for(int i = 0; i < A.size(); ++i)
        {
            points[2*i].value = A[i].start;
            points[2*i].type = 0;
            points[2*i+1].value = A[i].end;
            points[2*i+1].type = 1;
        }
        // comparation
        struct comparation
        {
            bool operator() (Point& _L, Point& _R)
            {
                if(_L.value == _R.value)
                    return _L.type < _R.type;
                return _L.value < _R.value;
            }
        };
        std::sort(points.begin(), points.end(), comparation());
        for(int i = 0; i < points.size(); ++i)
        {
            printf("%s%d%s, ", points[i].type ? "":"[", points[i].value, points[i].type ? "]":"");
            if(points[i].type == 0)
            {
                count++;
                if(count > 1)
                {
                    if(olap == 0)
                        olap = 2;
                    else
                        olap += 1;
                }
            }
            else
            {
                count--;
            }
        }
        printf("
    ");
        return olap;
    }

    将区间点排序的结果打印,代码运行结果如下:

    A:
    sort: [1, [5, 5], [10, 10], 15], [20, 30]
    olap: 3
    B:
    sort: [1, 4], [5, 9], [10, 15], [20, 30]
    olap: 0
    C:
    sort: [1, [1, 4], [5, 9], [10, 12], 15], [20, 30]
    olap: 4

    下面是我另一版本的代码,并不对区间点排序,而只对区间起点排序,一边合并区间一边对重叠区间进行计数。

    int getOverlappingCount_interval(std::vector<Interval>& _A)
    {
        int olap = 0;
        int count = 0;
        std::vector<Interval> A = _A;
    
        // comparation
        struct comparation
        {
            bool operator() (Interval& _L, Interval& _R)
            {
                return _L.start < _R.start;
            }
        };
        std::sort(A.begin(), A.end(), comparation());
        Interval merge = A[0];
        Interval next(0, 0);
        for(int i = 1; i < A.size(); ++i)
        {
            next = A[i];
            if(merge.end >= next.start)
            {
                merge.end = std::max(merge.end, next.end);
                if(olap == 0)
                    olap = 2;
                else
                    olap++;
            }
            else
            {
                printf("[%d, %d], ", merge.start, merge.end);
                merge = next;
            }
        }
        printf("[%d, %d]
    ", merge.start, merge.end);
        return olap;
    }

    将合并后的不重叠区间打印,代码运行结果如下:

    A:
    sort: [1, 15], [20, 30]
    olap: 3
    B:
    sort: [1, 4], [5, 9], [10, 15], [20, 30]
    olap: 0
    C:
    sort: [1, 15], [20, 30]
    olap: 4

    书中给这一题的难度为2星简单,可能因此作者不太关心,而太过随意。

  • 相关阅读:
    Spyder 快捷键大全
    上传代码到github,出现 git@github.com: Permission denied (publickey) 错误
    Linux ubuntu 安装 openssh-server 报错
    win10删除「此电脑」中的文档、视频、音乐、下载、图片、桌面等6个文件夹的方法
    IDEA 光标闪烁问题
    委托、Action泛型委托、Func泛型委托、Predicate泛型委托的用法
    C#6.0的新语法特性
    创建多个网站
    发送短信功能(C#)
    VS开发Windows服务
  • 原文地址:https://www.cnblogs.com/bbqzsl/p/5104309.html
Copyright © 2020-2023  润新知