1. 前言
本文的一些图片, 资料 截取自编程之美
2. 问题描述
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. 运行结果
6. 总结
两种思路倒是没有什么难度, 但是在设计归并区间段 以及移除区间段的操作, 比较复杂一点
注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!