题目
示例
思路
- 离线查询:
- 输入的结果数组queries[]是无序的。如果我们按照输入的queries[]本身的顺序逐个查看,时间复杂度会比较高。 于是,我们将queries[]数组按照数值大小,由小到大逐个查询,这种方法称之为离线查询。
- 位运算:
- 离线查询的时候,queries[]可以按照数值大小逐个查看,但最终返回的结果数组,是需要按照queries[]相同的顺序输出的,也就是说,queries[]数组的下标信息不能被丢失。 此时,我们可以考虑,将每一个queries[x]放在一个long int数值的高32位,将下标x放在这个long int数值的低32位。 由此,我们可以看出,这些新的long int数值,它们之间的相互大小关系,取决于高32位的数值,只有在高32位相等的时候,才需要比较低32位。如此,我们在比较大小时,优先看高32位,然后想要获取其下标信息时,直接取低32位。 类似的,我们在处理输入的intervals[]数组时也类似处理。将每一个intervals[x]的左边缘放在一个long int数值的高32位,右边缘放在这个long int数值的低32位。这样,这些long int数值,它们之间的相互大小关系,就取决于左边缘的大小关系,而想要获取右边缘时,直接取低32位即可。
- 小根堆(优先队列):
- 我们要由小到大处理queries[]数组和intervals[]数组,可以考虑排序,也可以考虑小根堆,这里采用小根堆。
【具体实现】
- 将输入的intervals[]数组,每一个intervals[x]的左边缘放在一个long int数值的高32位,右边缘放在这个long int数组的低32位,然后long int数值push入小根堆intervalsHeap。
- 将输入的queries[]数组,每一个queries[x]的数值放在一个long int数值的高32位,下标x放在这个long int数值的低32位,然后long int数值push入小根堆queriesHeap。
- 逐个从queriesHeap中pop出堆顶元素,这个堆顶元素的高32位就是上面第2步push入堆时,每一个queries[x]的数值,赋值给变量position,低32位就是下标x。
- 3.1 从intervalsHeap中pop出所有左边缘(高32位)小于等于position的区间。
- 3.2 上面3.1中pop出的每一个区间,把所有右边缘(低32位)大于等于position的区间,都push入一个有效区间小根堆validHeap中。这个validHeap中,以区间的range为高32位,右边缘为低32位,以保证堆顶的区间range始终是最小的。
- 3.3 如果validHeap堆顶区间的右边缘(低32位)小于当前的position,则将其弹出。
- 3.4 经过上面3.3的操作后,如果validHeap仍然非空,则说明存在包含这个position的区间,且validHeap的堆顶区间的range(高32位)就是题目要求的结果。
代码
/* 几个全局定义的说明。INVALID_VALUE: 无效值-1。小根堆中,根节点的父节点使用这个无效值。题目中结果数组里的无效值也是这个-1。FATHER_NODE(x): 小根堆中,每一个节点的父节点。其中,根节点的父节点是无效值。LEFT_NODE(x): 小根堆中,每一个节点的左子节点。当它大于等于堆size的时候无效。RIGHT_NODE(x): 小根堆中,每一个节点的右子节点。当它大于等于堆size的时候无效。MERGE_LONG(x, y): 两个int数值合并成一个long int数值,其中x占据高32位,y占据低32位。GET_HIGHER32(x): 获取一个long int数值的高32位值。GET_LOWER32(x): 获取一个long int数值的低32位值。 */
#define INVALID_VALUE -1
#define FATHER_NODE(x) ((0 == (x)) ? INVALID_VALUE : ((x) - 1 >> 1))
#define LEFT_NODE(x) (((x) << 1) + 1)
#define RIGHT_NODE(x) (((x) << 1) + 2)
#define MERGE_LONG(x, y) ((long int)(x) << 32 | (long int)(y))
#define GET_HIGHER32(x) ((x) >> 32)
#define GET_LOWER32(x) ((x) & 0xFFFFFFFF)/* 小根堆的结构定义,里面包括堆空间数组arr[],和堆size。 */
typedef struct
{long int *arr;int arrSize;
}
HeapStruct;static void heapPush(HeapStruct *heap, long int t);
static void heapPop(HeapStruct *heap);/* 主函数的几个变量说明:x: 循环下标。position: queries数组中的数值。因为这个数组会带着下标一起入堆,在出堆时,把它的实际数值取出到这个position。range: 题目中定义的一个左闭右闭区间的大小,等于“右边缘 - 左边缘 + 1”。t: long int临时数值。arr1,arr2,arr3: 几个小根堆中要用到的堆空间数组。intervalsHeap: 将输入的intervals数组加入到这个小根堆中,左边缘放高32位,右边缘放低32位。queriesHeap: 将输入的queries数组加入到这个小根堆中,数值放高32位,下标放低32位。validHeap: 包含着当前position的有效区间的小根堆,区间range放高32位,右边缘放低32位。 */
int *minInterval(int **intervals, int intervalsSize, int *intervalsColSize, int *queries, int queriesSize, int *returnSize)
{int x = 0, position = 0, range = 0;long int t = 0;long int arr1[intervalsSize], arr2[queriesSize], arr3[intervalsSize];HeapStruct intervalsHeap, queriesHeap, validHeap;int *result = NULL;/* 申请结果数组的内存空间。 */result = (int *)malloc(sizeof(int) * queriesSize);if(NULL == result){*returnSize = 0;return result;}/* 堆空间定义,初始化一开始堆都为空。 */intervalsHeap.arr = arr1;intervalsHeap.arrSize = 0;queriesHeap.arr = arr2;queriesHeap.arrSize = 0;validHeap.arr = arr3;validHeap.arrSize = 0;/* 把所有的区间放入堆中,左边缘为高优先级,右边缘为低优先级。 */while(intervalsSize > x){t = MERGE_LONG(intervals[x][0], intervals[x][1]);heapPush(&intervalsHeap, t);x++;}/* 把所有的查询放入堆中,位置为高优先级,下标为低优先级。 */x = 0;while(queriesSize > x){t = MERGE_LONG(queries[x], x);heapPush(&queriesHeap, t);x++;}/* 一个个查询逐个出堆。 */while(0 < queriesHeap.arrSize){/* 把下标和位置列出,然后出堆。 */x = GET_LOWER32(queriesHeap.arr[0]);position = GET_HIGHER32(queriesHeap.arr[0]);heapPop(&queriesHeap);/* 把所有左边缘小于等于position的区间弹出。 */while(0 < intervalsHeap.arrSize && position >= GET_HIGHER32(intervalsHeap.arr[0])){/* 右边缘大于等于position的区间放到有效堆中(右边缘小于position的不可能包含当前position)。这个有效堆以range为第一优先级,确保堆顶的区间是range最小的。 */if(position <= GET_LOWER32(intervalsHeap.arr[0])){range = GET_LOWER32(intervalsHeap.arr[0]) - GET_HIGHER32(intervalsHeap.arr[0]) + 1;t = MERGE_LONG(range, GET_LOWER32(intervalsHeap.arr[0]));heapPush(&validHeap, t);}heapPop(&intervalsHeap);}/* 有效堆中,如果堆顶的右边缘小于position,则弹出。这里用到了延迟删除的思想。因为最小堆的pop操作只能针对堆顶。上面对validHeap进行push操作之后,堆顶的区间右边缘值可能已经小于当前的位置了,这种情况必须pop出去。 */while(0 < validHeap.arrSize && position > GET_LOWER32(validHeap.arr[0])){heapPop(&validHeap);}/* 如果此时的有效堆非空,则当前查询的取值就是堆顶的区间大小,否则就是题目要求的-1。 */result[x] = (0 < validHeap.arrSize) ? GET_HIGHER32(validHeap.arr[0]) : INVALID_VALUE;}/* 返回数组长度等于queriesSize。 */*returnSize = queriesSize;return result;
}/* 小根堆的push操作。
为确保其保持为一棵完整二叉树,新push入的数值初始放到堆尾,然后,根据父子节点的大小关系调整位置。 */
static void heapPush(HeapStruct *heap, long int t)
{int son = heap->arrSize, father = FATHER_NODE(son);heap->arrSize++;while(INVALID_VALUE != father && heap->arr[father] > t){heap->arr[son] = heap->arr[father];son = father;father = FATHER_NODE(son);}heap->arr[son] = t;return;
}/* 小根堆的pop操作。
堆顶弹出后,堆顶空缺,为确保其保持为一棵完整二叉树,将堆尾的数值补充到堆顶,然后,根据父子节点的大小关系调整位置。 */
static void heapPop(HeapStruct *heap)
{int father = 0, left = LEFT_NODE(father), right = RIGHT_NODE(father), son = 0;long int t = heap->arr[heap->arrSize - 1];heap->arrSize--;while((heap->arrSize > left && heap->arr[left] < t)|| (heap->arrSize > right && heap->arr[right] < t)){son = (heap->arrSize > right && heap->arr[right] < heap->arr[left]) ? right : left;heap->arr[father] = heap->arr[son];father = son;left = LEFT_NODE(father);right = RIGHT_NODE(father);}heap->arr[father] = t;return;
}作者:恒等式
链接:https://leetcode.cn/problems/minimum-interval-to-include-each-query/solutions/2026632/bao-han-mei-ge-cha-xun-de-zui-xiao-qu-ji-lxkj/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。/* 几个全局定义的说明。INVALID_VALUE: 无效值-1。小根堆中,根节点的父节点使用这个无效值。题目中结果数组里的无效值也是这个-1。FATHER_NODE(x): 小根堆中,每一个节点的父节点。其中,根节点的父节点是无效值。LEFT_NODE(x): 小根堆中,每一个节点的左子节点。当它大于等于堆size的时候无效。RIGHT_NODE(x): 小根堆中,每一个节点的右子节点。当它大于等于堆size的时候无效。MERGE_LONG(x, y): 两个int数值合并成一个long int数值,其中x占据高32位,y占据低32位。GET_HIGHER32(x): 获取一个long int数值的高32位值。GET_LOWER32(x): 获取一个long int数值的低32位值。 */
#define INVALID_VALUE -1
#define FATHER_NODE(x) ((0 == (x)) ? INVALID_VALUE : ((x) - 1 >> 1))
#define LEFT_NODE(x) (((x) << 1) + 1)
#define RIGHT_NODE(x) (((x) << 1) + 2)
#define MERGE_LONG(x, y) ((long int)(x) << 32 | (long int)(y))
#define GET_HIGHER32(x) ((x) >> 32)
#define GET_LOWER32(x) ((x) & 0xFFFFFFFF)/* 小根堆的结构定义,里面包括堆空间数组arr[],和堆size。 */
typedef struct
{long int *arr;int arrSize;
}
HeapStruct;static void heapPush(HeapStruct *heap, long int t);
static void heapPop(HeapStruct *heap);/* 主函数的几个变量说明:x: 循环下标。position: queries数组中的数值。因为这个数组会带着下标一起入堆,在出堆时,把它的实际数值取出到这个position。range: 题目中定义的一个左闭右闭区间的大小,等于“右边缘 - 左边缘 + 1”。t: long int临时数值。arr1,arr2,arr3: 几个小根堆中要用到的堆空间数组。intervalsHeap: 将输入的intervals数组加入到这个小根堆中,左边缘放高32位,右边缘放低32位。queriesHeap: 将输入的queries数组加入到这个小根堆中,数值放高32位,下标放低32位。validHeap: 包含着当前position的有效区间的小根堆,区间range放高32位,右边缘放低32位。 */
int *minInterval(int **intervals, int intervalsSize, int *intervalsColSize, int *queries, int queriesSize, int *returnSize)
{int x = 0, position = 0, range = 0;long int t = 0;long int arr1[intervalsSize], arr2[queriesSize], arr3[intervalsSize];HeapStruct intervalsHeap, queriesHeap, validHeap;int *result = NULL;/* 申请结果数组的内存空间。 */result = (int *)malloc(sizeof(int) * queriesSize);if(NULL == result){*returnSize = 0;return result;}/* 堆空间定义,初始化一开始堆都为空。 */intervalsHeap.arr = arr1;intervalsHeap.arrSize = 0;queriesHeap.arr = arr2;queriesHeap.arrSize = 0;validHeap.arr = arr3;validHeap.arrSize = 0;/* 把所有的区间放入堆中,左边缘为高优先级,右边缘为低优先级。 */while(intervalsSize > x){t = MERGE_LONG(intervals[x][0], intervals[x][1]);heapPush(&intervalsHeap, t);x++;}/* 把所有的查询放入堆中,位置为高优先级,下标为低优先级。 */x = 0;while(queriesSize > x){t = MERGE_LONG(queries[x], x);heapPush(&queriesHeap, t);x++;}/* 一个个查询逐个出堆。 */while(0 < queriesHeap.arrSize){/* 把下标和位置列出,然后出堆。 */x = GET_LOWER32(queriesHeap.arr[0]);position = GET_HIGHER32(queriesHeap.arr[0]);heapPop(&queriesHeap);/* 把所有左边缘小于等于position的区间弹出。 */while(0 < intervalsHeap.arrSize && position >= GET_HIGHER32(intervalsHeap.arr[0])){/* 右边缘大于等于position的区间放到有效堆中(右边缘小于position的不可能包含当前position)。这个有效堆以range为第一优先级,确保堆顶的区间是range最小的。 */if(position <= GET_LOWER32(intervalsHeap.arr[0])){range = GET_LOWER32(intervalsHeap.arr[0]) - GET_HIGHER32(intervalsHeap.arr[0]) + 1;t = MERGE_LONG(range, GET_LOWER32(intervalsHeap.arr[0]));heapPush(&validHeap, t);}heapPop(&intervalsHeap);}/* 有效堆中,如果堆顶的右边缘小于position,则弹出。这里用到了延迟删除的思想。因为最小堆的pop操作只能针对堆顶。上面对validHeap进行push操作之后,堆顶的区间右边缘值可能已经小于当前的位置了,这种情况必须pop出去。 */while(0 < validHeap.arrSize && position > GET_LOWER32(validHeap.arr[0])){heapPop(&validHeap);}/* 如果此时的有效堆非空,则当前查询的取值就是堆顶的区间大小,否则就是题目要求的-1。 */result[x] = (0 < validHeap.arrSize) ? GET_HIGHER32(validHeap.arr[0]) : INVALID_VALUE;}/* 返回数组长度等于queriesSize。 */*returnSize = queriesSize;return result;
}/* 小根堆的push操作。
为确保其保持为一棵完整二叉树,新push入的数值初始放到堆尾,然后,根据父子节点的大小关系调整位置。 */
static void heapPush(HeapStruct *heap, long int t)
{int son = heap->arrSize, father = FATHER_NODE(son);heap->arrSize++;while(INVALID_VALUE != father && heap->arr[father] > t){heap->arr[son] = heap->arr[father];son = father;father = FATHER_NODE(son);}heap->arr[son] = t;return;
}/* 小根堆的pop操作。
堆顶弹出后,堆顶空缺,为确保其保持为一棵完整二叉树,将堆尾的数值补充到堆顶,然后,根据父子节点的大小关系调整位置。 */
static void heapPop(HeapStruct *heap)
{int father = 0, left = LEFT_NODE(father), right = RIGHT_NODE(father), son = 0;long int t = heap->arr[heap->arrSize - 1];heap->arrSize--;while((heap->arrSize > left && heap->arr[left] < t)|| (heap->arrSize > right && heap->arr[right] < t)){son = (heap->arrSize > right && heap->arr[right] < heap->arr[left]) ? right : left;heap->arr[father] = heap->arr[son];father = son;left = LEFT_NODE(father);right = RIGHT_NODE(father);}heap->arr[father] = t;return;
}