1. 前言

本文的一些图片, 资料 截取自编程之美

2. 问题描述

java怎么判断两个网段是否有重叠 java怎么判断区间重叠_i++

3. 问题分析

解法一 : 遍历目标区间, 从源区间中依次减去目标区间的区间段, 如果最后源区间还存在数据, 则说明源区间不是完全在目标区间中

解法二 : 首先归并目标区间的各个可以归并的区间段, 然后在采用思路一的方案, 遍历目标区间现在的区间段(们), 如果最后源区间还存在数据, 则说明源区间不是完全在目标区间中

4. 代码

/**
 * file name : Test28SectionCoincidence.java
 * created at : 9:35:55 AM May 26, 2015
 * created by 970655147
 */

package com.hx.test03;

public class Test28SectionCoincidence {

    // 判断区间是否重合问题
    public static void main(String []args) {

        Section src = new Section(new int[]{1 }, new int[]{16 } );
        Section target = new Section(new int[]{1, 2, 3 }, new int[]{2, 3, 9 } );

//      sectionCoincidence01(src, target);
        sectionCoincidence02(src, target);

    }

    // 思路1  从srcSection 中逐个减去target中的Section
    // 如果减去了target中所有的Section之后   src中没有了元素  则说明src是在target区间内
    public static void sectionCoincidence01(Section src, Section target) {
        remove(src, target);
        Log.log(src);
    }

    // 思路2  先归并target  在利用思路1的方式
    private static void sectionCoincidence02(Section src, Section target) {
        merge(target);
        remove(src, target);
        Log.log(src);
    }

    // from Section减去  to Section中所有的Section
    private static void remove(Section from, Section to) {
        for(int i=0; i<to.size; i++) {
            remove(from, to.start[i], to.end[i]);
        }
    }

    // from Section中减去(start, end) 构成的Section
    // 从from.section[i] 中移除(start, end)
    // 如果结果为null  则说明from.section[i]  被完整的移除了, 这时只需要从from中删除from.section[i]即可[注意需要更新i[i--], 因为删除了一个所有紧接着的下一个Section向前移动了一位]
        // 如果结果中只有一个Section   说明两个Section没有交集 或者存在开始端 或者末尾端的交集   更新from.section[i]为结果的Section
        // 如果结果中有两个Section    则说明第二个Section[(start, end)] 存在于from.section[i]中   这时候  将from.section[i]删除, 添加新加入的两个Section[注意需要更新i[i--], 因为删除了一个所有紧接着的下一个Section向前移动了一位]
    private static void remove(Section from, int start, int end) {
        for(int i=0; i<from.size; i++) {
            Section tmp = remove(from.start[i], from.end[i], start, end);
            if(tmp == null) {
                from.remove(i);
                i--;
            } else if(tmp.size == 1) {
                from.update(i, tmp.start[0], tmp.end[0]);
            } else if(tmp.size == 2) {
                from.remove(i);
                from.add(tmp);
                i--;
            }
        }
    }

    // (start01, end01) Section中减去(start02, end02) 构成的Section
    // 如果end02 < start01 或者 start02 > end01  说明这两个Section之间没有交集   则直接返回原Section
    // 如果start02>start01 并且end02<end01  则说明整个第二个Section都在第一个Section之中   则分为 两部分((start01, start02-1), (end02+1, end02) )
        // 否则如果start02<start01 并且end02>end01  则说明整个第一个Section都在第二个Section之中  返回null  因为第一个Section中已无可用数据 
        // 否则如果start02 < start01  则说明end02在(start01, end01) Section内   返回(end02+1, end01)
        // 否则如果end02 > end01  则说明start02在(start01, end01) Section内   返回(start01, start02-1)
        // 其他情况  返回null[但是应该没有其他情况了吧..]
    private static Section remove(int start01, int end01, int start02, int end02) {
        if((end02 < start01) || (start02 > end01) ) {
            return new Section(new int[]{start01 }, new int[]{end01 } );
        }

        if((start02 > start01) && (end02 < end01) ) {
            return new Section(new int[]{start01, start02-1}, new int[]{end02+1, end01 } );
        } else if((start02 <= start01) && (end01 <= end02) ) {
            return null;
        } else if (start02 <= start01) {
            return new Section(new int[]{end02+1 }, new int[]{end01 } );
        } else if (end02 >= end01) {
            return new Section(new int[]{start01 }, new int[]{start02-1 } );
        }

        return null;
    }

    // 归并Section
    // 遍历section的"区间"  例如当前是第一个元素   找到section中可以merge的其他"区间"  然后进行merge操作   直到找到第一个元素对应的所有的可merge的"区间"  然后向后遍历
    // 最后得到的section为  结果
    private static void merge(Section section) {    
        for(int i=0; i<section.size; i++) {
            int idx = -1;
            while(true ) {
                idx = findMergeable(section, section.start[i], section.end[i]);
                if(idx < 0) {
                    break;
                }

                Section tmp = merge(section.start[i], section.end[i], section.start[idx], section.end[idx]);
                // can't happen
                if(tmp == null) {

                } else {
//                  Log.log(i, idx);
                    section.remove(i);
                    // 如果i的索引 比idx小  则删除i的时候  idx对应的元素会向前移动一位
                    if(i < idx) {
                        idx --;
                    }
                    section.remove(idx);
                    section.add(tmp);
                    if(i > idx) {
                        i--;
                    }
                }
            } 
        }

    }

    // (start01, end01) Section归并(start02, end02) 构成的Section
    // 如果end02 < start01 或者 start02 > end01  说明这两个Section之间没有交集   则直接返null
    // 如果start02>start01 并且end02<end01  则说明整个第二个Section都在第一个Section之中   则合并为(start01, end01)
        // 否则如果start02<start01 并且end02>end01  则说明整个第一个Section都在第二个Section之中  则合并为(start02, end02)
        // 否则如果start02 < start01  则说明end02在(start01, end01) Section内   返回(start02, end01)
        // 否则如果end02 > end01  则说明start02在(start01, end01) Section内   返回(start01, end02)
        // 其他情况  返回null[但是应该没有其他情况了吧..]
    private static Section merge(int start01, int end01, int start02, int end02) {
        if((end02 < start01) || (start02 > end01) ) {
            return null;
        }

        if((start02 > start01) && (end02 < end01) ) {
            return new Section(new int[]{start01 }, new int[]{end01 } );
        } else if((start02 < start01) && (end01 < end02) ) {
            return new Section(new int[]{start02 }, new int[]{end02 } );
        } else if(start02 < start01) {
            return new Section(new int[]{start02 }, new int[]{end01 } );
        } else if (end02 > end01) {
            return new Section(new int[]{start01 }, new int[]{end02 } );
        }

        return null;
    }

    // 判断section是否与(start0, end0) 能够与归并, 找到第一个能够归并的区间段  
    public static int findMergeable(Section section, int start0, int end0) {
        int idx = -1;
        for(int i=0; i<section.size; i++) {
            if(section.start[i] < start0 || section.end[i] > end0) {
                idx = i;
                break;
            }
        }

        return idx;
    }

    // Section
    static class Section {
        // cap表示数组的容量, size表示有多少对start, end
        // 每一对(start[i], end[i])组合成一个 "区间"
        int[] start;
        int[] end;
        int cap;
        int size;

        // 初始化
        public Section() {
            super();
        }
        public Section(int[] start, int[] end) {
            this.start = start;
            this.end = end;
            size = start.length;
            cap = start.length;
        }

        // 这里 由于业务并不复杂  所以这里假设(start0, end0)  与已有的Section不存在重合的情况
        // 添加一个 "区间"  如果需要扩容  则进行扩容
        public void add(int start0, int end0) {     
            checkForArg(start0, end0);

            for(int i=0; i<start.length; i++) {
                if(start[i] == start0 && end[i] == end0) {
                    return ;
                }
            }

            // 当(cap-size) < 1的时候  需要进行扩容
            boolean needExpansion = cap - size < 1;
            if(needExpansion) {
                int[] newStart = new int[size + 2];
                int[] newEnd = new int[size + 2];

                System.arraycopy(start, 0, newStart, 0, size);
                System.arraycopy(end, 0, newEnd, 0, size);

                newStart[size] = start0;
                newEnd[size] = end0;

                this.start = newStart;
                this.end = newEnd;
                cap = start.length;
            } else {
                start[size] = start0;
                end[size] = end0;

            }

            size ++;
        }

        // 将section中的所有"区间"添加到当前Section中  如果需要扩容  则进行扩容
        public void add(Section section) {

            boolean needExpansion = cap - size < section.size;
            if(needExpansion) {
                int[] newStart = new int[size + section.size];
                int[] newEnd = new int[size + section.size];

                System.arraycopy(start, 0, newStart, 0, size);
                System.arraycopy(end, 0, newEnd, 0, size);

                this.start = newStart;
                this.end = newEnd;
                cap = start.length;
            } 

            for(int i=0; i<section.size; i++) {
                add(section.start[i], section.end[i]); 
            }
        }

        // 获取start0  对应的end  如果不存在 则抛出异常
        public int getStartForEnd(int start0) { 
            int idx = findByStart(start0);

            if(idx >= 0) {
                return end[idx];
            } else {
                throw new RuntimeException("have no this element with : " + start0 );
            }
        }

        // 更新idx处的(start, end)  如果不存在 则抛出异常
        public void update(int idx, int newStart, int newEnd) {
            checkForIdx(idx);

            start[idx] = newStart;
            end[idx] = newEnd;
        }

        // 更新值为(oldStart, oldEnd)的"区间"为(newStart, newEnd)  如果不存在 则抛出异常
        public void update(int oldStart, int oldEnd, int newStart, int newEnd) {
            checkForArg(oldStart, oldEnd);
            checkForArg(newStart, newEnd);

            int idx = findByStartAndEnd(oldStart, oldEnd);          
            update(idx, newStart, newEnd);
        }

        // 更新值为idx处的"区间"为(newStart, newEnd)  如果不存在 则抛出异常
        public void remove(int idx) {
            checkForIdx(idx);

            int needBeMove = size - idx - 1;
            System.arraycopy(start, idx+1, start, idx, needBeMove);
            System.arraycopy(end, idx+1, end, idx, needBeMove);
            // 如果start, end中引用的是对象的话  这里应该将其置空  help gc
            size --;
        }

        // 移除值为(oldStart, oldEnd)的"区间"  如果不存在 则抛出异常
        public void remove(int start0, int end0) {  
            checkForArg(start0, end0);

            int idx = findByStartAndEnd(start0, end0);
            remove(idx);
        }

        // 同过匹配start 来找到一个idx
        public int findByStart(int start0) {
            int idx = -1;
            for(int i=0; i<start.length; i++) {
                if(start[i] == start0) {
                    idx = i;
                    break ;
                }
            }

            return idx;
        }

        // 同过匹配start & end 来找到一个idx
        public int findByStartAndEnd(int start0, int end0) {
            int idx = -1;
            for(int i=0; i<start.length; i++) {
                if(start[i] == start0 && end[i] == end0) {
                    idx = i;
                    break ;
                }
            }

            return idx;
        }

        // 检查参数的大小
        private void checkForArg(int start0, int end0) {
            if(start0 > end0) {
                throw new RuntimeException("arg error, 'start' can't bigger than 'end0' ...");
            }
        }

        // 检查索引的位置是否合法
        private void checkForIdx(int idx) {
            if((idx >= size) || (idx < 0) ) {
                throw new RuntimeException("index error, index out of bounds ...");
            }
        }

        // toString  用于Debug
        public String toString() {
            StringBuilder sb = new StringBuilder(size << 4);
            for(int i=0; i<size; i++) {
                sb.append("idx : " + i + ", start : " + start[i] + ", end : " + end[i] + "\r\n");
            }

            if(size == 0) {
                sb.append("null");
            }

            return sb.toString();
        }
    }

}

5. 运行结果

java怎么判断两个网段是否有重叠 java怎么判断区间重叠_编程之美_02


java怎么判断两个网段是否有重叠 java怎么判断区间重叠_System_03

6. 总结

两种思路倒是没有什么难度, 但是在设计归并区间段 以及移除区间段的操作, 比较复杂一点

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!