Swift 解法详解 LeetCode 363:矩形区域不超过 K 的最大数值和
文章目录
- 摘要
- 描述
- 题解答案
- 题解代码分析
- 代码拆解
- 示例测试及结果
- 时间复杂度
- 空间复杂度
- 总结
摘要
这道题的名字听上去有点“硬核数学”,但其实本质就是:在一个二维矩阵里找一个子矩形,这个子矩形的元素和要尽可能大,但同时又不能超过一个给定的阈值 K。
现实中你可能会遇到类似问题:
- 数据分析里,我们需要找出某个时间区间内的最大收益,但不能超过某个风控上限。
- 在游戏里,可能要计算玩家在地图某个范围内收集的金币,不能超过背包容量。
今天我们就把这个题目拆开来讲,从暴力到优化,最后写出一个 Swift Demo 带你跑一遍。
描述
题目要求:
给定一个 m x n
的矩阵和一个整数 k
,请找出矩阵中 不超过 k 的最大子矩形和。
示例:
输入: matrix = [[1,0,1],[0,-2,3]], k = 2
输出: 2
解释: 子矩形 [[0, -2], [1, 3]] 的和是 2,正好等于 k。
注意:
- 子矩形就是连续的行和列围起来的一个小区域。
- 我们需要考虑所有可能的矩形组合。
- 题目难点在于矩阵可能很大,如果用暴力解法会超时。
题解答案
直观思路:
- 先固定矩阵的上下边界,把问题转化为一维数组的「子数组和不超过 k 的最大值」。
- 然后用前缀和 + 有序数据结构来加速查找。
- 最终得到的解法就是“枚举上下边界 + 求解一维子数组最大和”。
题解代码分析
下面是 Swift 代码实现(附带详细注释):
import Foundationclass Solution {func maxSumSubmatrix(_ matrix: [[Int]], _ k: Int) -> Int {let m = matrix.countlet n = matrix[0].countvar res = Int.min// 枚举左右边界for left in 0..<n {var rowSum = Array(repeating: 0, count: m)for right in left..<n {// 更新每行的区间和for i in 0..<m {rowSum[i] += matrix[i][right]}// 在 rowSum 里找不超过 k 的最大子数组和res = max(res, maxSubArrayNoMoreThanK(rowSum, k))if res == k { return res } // 提前结束}}return res}// 求一维数组中不超过k的最大子数组和private func maxSubArrayNoMoreThanK(_ nums: [Int], _ k: Int) -> Int {var prefix = 0var res = Int.minvar prefixSet = [0]for num in nums {prefix += num// 二分查找,找 >= prefix - k 的最小前缀和var left = 0, right = prefixSet.count - 1var candidate: Int? = nilwhile left <= right {let mid = (left + right) / 2if prefixSet[mid] >= prefix - k {candidate = prefixSet[mid]right = mid - 1} else {left = mid + 1}}if let cand = candidate {res = max(res, prefix - cand)}// 插入保持有序insertSorted(&prefixSet, prefix)}return res}// 插入有序数组private func insertSorted(_ arr: inout [Int], _ val: Int) {var l = 0, r = arr.countwhile l < r {let mid = (l + r) / 2if arr[mid] < val {l = mid + 1} else {r = mid}}arr.insert(val, at: l)}
}// Demo 演示
let solution = Solution()
let matrix = [[1,0,1],[0,-2,3]]
let k = 2
print("矩阵中不超过 \(k) 的最大矩形和是: \(solution.maxSumSubmatrix(matrix, k))")
代码拆解
-
固定左右边界:
我们从左列到右列一层层扩展,把二维问题降成一维。 -
构建 rowSum:
rowSum 表示在当前左右边界下,每行的列和。 -
一维问题求解:
这时候问题变成了「找一维数组的最大子数组和,且不超过 k」。- 用前缀和来快速求区间和。
- 用一个有序数组存储前缀和,方便二分查找。
-
插入有序数组:
保证 prefixSet 始终有序,这样才能用二分查找高效找到候选值。
示例测试及结果
运行上面的 Demo,会输出:
矩阵中不超过 2 的最大矩形和是: 2
你可以再试试:
let matrix2 = [[2,2,-1]]
print(solution.maxSumSubmatrix(matrix2, 3)) // 输出 3
这个例子里,矩阵只有一行,子数组 [2, -1] 的和是 1,小于等于 3,但最大的是 [2, 2, -1] = 3,正好等于 k。
时间复杂度
- 外层固定左右边界:O(n²)。
- 内层处理 rowSum:O(m)。
- 一维子数组求解:O(m log m)。
总复杂度:O(n² * m log m),其中 m 是行数,n 是列数。
空间复杂度
- rowSum 占用 O(m)。
- prefixSet 在最坏情况下 O(m)。
总空间复杂度:O(m)。
总结
这道题表面看是矩阵题,但真正的突破点在于把二维问题转成一维,再利用「前缀和 + 二分」解决。
这类题很有工程意义:
- 数据库里统计一段时间窗口内的数据总和上限。
- 金融风控里计算收益不能超过阈值的区间。
- 游戏设计里限制玩家的行为值上限。
所以说,不只是 LeetCode,这种滑动窗口 + 前缀和的思维方式,在日常开发里用处非常广。