Talentd
FleetCode
Make and follow sheets.Sign in

My Calendar II solution and explanation

MediumArrayBinary SearchDesignOrdered Set9 min read63.3% acceptanceUpdated 9 Apr 2026
My Calendar II - Leetcode 731 - PythonNeetCodeIO · 8:13 · 13K viewsWatch on YouTube
9 more videos on this problem
  1. My Calendar II - Leetcode 731 - PythonNeetCodeIO · 8:13 · 13K views
  2. 花花酱 LeetCode 731. My Calendar II - 刷题找工作 EP113Hua Hua · 15:49 · 9K views
  3. My Calendar II | Simplest Explanation | Full Dry Run | Leetcode 731 | codestorywithMIKcodestorywithMIK · 32:41 · 8K views
  4. My Calendar 2 Leetcode 731. Medium || 2 Solutions example + complete explanation Google InterviewCode with Alisha · 17:47 · 6K views
  5. My Calendar II | Leetcode 731 | Line Sweep Technique Concepts & Questions - 5 | codestorywithMIKcodestorywithMIK · 15:40 · 5K views
  6. LeetCode 731. My Calendar II Explanation and Solutionhappygirlzt · 10:48 · 4K views
  7. My Calendar II | Leetcode 731Techdose · 28:06 · 3K views
  8. 731. My Calendar II | intervals | Line Sweep algo | Leetcode Daily Challenge | DSA | JavaShashCode · 22:36 · 2K views
  9. Mastering LC#731: My Calendar II | Avoiding Triple Bookings with Overlapping Intervals ExplainedHustlerCoder · 25:17 · 2K views
  10. LeetCode 731 My Calendar II - Java Solution With TreeMap - Microsoft Interview QuestionAlgorithmicIQ · 25:29 · 873 views

The problem

You are implementing a program to use as your calendar. We can add a new event if adding the event will not cause a triple booking.

A triple booking happens when three events have some non-empty intersection (i.e., some moment is common to all the three events.).

The event can be represented as a pair of integers startTime and endTime that represents a booking on the half-open interval [startTime, endTime), the range of real numbers x such that startTime <= x < endTime.

Implement the MyCalendarTwo class:

  • MyCalendarTwo() Initializes the calendar object.
  • boolean book(int startTime, int endTime) Returns true if the event can be added to the calendar successfully without causing a triple booking. Otherwise, return false and do not add the event to the calendar.

Example 1:

Input
["MyCalendarTwo", "book", "book", "book", "book", "book", "book"]
[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]
Output
[null, true, true, true, false, true, true]

Explanation
MyCalendarTwo myCalendarTwo = new MyCalendarTwo();
myCalendarTwo.book(10, 20); // return True, The event can be booked. 
myCalendarTwo.book(50, 60); // return True, The event can be booked. 
myCalendarTwo.book(10, 40); // return True, The event can be double booked. 
myCalendarTwo.book(5, 15);  // return False, The event cannot be booked, because it would result in a triple booking.
myCalendarTwo.book(5, 10); // return True, The event can be booked, as it does not use time 10 which is already double booked.
myCalendarTwo.book(25, 55); // return True, The event can be booked, as the time in [25, 40) will be double booked with the third event, the time [40, 50) will be single booked, and the time [50, 55) will be double booked with the second event.

Constraints:

  • 0 <= start < end <= 109
  • At most 1000 calls will be made to book.

Approach overview

Problem Overview: You need to design a calendar that supports booking events represented as half-open intervals [start, end). A booking is allowed if it does not create a triple booking. Double bookings are fine, but three overlapping events at the same time must be rejected.

Approach 1: Using Two Lists for Overlapping Intervals (O(n) per booking, O(n) space)

This approach tracks two lists: bookings (all accepted events) and overlaps (intervals that are already double booked). When a new event arrives, first check if it overlaps with anything in overlaps. If it does, the booking would create a triple overlap, so reject it immediately.

If it passes that check, iterate through existing bookings. For every interval that intersects with the new one, compute the intersection and add that range to overlaps. This records the newly created double-booked region. Finally, add the new interval to bookings. The key insight: instead of tracking counts everywhere, explicitly track the ranges where double booking already exists.

This method relies heavily on interval intersection logic and sequential scans using an array. Each booking may compare with all previous events, giving O(n) time per operation and O(n^2) total across all calls in the worst case.

Approach 2: Segment Tree for Interval Overlaps (O(log C) per booking, O(n log C) space)

When the time range becomes large (up to 10^9), scanning intervals repeatedly becomes inefficient. A dynamic segment tree solves this by storing the maximum booking count for each range. Each node represents a segment of time and keeps track of the maximum overlap inside that range.

For every booking request, query the tree to check the maximum overlap in [start, end). If the value is already 2, adding another event would create a triple booking, so reject it. Otherwise, update the range by incrementing its count. Lazy propagation allows efficient range updates without expanding the entire tree.

This method treats the problem as a range frequency update problem similar to sweep-line or prefix sum techniques, but implemented with a tree so queries stay logarithmic. Each booking runs in O(log C) time where C is the coordinate range.

Recommended for interviews: The two-list interval method is the most commonly expected answer. It is short, easy to reason about, and demonstrates clear understanding of interval overlaps. The segment tree approach shows deeper knowledge of advanced data structures and range queries, which can impress in system-heavy or follow-up interview questions.

Approach 1: Approach 1: Using Two Lists for Overlapping Intervals

In this approach, we maintain two separate lists: `single_booked` for bookings that have been added without conflicts, and `double_booked` for intervals where two events overlap. To add a new booking:

  1. First, check if it causes a triple booking by comparing it with the existing `double_booked` intervals. If any interval in `double_booked` overlaps with the new booking, it indicates a triple booking.
  2. If no triple booking is detected, proceed to update the lists:
  3. Check the new booking against `single_booked`. If there's an overlap with an interval in `single_booked`, this represents a double booking, and the overlapping part should be added to `double_booked`.
  4. Finally, add the current booking to `single_booked`.
class MyCalendarTwo:
    def __init__(self):
        self.single_booked = []
        self.double_booked = []

    def book(self, start: int, end: int) -> bool:
        for i, j in self.double_booked:
            if i < end and start < j:
                return False
        for i, j in self.single_booked:
            if i < end and start < j:
                self.double_booked.append((max(start, i), min(end, j)))
        self.single_booked.append((start, end))
        return True
import java.util.ArrayList;
import java.util.List;

class MyCalendarTwo {
    private List<int[]> single_booked;
    private List<int[]> double_booked;
    
    public MyCalendarTwo() {
        single_booked = new ArrayList<>();
        double_booked = new ArrayList<>();
    }
    
    public boolean book(int start, int end) {
        for (int[] interval : double_booked) {
            if (interval[0] < end && start < interval[1]) {
                return false;
            }
        }
        for (int[] interval : single_booked) {
            if (interval[0] < end && start < interval[1]) {
                double_booked.add(new int[] {Math.max(start, interval[0]), Math.min(end, interval[1])});
            }
        }
        single_booked.add(new int[] {start, end});
        return true;
    }
}
#include <vector>
#include <algorithm>

class MyCalendarTwo {
public:
    std::vector<std::pair<int, int>> single_booked;
    std::vector<std::pair<int, int>> double_booked;
    
    MyCalendarTwo() {}
    
    bool book(int start, int end) {
        for (const auto& interval : double_booked) {
            if (interval.first < end && start < interval.second) {
                return false;
            }
        }
        for (const auto& interval : single_booked) {
            if (interval.first < end && start < interval.second) {
                double_booked.push_back({std::max(start, interval.first), std::min(end, interval.second)});
            }
        }
        single_booked.push_back({start, end});
        return true;
    }
};
class MyCalendarTwo {
  constructor() {
    this.single_booked = [];
    this.double_booked = [];
  }

  book(start, end) {
    for (const [i, j] of this.double_booked) {
      if (i < end && start < j) {
        return false;
      }
    }
    for (const [i, j] of this.single_booked) {
      if (i < end && start < j) {
        this.double_booked.push([Math.max(start, i), Math.min(end, j)]);
      }
    }
    this.single_booked.push([start, end]);
    return true;
  }
}
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

typedef struct {
    int start;
    int end;
} Interval;

typedef struct {
    Interval *single_booked;
    int single_size;
    Interval *double_booked;
    int double_size;
} MyCalendarTwo;

MyCalendarTwo* myCalendarTwoCreate() {
    MyCalendarTwo* obj = malloc(sizeof(MyCalendarTwo));
    obj->single_booked = malloc(1000 * sizeof(Interval));
    obj->double_booked = malloc(1000 * sizeof(Interval));
    obj->single_size = 0;
    obj->double_size = 0;
    return obj;
}

bool book(MyCalendarTwo* obj, int start, int end) {
    for (int i=0; i<obj->double_size; i++) {
        if (obj->double_booked[i].start < end && start < obj->double_booked[i].end) {
            return false;
        }
    }
    for (int i=0; i<obj->single_size; i++) {
        if (obj->single_booked[i].start < end && start < obj->single_booked[i].end) {
            int overlap_start = obj->single_booked[i].start > start ? obj->single_booked[i].start : start;
            int overlap_end = obj->single_booked[i].end < end ? obj->single_booked[i].end : end;
            obj->double_booked[obj->double_size].start = overlap_start;
            obj->double_booked[obj->double_size].end = overlap_end;
            obj->double_size++;
        }
    }
    obj->single_booked[obj->single_size].start = start;
    obj->single_booked[obj->single_size].end = end;
    obj->single_size++;
    return true;
}

void myCalendarTwoFree(MyCalendarTwo* obj) {
    free(obj->single_booked);
    free(obj->double_booked);
    free(obj); 
}
using System.Collections.Generic;

public class MyCalendarTwo {
    private List<int[]> single_booked;
    private List<int[]> double_booked;

    public MyCalendarTwo() {
        single_booked = new List<int[]>();
        double_booked = new List<int[]>();
    }

    public bool Book(int start, int end) {
        foreach (var interval in double_booked) {
            if (interval[0] < end && start < interval[1]) {
                return false;
            }
        }
        foreach (var interval in single_booked) {
            if (interval[0] < end && start < interval[1]) {
                double_booked.Add(new int[] {Math.Max(start, interval[0]), Math.Min(end, interval[1])});
            }
        }
        single_booked.Add(new int[] {start, end});
        return true;
    }
}

This Python solution uses two lists to manage single and double bookings respectively. A potential triple booking is checked before any modification of `double_booked`. New booking is always appended to `single_booked` after checks.

Complexity

Time Complexity: O(N^2)
Space Complexity: O(N)

Try this approach in the editor

Approach 2: Approach 2: Segment Tree for Interval Overlaps

In this approach, we use a segment tree to efficiently keep track of overlapping intervals. By maintaining a segment tree, we can:

  1. Efficiently query the total number of active bookings over any particular interval to avoid triple bookings.
  2. Allow immediate updates whenever a new booking starts or ends, thus adjusting the tree in place.

This approach is optimal for datasets with large integer boundaries due to the logarithmic nature of segment trees for both update and query operations.

from sortedcontainers import SortedDict

class MyCalendarTwo:
    def __init__(self):
        self.timeline = SortedDict()

    def book(self, start: int, end: int) -> bool:
        self.timeline[start] = self.timeline.get(start, 0) + 1
        self.timeline[end] = self.timeline.get(end, 0) - 1

        ongoing = 0
        for count in self.timeline.values():
            ongoing += count
            if ongoing >= 3:
                self.timeline[start] -= 1
                self.timeline[end] += 1
                return False
        return True
import java.util.TreeMap;

class MyCalendarTwo {
    private TreeMap<Integer, Integer> timeline;

    public MyCalendarTwo() {
        timeline = new TreeMap<>();
    }

    public boolean book(int start, int end) {
        timeline.put(start, timeline.getOrDefault(start, 0) + 1);
        timeline.put(end, timeline.getOrDefault(end, 0) - 1);

        int ongoing = 0;
        for (int key : timeline.keySet()) {
            ongoing += timeline.get(key);
            if (ongoing >= 3) {
                timeline.put(start, timeline.get(start) - 1);
                timeline.put(end, timeline.get(end) + 1);
                return false;
            }
        }
        return true;
    }
}
#include <map>

class MyCalendarTwo {
public:
    std::map<int, int> timeline;

    MyCalendarTwo() {}

    bool book(int start, int end) {
        timeline[start]++;
        timeline[end]--;

        int ongoing = 0;
        for (const auto &entry : timeline) {
            ongoing += entry.second;
            if (ongoing >= 3) {
                timeline[start]--;
                timeline[end]++;
                return false;
            }
        }
        return true;
    }
};
class MyCalendarTwo {
  constructor() {
    this.timeline = new Map();
  }

  book(start, end) {
    this.timeline.set(start, (this.timeline.get(start) || 0) + 1);
    this.timeline.set(end, (this.timeline.get(end) || 0) - 1);

    let ongoing = 0;
    for (let time of [...this.timeline.keys()].sort((a, b) => a - b)) {
      ongoing += this.timeline.get(time);
      if (ongoing >= 3) {
        this.timeline.set(start, this.timeline.get(start) - 1);
        this.timeline.set(end, this.timeline.get(end) + 1);
        return false;
      }
    }
    return true;
  }
}
using System;
using System.Collections.Generic;

public class MyCalendarTwo {
    private SortedDictionary<int, int> timeline;
    
    public MyCalendarTwo() {
        timeline = new SortedDictionary<int, int>();
    }

    public bool Book(int start, int end) {
        if (!timeline.ContainsKey(start)) timeline[start] = 0;
        if (!timeline.ContainsKey(end)) timeline[end] = 0;
        
        timeline[start]++;
        timeline[end]--;
        
        int ongoing = 0;
        foreach (var kvp in timeline) {
            ongoing += kvp.Value;
            if (ongoing >= 3) {
                timeline[start]--;
                timeline[end]++;
                return false;
            }
        }
        return true;
    }
}

The Python solution employs `SortedDict` from `sortedcontainers` to manage bookings efficiently. With each booking event, the timeline is checked for overlap counts that could imply triple booking, altering the dictionary accordingly.

Complexity

Time Complexity: O(N log N)
Space Complexity: O(N)

Try this approach in the editor

Approach 3: Difference Array

We can use the concept of a difference array to record the booking status at each time point. Then, we traverse all the time points and count the booking status at the current time point. If the number of bookings exceeds 2, we return false. Otherwise, we return true.

The time complexity is O(n^2), and the space complexity is O(n), where n is the number of bookings.

class MyCalendarTwo {
    private tm: Record<number, number> = {};

    constructor() {}

    book(startTime: number, endTime: number): boolean {
        this.tm[startTime] = (this.tm[startTime] ?? 0) + 1;
        this.tm[endTime] = (this.tm[endTime] ?? 0) - 1;
        let s = 0;
        for (const v of Object.values(this.tm)) {
            s += v;
            if (s > 2) {
                if (--this.tm[startTime] === 0) {
                    delete this.tm[startTime];
                }
                if (++this.tm[endTime] === 0) {
                    delete this.tm[endTime];
                }
                return false;
            }
        }
        return true;
    }
}

/**
 * Your MyCalendarTwo object will be instantiated and called as such:
 * var obj = new MyCalendarTwo()
 * var param_1 = obj.book(startTime,endTime)
 */
type MyCalendarTwo struct {
	rbt *redblacktree.Tree[int, int]
}

func Constructor() MyCalendarTwo {
	return MyCalendarTwo{rbt: redblacktree.New[int, int]()}
}

func (this *MyCalendarTwo) Book(startTime int, endTime int) bool {
	merge := func(x, v int) {
		c, _ := this.rbt.Get(x)
		if c+v == 0 {
			this.rbt.Remove(x)
		} else {
			this.rbt.Put(x, c+v)
		}
	}

	merge(startTime, 1)
	merge(endTime, -1)

	s := 0
	for _, v := range this.rbt.Values() {
		s += v
		if s > 2 {
			merge(startTime, -1)
			merge(endTime, 1)
			return false
		}
	}
	return true
}

/**
 * Your MyCalendarTwo object will be instantiated and called as such:
 * obj := Constructor();
 * param_1 := obj.Book(startTime,endTime);
 */
Try this approach in the editor

Approach 4: Segment Tree

A segment tree divides the entire interval into multiple non-contiguous subintervals, with the number of subintervals not exceeding log(width). To update the value of an element, only log(width) intervals need to be updated, and these intervals are all contained within a larger interval that includes the element. When modifying intervals, a lazy mark is used to ensure efficiency.

  • Each node of the segment tree represents an interval;
  • The segment tree has a unique root node representing the entire statistical range, such as [1, N];
  • Each leaf node of the segment tree represents a unit interval of length 1, [x, x];
  • For each internal node [l, r], its left child is [l, mid] and its right child is [mid + 1, r], where mid = \lfloor(l + r) / 2\rfloor (i.e., floor division).

For this problem, the segment tree nodes maintain the following information:

  1. The maximum number of bookings within the interval v
  2. Lazy mark add

Since the time range is 10^9, which is very large, we use dynamic node creation.

The time complexity is O(n times log n), and the space complexity is O(n). Here, n is the number of bookings.

class Node {
    left: Node | null = null;
    right: Node | null = null;
    l: number;
    r: number;
    mid: number;
    v: number = 0;
    add: number = 0;

    constructor(l: number, r: number) {
        this.l = l;
        this.r = r;
        this.mid = (l + r) >> 1;
    }
}

class SegmentTree {
    private root: Node = new Node(1, 1e9 + 1);

    modify(l: number, r: number, v: number, node: Node | null = this.root): void {
        if (l > r || !node) {
            return;
        }
        if (node.l >= l && node.r <= r) {
            node.v += v;
            node.add += v;
            return;
        }
        this.pushdown(node);
        if (l <= node.mid) {
            this.modify(l, r, v, node.left);
        }
        if (r > node.mid) {
            this.modify(l, r, v, node.right);
        }
        this.pushup(node);
    }

    query(l: number, r: number, node: Node | null = this.root): number {
        if (l > r || !node) {
            return 0;
        }
        if (node.l >= l && node.r <= r) {
            return node.v;
        }
        this.pushdown(node);
        let v = 0;
        if (l <= node.mid) {
            v = Math.max(v, this.query(l, r, node.left));
        }
        if (r > node.mid) {
            v = Math.max(v, this.query(l, r, node.right));
        }
        return v;
    }

    private pushup(node: Node): void {
        if (node.left && node.right) {
            node.v = Math.max(node.left.v, node.right.v);
        }
    }

    private pushdown(node: Node): void {
        if (!node.left) {
            node.left = new Node(node.l, node.mid);
        }
        if (!node.right) {
            node.right = new Node(node.mid + 1, node.r);
        }
        if (node.add) {
            let left = node.left;
            let right = node.right;
            left.add += node.add;
            right.add += node.add;
            left.v += node.add;
            right.v += node.add;
            node.add = 0;
        }
    }
}

class MyCalendarTwo {
    private tree: SegmentTree = new SegmentTree();

    constructor() {}

    book(startTime: number, endTime: number): boolean {
        if (this.tree.query(startTime + 1, endTime) >= 2) {
            return false;
        }
        this.tree.modify(startTime + 1, endTime, 1);
        return true;
    }
}

/**
 * Your MyCalendarTwo object will be instantiated and called as such:
 * var obj = new MyCalendarTwo()
 * var param_1 = obj.book(startTime,endTime)
 */
type node struct {
	left      *node
	right     *node
	l, mid, r int
	v, add    int
}

func newNode(l, r int) *node {
	return &node{
		l:   l,
		r:   r,
		mid: int(uint(l+r) >> 1),
	}
}

type segmentTree struct {
	root *node
}

func newSegmentTree() *segmentTree {
	return &segmentTree{
		root: newNode(1, 1e9+1),
	}
}

func (t *segmentTree) modify(l, r, v int, n *node) {
	if l > r {
		return
	}
	if n.l >= l && n.r <= r {
		n.v += v
		n.add += v
		return
	}
	t.pushdown(n)
	if l <= n.mid {
		t.modify(l, r, v, n.left)
	}
	if r > n.mid {
		t.modify(l, r, v, n.right)
	}
	t.pushup(n)
}

func (t *segmentTree) query(l, r int, n *node) int {
	if l > r {
		return 0
	}
	if n.l >= l && n.r <= r {
		return n.v
	}
	t.pushdown(n)
	v := 0
	if l <= n.mid {
		v = max(v, t.query(l, r, n.left))
	}
	if r > n.mid {
		v = max(v, t.query(l, r, n.right))
	}
	return v
}

func (t *segmentTree) pushup(n *node) {
	n.v = max(n.left.v, n.right.v)
}

func (t *segmentTree) pushdown(n *node) {
	if n.left == nil {
		n.left = newNode(n.l, n.mid)
	}
	if n.right == nil {
		n.right = newNode(n.mid+1, n.r)
	}
	if n.add != 0 {
		n.left.add += n.add
		n.right.add += n.add
		n.left.v += n.add
		n.right.v += n.add
		n.add = 0
	}
}

type MyCalendarTwo struct {
	tree *segmentTree
}

func Constructor() MyCalendarTwo {
	return MyCalendarTwo{newSegmentTree()}
}

func (this *MyCalendarTwo) Book(startTime int, endTime int) bool {
	if this.tree.query(startTime+1, endTime, this.tree.root) >= 2 {
		return false
	}
	this.tree.modify(startTime+1, endTime, 1, this.tree.root)
	return true
}

/**
 * Your MyCalendarTwo object will be instantiated and called as such:
 * obj := Constructor();
 * param_1 := obj.Book(startTime,endTime);
 */
Try this approach in the editor

Complexity comparison

ApproachComplexity
Approach 1: Using Two Lists for Overlapping Intervals

Time Complexity: O(N^2)
Space Complexity: O(N)

Approach 2: Segment Tree for Interval Overlaps

Time Complexity: O(N log N)
Space Complexity: O(N)

Difference Array–
Segment Tree–

Complexity analysis

ApproachTimeSpaceWhen to Use
Two Lists for Overlapping IntervalsO(n) per booking, O(n^2) totalO(n)Best for interviews and moderate constraints; simple logic using interval comparisons
Segment Tree with Lazy PropagationO(log C) per bookingO(n log C)When time coordinates are large and frequent range queries or updates are required

Frequently asked questions

Is My Calendar II easy or hard?

My Calendar II is rated Medium because it requires careful handling of interval overlaps. The core trick is realizing that tracking double-booked regions prevents triple bookings. Once that insight is clear, the implementation becomes straightforward.

My Calendar II Python/Java solution

In Python or Java, the simplest implementation keeps two lists: bookings and overlaps. For each new interval, check conflicts with overlaps first, then compute intersections with existing bookings before inserting the event. The logic is identical across Python, Java, C++, C#, and JavaScript.

How to solve My Calendar II in O(n)?

Each booking can be handled in O(n) time by storing two arrays: booked intervals and double-booked intervals. First check the new event against all double-booked ranges to prevent triple overlaps. Then compute intersections with existing bookings and store those intersections as new double-booked ranges.

What is the best approach for My Calendar II?

The most practical approach uses two lists: one for all bookings and another for already double-booked intervals. Before adding a new event, check whether it overlaps with any interval in the double-booked list. This ensures triple bookings never occur. The method runs in O(n) time per booking and is the solution most interviewers expect.

Is My Calendar II asked at Google/Amazon/Meta?

My Calendar II is a common design-style interval problem seen in interviews at companies like Google, Amazon, and Meta. It tests understanding of interval overlaps, event scheduling logic, and sometimes advanced structures like segment trees or sweep-line techniques.

What data structure is used in My Calendar II?

Typical solutions use arrays or lists to store intervals and track overlaps. Advanced implementations may use a segment tree, ordered map, or sweep-line technique with prefix sums to maintain the number of active bookings across ranges.

What is the time complexity of My Calendar II?

The common two-list interval approach runs in O(n) time for each booking because the new event may be compared with every previous booking. Across n bookings the worst-case complexity becomes O(n^2). Using a segment tree reduces each operation to O(log C), where C is the time coordinate range.

Comments

Now write it yourselfClose this page and solve My Calendar II in the editor, without looking.
Practise My Calendar II