LeetCode 2732. 找到矩阵中的好子集

一、题目描述

给定一个 m x n 的整数矩阵 mat 和一个整数 k,我们需要找到一个大小为 k 的子集 rows,使得这个子集对应的行在矩阵 mat 中构成的子矩阵中,所有元素之和最大。返回这个子矩阵中所有元素之和的最大值。

注意:

  • 子集 rows 中的每个元素(即行索引)都应该是唯一的。
  • 子集 rows 的大小应为 k。

示例:

输入:mat = [[1,2,3],[4,5,6],[7,8,9]], k = 2

输出:18

解释:选择第 1 行和第 2 行,得到子矩阵 [[4,5,6],[7,8,9]],子矩阵中所有元素之和为 18。

二、解题思路

为了解决这个问题,我们需要从几个不同的角度进行思考。首先,由于题目要求返回的是子矩阵中所有元素之和的最大值,我们可以考虑使用贪心算法来尝试解决这个问题。但是,贪心算法在这里可能并不适用,因为我们不能保证每一步的选择都是最优的。

其次,我们可以考虑使用回溯法(Backtracking)来解决这个问题。回溯法是一种通过穷举所有可能的解来找出所有解的算法。在这个问题中,我们可以将回溯法应用于选择行索引的过程中,通过递归地尝试所有可能的组合,来找到满足条件的最优解。

具体的解题步骤如下:

  1. 定义回溯函数:我们需要定义一个回溯函数,该函数接受当前选择的行索引集合、当前选择的行索引数量以及当前子矩阵的元素和作为参数。
  2. 判断终止条件 :在回溯函数中,我们首先判断当前选择的行索引数量是否达到了 k。如果达到了 k,则更新最大元素和(如果当前子矩阵的元素和大于之前的最大元素和)。
  3. 递归选择:然后,我们遍历矩阵中的每一行,对于每一行,我们有两种选择:选择该行(将其添加到当前选择的行索引集合中,并递归调用回溯函数处理下一行),或者不选择该行(直接递归调用回溯函数处理下一行)。
  4. 回溯操作:在递归调用完成后,我们需要将当前选择的行索引从集合中移除,以便尝试其他可能的组合。
  5. 初始化参数并调用回溯函数 :最后,我们初始化最大元素和为一个较小的值(如0),并调用回溯函数开始处理第一行。
    但除了回溯法之外,如果矩阵的行数不是非常大,我们还可以考虑使用动态规划(Dynamic Programming)结合位运算来优化解决方案。以下是两种代码实现:

1. 回溯法(Backtracking)

python 复制代码
def goodSubsetSum(mat, k):
    m, n = len(mat), len(mat[0])
    max_sum = 0

    def backtrack(rows, curr_sum, start):
        nonlocal max_sum
        if len(rows) == k:
            max_sum = max(max_sum, curr_sum)
            return

        for i in range(start, m):
            if i > start and mat[i] == mat[i - 1]:  # 避免重复选择相同的行
                continue
            backtrack(rows + [i], curr_sum + sum(mat[i]), i + 1)

    backtrack([], 0, 0)
    return max_sum

# 示例测试
mat = [[1,2,3],[4,5,6],[7,8,9]]
k = 2
print(goodSubsetSum(mat, k))  # 输出: 18

接下来,我们可以考虑使用动态规划的方法。然而,动态规划通常需要定义一个状态数组来保存中间结果,但在这个问题中,由于我们需要选择的是行索引,而不是具体的元素值,因此状态的定义可能会变得比较复杂。

2. 动态规划结合位运算(Dynamic Programming with Bit Manipulation)

由于矩阵中的行是唯一的(或者可以视为唯一,如避免选择重复行),我们可以使用位掩码(bitmask)来表示一个行集合。对于m行矩阵,我们可以使用一个m位的二进制数来表示选择了哪些行。

python 复制代码
def goodSubsetSum(mat, k):
    m, n = len(mat), len(mat[0])
    dp = [0] * (1 << m)  # 初始化dp数组,大小为2^m

    # 初始化只选择一行的情况
    for i in range(m):
        dp[1 << i] = sum(mat[i])

    # 动态规划填充dp数组
    for mask in range(1, 1 << m):
        if bin(mask).count('1') <= k:  # 确保选择的行数不超过k
            for i in range(m):
                if mask & (1 << i):  # 如果第i行被选择
                    # 尝试移除第i行并更新最大和(实际上不会真的移除,只是计算不包括第i行的和)
                    next_mask = mask ^ (1 << i)
                    dp[mask] = max(dp[mask], dp[next_mask] + sum(mat[i]))

    # 查找包含k行的最大和
    max_sum = 0
    for mask in range(1 << m):
        if bin(mask).count('1') == k:
            max_sum = max(max_sum, dp[mask])

    return max_sum

# 示例测试
mat = [[1,2,3],[4,5,6],[7,8,9]]
k = 2
print(goodSubsetSum(mat, k))  # 输出: 18

注意:动态规划结合位运算的方法在m(行数)较大时可能会因为空间复杂度过高而不太实用,因为它需要一个大小为2^m的数组来存储中间结果。然而,在m较小的情况下,这种方法可能会比回溯法更快,因为它避免了不必要的重复计算。

相关推荐
UIU11411 小时前
P1233 [ICPC 2001 Taejon R] 木棍加工
数据结构·c++·算法·动态规划
论文复现现场11 小时前
WRF、FEA 能用 L40S 加速吗?求解器检查、断点恢复与长期运行
java·数据库·算法·wrf·有限元分析·gpu计算·断点续算
ZhengEnCi12 小时前
用 AI 写代码为什么拼到最后还是拼人:算力瓶颈与领域专家提示词的本质差异
算法·架构
秋天的怀念_David13 小时前
第二十一夜 · 数字黑洞与西西弗斯的惩罚
java·程序人生·算法
碧海银沙音频科技研究院13 小时前
在PC电脑上用Whisper→Marian算法实现离线语音翻译功能
算法·whisper
niyongsheng14 小时前
3D 装箱算法工程化:数据结构、放置启发式与支撑规则
数据结构·算法·rust
j7~16 小时前
【穷举、深搜与回溯算法】一棵决策树讲透全排列与子集
数据结构·c++·算法·leetcode·回溯·递归
傅科摆 _ py17 小时前
DP,贪心,堆和栈,二分力扣复习
开发语言·python·算法
Shaoshing18 小时前
LeetCode100 42.接雨水
java·算法·leetcode
域限奇点访客18 小时前
用AI生成问卷的网站怎么选?看题目录入结构、题型映射和生成后的校验
算法