前缀和数组、差分数组、树状数组在Leetcode中的应用

文章目录

  • 前缀和数组、差分数组、树状数组知识简单回顾
  • Leetcode 1109. 航班预订统计
  • Leetcode 307. 区域和检索-数组可修改
  • LeetCode 面试题10.10. 数字流的秩
  • LeetCode 1310. 子数组异或查询
  • LeetCode 1409. 查询带键的排列

前缀和数组、差分数组、树状数组知识简单回顾

之前的文章中讲了前缀和数组、差分数组和树状数组。主要总结一下:

原数组: a i a_i ai

前缀和数组: S i = a 1 + a 2 + . . . + a i S_i = a_1 + a_2 + ... + a_i Si=a1+a2+...+ai

差分数组: X i = a i − a i − 1 X_i = a_i - a_{i - 1} Xi=aiai1

树状数组:前缀和数组的一种特殊表现形式,使得前缀和数组更方便进行单点修改。

关系:原数组是前缀和数组的差分数组;原数组是差分数组的前缀和数组。

前缀和数组与差分数组、树状数组并没有增加信息,只是原数组信息的另外一种表示,在处理问题时数据的处理时间复杂度不同。

问题1:求原数组的区间和,原数组上的时间复杂度为 O ( n ) O(n) O(n),前缀和数组上操作时间复杂度为 O ( 1 ) O(1) O(1).

问题2:原数组区间元素修改(同时加一个值)

例如a2到a5的元素都增加d, 体现在差分数组上为x2+d, x6 - d, 其他元素不变。即只有两个点发生变化。

上面两类问题下原数组时间复杂度为 O ( n ) O(n) O(n), 差分数组或前缀和数组上的时间复杂度为 O ( 1 ) O(1) O(1).

Leetcode 1109. 航班预订统计

题目描述:
在这里插入图片描述
题目分析:相当于对于原数组,某一个区间上的值同时增加一个数。即区间修改。这种情况下用差分数组更方便。例如a2到a5的元素都增加d, 体现在差分数组上为x2 + d, x6 - d, 其他元素不变。即只有两个点发生变化。

所以本题先根据输入,对差分数组进行单点修改(相当于对原数组进行区间修改),最后再对差分数组求前缀和,即得到了原数组。

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        vector<int> d(n + 1); //原数组的差分数组
        for (int i = 0; i < bookings.size(); i++) {
            int start = bookings[i][0];
            int last = bookings[i][1];
            int seat = bookings[i][2];
            d[start] += seat;
            if (last + 1 <= n) d[last + 1] -= seat;
        }
        // for (int i = 1; i <= n; i++) printf("%d ", d[i]);
        // printf("\n");
        // printf("s finish\n");
        vector<int> res(n); //res[i]代表原数组的第i + 1位的值
        res[0] = d[1];  //原数组为a, res[i] = a[i + 1]
        for (int i = 2; i <= n; i++) {
            // 原数组: a[i] = a[i - 1] + d[i];

            //res原数组是原数组的位移: res[i] = a[i + 1]
            res[i - 1] = res[i - 2] + d[i];
        }
        return res;
    }
};

代码运行结果:
在这里插入图片描述
总结:本题直接用差分数组,求前缀和即得到原数组。这里求前缀和可以直接求,也可以利用树状数组来求。

本题也可以在区间修改的过程中,直接利用树状数组来维护原数组:

#define lowbit(x) (x & (-x))

class FenwickTree{
	public:
		FenwickTree(int size): n(size), c(size + 1){}
		//size表示最大访问到的下标,所以c数组的长度就是size + 1;

		void add(int i, int x) {
			//单点修改:在原数组的第i个位置上的元素加x
			while(i <= n) {
				c[i] = c[i] + x;
				i = i + lowbit(i);
			}
			return;
		}

		int query(int x) {
			//对原数组的前x位进行前缀和查询
			int sum = 0;
			while (x) {
				sum += c[x];
				x -= lowbit(x);
			}
			return sum;
		}
		int at(int ind) {
			//原数组在ind位置的值
			return query(ind) - query(ind - 1);
		}
		void output() {
			int len = 0;
			for (int i = 1; i <= n; i++) {
				len += printf("%5d", i);
			}
			printf("\n");
			for (int i = 1; i <= len + 6; i++) printf("=");
			printf("\n");
			for (int i = 1; i <= n; i++) {
				printf("%5d", c[i]);
			}
			printf("\n");
			for (int i = 1; i <= n; i++) printf("%5d", query(i) - query(i - 1));
			printf("\n\n\n");
			return;
		}

	private:
		int n; //树状数组所维护的下标的最大上限
		vector <int> c;  //树状数组



};

class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        FenwickTree tree(n);
        for (auto x: bookings) {
            tree.add(x[0], x[2]);
            tree.add(x[1] + 1, -x[2]);
        }
        vector<int> res(n);
        for (int i = 0; i < n; i++) {
            res[i] = tree.query(i + 1);
        }
        return res;
	}
};

上述代码相当于把差分数组看做原数组,对差分数组单点修改,然后这个过程中直接利用树状数组维护其前缀和数组。最后输出差分数组的树状数组。

Leetcode 307. 区域和检索-数组可修改

在这里插入图片描述
题目分析:同时涉及到原数组的单点修改和区间和查询,如果存储原数组求区间和的时候复杂度为O(n), 如果存储前缀和数组单点修改的时候复杂度为O(n), 所以可以直接存储树状数组,无论单点修改还是求区间和,复杂度均为O(logn)。

需要注意前缀和数组我们实现的时候下标是从1开始的,需要和本题中的数组有一个下标偏移转换。

#define lowbit(x) (x & (-x))

class FenwickTree{
	public:
		FenwickTree(int size): n(size), c(size + 1){}
		//size表示最大访问到的下标,所以c数组的长度就是size + 1;

		void add(int i, int x) {
			//单点修改:在原数组的第i个位置上的元素加x
			while(i <= n) {
				c[i] = c[i] + x;
				i = i + lowbit(i);
			}
			return;
		}

		int query(int x) {
			//对原数组的前x位进行前缀和查询
			int sum = 0;
			while (x) {
				sum += c[x];
				x -= lowbit(x);
			}
			return sum;
		}
		int at(int ind) {
			//原数组在ind位置的值
			return query(ind) - query(ind - 1);
		}
		void output() {
			int len = 0;
			for (int i = 1; i <= n; i++) {
				len += printf("%5d", i);
			}
			printf("\n");
			for (int i = 1; i <= len + 6; i++) printf("=");
			printf("\n");
			for (int i = 1; i <= n; i++) {
				printf("%5d", c[i]);
			}
			printf("\n");
			for (int i = 1; i <= n; i++) printf("%5d", query(i) - query(i - 1));
			printf("\n\n\n");
			return;
		}

	private:
		int n; //树状数组所维护的下标的最大上限
		vector <int> c;  //树状数组



};
class NumArray {
public:
    FenwickTree tree;
    NumArray(vector<int>& nums):tree(nums.size()) {
        int n = nums.size();
        // printf("n : %d\n", n);
        for (int i = 0; i < n; i++) {
            tree.add(i + 1, nums[i]);
            // num的 i 索引 ~ tree的 i+1 索引
        }
    }
    
    void update(int index, int val) {
        tree.add(index + 1, val - tree.at(index + 1));
        return;
    }
    
    int sumRange(int left, int right) {
        return tree.query(right + 1) - tree.query(left - 1 + 1);
    }
// private:
//     FenwickTree tree;
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray* obj = new NumArray(nums);
 * obj->update(index,val);
 * int param_2 = obj->sumRange(left,right);
 */

在这里插入图片描述
总结:树状数组最直接的应用。

LeetCode 面试题10.10. 数字流的秩

在这里插入图片描述
题目分析:一开始看到找到小于等于x的个数,先想到的是二叉排序树,同时维护一个二叉排序树,以及每个节点所包含的节点个数。但是二叉排序树又有一个弊端,就是当读入数字有序的时候,会退化为一个链表。这时候就又要用到AVL数,甚至红黑树,问题变得复杂。

看到本题的数字范围,x <= 50000, 不是很大,事实上本题的数字范围还是>=0的。所以可以用计数数组来建模(计数排序时有用到计数数组)。

首先建立一个计数数组a[n],下标最大范围为50000. 然后读入一个数字x时,只需让a[x]+=1;

当要求某个数字x,有多少个数字小于等于它时,实际上就是求a[n]数组中x位置对应的前缀和。

所以本题实际上就是只涉及计数数组的单点修改和前缀和查询。 而为了更快速的求前缀和,我们可以用树状数组来实现。

#define lowbit(x) (x & (-x))

class FenwickTree{
	public:
		FenwickTree(int size): n(size), c(size + 1){}
		//size表示最大访问到的下标,所以c数组的长度就是size + 1;

		void add(int i, int x) {
			//单点修改:在原数组的第i个位置上的元素加x
			while(i <= n) {
				c[i] = c[i] + x;
				i = i + lowbit(i);
			}
			return;
		}

		int query(int x) {
			//对原数组的前x位进行前缀和查询
			int sum = 0;
			while (x) {
				sum += c[x];
				x -= lowbit(x);
			}
			return sum;
		}
		int at(int ind) {
			//原数组在ind位置的值
			return query(ind) - query(ind - 1);
		}
		void output() {
			int len = 0;
			for (int i = 1; i <= n; i++) {
				len += printf("%5d", i);
			}
			printf("\n");
			for (int i = 1; i <= len + 6; i++) printf("=");
			printf("\n");
			for (int i = 1; i <= n; i++) {
				printf("%5d", c[i]);
			}
			printf("\n");
			for (int i = 1; i <= n; i++) printf("%5d", query(i) - query(i - 1));
			printf("\n\n\n");
			return;
		}

	private:
		int n; //树状数组所维护的下标的最大上限
		vector <int> c;  //树状数组
};

class StreamRank {
public:
    FenwickTree tree;
    StreamRank():tree(50001) {
        // tree的索引i+1 ~ 数字i
        // tree为计数数组
    }
    
    void track(int x) {
        tree.add(x + 1, 1);
        return;
    }
    
    int getRankOfNumber(int x) {
        return tree.query(x + 1);
    }
};

/**
 * Your StreamRank object will be instantiated and called as such:
 * StreamRank* obj = new StreamRank();
 * obj->track(x);
 * int param_2 = obj->getRankOfNumber(x);
 */

提交结果:
在这里插入图片描述
总结:需要先分析题目,如何建模解决问题。然后再建模解模的过程中,为了快速求解其中的前缀和,我们用到了树状数组。

LeetCode 1310. 子数组异或查询

在这里插入图片描述
题目分析:本题求的是区间所有元素的异或。我们已经知道区间所有元素的和,可以用前缀和的差来求。因为和的逆运算就是减法。那么仿照前缀和的思路,区间和的异或运算,我们也可以采用关于前缀和的异或来求。

这里就涉及到异或运算的的一个性质,那就是它的逆运算还是它自己。

比如对于加减法, a + b = c a+b = c a+b=c, 因为减法是加法的逆运算,所以用减法,可以由c和a运算得到 b . ( c − a = b ) b. (c - a = b) b.(ca=b), 或者由c和b运算得到 a . ( c − b = a ) a. (c - b = a) a.(cb=a);

对于异或运算, 如果已知a ^ b = c, 那么如何由a和c运算得到b呢?答案就是异或运算,即c ^ a = b, 同样 c ^ b = a。

所以对于本题,完全可以建立一个异或运算的前缀和数组,对于区间和,用两个端点的异或前缀和再做异或运算,即可解决。

class Solution {
public:
    vector<int> xorQueries(vector<int>& arr, vector<vector<int>>& queries) {
        int n = arr.size();
        int m = queries.size();
        vector<int> sum_xor(n);  //异或的前缀和数组
        vector<int> res(m);
        for (int i = 0; i < n; i++) {
            if (i == 0) sum_xor[i] = arr[i];
            else sum_xor[i] = sum_xor[i - 1] ^ arr[i];
        }
        for (int i = 0; i < m; i++) {
            int start = queries[i][0];
            int end = queries[i][1];
            if (start == 0) res[i] = sum_xor[end];
            else res[i] = sum_xor[end] ^ sum_xor[start - 1];
        }

        return res;

    }
};

在这里插入图片描述
总结:用前缀和的思路,需要了解异或运算的一个性质,其逆运算仍为自己。

LeetCode 1409. 查询带键的排列

在这里插入图片描述
题目分析:本题比较复杂的是需要多次移动数组P中的元素。不论是直接存储维护原数组还是用哈希表,每次移动的时间复杂度都是O(n)。

所以这里考虑使用一个下标处理小技巧,即使用计数数组。将P数组中的数字存储在计数数组中:

在这里插入图片描述
例如P数组中的数字3对应计数数组的前缀和是3,就说明3这个数字在P数组中是第三个数(对应P数组的索引)。

然后本题需要将P数组中特定位置的数字移动到最前面,所以可以对计数数组做一定量的向右偏移,移动的过程就可以这样操作:

在这里插入图片描述

这样通过计算计数数组的前缀和,就可以快速的求出P数组对应元素的索引。 移动的过程也仅仅是计数数组的单点修改。

所以本题主要维护一个计数数组,再维护数组P中的每一个数在计数数组中的索引,即可解决题目要求。

#define lowbit(x) (x & (-x))

class FenwickTree{
	public:
		FenwickTree(int size): n(size), c(size + 1){}
		//size表示最大访问到的下标,所以c数组的长度就是size + 1;

		void add(int i, int x) {
			//单点修改:在原数组的第i个位置上的元素加x
			while(i <= n) {
				c[i] = c[i] + x;
				i = i + lowbit(i);
			}
			return;
		}

		int query(int x) {
			//对原数组的前x位进行前缀和查询
			int sum = 0;
			while (x) {
				sum += c[x];
				x -= lowbit(x);
			}
			return sum;
		}
		int at(int ind) {
			//原数组在ind位置的值
			return query(ind) - query(ind - 1);
		}
		void output() {
			int len = 0;
			for (int i = 1; i <= n; i++) {
				len += printf("%5d", i);
			}
			printf("\n");
			for (int i = 1; i <= len + 6; i++) printf("=");
			printf("\n");
			for (int i = 1; i <= n; i++) {
				printf("%5d", c[i]);
			}
			printf("\n");
			for (int i = 1; i <= n; i++) printf("%5d", query(i) - query(i - 1));
			printf("\n\n\n");
			return;
		}

	private:
		int n; //树状数组所维护的下标的最大上限
		vector <int> c;  //树状数组
};
class Solution {
public:
    vector<int> processQueries(vector<int>& queries, int m) {
        
        //计数数组数组a(对应树状数组tree):0~2m+5, 初始时m~2m的位置存储1, 其前缀和即为P数组对应的索引。

        //索引数组arr_ind: 存储排列P在计数数组中的存放位置,初始时0-m分别存储m~2m

        FenwickTree tree(2 * m + 5); //大数组,计数数组对应的树状数组
        vector<int> arr_ind(m + 1); 
        
        for (int i = 1; i <= m; i++) {
            tree.add(m + i, 1); //计数数组先完成初始化计数
            arr_ind[i] = m + i;
        }

        int n = queries.size();
        vector<int> ret(n);
        int prefix_ind = m;

        for (int i = 0; i < n; i++) {
            int num = queries[i];
            int ind_a = arr_ind[num];
            
            int ind_p = tree.query(ind_a) - 1; //通过计数数组的前缀和求出P数组的索引
            ret[i] = ind_p;

			//将num这个数字移动到了原数组a的prefix_ind位置上,其余不变
            tree.add(prefix_ind, 1); 
            tree.add(ind_a, -1);
            arr_ind[num] = prefix_ind;

            prefix_ind--;
        }
        return ret;

    }
};

提交结果:
在这里插入图片描述
总结:本题通过维护计数数组,既可以快速求出原数组P的索引,也可以通过单点修改就实现数字的移动。思路巧妙,相关技巧可供参考。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

揭开`this`的神秘面纱:探索 JavaScript 中的上下文密钥(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

c语言编写http服务器(Linux下运行)

参考文章&#xff1a;https://blog.csdn.net/baixingyubxy/article/details/125964986?spm1001.2014.3001.5506 上面是详细讲解&#xff0c;我这篇是总结了他的代码&#xff0c;因为他没给整体代码 所有代码&#xff1a; #include <stdio.h> #include <stdlib.h&g…

Python Django Jet:优化 Django 后台管理

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 大家好&#xff0c;今天分享 Python 中的 Django Jet 库。 Github项目地址&#xff1a;https://github.com/geex-arts/django-jet Django Jet 是一个强大的 Django 后台管理界面扩展&#xff0c;旨在提供更现代…

人工智能125个常用名词解释

1 什么是人工智能 人工智能&#xff08;Artificial Intelligence&#xff0c;简称AI&#xff09;是指计算机系统通过模拟人类的思维和行为来完成特定任务的技术和方法。人工智能的研究涉及多个学科&#xff0c;包括计算机科学、数学、心理学、哲学等领域。 人工智能可以被分为…

SVM —— 理论推导

SVM 支持向量线性可分最大间隔超平面最大间隔超平面的推导支持向量分类间隔的推导最优化问题 对偶问题拉格朗日乘子法强对偶性 SVM 优化软间隔解决问题优化目标及求解 核函数线性不可分核函数的作用常见核函数 SVM 算法优缺点 支持向量机&#xff08;Support Vector Machine&am…

Collecting package metadata (current_repodata.json): failed(解决方案)

如果有重装过anaconda&#xff0c;在C盘的用户目录下&#xff0c;会有一个名叫.condarc的文件会自动生成。 当使用conda install和conda create命令会出现下面的问题&#xff1a;Collecting package metadata (current_repodata.json): failed 解决方案&#xff1a; 1.打开Anac…

Leetcod面试经典150题刷题记录 —— 双指针篇

双指针篇 1. 验证回文串Python3 2. 判断子序列Python3双指针 3. 两数之和 II - 输入有序数组Python3 4. 盛最多水的容器Python3双指针 5. 三数之和 1. 验证回文串 题目链接&#xff1a;验证回文串 - leetcode 题目描述&#xff1a; 如果在将所有大写字符转换为小写字符、并移除…

Spring Cloud + Vue前后端分离-第6章 通用代码生成器开发

Spring Cloud Vue前后端分离-第6章 通用代码生成器开发 6-1 代码生成器原理介绍 1.增加generator模块&#xff0c;用于代码生成 2.集成freemarker 通用代码生成器开发 FreeMarker 是一款模版引擎&#xff0c;通过模板生成文件&#xff0c;包括html页面&#xff0c;excel …

基于vue+element-plus+echarts制作动态绘图页面(柱状图,饼图和折线图)

前言 我们知道echarts是一个非常强大的绘图库&#xff0c;基于这个库&#xff0c;我们可以绘制出精美的图表。对于一张图来说&#xff0c;其实比较重要的就是配置项&#xff0c;填入不同的配置内容就可以呈现出不同的效果。 当然配置项中除了样式之外&#xff0c;最重要的就是…

腾讯云debian服务器的连接与初始化

目录 1. 远程连接2. 软件下载3. 设置开机自启动 1. 远程连接 腾讯云给的服务器在安装好系统之后&#xff0c;只需要在防火墙里面添加一个白名单&#xff08;ip 或者域名&#xff09;就能访问了。 防火墙添加本机WLAN的IPv4白名单&#xff0c;本地用一个远程工具连接&#xff…

C++设计模式之——命令模式

命令模式 概念创建步骤示例示例一代码实现运行结果 示例二代码实现运行结果 示例三示例代码运行结果 示例四代码实现运行结果 应用场景 概念 命令模式是一种行为型设计模式&#xff0c;它允许将请求封装为一个对象&#xff0c;从而使得可以参数化客户端请求、将请求排队或者记…

npm login报错:Public registration is not allowed

npm login报错:Public registration is not allowed 1.出现场景2.解决 1.出现场景 npm login登录时,出现 2.解决 将自己的npm镜像源改为npm的https://registry.npmjs.org/这个&#xff0c;解决&#xff01;

安防视频云平台/可视化监控云平台EasyCVR获取设备录像失败,该如何解决?

视频云存储/安防监控EasyCVR视频汇聚平台基于云边端智能协同&#xff0c;支持海量视频的轻量化接入与汇聚、转码与处理、全网智能分发、视频集中存储等。GB28181音视频流媒体视频平台EasyCVR拓展性强&#xff0c;视频能力丰富&#xff0c;具体可实现视频监控直播、视频轮播、视…

打响指针的第一枪:指针家族

前言 指针其实是我们学习C语言中最难的知识点&#xff0c;很多人在学习指针的时候会被绕晕&#xff0c;包括博主也是&#xff0c;当初百思不得其解&#xff0c;脑袋都要冒烟了&#xff0c;本来打算在学习指针的时候就写一篇博客&#xff0c;但是当初自己的能力还是没有办法去完…

harmonyOS 自定义组件基础演示讲解

上文 HarmonyOS组件属性控制 链式编程格式推荐我们讲了一些系统组件 可以传入一些事件和参数 来达到一些不同的效果 其实 我们还可以用自己写的组件 那么 组件这么写&#xff1f; 其实 我们的 page 内部结果 就是一个组件 harmonyOS的概念 万物皆组件 那么 我们就可以在他下面…

低代码软件开发的革命

一、前言 如果一个概念能在科技圈火起来&#xff0c;它往往兼具字面简明和内涵丰富的特征&#xff0c;并具有某种重塑产业格局的潜力。低代码&#xff08;Low Code&#xff09;就是这样一个典型。顾名思义&#xff0c;低代码是指少用代码&#xff0c;甚至不用代码&#xff0c;仅…

自动化测试 (五) 读写64位操作系统的注册表

自动化测试经常需要修改注册表 很多系统的设置&#xff08;比如&#xff1a;IE的设置&#xff09;都是存在注册表中。 桌面应用程序的设置也是存在注册表中。 所以做自动化测试的时候&#xff0c;经常需要去修改注册表 Windows注册表简介 注册表编辑器在 C:\Windows\regedit…

Netty入门基础知识

简介 Netty是一款高性能java网络编程框架&#xff0c;被广泛应用在中间件、直播、社交、游戏等领域。Netty对java NIO进行高级封装&#xff0c;简化了网络应用的开发过程。 stream与channel stream不会自动缓冲数据&#xff0c;channel会利用系统提供的发送缓冲区&#xff0c;接…

科创金融的向善力量:浙商银行多措并举赋能科创企业,打造科技金融服务生态圈

近日&#xff0c;浙商银行科技金融服务发布会在杭州举行。 发布会以“智汇科创&#xff0c;善行未来”为主题&#xff0c;围绕科技金融服务“向善”新生态&#xff0c;浙商银行重磅推出科创企业全图景服务方案&#xff0c;正式发布科创积分贷&#xff0c;与浙江大学联合发布人…

初冬天气变化大,长辈身上的这些小毛病千万不能轻视

心率、血氧、肺功能&#xff0c;甚至是一次次不起眼的咳嗽&#xff0c;背后都可能藏着健康问题。但是我们可以利用好手表上的健康检测功能&#xff0c;提前获知健康数据的变化&#xff0c;有的放矢&#xff0c;科学应对身体的不适&#xff0c;度过一个有准备的温暖冬天&#xf…