LeetCode·每日一题·1851. 包含每个查询的最小区间·优先队列(小顶堆)

题目

示例

思路

  • 离线查询:
    • 输入的结果数组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[]数组,可以考虑排序,也可以考虑小根堆,这里采用小根堆。

【具体实现】

  1. 将输入的intervals[]数组,每一个intervals[x]的左边缘放在一个long int数值的高32位,右边缘放在这个long int数组的低32位,然后long int数值push入小根堆intervalsHeap。
  2. 将输入的queries[]数组,每一个queries[x]的数值放在一个long int数值的高32位,下标x放在这个long int数值的低32位,然后long int数值push入小根堆queriesHeap。
  3. 逐个从queriesHeap中pop出堆顶元素,这个堆顶元素的高32位就是上面第2步push入堆时,每一个queries[x]的数值,赋值给变量position,低32位就是下标x。
    1. 3.1 从intervalsHeap中pop出所有左边缘(高32位)小于等于position的区间。
    2. 3.2 上面3.1中pop出的每一个区间,把所有右边缘(低32位)大于等于position的区间,都push入一个有效区间小根堆validHeap中。这个validHeap中,以区间的range为高32位,右边缘为低32位,以保证堆顶的区间range始终是最小的。
    3. 3.3 如果validHeap堆顶区间的右边缘(低32位)小于当前的position,则将其弹出。
    4. 3.4 经过上面3.3的操作后,如果validHeap仍然非空,则说明存在包含这个position的区间,且validHeap的堆顶区间的range(高32位)就是题目要求的结果。

代码

cpp 复制代码
/* 几个全局定义的说明。
  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;
}
相关推荐
confiself10 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
Wlq041515 分钟前
J2EE平台
java·java-ee
XiaoLeisj22 分钟前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
为什么这亚子31 分钟前
九、Go语言快速入门之map
运维·开发语言·后端·算法·云原生·golang·云计算
豪宇刘36 分钟前
SpringBoot+Shiro权限管理
java·spring boot·spring
41 分钟前
开源竞争-数据驱动成长-11/05-大专生的思考
人工智能·笔记·学习·算法·机器学习
Elaine20239141 分钟前
02多线程基础知识
java·多线程
gorgor在码农43 分钟前
Redis 热key总结
java·redis·热key
~yY…s<#>1 小时前
【刷题17】最小栈、栈的压入弹出、逆波兰表达式
c语言·数据结构·c++·算法·leetcode
百事老饼干1 小时前
Java[面试题]-真实面试
java·开发语言·面试