大纲
- 题目
- 解法
- Range
- add
- remove
- Tools
- RangeList
- add
- remove
- 代码
最近看到一个比较有意思的面试题。题目不算难,但是想把效率优化做好,也没那么容易。
我们先看下题目
题目
// Task: Implement a class named 'RangeList'
// A pair of integers define a range, for example: [1, 5). This range includes integers: 1, 2, 3, and 4.
// A range list is an aggregate of these ranges: [1, 5), [10, 11), [100, 201)
/**
** NOTE: Feel free to add any extra member variables/functions you like.
*/
class RangeList {/**** Adds a range to the list* @param {Array<number>} range - Array of two integers that specify beginning andend of range.*/add(range) {// TODO: implement this}/**** Removes a range from the list* @param {Array<number>} range - Array of two integers that specify beginning andend of range.*/remove(range) {// TODO: implement this}/**** Convert the list of ranges in the range list to a string* @returns A string representation of the range list*/toString() {// TODO: implement this}
}
// Example run
const rl = new RangeList();
rl.toString(); // Should be ""
rl.add([1, 5]);
rl.toString(); // Should be: "[1, 5)"
rl.add([10, 20]);
rl.toString(); // Should be: "[1, 5) [10, 20)"
rl.add([20, 20]);
rl.toString(); // Should be: "[1, 5) [10, 20)"
rl.add([20, 21]);
rl.toString(); // Should be: "[1, 5) [10, 21)"
rl.add([2, 4]);
rl.toString(); // Should be: "[1, 5) [10, 21)"
rl.add([3, 8]);
rl.toString(); // Should be: "[1, 8) [10, 21)"
rl.remove([10, 10]);
rl.toString(); // Should be: "[1, 8) [10, 21)"
rl.remove([10, 11]);
rl.toString(); // Should be: "[1, 8) [11, 21)"
rl.remove([15, 17]);
rl.toString(); // Should be: "[1, 8) [11, 15) [17, 21)"
rl.remove([3, 19]);
rl.toString(); // Should be: "[1, 3) [19, 21)
这题大体的意思是:设计一个RangeList类,它保存了一批左闭右开的区间。它支持add操作,可以新增一个包含区间,但是可能会影响之前的区间,比如之前的区间是:[3,5) [7,9),新增区间[5,7)之后,区间就变成[3,9);它还支持remove操作,可以删除一个区间,也可能影响之前的区间,比如之前的区间是[3,9),删除[5,7)之后,变成[3,5) [7,9)。
还有一种特殊区间需要考虑,就是左右值相等的区间。比如[5,5)代表的是一个空区间。
解法
Range
首先我们设计一个Range类,它只是单个区间。
add
如果对其进行add操作,即新增一个区间,则要考虑这两个区间是否相交。如果相交,则返回一个重新整合过的区间;如果不相交,则抛出异常。
# add the other range to this range.For example, [1, 5) add [5, 7) is [1, 7).# @param other - the other range to add to this range# @return - the new range after adding# @throws TypeError if other is not a Range object or a list of integers# @throws ValueError if other is not a list of 2 integers# @throws TypeError if other range is not overlap with this rangedef add(self, other) -> object:other = self.conv(other)if self.end < other.start or self.start > other.end:raise ValueError("other range must be overlap with this range")if self.start >= other.start and self.end <= other.end:return Range(other.start, other.end)if self.start >= other.start and self.end > other.end:return Range(other.start, self.end)if self.start < other.start and self.end <= other.end:return Range(self.start, other.end)if self.start < other.start and self.end > other.end:return Range(self.start, self.end)
remove
如果对其进行remove操作,即删除一个区间,也要考虑两个区间相交的情况。如果相交,则返回一个Range数组,其中可能0~2个区间。
# remove the other range from this range.For example, [1, 5) remove [2, 3) is [1, 2) [3, 5).# @param other - the other range to remove from this range.the other range must be a Range object or a list of 2 integers# @return - a list of Range objects that are the result of removing other from this range# @throws TypeError if other is not a Range object or a list of integers# @throws ValueError if other is not a list of 2 integersdef remove(self, other) -> list:other = self.conv(other)if self.end < other.start or self.start > other.end:return [self]if self.start >= other.start and self.end <= other.end:return []if self.start >= other.start and self.end > other.end:return [Range(other.end, self.end)]if self.start < other.start and self.end <= other.end:return [Range(self.start, other.start)]if self.start < other.start and self.end > other.end:return [Range(self.start, other.start), Range(other.end, self.end)]
Tools
在设计完Range类后,我们还需要解决下面两个问题:
- 被修正的区间有哪些
- 需要调整位置的区间有哪些
上图中标红的表示可能要调整区间的区域。
对于没有没有需要调整的区域,则要找到临近的区域。比如上图中第一组中,[7,8)需要找到[5,6)这组区间。如果是add操作,则需要将[7,8)插入到区间数组的[5,6)后面。
#!/usr/bin/env python
# -*- coding: utf-8; py-indent-offset:4 -*-
# ======================================================================================================================
# Copyrigth (C) 2024 fangliang <304646673@qq.com>
#
# This program is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later
# version.
#
# ======================================================================================================================from rangelist.range import Rangeclass Tools(object): # search the index of the range which contains the value.First value is the index of the range where to compare with the value, # second value is True if the range contains the value, False otherwise. # @param ranges - the list of ranges# @param value - the value to search# @param start_index - the start index of the ranges to search# @return the index of the range where to compare with the value, True if the range contains the value, False otherwise@staticmethoddef search(ranges, value, start_index = 0):if start_index < 0:start_index = 0end_index = len(ranges) - 1while start_index <= end_index:mid = (start_index + end_index) // 2if ranges[mid].start <= value and ranges[mid].end >= value:return (mid, True)elif ranges[mid].end < value:start_index = mid + 1else:end_index = mid - 1return (end_index, False)# search the index of the ranges which overlap with the search range.# First value is the index of the range where to compare with the value, second value is True if the range contains the value,# False otherwise.# @param ranges - the list of ranges# @param search_range - the range to search# @return a list of (index, overlap) of the ranges which overlap with the search range@staticmethoddef search_overlap(ranges, search_range):if search_range.start == search_range.end:return []start = Tools.search(ranges, search_range.start)end = Tools.search(ranges, search_range.end, start[0])index_list = [start]for i in range(start[0]+1, end[0]+1):index_list.append((i, True))return index_list
search_overlap方法返回的数据如下:
[(-1, False), (0, True), (1, True)]
-1代表对比的区间(可能是新增或者删除)的起始值在第0个区间的左侧。
True和False表示区间是否会调整(因为有覆盖)。
RangeList
RangeList用于保存一组Range序列。
这题的解法也主要依赖于其add和remove方法。
add
# add a range to the list.For example, [[1, 5)] add [2, 3) is [[1, 5)].[[1, 5)] add [6, 8) is [[1, 5) [6, 8)].# @param other - the other range to compare with# @return True if the other range is overlap with this range, False otherwise# @throws TypeError if other is not a Range object or a list of integers# @throws ValueError if other is not a list of 2 integersdef add(self, other):other = Range.conv(other)indexes = Tools.search_overlap(self.ranges, other)del_start_index = -1for i in indexes:if i[1]:other = self.ranges[i[0]].add(other)if -1 == del_start_index:del_start_index = i[0]if -1 != del_start_index:del self.ranges[del_start_index : indexes[-1][0]+1]self.ranges.insert(del_start_index, other)elif len(indexes) > 0:self.ranges.insert(indexes[0][0]+1, other)return self
add方法会让一个Range不停“合并”被其覆盖的Range。然后删除被覆盖的Range,把新组合的Range插入到第一个覆盖的Range位置。
如果没有覆盖的区间,则在适当的位置插入。
remove
# remove the other range from this range.For example, [[1, 5) [10, 14)]] remove [2, 3) is [[1, 2) [3, 5) [10, 14)]].# @param other - the other range to remove from this range# @return - the new range after removing# @throws TypeError if other is not a Range object or a list of integers# @throws ValueError if other is not a list of 2 integersdef remove(self, other):other = Range.conv(other)indexes = Tools.search_overlap(self.ranges, other)del_start_index = -1range_list_from_remove_all = []for i in indexes:if i[1]:range_list_from_remove = self.ranges[i[0]].remove(other)if range_list_from_remove != None:range_list_from_remove_all.extend(range_list_from_remove)if -1 == del_start_index:del_start_index = i[0]if -1 != del_start_index:del self.ranges[del_start_index : indexes[-1][0]+1]self.ranges[del_start_index:del_start_index] = range_list_from_remove_allreturn self
remove方法则是让Range List中Range不停remove待删除Range,最后把切割的Range重新插入到Range List中。
代码
https://github.com/f304646673/rangelist.git