每日一题 力扣 3546. 等和矩阵分割 I 前缀和 贪心 剪枝 C++ 题解

文章目录

题目描述

力扣 3546. 等和矩阵分割 I

示例 1:

输入: grid = [[1,4],[2,3]]

输出: true

解释:

在第 0 行和第 1 行之间进行水平分割,得到两个非空部分,每部分的元素之和为 5。因此,答案是 true。
示例 2:

输入: grid = [[1,3],[2,4]]

输出: false

解释:

无论是水平分割还是垂直分割,都无法使两个非空部分的元素之和相等。因此,答案是 false。
提示:

1 <= m == grid.length <= 105

1 <= n == grid[i].length <= 105

2 <= m * n <= 105

1 <= grid[i][j] <= 105

方法一:前缀和

思路简述

我们可以使用二维数组 sums[i][j] 来统计以 (i-1, j-1) 为右下角、(0, 0) 为左上角的矩形区域的元素和。

前缀和的计算有两种常见写法:

  • 写法一:sums[i+1][j+1] = sums[i][j+1] + sums[i+1][j] - sums[i][j] + grid[i][j](对应 sums[i][j] 表示原矩阵前 i 行、前 j 列的和);
  • 写法二:sums[i][j] = sums[i][j-1] + sums[i-1][j] - sums[i-1][j-1] + grid[i][j](对应 sums[i][j] 表示以 (i,j) 为右下角的矩形和)。
    两种写法本质相同,仅下标偏移不同,选择一种即可

关于二维前缀和如何计算任意矩形区域的和,我在之前的博客中已配合图片详细讲解,这里不再赘述,感兴趣的朋友可以参考:C++ 前缀和 高频笔试考点 实用技巧 牛客 DP35 二维模板 题解 每日一题

得到前缀和数组后,整个矩阵的总和即为 sums[m][n](也可单独统计变量 sum,代码更直观)。我们只需检查是否存在某一行 i,使得 sums[i][n] * 2 == sum(水平分割,蓝色部分),或某一列 j,使得 sums[m][j] * 2 == sum(垂直分割,绿色部分)。如下图所示,边界处的前缀和若满足两倍关系,则对应位置存在合法分割线:

注:图片中的颜色并不是只得具体矩形而是区域,具体每个点代表的矩形太多画出来太乱所以就没画

代码实现

cpp 复制代码
class Solution {
public:
    bool canPartitionGrid(vector<vector<int>>& grid) 
    {
        // 获取矩阵的行数 m 和列数 n
        int m = grid.size(), n = grid[0].size();

        // 定义前缀和数组 sums,大小为 (m+1) x (n+1),使用 1-based 索引避免边界越界
        // sums[i][j] 表示原矩阵中前 i 行、前 j 列组成的矩形区域的元素和
        vector<vector<long long>> sums(m+1, vector<long long>(n+1, 0));
        long long sum = 0; // 用于统计整个矩阵的元素总和

        // 遍历原矩阵,计算前缀和数组并累加总和
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                // 前缀和核心公式:
                // sums[i+1][j+1] = 上方矩形和 + 左侧矩形和 - 重复计算的左上角矩形和 + 当前元素
                sums[i+1][j+1] = sums[i][j+1] + sums[i+1][j] - sums[i][j] + grid[i][j];
                sum += grid[i][j]; // 累加当前元素到总和
            }
        }

        // 检查是否存在水平分割线:遍历每一行 i
        // 若前 i 行的和 sums[i][n] 的两倍等于总和,说明可以水平分割
        for(int i = 0; i <= m; i++)
        {
            if(sum == sums[i][n] * 2)
                return true;
        }

        // 检查是否存在垂直分割线:遍历每一列 j
        // 若前 j 列的和 sums[m][j] 的两倍等于总和,说明可以垂直分割
        for(int i = 0; i <= n; i++)
        {
            if(sum == sums[m][i] * 2)
                return true;
        }

        // 若水平和垂直分割都不满足,返回 false
        return false;
    }
};

复杂度分析

  • 时间复杂度 : O ( m n ) O(mn) O(mn)。需遍历一次矩阵预处理前缀和,再分别遍历行和列检查分割条件,总操作数与矩阵元素数量成正比。
  • 空间复杂度 : O ( m n ) O(mn) O(mn)。需创建一个 ( m + 1 ) × ( n + 1 ) (m+1) \times (n+1) (m+1)×(n+1) 的前缀和数组。

方法二:暴力+贪心+剪枝

思路简述

为了避免前缀和数组的空间开销,我们可以结合剪枝贪心思想,仅通过变量累加完成判断,实际运行速度通常比方法一更优。

  • 剪枝逻辑 :若矩阵总和为奇数,则必然无法分割为两个和相等的整数部分,直接返回 false。(总和除二为小数,但是题目中数组中的数字必然为整数,逻辑上不成立)
  • 贪心策略 :分别尝试水平和垂直分割:
    • 水平分割时,逐行累加元素和,若累加和等于总和的一半,则找到合法分割;若超过一半,由于元素均为正整数,后续行累加只会更大,可提前终止(剪枝)。
    • 垂直分割同理,逐列累加检查即可。

该方法的缺点是通用性较弱,仅适用于本题场景,但在时间和空间上更具优势。

代码实现

cpp 复制代码
class Solution {
public:
    using ll = long long; // 为 long long 定义别名 ll,简化代码书写
    bool canPartitionGrid(vector<vector<int>>& grid) {
        const int rows = grid.size(); // 获取矩阵行数(用 const 防止意外修改)
        const int cols = grid[0].size(); // 获取矩阵列数
        
        // 第一步:计算整个矩阵的元素总和
        ll total = 0;
        // 范围 for 循环遍历每一行
        for (auto& row : grid) 
        { 
            for (int num : row) 
            	total += num; // 累加当前行的所有元素
        }

        // 关键剪枝:若总和为奇数,必然无法分割为两个和相等的整数部分,直接返回 false
        if (total % 2 != 0) 
        	return false;
        	
        ll target = total / 2; // 目标值:分割后每部分的和需要等于总和的一半

        // 第二步:检查是否存在水平分割线(逐行累加)
        ll cur = 0; // 记录当前累加和
        for (auto& row : grid) 
        {
        	// 累加当前行的所有元素
            for (int num : row) 
            	cur += num; 
            // 若当前累加和等于目标值,找到合法分割
            if (cur == target) 
            	return true; 
            // 贪心剪枝:元素均为正整数,若超过目标值,后续只会更大,直接终止
            if (cur > target) 
            	break; 
        }

        // 第三步:检查是否存在垂直分割线(逐列累加)
        cur = 0; // 重置当前累加和
		
		// 外层循环遍历列
        for (int j = 0; j < cols; ++j) 
        { 
        	// 内层循环遍历行,累加当前列的所有元素
            for (int i = 0; i < rows; ++i) 
            	cur += grid[i][j]; 

			// 若当前累加和等于目标值,找到合法分割	
            if (cur == target) 
            	return true; 
            	
			// 同理,超过目标值则提前终止
            if (cur > target) 
            	break; 
        }

        // 若水平和垂直分割都不满足,返回 false
        return false;
    }
};

复杂度分析

  • 时间复杂度 : O ( m n ) O(mn) O(mn)。最坏情况下需遍历整个矩阵,但由于剪枝的存在,实际运行通常更快。
  • 空间复杂度 : O ( 1 ) O(1) O(1)。仅使用常数个变量存储总和、目标值和当前累加和。

踩坑记录

  1. 边界处理:注意前缀和数组的下标偏移需与定义一致,避免越界或计算错误;
  2. 映射关系 :理清前缀和数组与原矩阵的对应关系,切勿混淆 sums[i][j] 表示的区域范围;
  3. 溢出风险 :矩阵元素和可能较大,需使用 long long 类型存储前缀和与总和,防止整数溢出。

如果这篇博客对你有帮助,别忘了点赞支持一下~也可以收藏起来,方便后续刷题复习时随时翻看。要是能顺手点个关注,爱弥斯还能得到漂泊者批准的游戏时间哦!

相关推荐
我是咸鱼不闲呀4 小时前
力扣Hot100系列23(Java)——[回溯]总结(上)(全排列,子集,电话号码的字母组合,组合总和)
java·算法·leetcode
tobias.b4 小时前
深度学习 超清晰通俗讲解 + 核心算法 + 使用场景
人工智能·深度学习·算法
七夜zippoe4 小时前
量子计算入门:Qiskit框架实战
python·算法·量子计算·ibm·qiskit
小此方4 小时前
Re:从零开始的 C++ STL篇(八)深度解构AVL树自平衡机制:平衡维护与旋转调整背后的严密逻辑
开发语言·数据结构·c++·算法·stl
2301_789015624 小时前
封装哈希表实现unordered_set/undered_map
c语言·数据结构·c++·算法·哈希算法
落羽的落羽4 小时前
【Linux系统】中断机制、用户态与内核态、虚拟地址与页表的本质
java·linux·服务器·c++·人工智能·算法·机器学习
神工坊4 小时前
技术分享︱多重参考系模型在风扇通风仿真中的自动化实现:精度与效率的工程平衡
算法·hpc·并行计算·cfd·cae·流体力学·风扇仿真
独断万古他化4 小时前
【算法通关】递归:汉诺塔、合并链表、反转链表、两两交换、快速幂全解
数据结构·算法·链表·递归
前端摸鱼匠12 小时前
【AI大模型春招面试题11】什么是模型的“涌现能力”(Emergent Ability)?出现条件是什么?
人工智能·算法·ai·自然语言处理·面试·职场和发展