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)

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位的二进制数来表示选择了哪些行。

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较小的情况下,这种方法可能会比回溯法更快,因为它避免了不必要的重复计算。

  • 8
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

科技之歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值