力扣每日一题0620-715. Range 模块

142 阅读1分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第20天,点击查看活动详情

Range模块是跟踪数字范围的模块。设计一个数据结构来跟踪表示为 半开区间 的范围并查询它们。

半开区间 [left, right) 表示所有 left <= x < right 的实数 x

实现 RangeModule 类:

  • RangeModule() 初始化数据结构的对象。
  • void addRange(int left, int right) 添加 半开区间 [left, right),跟踪该区间中的每个实数。添加与当前跟踪的数字部分重叠的区间时,应当添加在区间 [left, right) 中尚未跟踪的任何数字到该区间中。
  • boolean queryRange(int left, int right) 只有在当前正在跟踪区间 [left, right) 中的每一个实数时,才返回 true ,否则返回 false
  • void removeRange(int left, int right) 停止跟踪 半开区间 [left, right) 中当前正在跟踪的每个实数。

示例 1:

输入
["RangeModule", "addRange", "removeRange", "queryRange", "queryRange", "queryRange"]
[[], [10, 20], [14, 16], [10, 14], [13, 15], [16, 17]]
输出
[null, null, null, true, false, true]

解释
RangeModule rangeModule = new RangeModule();
rangeModule.addRange(10, 20);
rangeModule.removeRange(14, 16);
rangeModule.queryRange(10, 14); 返回 true (区间 [10, 14) 中的每个数都正在被跟踪)rangeModule.queryRange(13, 15); 返回 false(未跟踪区间 [13, 15) 中像 14, 14.03, 14.17 这样的数字)rangeModule.queryRange(16, 17); 返回 true (尽管执行了删除操作,区间 [16, 17) 中的数字 16 仍然会被跟踪)

解决方法并无特别之处,同样用到了二分搜索。唯一须要注意的地方是,在addRange和removeRange中,所有的边界情况一次分析完成,保证每次调用只会调用一次splice方法,避免额外开销。

function isEven(val) {
    return val % 2 == 0;
}

function isOdd(val) {
    return !isEven(val);
}

var RangeModule = function () {
    this.ranges = [];
};

RangeModule.prototype.binarySearch = function (val) {
    let low = 0, high = this.ranges.length - 1;
    while (low <= high) {
        const mid = Math.trunc((high + low) / 2);
        const curValue = Math.abs(this.ranges[mid]);
        switch (true) {
            case val < curValue:
                high = mid - 1;
                break;
            case val > curValue:
                low = mid + 1;
                break;
            case val == curValue:
                return mid;
        }
    }
    return low;
}

RangeModule.prototype.addRange = function (left, right) {
    const startIndex = this.binarySearch(left);
    const endIndex = this.binarySearch(right);
    const insertNums = [];
    let spliceCount = endIndex - startIndex;
    if (isEven(startIndex)) {
        insertNums.push(left);
    }
    if (isEven(endIndex)) {
        if (endIndex < this.ranges.length && right == this.ranges[endIndex]) {
            spliceCount++;
        }
        else {
            insertNums.push(-right);
        }
    }
    this.ranges.splice(startIndex, spliceCount, ...insertNums);
};

RangeModule.prototype.queryRange = function (left, right) {
    const startIndex = this.binarySearch(left);
    const endIndex = this.binarySearch(right);
    if (startIndex < this.ranges.length) {
        if (isEven(startIndex)) {
            if (left == this.ranges[startIndex]) {
                return startIndex + 1 == endIndex;
            }
        }
        else {
            if (left < Math.abs(this.ranges[startIndex])) {
                return startIndex == endIndex;
            }
        }
    }
    return false;
};

RangeModule.prototype.removeRange = function (left, right) {
    const startIndex = this.binarySearch(left);
    const endIndex = this.binarySearch(right);
    const insertNums = [];
    let spliceCount = endIndex - startIndex;
    if (isOdd(startIndex)) {
        insertNums.push(-left);
    }
    if (isOdd(endIndex)) {
        if (right == Math.abs(this.ranges[endIndex])) {
            spliceCount++;
        }
        else {
            insertNums.push(right);
        }
    }
    this.ranges.splice(startIndex, spliceCount, ...insertNums);
};