首页 > 其他分享 >数组前缀和

数组前缀和

时间:2024-01-28 19:46:06浏览次数:24  
标签:obj 前缀 nums int 数组 preSum hashMap

一维数组中的前缀和

https://leetcode.com/problems/range-sum-query-immutable/description/

区域和检索

#include <stdio.h>
#include <stdlib.h>

// 定义 NumArray 结构体
typedef struct {
int* preSum;  // 前缀和数组
} NumArray;

// 创建 NumArray 对象,并计算前缀和数组
NumArray* NumArrayCreate(int* nums, int numsSize) {
NumArray* obj = (NumArray*)malloc(sizeof(NumArray));  // 分配内存以存储 NumArray 结构体
obj->preSum = (int*)malloc(sizeof(int) * (numsSize + 1));  // 分配内存以存储前缀和数组
obj->preSum[0] = 0;

// 计算前缀和
for (int i = 1; i <= numsSize; i++) {
obj->preSum[i] = obj->preSum[i - 1] + nums[i - 1];
}
return obj;
}

// 计算闭区间 [left, right] 的累加和
int NumArraySumRange(NumArray* obj, int left, int right) {
return obj->preSum[right + 1] - obj->preSum[left];
}

// 释放内存
void NumArrayFree(NumArray* obj) {
free(obj->preSum);  // 释放前缀和数组内存
free(obj);  // 释放 NumArray 结构体内存
}

int main() {
// 测试用例
int nums[] = { 1, 2, 3, 4, 5 };
int numsSize = sizeof(nums) / sizeof(nums[0]);

// 创建 NumArray 对象
NumArray* obj = NumArrayCreate(nums, numsSize);

// 计算闭区间的累加和
int sum = NumArraySumRange(obj, 1, 3);
printf("Sum Range = %d\n", sum);

// 释放内存
NumArrayFree(obj);
return 0;
}

看这个 preSum 数组,如果我想求索引区间 [1, 4] 内的所有元素之和,就可以通过 preSum[5] -
preSum[1] 得出。
这样,sumRange 函数仅仅需要做⼀次减法运算,避免了每次进⾏ for 循环调⽤,最坏时间复杂度为常数
O(1)。
这个技巧在⽣活中运⽤也挺⼴泛的,⽐⽅说,你们班上有若⼲同学,每个同学有⼀个期末考试的成绩(满分
100 分),那么请你实现⼀个 API,输⼊任意⼀个分数段,返回有多少同学的成绩在这个分数段内。
那么,你可以先通过计数排序的⽅式计算每个分数具体有多少个同学,然后利⽤前缀和技巧来实现分数段查
询的 API

二维矩阵中的前缀和

https://leetcode.com/problems/range-sum-query-2d-immutable/description/

#include <stdio.h>
#include <stdlib.h>

typedef struct {
int** preSum;
} NumMatrix;

// 创建 NumMatrix 对象,并计算前缀和矩阵
NumMatrix* NumMatrixCreate(int** matrix, int matrixSize, int* matrixColSize) {
int m = matrixSize, n = *matrixColSize;
if (m == 0 || n == 0) return NULL;

// 分配内存以存储前缀和矩阵
NumMatrix* obj = (NumMatrix*)malloc(sizeof(NumMatrix));
obj->preSum = (int**)malloc(sizeof(int*) * (m + 1));
for (int i = 0; i <= m; i++) {
obj->preSum[i] = (int*)malloc(sizeof(int) * (n + 1));
}

// 计算前缀和矩阵
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
obj->preSum[i][j] = obj->preSum[i - 1][j] + obj->preSum[i][j - 1] + matrix[i - 1][j - 1] - obj->preSum[i - 1][j - 1];
}
}
return obj;
}

// 计算子矩阵 [x1, y1, x2, y2] 的元素和
int sumRegion(NumMatrix* obj, int x1, int y1, int x2, int y2) {
// 目标矩阵之和由四个相邻矩阵运算获得
return obj->preSum[x2 + 1][y2 + 1] - obj->preSum[x1][y2 + 1] - obj->preSum[x2 + 1][y1] + obj->preSum[x1][y1];
}

// 释放内存
void NumMatrixFree(NumMatrix* obj) {
for (int i = 0; i <= sizeof(obj->preSum) / sizeof(int*); i++) {
free(obj->preSum[i]);
}
free(obj->preSum);
free(obj);
}

int main() {
// 测试用例
int matrix[3][3] = { {1, 2, 3}, {4, 5, 6}, {7, 8, 9} };
int* matrixPtr[3] = { matrix[0], matrix[1], matrix[2] };

// 创建 NumMatrix 对象
NumMatrix* obj = NumMatrixCreate(matrixPtr, 3, (int[]) { 3 });

// 计算子矩阵的元素和
int sum = sumRegion(obj, 0, 0, 1, 1);
printf("Sum Region = %d\n", sum);

// 释放内存
NumMatrixFree(obj);
return 0;
}

按照题⽬要求,矩阵左上角为坐标原点 (0, 0),那么 sumRegion([2,1,4,3]) 就是图中红色的子矩阵,
你需要返回该子矩阵的元素和 8。

当然,你可以用一个嵌套for循环去遍历这个矩阵,但这样的话 sumRegion 函数的时间复杂度就⾼了,你算
法的格局就低了。

做这道题更好的思路和⼀维数组中的前缀和是⾮常类似的,如下图:

如果我想计算红色的这个子矩阵的元素之和,可以用绿色矩阵减去蓝色矩阵减去橙色矩阵最后加上粉色矩
阵,而绿蓝橙粉这四个矩阵有一个共同的特点,就是左上角就是 (0, 0) 原点。
那么我们可以维护一个二维 preSum 数组,专门记录以原点为顶点的矩阵的元素之和,就可以用几次加减运
算算出任何一个子矩阵的元素和

和为k的子数组

https://leetcode.cn/problems/subarray-sum-equals-k/description/?utm_source=LCUS&utm_medium=ip_redirect&utm_campaign=transfer2china

#include <stdio.h>
#include <stdlib.h>

int subarraySum(int* nums, int numsSize, int k) {
// 构造前缀和数组
int* preSum = (int*)malloc(sizeof(int) * (numsSize + 1));
preSum[0] = 0;
for (int i = 0; i < numsSize; i++) {
preSum[i + 1] = preSum[i] + nums[i];
}

int res = 0;
// 穷举所有子数组
for (int i = 1; i <= numsSize; i++) {
for (int j = 0; j < i; j++) {
// 子数组 nums[j..i-1] 的元素和
if (preSum[i] - preSum[j] == k) {
res++;
}
}
}

free(preSum);
return res;
}

int main() {
int nums[] = { 1, 2, 3, 4, 5 };
int k = 3;
int numsSize = sizeof(nums) / sizeof(nums[0]);

int count = subarraySum(nums, numsSize, k);
printf("Number of subarrays with sum %d = %d\n", k, count);

return 0;
}

这个解法的时间复杂度 O(N^2) 空间复杂度 O(N),并不是最优的解法。不过通过这个解法理解了前缀和数
组的工作原理之后,可以使用一些巧妙的办法把时间复杂度进一步降低。

注意前面的解法有嵌套的 for 循环:

for (int i = 1; i <= n; i++)
 for (int j = 0; j < i; j++)
 if (preSum[i] - preSum[j] == k)
 res++;

第二层 for 循环在干嘛呢?翻译一下就是,在计算,有几个 j 能够使得 preSum[i] 和 preSum[j] 的差为
k。毎找到一个这样的 j,就把结果加一。

我们可以把 if 语句里的条件判断移项,这样写:

	if (preSum[j] == preSum[i] - k)
	 res++;

优化的思路是:我直接记录下有几个 preSum[j] 和 preSum[i] - k 相等,直接更新结果,就避免了内层
的 for 循环。我们可以用哈希表,在记录前缀和的同时记录该前缀和出现的次数。(看不懂版)

#include <stdio.h>
#include <stdlib.h>

typedef struct {
int key;
int value;
} Entry;

typedef struct {
Entry* entries;
int size;
int capacity;
} HashMap;

HashMap* createHashMap(int capacity) {
HashMap* hashMap = (HashMap*)malloc(sizeof(HashMap));
hashMap->entries = (Entry*)calloc(capacity, sizeof(Entry));
hashMap->size = 0;
hashMap->capacity = capacity;
return hashMap;
}

void put(HashMap* hashMap, int key, int value) {
int index = key % hashMap->capacity;

while (hashMap->entries[index].value != 0) {
index = (index + 1) % hashMap->capacity;
}

hashMap->entries[index].key = key;
hashMap->entries[index].value = value;
hashMap->size++;
}

int getOrDefault(HashMap* hashMap, int key, int defaultValue) {
int index = key % hashMap->capacity;

while (hashMap->entries[index].value != 0 && hashMap->entries[index].key != key) {
index = (index + 1) % hashMap->capacity;
}

if (hashMap->entries[index].value != 0) {
return hashMap->entries[index].value;
}

return defaultValue;
}

int subarraySum(int* nums, int numsSize, int k) {
int n = numsSize;
HashMap* preSum = createHashMap(n + 1);
put(preSum, 0, 1);
int res = 0, sum0_i = 0;

for (int i = 0; i < n; i++) {
sum0_i += nums[i];
int sum0_j = sum0_i - k;
res += getOrDefault(preSum, sum0_j, 0);
int freq = getOrDefault(preSum, sum0_i, 0);
put(preSum, sum0_i, freq + 1);
}

free(preSum->entries);
free(preSum);
return res;
}

int main() {
int nums[] = { 1, 1, 1 };
int k = 2;
int numsSize = sizeof(nums) / sizeof(nums[0]);
int count = subarraySum(nums, numsSize, k);

printf("Number of subarrays with sum %d = %d\n", k, count);

return 0;
}

这样,就把时间复杂度降到了 O(N),是最优解法了。

前缀和技巧就讲到这⾥,应该说这个算法技巧是会者不难难者不会,实际运⽤中还是要多培养⾃⼰的思维灵
活性,做到⼀眼看出题⽬是⼀个前缀和问题。

标签:obj,前缀,nums,int,数组,preSum,hashMap
From: https://www.cnblogs.com/lulixiu1999/p/17993199

相关文章

  • Java中的数组
    数组1、数组概述数组是相同类型数据的有序集合数组描述的是相同类型的若干个数据,按照一定的先后次序排列组合而成其中,每一个数据称作一个数组元素,每个数组元素可以通过一个下标来访问它们2、数组声明创建首先必须声明数组变量,才能在程序中使用数组。下面是声明数......
  • P5369 [PKUSC2018] 最大前缀和
    [PKUSC2018]最大前缀和LuoguP5369题目描述小C是一个算法竞赛爱好者,有一天小C遇到了一个非常难的问题:求一个序列的最大子段和。但是小C并不会做这个题,于是小C决定把序列随机打乱,然后取序列的最大前缀和作为答案。小C是一个非常有自知之明的人,他知道自己的算法完全......
  • 前缀和与差分典题
    includeusingnamespacestd;constintN=1010;inta[N][N],b[N][N];voidinsert(intx1,inty1,intx2,inty2,intc){b[x1][y1]+=c;b[x2+1][y1]-=c;b[x1][y2+1]-=c;b[x2+1][y2+1]+=c;}intmain(){intn,m,q,w;scanf("%d%d%d",&n,&m,&......
  • 数组:讲解一维数组和多维数组的声明、初始化和访问。
    一维数组是最简单的数组形式,它可以存储一系列相同类型的元素。多维数组则是由多个一维数组组成的数据结构,可以用于存储多维数据。下面我将分别讲解一维数组和多维数组的声明、初始化和访问。一维数组一维数组的声明和初始化可以分为两步进行。声明一维数组在C语言中,可以使用以下语......
  • 80. 删除有序数组中的重复项 II(中)
    目录题目题解:双指针题目给你一个有序数组nums,请你原地删除重复出现的元素,使得出现次数超过两次的元素只出现两次,返回删除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(1)额外空间的条件下完成。说明:为什么返回数值是整数,但输......
  • 动态区间求和——树状数组的基本应用
    目录前言问题引入思路一览具体分析前言准确来说,不应该叫做动态区间求和问题,只能说树状数组经常用于求和操作而已,但是正常的动态条件查询树状数组也是可以做的,具体的下面再说吧问题引入给出一个数组a[],要求做两种操作,一种是修改数组中的一个数,一种是实现查询区间[lt,rt](lt<=......
  • C#数组对象池ArrayPool<T>底层
    深度解析C#数组对象池ArrayPool<T>底层原理 提到池化技术,很多同学可能都不会感到陌生,因为无论是在我们的项目中,还是在学习的过程的过程,都会接触到池化技术。池化技术旨在提高资源的重复使用和系统性能,在.NET中包含以下几种常用的池化技术。(1)、连接池(Connecti......
  • 【板子】树状数组(BIT)
    //lg1908求逆序对//Copyrightyeyou26#include<bits/stdc++.h>usingnamespacestd;#definelllonglongconstintN=(int)1e6+6;llsum;intn;structData{intorigin;intls;intid;}data[N];boolcmporigin(Datax,Datay){r......
  • 树状数组(区间修改&&区间查询)
    #include<bits/stdc++.h> #defineintlonglongusingnamespacestd;intn,m,x,x1,y,z;inta[100010],d[100010],c[100010];intlowbit(intnum){returnnum&-num;}voidadd(intx,inty){ inta=x; while(x<=n)d[x]+=y,c[x]+=(a-1)*y,x+=lowbit(x); ......
  • 167. 两数之和 II - 输入有序数组(中)
    目录题目题解:双指针题目给你一个下标从1开始的整数数组numbers,该数组已按非递减顺序排列,请你从数组中找出满足相加之和等于目标数target的两个数。如果设这两个数分别是numbers[index1]和numbers[index2],则1<=index1<index2<=numbers.length。以长度......