最大子矩阵:前缀和、动态规划

news2024/11/26 16:53:33

最近在学习动态规划,在牛客上刷题时碰到了这一题。其实最初的想法是暴力和前缀和,但是时间复杂度极高,需要套4层循环。后来去网上搜了一下相关的题解和做法,进而了解到了前缀和+线性动态规划的做法。但是在成功做出这题之前,个人感觉所搜到的博主讲解偏向于代码的编写,对于我这种初入算法的小白来说还是蛮费力气的,所以本节内容我将和大家一起从算法原理到代码一一剖析,争取写出清晰且容易理解的算法思路。

题目链接:最大子矩阵_牛客题霸_牛客网 (nowcoder.com)

 

首先我们来明确一下题目要求:本题要求的是我们在一个给定的N x N 的“矩阵”中找到 “元素和”最大的“子矩阵”。这个N x N 的矩阵其实就是一个二维数组,我们把它理解为一个矩阵。那么它的子矩阵就是矩阵中的某一片矩阵型的区域,那所要求的“最大子矩阵”的自然是找到在所有子矩阵中,矩阵元素和最大的那个矩阵。

我们先来看示例1来帮助我们理解一下题意:

我们可以发现,除该子矩阵外,在其他任意地方随意圈出一个子矩阵中的元素和均比图示矩阵的元素和小,此时我们就找到了该矩阵的最大子矩阵。

以下是两种解法:

 

 

说实话,第一种纯暴力解法就不用看了,数据量稍微大一点,就超时了。

第二种使用二维数组前缀和对原始数组进行了预处理,得到了二维前缀和数组,之后在我们求圈定范围的子矩阵元素和时会带来不小便利,但是4层循环O(n^4)的时间复杂度仍然不可小觑。 

那么我们有什么方法能减少时间复杂度呢?

我们先来了解一维数组的前缀和:

一维数组的前缀和是指将数组中从起始位置到当前位置的所有元素相加的结果,即上图中的prefix数组。

我们再来复习一下求一维数组最大子序列所使用到的线性dp算法:

算法原理:

状态表示:dp[i]所表示的是以i结尾的所有子数组中元素的最大和。 

状态转移方程:用来更新动态规划数组dp[i]:

  • nums[i - 1]表示当前位置i对应的原始数组 nums 中的元素值。

  • dp[i - 1] + nums[i - 1] 表示从当前位置向前延伸的子序列的和,即以 nums[i - 1] 结尾的子序列和加上当前位置的元素值。这个值表示了当前位置开始的新子序列的和。

  • max(nums[i - 1], dp[i - 1] + nums[i - 1]) 选择了两种情况中的较大值:第一种情况是只包含当前元素 nums[i - 1],即以当前位置 i 结尾的子序列。第二种情况是将当前位置的元素加入到之前的子序列中,即从当前位置开始新的子序列。

  • 将较大值更新到 dp[i],表示以 nums[i - 1] 结尾的最大子序列和。

这样,通过动态规划数组 dp 的更新,每个位置 i 都计算出了以该位置结尾的最大子序列和,最终找到整个数组的最大子序列和。

由于最后要输出的是dp数组中的最大值,我们可以使用一个变量来记录以 i 位置为结尾的最大子序列和,这样空间复杂度就从O(N)减少到了O(1)。

 其实,对于我们这一题,也可以使用一维数组前缀和与线性dp来解决,我们只需要将二维数组“压缩”为一维数组就好了。那么压缩方式自然是使用前缀和了。

怎么压缩呢?压缩后如何使用前缀和搭配线性dp解题呢?我们接着往下看:

进行完这一步操作后,第 i 行中第 j 列的元素即为:第 j 列从起始位置到当前位置 i 的所有元素相加的结果。 

其实通过上面的例子,我们不难发现进行预处理后的前缀和数组,仍然可以表示原数组的元素,更方便的是对于求原数组圈定矩阵元素和,在处理后的数组中仅通过使用末行数组元素减去起始行前一行的数组元素就可以得到所求矩阵的元素和。

通过不同的末行对起始行的减法操作,我们最后可以得到如下序列:

 通过前后两张图的解析,其实我们不难发现在最终生成的任意一个子序列中,随机取一段连续的数字即可表示原数组中的子矩阵。即原矩阵中所有的子矩阵均可由生成的子序列得到。

大家一定要好好理解并验证上面的两张图和两段话,当理解通透了才便于进行后续代码的编写。

既然 原矩阵中所有的子矩阵均可由生成的子序列得到,那么最大子矩阵必定是所有子序列数组中的最大连续子序列(注意:是上面通过两层循环得到的10个子序列数组中最大连续子序列元素和)。

那么我们接下来的目标很简单,就是对10个子序列数组中的每一个进行对最大连续子序列元素和的求解。所以我们需要再加上一层循环,用来遍历子序列数组。并且我们需要一个变量 ans 来记录每个子序列数组中的最大子序列元素和,需要注意的是 ans 在每次进入循环之前要更新为0,防止对后续最大子序列的求解造成影响。同时需要一个变量maxi 来记录所有子序列数组中最大的连续子序列元素和,而这个值就是我们的最大子矩阵元素和。 

最终代码:

通常我们会对二维数组多申请一行和一列的空间并初始化为0,是为了dp的状态转移方程在使用时不需要对边界情况进行特殊处理,并且不对dp数组元素的结构造成影响,提高代码的简洁性。

#include <iostream>
#include <vector>
using namespace std;

const int N = 101; // 数组的最大大小

int main() {
    int n = 0;
    cin >> n; 
    
    // 初始化大小为 (n+1) x (n+1) 的二维数组,所有元素为 0
    vector<vector<int> > arr(n + 1, vector<int>(n + 1, 0)); 

    // 初始化大小为 (n+1) x (n+1) 的二维向量 dp,所有元素为 0
    vector<vector<int> > dp(n + 1, vector<int>(n + 1, 0)); 

    // 输入矩阵的元素,并计算每列的前缀和
    for(int i = 1; i <= n; i++) {
        for(int j = 1; j <= n; j++) {
            cin >> arr[i][j];
            dp[i][j] = dp[i - 1][j] + arr[i][j]; // 计算每列的前缀和
        }
    }

    int maxi = -128 * 1E4; // 初始化最大值为一个该题中最小的数
    int ans = 0;

    // 遍历所有的行
    for(int i = 0; i < n; i++)  // 从第 0 行到第 n-1 行
    {
        for(int j = i + 1; j <= n; j++) // 从第 1 行到第 n 行
        { 
            ans = 0;
            // 遍历每一列,并计算当前行对的最大子序列和
            for(int k = 1; k <= n; k++)  // 遍历每一列的元素
            {
                // 计算当前行对的最大子序列和,并更新 ans
                ans = max(dp[j][k] - dp[i][k], ans + dp[j][k] - dp[i][k]);

                // 更新目前为止找到的最大子序列和(maxi)
                maxi = max(ans, maxi);
            }
        }
    }

    cout << maxi << endl; // 输出最大子序列和
    return 0;
}

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

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

相关文章

富阳区石弹村全景图-没拍到景点内容

- - - 石梯山庄旁停车场拍摄 建议雨后去 整个山到处都是消息 整座山都在渗水

Python 编程语言中的 None 到底是什么?

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 让我们一起深入了解 Python 中的 None。 什么是 None&#xff1f; 在 Python 编程语言中&#xff0c;None 是一个特殊的常量&#xff0c;它代表了 “无” 或 “没有值”。你可以把它想象成一个空盒子…

Redis-详解(基础)

文章目录 什么是Redis&#xff1f;用Redis的特点&#xff1f;用Redis可以实现哪些功能&#xff1f;Redis的常用数据类型有哪些?Redis的常用框架有哪些?本篇小结 更多相关内容可查看 什么是Redis&#xff1f; Redis&#xff08;Remote DictionaryServer&#xff09;是一个开源…

数据结构与算法===回溯法

文章目录 原理使用场景括号生成代码 小结 原理 回溯法是采用试错的思想&#xff0c;它尝试分步骤的去解决一个问题。在分步骤解决问题的过程中&#xff0c;当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候&#xff0c;它将取消上一步甚至是上几步的计算&#x…

NodeMCU ESP8266 获取I2C从机地址

文章目录 前言关于地址位读写位程序总结前言 I2C总线上可以挂载很多的从设备,每个设备都会有一个自己唯一的一个地址; 关于地址位 通常地址位占7位数据,主设备如果需要向从机发送/接收数据,首先要发送对应从机的地址,然后会匹配总线上挂载的从机的地址; 读写位 该位…

Github学习

1.Git与Github 区别: Git是一个分布式版本控制系统&#xff0c;简单的说就是一个软件&#xff0c;用于记录一个或若干个文件内容变化&#xff0c;以便将来查阅特点版本修订情况的软件。 Github是一个为用户提高Git服务的网站&#xff0c;简单说就是一个可以放代码的地方。Gi…

摩苏尔大坝形变监测

摩苏尔大坝&#xff0c;是伊拉克最大的大坝。它位于底格里斯河35公里&#xff0c;北距摩苏尔市&#xff0c;这是一座粘土质地的水坝&#xff0c;高113米&#xff0c;长3.2公里&#xff0c;于1986落成。 大坝建成后不久&#xff0c;大坝就遇到了由软石膏地基造成的一些结构性问题…

09.zabbix自定义模块并使用

zabbix自定义模块并使用 根据tcp的11中状态获取值&#xff0c;进行批量配置监控项 [rootyunlong66 ~]# cat /etc/zabbix/zabbix_agentd.d/tcp.conf UserParameterESTABLISHED,netstat -antp |grep -c ESTABLISHED UserParameterSYN_SENT,netstat -antp |grep -c SYN_SENT Use…

基于深度学习神经网络的AI图像PSD去雾系统源码

第一步&#xff1a;PSD介绍 以往的研究主要集中在具有合成模糊图像的训练模型上&#xff0c;当模型用于真实世界的模糊图像时&#xff0c;会导致性能下降。 为了解决上述问题&#xff0c;提高去雾的泛化性能&#xff0c;作者提出了一种Principled Synthetic-to-real Dehazing (…

软件体系结构风格

目录 一、定义 二、.经典软件体系结构风格&#xff1a; 1.管道和过滤器 2.数据抽象和面向对象系统 3.基于事件系统&#xff08;隐式调用&#xff09; 4.分层系统 5.仓库 6.C2风格 7.C/S 8.三层C/S 9.B/S 题&#xff1a; 一、定义 软件体系机构风格是描述某一特定应用…

【CSP CCF记录】202104-1 灰度直方图

题目 过程 #include<bits/stdc.h> using namespace std; int n,m,L; int A[502][502]; int main() {cin>>n>>m>>L;int h[L]{0};for(int i0;i<n;i){for(int j0;j<m;j){cin>>A[i][j];h[A[i][j]];}}for(int i0;i<L;i)cout<<h[i]<…

第四届上海理工大学程序设计全国挑战赛 J.上学 题解 DFS 容斥

上学 题目描述 usst 小学里有 n 名学生&#xff0c;他们分别居住在 n 个地点&#xff0c;第 i 名学生居住在第 i 个地点&#xff0c;这些地点由 n−1 条双向道路连接&#xff0c;保证任意两个地点之间可以通过若干条双向道路抵达。学校则位于另外的第 0 个地点&#xff0c;第…

SeetaFace6人脸活体检测C++代码实现Demo

SeetaFace6包含人脸识别的基本能力&#xff1a;人脸检测、关键点定位、人脸识别&#xff0c;同时增加了活体检测、质量评估、年龄性别估计&#xff0c;并且顺应实际应用需求&#xff0c;开放口罩检测以及口罩佩戴场景下的人脸识别模型。 官网地址&#xff1a;https://github.co…

Rerank进一步提升RAG效果

RAG & Rerank 目前大模型应用中&#xff0c;RAG&#xff08;Retrieval Augmented Generation&#xff0c;检索增强生成&#xff09;是一种在对话&#xff08;QA&#xff09;场景下最主要的应用形式&#xff0c;它主要解决大模型的知识存储和更新问题。 简述RAG without R…

【前端系列】什么是yarn

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Python 全栈系列245 nginx 前端web页面透传

说明 过去的几年&#xff0c;我已经构造了很多组件&#xff0c;从图的角度来看&#xff0c;完成了很多点。这些点的单点测试看起来都不错&#xff0c;但是因为没有连起来&#xff0c;所以无法体现系统价值。好比发动机的马力虽然大&#xff0c;但是没有传动轴&#xff0c;那就…

重学JavaScript核心知识点(二)—— 详解Js中的模块化

详解Js中的模块化 1. 模块化的背景2. 来看一个例子3. 优雅的做法 —— 创建模块对象4. 模块与类&#xff08;class&#xff09;5. 合并模块6. 动态加载模块 1. 模块化的背景 JavaScript 在诞生之初是体积很小的&#xff0c;早期&#xff0c;它们大多被用来执行独立的脚本任务&…

Shell编程之循环语甸与函数

for 遍历循环 1&#xff09;for 变量 in 取值列表 for i in $(seq 1 10) do 命令序列 .... done 2&#xff09;for ((变量初始值; 变量范围; 变量的迭代方式)) for ((i1; i<10; i)) do 命令序列 .... done IFS for循环取值列表分隔符 set | grep IFS …

---随笔--Java实现TCP通信(双端通信接收与发送)

---随笔--Java实现TCP通信&#xff08;双端通信接收与发送&#xff09; 引言1. 什么是TCP通信2. 服务器与客户端核心代码2.1 服务器ServerSocket端核心代码2.2 用户Socket端核心代码2.3 小贴士之关于try-with-resources自动关闭资源的使用 3. 具体服务器端实现4. 具体客户端实现…

WordPress 、Typecho 站点的 MySQL/MariaDB 数据库优化

今天明月给大家分享一下 WordPress 、Typecho 站点的 MySQL/MariaDB 数据库优化&#xff0c;无论你的站点采用是 WordPress 还是 Typecho&#xff0c;都要用到 MySQL/MariaDB 数据库&#xff0c;我们以 MySQL 为主&#xff08;MariaDB 其实跟 MySQL 基本没啥大的区别&#xff0…