題目描述
MyCalendar主要實現一個功能就是插入指定起始結束時間的事件,對於重合的次數有要求。
- MyCalendar I要求任意兩個事件不能有重疊的部分,如果插入這個事件會導致重合,則插入失敗,不進行插入;否則插入並返回true。
- My Calendar II要求任意三個事件不能有重疊的部分,但是兩個事件可以有重疊。同樣是成功返回true,失敗返回false。
- My Calendar III沒有要求,對於每次插入新事件,求當前總體最大的重疊事件的個數。
MyCalendar I
對於不能重合的事件,可以利用BST二叉搜索樹,每個節點代表一個事件區間,如果要插入的部分全部在當前節點的左側或者右側,則左遞歸或者右遞歸,否則,插入失敗。
如果是用循環實現,則需要保存插入節點的父節點以及是父節點的左子還是右子。循環實現的代碼如下:
class Node {//節點有起始結束時間和左右子節點
public Node(int start, int end) {
l = start;
r = end;
}
int l, r;
Node left, right;
}
Node root = null;
public boolean book(int start, int end) {
if (root == null) {
root = new Node(start, end);
} else {
Node cur = root;
Node pre = null;//父節點
boolean leftTag = false;//記錄該插入的節點是左子還是右子
while (cur != null) {
pre = cur;
if (end <= cur.l) {//應該在當前節點的左側,往左子遞歸
leftTag = true;
cur = cur.left;
} else if (start >= cur.r) {//應該在當前節點的右側,往右子遞歸
leftTag = false;
cur = cur.right;
} else {// 有重疊,不應該插入,返回false
return false;
}
}
if (leftTag) {//根據tag確定是父親的左子還是右子
pre.left = new Node(start, end);
} else {
pre.right = new Node(start, end);
}
}
return true;
}
My Calendar II
用TreeMap保存所有事件開始及終止的位置以及它們的次數,<start,次數(正)>,和<end,次數(負數)>。要插入這個事件的實現過程是:先插入這個事件,再檢測這個事件如果會導致>2個的區間有重合,則又取消插入,返回false,否則返回true。檢測的方法是:遍歷treemap中的entry(TreeMap是有序的),cnt+=entry.getValue()記錄當前時刻開始了還沒結束的事件個數。
TreeMap<Integer,Integer> treeMap;
public MyCalendarTwo() {
treeMap=new TreeMap<>();
}
public boolean book(int start, int end) {
int a=treeMap.getOrDefault(start,0);
int b=treeMap.getOrDefault(end,0);
treeMap.put(start,a+1);
treeMap.put(end,b-1);
int count=0;
for (Integer val : treeMap.values()) {
count+=val;//記錄當前已開始但未結束的事件個數
if(count>2){//如果事件個數>2,則說明有三個或者以上的重疊,不滿足條件,要取消剛剛的插入
if(a==0){//如果插入前的個數為0則可以直接刪除這條記錄,否則對次數進行更改
treeMap.remove(start);
}else{
treeMap.put(start,a);
}
if(b==0){
treeMap.remove(end);
}else{
treeMap.put(end,b);
}
return false;
}
}
return true;
}
My Calendar III
和第一題一樣使用BST,沒有重疊的區間的節點操作類似第一題,但是對於有重疊區間的節點,要進行分裂,把lser,lsre,slre,sler四種情況總結起來就是中間兩個值作為當前節點的起始和終止時間,且次數要增加,兩側分別進行左遞歸和右遞歸,次數根據lr還是se再外側來決定。【selr分別為待插入的start,end,當前節點的left和right】
注意,次數不能簡單的為1,對於分裂了lr的情況(如lser和lsre、sler),遞歸的時候次數可能要指定為當前節點的已有次數,而這個不是固定為1的。所以插入次數也要作為參數進行傳遞。
class Node {//節點有起始終止事件,左右子節點,這個時間區間的重疊次數
int left, right;
int count;
Node leftChild, rightChild;
public Node(int l, int r, int c) {
left = l;
right = r;
count = c;
}
}
int maxK = 1;//只要調用1次book,則最大記錄至少為1,所以可以直接初始化為1
Node root;
public int book(int start, int end) {
root = insert(root, start, end, 1);
return maxK;
}
private Node insert(Node root2, int start, int end, int c) {//由於需要修改節點的鏈接關系,所以需要返回節點
if (start >= end) {// no need to take action
return root2;
}
if (root2 == null) {
root2 = new Node(start, end, c);
return root2;
}
int l = root2.left;
int r = root2.right;
if (end <= l) {//一定落在當前節點的左側即左子樹上,進行左遞歸
root2.leftChild = insert(root2.leftChild, start, end, c);
} else if (start >= r) {
root2.rightChild = insert(root2.rightChild, start, end, c);
} else {
int[] a = new int[4];//給四個值排序
if (start <= l) {
a[0] = start;
a[1] = l;
} else {
a[0] = l;
a[1] = start;
}
if (end <= r) {
a[2] = end;
a[3] = r;
} else {
a[2] = r;
a[3] = end;
}
root2.left = a[1];//中間的兩個值作為當前節點的值
root2.right = a[2];
root2.leftChild = insert(root2.leftChild, a[0], a[1], start <= l ? c : root2.count);//左遞歸,如果start在外側,則次數為c;如果l在外側,則次數為當前節點的次數
root2.rightChild = insert(root2.rightChild, a[2], a[3], end >= r ? c : root2.count);
root2.count += c;//當前節點的次數要增加,並且根據大小情況選擇性的更新maxK
maxK = Math.max(maxK, root2.count);
}
return root2;
}