【算法】一维二维数组前缀和,以及计算二维矩阵中的子矩阵和

server/2024/12/25 14:31:53/

前缀和的概念

通过构建一个前缀和数组,我们可以在常数时间(O(1))内使用前缀和数组计算任意子数组或子矩阵的和。

简单来说,就是把前面的项加在一起,使得新构建的前缀和数组中每一项都是原数组对应项之前的总和。

一维前缀和

前缀和数组 prefixSum 来记录从 arr[0]arr[i] 的和:

prefixSum[i] = arr[0] + arr[1] + ... + arr[i]

对于任意一个区间 [l, r],我们可以利用前缀和数组来快速计算该区间的和:

sum(l, r) = prefixSum[r] - prefixSum[l - 1]
  • 这里,prefixSum[r] 表示从数组的开始到 r 的和。
  • prefixSum[l-1] 表示从数组的开始到 l-1 的和。
  • 通过相减即可得到区间 [l, r] 的和。

cpp案例
// # 一维前缀和的应用:快速计算数组区间的和
#include <iostream>
#include <vector>using namespace std;int main() {// 输入数组int n;cout << "请输入数组大小: ";cin >> n;vector<int> arr(n);cout << "请输入数组元素: ";for (int i = 0; i < n; ++i) {cin >> arr[i];}// 计算前缀和vector<int> prefixSum(n + 1, 0); // 前缀和数组,大小为 n+1for (int i = 1; i <= n; ++i) {prefixSum[i] = prefixSum[i - 1] + arr[i - 1];}// 输出前缀和数组cout << "前缀和数组: ";for (int i = 1; i <= n; ++i) {cout << prefixSum[i] << " ";}cout << endl;// 查询区间和int queries;cout << "请输入查询的区间数: ";cin >> queries;while (queries--) {int l, r;cout << "请输入区间 [l, r] (1-based index): ";cin >> l >> r;// 利用前缀和计算区间和int sum = prefixSum[r] - prefixSum[l - 1];cout << "区间 [" << l << ", " << r << "] 的和是: " << sum << endl;}return 0;
}

二维前缀和

  1. 二维前缀和的构建
    二维前缀和用于快速计算矩阵中任意子矩阵的元素和。每个前缀和元素 prefixSum[i][j] 表示原矩阵从左上角 (1,1)(i,j) 的所有元素总和。

构建公式:

prefixSum[i][j] = prefixSum[i-1][j] + prefixSum[i][j-1] - prefixSum[i-1][j-1] + matrix[i-1][j-1]

示例矩阵

matrix:
1 2 3
4 5 6
7 8 9

对应前缀和矩阵

prefixSum:
0  0  0  0
0  1  3  6
0  5 12 21
0 12 27 45

每个值计算方法:

  • prefixSum[1][1] = matrix[0][0] = 1
  • prefixSum[2][3] = prefixSum[1][3] + prefixSum[2][2] - prefixSum[1][2] + matrix[1][2] = 6 + 12 - 3 + 6 = 21
  1. 矩阵求和
    要快速计算左上角 (i,j) 到右下角 (s,t) 的子矩阵和,公式为:
sum = prefixSum[s][t] - prefixSum[i-1][t] - prefixSum[s][j-1] + prefixSum[i-1][j-1]

计算逻辑:

  • prefixSum[s][t] 是从左上角到 (s,t) 的所有元素和。
  • 减去 prefixSum[i-1][t] 去掉上方部分。
  • 减去 prefixSum[s][j-1] 去掉左方部分。
  • 加上 prefixSum[i-1][j-1] 补回重复减掉的部分。
  1. 示例计算
    矩阵
matrix:    
1 2 3        
4 5 6        
7 8 9        

目标子矩阵:从 (2,2)(3,3)

矩阵:        
5 6
8 9

计算步骤:

prefixSum[3][3] = 45        
prefixSum[1][3] = 6        
prefixSum[3][1] = 12        
prefixSum[1][1] = 1        
sum = 45 - 6 - 12 + 1 = 28        

cpp案例
#include <iostream>
#include <vector>using namespace std;// 计算二维矩阵前缀和
vector<vector<int>> computePrefixSum(const vector<vector<int>>& matrix)
{int m{ static_cast<int>(matrix.size()) };	// 矩阵行数int n{ static_cast<int>(matrix[0].size()) };	// 列// 创建一个(m+1)x(n+1)的前缀和矩阵,初始化为0vector<vector<int>> prefixSum(m + 1, vector<int>(n + 1, 0));/*
1  2  3
4  5  6
7  8  9
我们计算 (1, 1) 的前缀和(对应 prefixSum[2][2]):当前值是 matrix[1][1] = 5。
上方部分是 prefixSum[1][2] = 3。
左侧部分是 prefixSum[2][1] = 5。
重叠区域是 prefixSum[1][1] = 1。
公式:
prefixSum[2][2] = 5 + 3 + 5 - 1 = 12*/for (int i{ 1 }; i <= m; ++i){for (int j{ 1 }; j <= n; ++j){prefixSum[i][j] = matrix[i - 1][j - 1] + prefixSum[i - 1][j] + prefixSum[i][j - 1] - prefixSum[i - 1][j - 1];}}return prefixSum;
}// 根据前缀和矩阵计算子矩阵
int getSubmatrixSum(const vector<vector<int>>& prefixSum, int i, int j, int s, int t)
{	// 使用前缀和公式计算子矩阵return prefixSum[s + 1][t + 1] - prefixSum[i][t + 1] - prefixSum[s + 1][j] + prefixSum[i][j];// 可以在 O(1) 时间内得到任意区间(或子矩阵)的和。
int main_11()
{int m{}, n{};cout << "请输入矩阵行数m列数n(2 < m, n < 100): ";cin >> m >> n;// 检查矩阵大小是否合法if (m <= 2 || n <= 2 || m >= 100 || n >= 100){cout << " 行数或者列数不符合要求!\n";return 1;}vector<vector<int>> matrix(m, vector<int>(n));cout << "请输入矩阵元素:\n";for (int i{}; i < m; ++i){for (int j{}; j < n; ++j){cin >> matrix[i][j];}}// 计算前缀和vector<vector<int>> prefixSum = computePrefixSum(matrix);// 处理子矩阵查询int testCases{};cout << "请输入要测试的子矩阵个数:";cin >> testCases;// 查询并输出每个子矩阵的和for (int k{}; k < testCases; ++k){int i, j, s, t;cout << "请输入第 " << k + 1 << " 个子矩阵的左上角坐标(i, j) 和右下角坐标(s, t):";cin >> i >> j >> s >> t;// 将输入的坐标转换成从0开始的索引--i; --j; --s; --t;// 验证坐标是否有效if (i < 0 || j < 0 || s >= m || t >= n || i > s || j > t){cout << "坐标输入错误!\n";continue;}int sum{ getSubmatrixSum(prefixSum, i, j, s, t) };cout << "子矩阵的和是:" << sum << '\n';}return 0;
}

http://www.ppmy.cn/server/153057.html

相关文章

HTTP 协议规定的协议头和请求头

一、协议头&#xff08;HTTP Headers&#xff09;概述 HTTP 协议头是 HTTP 请求和响应消息的一部分&#xff0c;它们包含了关于消息的各种元信息。这些信息对于客户端和服务器之间正确地传输和理解数据至关重要。 协议头可以分为请求头&#xff08;Request Headers&#xff0…

低代码软件搭建自学第二天——构建拖拽功能

文章目录 第 3 步&#xff1a;实现拖拽功能3.1 拖拽的基本概念3.2 创建基础拖拽界面代码示例&#xff1a;拖拽矩形运行结果&#xff1a; 3.3 添加多个拖拽元素代码示例&#xff1a;多个拖拽元素运行结果&#xff1a; 3.4 添加工具箱代码示例&#xff1a;工具箱 拖拽运行结果&a…

GESP CCF C++八级编程等级考试认证真题 2024年12月

202412 GESP CCF C八级编程等级考试认证真题 1 单选题&#xff08;每题 2 分&#xff0c;共 30 分&#xff09; 第 1 题 小杨家响应国家“以旧换新”政策&#xff0c;将自家的汽油车置换为新能源汽车&#xff0c;正在准备自编车牌。自编车牌包括5 位数字或英文字母&#xff0c…

力扣题目解析--两数相除

题目 给你两个整数&#xff0c;被除数 dividend 和除数 divisor。将两数相除&#xff0c;要求 不使用 乘法、除法和取余运算。 整数除法应该向零截断&#xff0c;也就是截去&#xff08;truncate&#xff09;其小数部分。例如&#xff0c;8.345 将被截断为 8 &#xff0c;-2.…

微调大模型时,如何进行数据预处理? 将<input, output>转换为模型所需的<input_ids, labels, attention_mask>

原始训练数据集格式如下&#xff1a; <input, output> 形式&#xff1a;字符 模型训练所需数据格式如下&#xff1a; # tokenizer处理后 return {"input_ids": example,"labels": labels,"attention_mask": example_mask, } 将字符转…

鸿蒙项目云捐助第十七讲云捐助我的页面上半部分的实现

鸿蒙项目云捐助第十七讲云捐助我的页面上半部分的实现 在一般的应用app中都会有一个“我的”页面&#xff0c;在“我的”页面中可以完成某些设置&#xff0c;也可以完成某些附加功能&#xff0c;如“修改密码”等相关功能。这里的鸿蒙云捐助也有一个“我的”功能页面。这里对“…

第二节:让电机转起来【51单片机-L298N-步进电机教程】

摘要&#xff1a;本节介绍用简单的方式&#xff0c;让步进电机转起来。其目的之一是对电机转动有直观的感受&#xff0c;二是熟悉整个开发流程 本系列教程必要的51单片机基础包括IO口操作、中断、定时器三个部分&#xff0c;可先行学习 一、软件清单 需要用到的软件有keil5编译…

opencv sdk for java中提示无stiching模块接口的问题

1、问题介绍 安卓项目中有新的需求&#xff0c;在 jni 中增加 stiching_detail.cpp 中全景拼接的实现。 但是在编译时&#xff0c;出现大量报错&#xff0c;如下截图所示 实际上&#xff0c;其他opencv的接口函数 例如 core dnn等都能正常使用&#xff0c;直觉上初步怀疑 ope…