C语言 | Leetcode C语言题解之第327题区间和的个数

news2024/9/20 8:37:32

题目:

题解:

#define FATHER_NODE(i)      (0 == (i) ? -1 : ((i) - 1 >> 1))
#define LEFT_NODE(i)        (((i) << 1) + 1)
#define RIGHT_NODE(i)       (((i) << 1) + 2)

/* 优先队列(小根堆)。 */
typedef struct
{
    int *arr;
    int arrSize;
}
PriorityQueue;

/* 二进制树(01字典树)。 */
typedef struct
{
    int *arr;
    int highestBit;
}
BinaryTree;

/* 几个自定义函数的声明,具体实现见下。 */
static void queuePush(PriorityQueue *queue, long long *prefix, int x);
static void queuePop(PriorityQueue *queue, long long *prefix);
static int treeHighestBit(int mostVal);
static void treePush(BinaryTree *tree, int x);
static void treePop(BinaryTree *tree, int x);
static int treeSmaller(BinaryTree *tree, int x);

/* 主函数:
  treeSize:         二进制树的数组空间大小,等于里面最大值的3倍,具体证明略,大致就是等比数列求和的结果。
  prefix[]:         其中prefix[x]表示[0, x]范围内子数组的前缀和。这里必须以long long类型存储。
  window[]:         里面存储prefix[]数组的下标,即prefix[window[x]]才真正表示一个前缀和。
  buff1[],buff2[]:  优先队列与二进制树各自使用的数组空间。其中buff2[]必须初始化为全0。
  queue:            优先队列(小根堆),为了不打乱prefix[]数组中的数值顺序,而且window[]数组中实际存放的是下标,所以借用堆排序。
  tree:             二进制树(01字典树)。 */
int countRangeSum(int *nums, int numsSize, int lower, int upper)
{
    const int treeSize = numsSize * 3;
    int x = 0, y = 0, z = 0, t = 0, result = 0;
    long long sum = 0;
    long long prefix[numsSize];
    int window[numsSize], buff1[numsSize], buff2[treeSize];
    PriorityQueue queue;
    BinaryTree tree;
    /* 初始化。 */
    queue.arr = buff1;
    queue.arrSize = 0;
    memset(buff2, 0, sizeof(buff2));
    tree.arr = buff2;
    tree.highestBit = treeHighestBit(numsSize - 1);
    /* 计算前缀和,并将前缀和的下标放到一个小根堆里面,小根堆里面以对应的前缀和为优先级。 */
    for(x = 0; numsSize > x; x++)
    {
        sum += nums[x];
        prefix[x] = sum;
        queuePush(&queue, prefix, x);
        /* 如果[0, x]的子数组和本来就在[lower, upper]之间,计数累计。 */
        if((long long)lower <= sum && (long long)upper >= sum)
        {
            result++;
        }
    }
    /* 将前缀和数组的下标,以对应的prefix值从小到大的顺序,放到window数组中。 */
    while(0 < queue.arrSize)
    {
        window[t] = queue.arr[0];
        t++;
        queuePop(&queue, prefix);
    }
    /* 开始以滑动窗口的形式移动窗口的左右指针。 */
    for(x = 0; numsSize > x; x++)
    {
        /* 将所有prefix[window[x]] - prefix[window[y]] >= lower的下标y都加入。 */
        while(numsSize > y && prefix[window[x]] - lower >= prefix[window[y]])
        {
            treePush(&tree, window[y]);
            y++;
        }
        /* 将所有prefix[window[x]] - prefix[window[z]] > upper的下标z都移除。 */
        while(numsSize > z && prefix[window[x]] - upper > prefix[window[z]])
        {
            treePop(&tree, window[z]);
            z++;
        }
        /* 将窗口内(树内)剩余的下标值中,小于window[x]的数量加到结果中。 */
        t = treeSmaller(&tree, window[x]);
        result += t;
    }
    return result;
}

/* 小根堆的push操作。由于堆中存储的是prefix[]数组的下标,所以入参需带上prefix。 */
static void queuePush(PriorityQueue *queue, long long *prefix, int x)
{
    int son = queue->arrSize, father = FATHER_NODE(son);
    /* 新加入数值之后,数量加一。 */
    queue->arrSize++;
    /* 根据对应prefix[]值的大小关系,调整父子节点的位置。 */
    while(-1 != father && prefix[x] < prefix[queue->arr[father]])
    {
        queue->arr[son] = queue->arr[father];
        son = father;
        father = FATHER_NODE(son);
    }
    /* 放到实际满足父子节点大小关系的位置。 */
    queue->arr[son] = x;
    return;
}

/* 小根堆的pop操作。由于堆中存储的是prefix[]数组的下标,所以入参需带上prefix。 */
static void queuePop(PriorityQueue *queue, long long *prefix)
{
    int father = 0, left = LEFT_NODE(father), right = RIGHT_NODE(father), son = 0;
    /* 堆顶pop之后,数量减一。默认将堆尾的数值补充上来填补空位。 */
    queue->arrSize--;
    /* 根据对应prefix[]值的大小关系,调整父子节点的位置。 */
    while((queue->arrSize > left && prefix[queue->arr[queue->arrSize]] > prefix[queue->arr[left]])
        || (queue->arrSize > right && prefix[queue->arr[queue->arrSize]] > prefix[queue->arr[right]]))
    {
        son = (queue->arrSize > right && prefix[queue->arr[left]] > prefix[queue->arr[right]]) ? right : left;
        queue->arr[father] = queue->arr[son];
        father = son;
        left = LEFT_NODE(father);
        right = RIGHT_NODE(father);
    }
    /* 放到实际满足父子节点大小关系的位置。 */
    queue->arr[father] = queue->arr[queue->arrSize];
    return;
}

/* 计算二进制树中,可能出现的最大的数值,所占据的最高二进制比特。
  比如,最大值的二进制是101100(b),那么返回的最高比特是100000(b)。特殊的,最大是0的时候返回1(b)。 */
static int treeHighestBit(int mostVal)
{
    int x = 1;
    if(0 < mostVal)
    {
        while(0 < mostVal)
        {
            x++;
            mostVal = mostVal >> 1;
        }
        x = 1 << x - 2;
    }
    return x;
}

/* 往二进制树中push一个数值。 */
static void treePush(BinaryTree *tree, int x)
{
    int i = 0, bit = tree->highestBit;
    /* 从最高位到最低位的顺序,该位为1就给右分支加1,为0就给左分支加1。 */
    while(0 < bit)
    {
        i = (bit & x) ? RIGHT_NODE(i) : LEFT_NODE(i);
        tree->arr[i]++;
        bit = bit >> 1;
    }
    return;
}

/* 从二进制树中pop一个数值。 */
static void treePop(BinaryTree *tree, int x)
{
    int i = 0, bit = tree->highestBit;
    /* 从最高位到最低位的顺序,该位为1就给右分支减1,为0就给左分支减1。 */
    while(0 < bit)
    {
        i = (bit & x) ? RIGHT_NODE(i) : LEFT_NODE(i);
        tree->arr[i]--;
        bit = bit >> 1;
    }
    return;
}

/* 在二进制树中查找比输入数值小的数值数量。 */
static int treeSmaller(BinaryTree *tree, int x)
{
    int i = 0, bit = tree->highestBit, result = 0;
    /* 从高位到低位的顺序查看。 */
    while(0 < bit)
    {
        /* 该位为1,则肯定比高位相同,该位为0的数值更大,即把左分支的数量加到结果中。 */
        if(bit & x)
        {
            result += tree->arr[LEFT_NODE(i)];
            i = RIGHT_NODE(i);
        }
        /* 该位为0,就往左分支走,不做任何其它处理。 */
        else
        {
            i = LEFT_NODE(i);
        }
        bit = bit >> 1;
    }
    return result;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1987246.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数字人解决方案——音频驱动机器人

音频集成 机器人 标志着 人工智能&#xff08;AI&#xff09;。 想象一下&#xff0c;机器人可以通过视觉和听觉导航并与周围环境互动。音频驱动的机器人使这成为可能&#xff0c;提高了它们更高效、更直观地执行任务的能力。这一发展可能会影响到各个领域&#xff0c;包括家庭…

解决PermissionError: [Errno 13] Permission denied: “xx“报错

这个报错我是使用 shutil.copy(src_file, dst_file) 语句产生的&#xff0c;因此有些问题我会围绕此句代码来进行解决&#xff0c;如果有更好的建议&#xff0c;欢迎积极留言。 目录 1.路径拼写错误&#xff0c;建议使用绝对路径 2.此文件正在使用&#xff0c;关闭当前打开的…

vba 保存word里面的图片_1分钟批量处理100张图片,有Word在

天下苦Word久矣&#xff01;Word不仅是个码字工具&#xff0c;还是个排版工具&#xff0c;而Word在排版方面经常遇到的问题&#xff0c;恐怕说个三天三夜都说不完&#xff01; 好不容易做完了100页的活动方案&#xff0c;交到处女座上司那里&#xff0c;他告诉我&#xff1a;“…

调用azure的npm实现outlook_api模拟查看邮件、发送邮件(实现web版接受outlook邮件第一步)

文章目录 ⭐前言⭐注册azure应用&#x1f496;添加权限 ⭐调用npm 实现收发邮件&#x1f496;安装依赖&#x1f496;创建appSettings.js 放置密钥&#x1f496;创建graphHelper.js封装功能&#x1f496;主文件index.js 对外暴露&#x1f496;效果 ⭐结束 ⭐前言 大家好&#x…

我的cesium for UE踩坑之旅(蓝图、UI创建)

我的小小历程 过程创建对应目录&#xff0c;并将要用到的图片、资源放入对应目录下内容浏览器 窗口中右键&#xff0c;创建一个控件蓝图&#xff0c;用来编辑界面UI绘制画布面板&#xff08;canvas&#xff09;调整整体布局加入对应的控件将UI加入到关卡中 备注搜索不到 Add To…

【原创】简易学生成绩查询系统Excel版

简易学生成绩查询系统通常是为了方便学校、教师和学生能够快速查询和管理成绩而设计的一种工具。从之前提供的信息来看&#xff0c;我们可以总结出简易学生成绩查询系统的一些常见功能&#xff1a; ### 易查分成绩查询系统功能特点&#xff1a; - **成绩导入与管理**&#xff…

Spark_获取id对应日期的所在月份的天数完整指南

开发背景 前段时间有一个开发需求的一小块用到了这&#xff0c;是一个利用率的计算。规则是某id下的近半年的值的小时利用率。 计算规则是某值除以近半年 天数以及24h,但是月份里面数据有空值&#xff0c;所以要计算一下id对应的月份的天数&#xff0c;并且过滤掉数据有空值的天…

Azure openai connection with javascript

题意&#xff1a;使用JavaScript与Azure OpenAI进行连接 问题背景&#xff1a; I have created my chatbot with javascript and used open ai. I need to change it to azure open ai but can not find the connection details for javascript. This is how i connect with p…

十九、虚拟机VMware Workstation(CentOSDebian)的安装

目录 &#x1f33b;&#x1f33b; 一、安装 VMware Workstation1.1 安装 VMware Workstation1.2 虚拟机上安装 CentOS1.3 虚拟机安装 Debian 二、配置Debian方便第三方工具远程连接2.1 配置debian2.2 安装远程SSH工具并连接 一、安装 VMware Workstation 官网下载 本地资源库…

端到端自动驾驶:终局还是误区?

近年来&#xff0c;端到端自动驾驶技术成为了汽车行业的热议话题。尤其是在2024年&#xff0c;各家新兴车企纷纷打出端到端的旗号&#xff0c;似乎谁没有搞端到端&#xff0c;就会被市场淘汰。然而&#xff0c;端到端自动驾驶真的是自动驾驶技术的终局吗&#xff1f;本文将深入…

使用QML的ListView自制树形结构图TreeView

背景 感觉QML自带的TreeView不是很好用&#xff0c;用在文件路径树形结构比较多&#xff0c;但是想用在自己数据里&#xff0c;就不太方便了&#xff0c;所以自己做一个。 用‘ListView里迭代ListView’的方法&#xff0c;制作树形结构&#xff0c;成果图&#xff1a; 代码…

尚硅谷谷粒商城项目笔记——四、使用docker安装redis【电脑CPU:AMD】

四、使用docker安装redis 注意&#xff1a; 因为电脑是AMD芯片&#xff0c;自己知识储备不够&#xff0c;无法保证和课程中用到的环境一样&#xff0c;所以环境都是自己根据适应硬件软件环境重新配置的&#xff0c;这里的虚拟机使用的是VMware。 在解决了 Docker 安装的问题之…

app逆向抓包技巧:SSL Pinning检测绕过

本篇博客旨在记录学习过程&#xff0c;不可用于商用等其它途径 场景 在charles抓包下&#xff0c;某斑马app在注册时发现点击登录毫无反应&#xff0c;看抓包结果提示SSL handshake with client failed&#xff0c;确定是触发了SSL/TLS Pinning&#xff08;证书锁定&#xff…

Flutter 正在迁移到 Swift Package Manager ,未来会弃用 CocoaPods 吗?

什么是 Swift Package Manager &#xff1f;其实 Swift Package Manager (SwiftPM) 出现已经挺长一段时间了&#xff0c;我记得第一次听说 SwiftPM 的时候&#xff0c;应该还是在 2016 年&#xff0c;那时候 Swift 3 刚发布&#xff0c;不过正式出场应该还是在 2018 年的 Apple…

【研发日记】嵌入式处理器技能解锁(二)——TI C2000 DSP的SCI(串口)通信

文章目录 前言 背景介绍 SCI通信 Transmitter Receiver SCI中断 分析和应用 总结 参考资料 前言 见《【研发日记】嵌入式处理器技能解锁(一)——多任务异步执行调度的三种方法》 背景介绍 近期使用TI C2000 DSP做的一个嵌入式系统开发项目中&#xff0c;在使用它的SCI&…

缓存异常:缓存雪崩、击穿、穿透

缓存异常&#xff1a;缓存雪崩、击穿、穿透 缓存雪崩 定义 大量的应用请求无法在 Redis 缓存中进行处理&#xff0c;会将这些请求发送到数据库&#xff0c;导致数据库的压力激增&#xff0c;是发生在大量数据同时失效的场景下 原因 1. 缓存中有大量数据同时过期&#xff0…

常见中间件漏洞复现之【Apache】!

CVE-2021-41773 Apache HTTP Server 路径穿越漏洞 漏洞简介 该漏洞是由于Apache HTTP Server 2.4.49版本存在⽬录穿越漏洞,在路径穿越⽬录 <Directory/>Require all granted</Directory>允许被访问的的情况下&#xff08;默认开启&#xff09;&#xff0c;攻击者…

【机器人学】6-4.六自由度机器人运动学参数辨识-机器人精度验证【附MATLAB代码】

前言 前两个章节以及完成了机器人参数辨识。 【机器人学】6-1.六自由度机器人运动学参数辨识-辨识数学模型的建立 【机器人学】6-2.六自由度机器人运动学参数辨识-优化方法求解辨识参数 标定了工具端、基座以及机器人本身的DH参数。那么我们的机器人精度如何呢&#xff1f;机…

实操: 如何在AirBox上跑Stable Diffusion 3

以下文章来源于Radxa &#xff0c;作者瑞莎 Stable Diffusion 3 Medium 是一种多模态扩散变换器 (MMDiT) 文本到图像模型&#xff0c;在图像质量、排版、复杂提示理解和资源效率方面具有显著提升的性能。 目前瑞莎团队使用 Stable Diffusion 3 Medium 开源模型&#xff0c;通过…

领域驱动设计实战:使用Wow框架重构银行转账系统

银行账户转账案例是一个经典的领域驱动设计&#xff08;DDD&#xff09;应用场景。 接下来我们通过一个简单的银行账户转账案例&#xff0c;来了解如何使用 Wow 进行领域驱动设计以及服务开发。 银行转账流程 准备转账&#xff08;Prepare&#xff09;&#xff1a; 用户发起…