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

这篇具有很好参考价值的文章主要介绍了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[]数组,可以考虑排序,也可以考虑小根堆,这里采用小根堆。

【具体实现】文章来源地址https://www.toymoban.com/news/detail-588434.html

  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位)就是题目要求的结果。

代码

/* 几个全局定义的说明。
  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;
}

到了这里,关于LeetCode·每日一题·1851. 包含每个查询的最小区间·优先队列(小顶堆)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • LeetCode每日一题:56. 合并区间(2023.8.27 C++)

    目录 56. 合并区间 题目描述: 实现代码与解析: 排序 + 贪心 原理思路:         以数组  intervals  表示若干个区间的集合,其中单个区间为  intervals[i] = [starti, endi]  。请你合并所有重叠的区间,并返回  一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间  。

    2024年02月11日
    浏览(32)
  • 每日一题——LeetCode1299.将每个元素替换为右侧最大元素

    方法一 个人方法:  题目意思就是求在i=1;i++的循环条件下,arr[i]-arr[arr.length-1]的最大值分别为多少,最后一项默认为-1 用slice方法可以每次把数组第一位去除,得到求最大值的目标数组 Math的max方法可以直接返回数组里的最大值 但是不能每次循环都求一遍目标数组的最大值,

    2024年01月23日
    浏览(46)
  • 2023-07-13 LeetCode每日一题(下降路径最小和)

    点击跳转到题目位置 给你一个 n x n 的 方形 整数数组 matrix ,请你找出并返回通过 matrix 的 下降路径 的 最小和 。 下降路径 可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列(即位于正下方或者沿对角线向左

    2024年02月15日
    浏览(65)
  • (滑动窗口) 76. 最小覆盖子串 ——【Leetcode每日一题】

    难度:困难 给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 \\\"\\\" 。 注意: 对于 t 中 重复字符 ,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。 如果 s 中存在这样的子串,我们

    2024年02月08日
    浏览(38)
  • ( 数组) 209. 长度最小的子数组——【Leetcode每日一题】

    难度:中等 给定一个含有 n 个正整数的数组和一个正整数 target 。 找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。 示例 1: 输入:target = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数

    2024年02月06日
    浏览(28)
  • 每日一题——LeetCode1266.访问所有点的最小时间

    方法一 个人方法 找规律: 当前的点为current,下一个点为next,x为两点横坐标之间距离,y为两点竖坐标之间距离 1、当两点横坐标相同时,两点距离为y 2、当两点竖坐标相同时,两点距离为x 3、当两点x与y相同时,则两点的连线在直线y=x上,那么两点的距离既是x也是y 4、当

    2024年01月20日
    浏览(45)
  • Leetcode-每日一题【剑指 Offer 11. 旋转数组的最小数字】

    把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的

    2024年02月14日
    浏览(31)
  • 2023-08-10LeetCode每日一题(下降路径最小和 II)

    点击跳转到题目位置 给你一个 n x n 整数矩阵 grid ,请你返回 非零偏移下降路径 数字和的最小值。 非零偏移下降路径 定义为:从 grid 数组中的每一行选择一个数字,且按顺序选出来的数字中,相邻数字不在原数组的同一列。 示例 1: 示例 2: 提示: n == grid.length == grid[i].

    2024年02月13日
    浏览(33)
  • Leetcode每日一题:931. 下降路径最小和(2023.7.13 C++)

    目录 931. 下降路径最小和 题目描述: 实现代码与解析: 动态规划 原理思路:         给你一个  n x n  的  方形  整数数组  matrix  ,请你找出并返回通过  matrix  的 下降路径   的   最小和  。 下降路径  可以从第一行中的任何元素开始,并从每一行中选择一个元素

    2024年02月16日
    浏览(38)
  • (排序) 剑指 Offer 45. 把数组排成最小的数 ——【Leetcode每日一题】

    难度:中等 输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 示例 1: 输入: [10,2] 输出: “102” 示例 2: 输入: [3,30,34,5,9] 输出: “3033459” 提示 : 0 nums.length = 100 说明: 输出结果可能非常大,所以你需要返回一个字符串而不

    2024年02月10日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包