“ 关于合并区间类题型和实际业务的应用... ”
01 — 需求说明 统计某个企业的保险保障信息,关键信息包括: 企业维度的已保障天数、剩余保障天数。
业务领域上分为:保险方案领域、保单领域。保障信息对应保单对接状态为已生效、满期终止状态,不包括:未投保、投保中、投保成功等中间态保单。
一家企业可同时存在多个方案、对应多个保单。各方案保障时间可能存在保障时间或重叠或不连续的场景;
筛选出某个企业的保单对接状态为已生效、满期终止关联的方案保障时间可能存在以下场景:
-
保障时间不重叠,且无断保情况(周期连续)
方案1 [2022-02-01 00:00:00, 2022-02-09 23:59:59]
方案2 [2022-02-10 00:00:00, 2022-03-10 23:59:59]
方案3 [2022-03-11 00:00:00, 2022-04-10 23:59:59]
-
保障时间不重叠,周期不连续
方案1 [2022-03-27 00:00:00, 2022-03-28 23:59:59]
方案2 [2022-03-09 00:00:00, 2022-03-16 23:59:59]
方案3 [2022-02-01 00:00:00, 2022-02-09 23:59:59]
-
保障时间重叠
方案1 [2022-03-16 00:00:00, 2022-03-24 23:59:59]
方案2 [2022-03-21 00:00:00, 2022-03-30 23:59:59]
方案3 [2022-02-23 00:00:00, 2022-03-01 23:59:59]
02 — 实现思路 以上三类场景从处理逻辑复杂度,1<2<3。
场景1: 只需要考虑首尾两个时间点,再通过与当前时间比对来计算天数即可。
场景2: 存在周期不连续问题,需要先计算出当前时间属于哪个周期,再来计算天数即可。
场景3: 需要先合并时间区间,再按照场景ii处理即可。
合并完时间区块
1.[2022-02-23 00:00:00,2022-03-01 23:59:59]
2.[2022-03-16 00:00:00,2022-03-30 23:59:59]
当前时间:2022-03-29 02:53:31
已保障天数:21
剩余保障天数:1
合并时间区间代码:
力扣地址:
public List < List < Long >> merge(List < List < Long >> intervals) {
if (intervals.size() == 0) {
return new ArrayList < > ();
}
//按左边界按升序排序
intervals.sort(new Comparator < List < Long >> () {@
Override public int compare(List < Long > o1, List < Long > o2) {
return o1.get(0).compareTo(o2.get(0));
}
});
List < List < Long >> merged = new ArrayList < List < Long >> ();
for (int i = 0; i < intervals.size(); ++i) {
Long L = intervals.get(i).get(0), R = intervals.get(i).get(1);
//如果merged容器为空 或者 merged最后一个区块的右边界小于当前区块左边界
//此时为初始状态或无交集,加入新区块
if (merged.size() == 0 || merged.get(merged.size() - 1).get(1) < L) {
merged.add(Arrays.asList(L, R));
} else {
//否则 合并区块
merged.get(merged.size() - 1).set(1, Math.max(merged.get(merged.size() - 1).get(1), R));
}
}
return merged;
}
计算天数代码:
//计算剩余保障天数
public Long calcuRestInsureDays(Long target, List < List < Long >> blocks) {
Long res = 0 L;
for (int i = blocks.size() - 1; i >= 0; i--) {
//当前时间大于当前区块右边界,至此可以得到剩余保障天数
if (target > blocks.get(i).get(1)) {
break;
}
if (target >= blocks.get(i).get(0) && target <= blocks.get(i).get(1)) {
//在某块区间内,可跳出循环
res = blocks.get(i).get(1) - target + res;
break;
} else {
//endTime + 1ms 便于计算天数 23:59:59算完整一天
res += blocks.get(i).get(1) + 1 - blocks.get(i).get(0);
}
}
return res / DateUnit.DAY.getMillis();
}
//计算已保障时间-天数
public Long calcuInsuredDays(Long target, List < List < Long >> blocks) {
Long res = 0 L;
for (int i = 0; i < blocks.size(); i++) {
if (target < blocks.get(i).get(0)) {
break;
}
if (target >= blocks.get(i).get(0) && target <= blocks.get(i).get(1)) {
//在某块区间内,可跳出循环
//考虑当前时间不管是一天中哪个时间点, 都算作一天 +res+DateUnit.DAY.getMillis()
res = target - blocks.get(i).get(0) + res + DateUnit.DAY.getMillis();
break;
} else {
//endTime + 1ms 便于计算天数 23:59:59算完整一天
res += blocks.get(i).get(1) + 1 - blocks.get(i).get(0);
}
}
return res / DateUnit.DAY.getMillis();
}
测试方法:
@Test
public void calTimeBlock() {
Date date1 = DateUtil.beginOfYear(new Date());
DateTime dateTime = DateUtil.beginOfDay(date1);
//2022.01.01
System.out.println(dateTime);
List < List < Long >> times = new ArrayList < > ();
List < List < DateTime >> times2 = new ArrayList < > ();
for (int i = 0; i < 3; i++) {
int r1 = new Random().nextInt(100);
int r2 = r1 + new Random().nextInt(10);
DateTime dateTime1 = DateUtil.offsetDay(date1, r1);
DateTime dateTime2 = DateUtil.endOfDay(DateUtil.offsetDay(date1, r2));
times.add(Arrays.asList(dateTime1.getTime(), dateTime2.getTime()));
times2.add(Arrays.asList(dateTime1, dateTime2));
}
System.out.println(times);
System.out.println(times2);
List < List < Long >> blocks = merge(times);
for (List < Long > tmp: blocks) {
System.out.println(DateTime.of(tmp.get(0)) + " " + DateTime.of(tmp.get(1)));
}
//2022-03-29 02:53:31 1648493611000
long cur = 1648493611000 L;
System.out.println(calcuInsuredDays(cur, blocks));
System.out.println(calcuRestInsureDays(cur, blocks));
}