区间求交集算法

来源:互联网 发布:spss多维数据分析 编辑:程序博客网 时间:2024/05/29 23:24

最近要去网易笔试,做往年笔试题的时候遇到一个比较难搞的,原题:有两个有序的集合,集合的每个元素都是一段范围,求其交集,例如集合{[4,8],[9,13]}和{[6,12]}的交集为{[6,8],[9,12]}

在网上看了一下没有比较巧妙地解决方案,于是自己想了一个,思路如下,我假设了两个区间集合A{[2,4],[8,14],[15,20],[22,25]},B{[1,5],[8,16],[19,25],[30,40]}

将这两个集合表示在图上就是下面这样子的:



我们可以画一条线,这条线是假想的,你可以把这条线想象是一个传感器,它能统计穿过了多少个点,比如下图线就是有一个点穿过了线。




这样我们就能通过穿过线的点的个数来判断是否区间重合了。我们有结论,当穿过线的点多余一个的时候(必须有两个点,一个来自A,一个来自B)那么区间重合,这条线有一个专业名词叫做扫线,这名字很形象。


好的,思路讲完了,直接看代码:


public class SetSort {

    //声明两个数组,分别表示集合A,B

    ArrayList<Range> ralist = new ArrayList<Range>();
    ArrayList<Range> rblist = new ArrayList<Range>();

   //这里是初始化一下数据,没啥可说的
    public void DataInial() {


      
        Range r1 = new Range();
        r1.left = 2;
        r1.right = 4;


        Range r2 = new Range();
        r2.left = 8;
        r2.right = 14;




        Range r3 = new Range();
        r3.left = 15;
        r3.right = 20;


        Range r4 = new Range();
        r4.left = 22;
        r4.right = 25;


        Range r5 = new Range();
        r5.left = 1;
        r5.right = 5;


        Range r6 = new Range();
        r6.left = 8;
        r6.right = 16;


        Range r7 = new Range();
        r7.left = 19;
        r7.right = 25;


        Range r8 = new Range();
        r8.left = 30;
        r8.right = 40;


        ralist.add(r1);
        ralist.add(r2);
        ralist.add(r3);
        ralist.add(r4);


        rblist.add(r5);
        rblist.add(r6);
        rblist.add(r7);
        rblist.add(r8);
    }


    public static void main(String[] args) {

//交集集合
        ArrayList<Range> list = new ArrayList<Range>();
        SetSort ss = new SetSort();
        ss.DataInial();
        list = Help.FindRange(ss.ralist, ss.rblist);
        for (Range r : list) {
            System.out.println("[" + r.left + "," + r.right + "]");
        }
    }
}




class Help {
    public static ArrayList<Range> FindRange(ArrayList<Range> ra, ArrayList<Range> rb) {

        int max = 0;//这个值表示count所要循环的上界
        ArrayList<Range> rclist = new ArrayList<Range>();//交集的集合
        if (ra.get(ra.size() - 1).right > rb.get(rb.size() - 1).right) {
            max = rb.get(rb.size() - 1).right;
        } else {
            max = ra.get(ra.size() - 1).right;
        }

        int count = 0;//这个值就是扫线的坐标
        if (ra.get(0).left > rb.get(0).left) {
            count = ra.get(0).left;
        } else {
            count = rb.get(0).left;
        }

        int left = 0;
        int right = 0;

        boolean flage = false;

        //核心算法
        for (; count < max+1; count++) {
            //如果扫线刚好有两个点穿过,则记载第一次的值,作为区间的左值,当穿过扫线的点由多个变为一个或者0个的时候,

    //则记录正要变化的那个count的值作为区间的右值

    //这里面的flage用来防止重复赋值给区间
            if (IS_ELEMENT(ra, count) && IS_ELEMENT(rb, count)) {
                if (!flage) {
                    left = count;
                    flage = true;
                }
            } else {
                if (flage) {
                    right = count;
                    flage = false;

   //new一个区间,添加到交集集合中去
                    rclist.add(new Range(left, right));
                }
            }
        }
        return rclist;
    }

    //这个方法用来判断,当前扫线的值,是否在集合某个元素中
    public static boolean IS_ELEMENT(ArrayList<Range> list, int count) {
        boolean flag = false;
        for (int i = 0; i < list.size(); i++) {
            if (list.get(i).right <= count) {
                continue;
            }
            if (list.get(i).left > count) {
                break;
            }
            if (list.get(i).left <= count && list.get(i).right >= count) {
                flag = true;
                break;
            }
        }
        return flag;
    }
}

 //数组中的元素结构
class Range {
    public Range() {
    }


    int right = 0;
    int left = 0;


    public Range(int left, int right) {
        this.right = right;
        this.left = left;
    }
}


这个算法想了好几个小时,总算是想出来了,其实这个算法有个缺点,就是当集合中的区间比较少,但是区间长度比较大的时候,count会循环很多次,比如A{[2,6],[10,100000]};B{[3,8],[9,100000]}这个区间的交集是[3,6],[10,100000]这意味着count需要循环100000次左右,这是非常浪费的。有兴趣的朋友可以自己试着改进一下我的算法。



0 0