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

相关推荐
小梁不秃捏35 分钟前
科比老大职业生涯数据预测(基于随机森林模型)
算法·随机森林·机器学习
WoShop商城源码36 分钟前
快手矩阵系统源码:技术优势解析
人工智能·线性代数·矩阵
科技之歌43 分钟前
Leetcode 115 不同的子序列
算法·leetcode·职场和发展
斯择微韵1 小时前
力扣习题--哈沙德数
算法·leetcode·职场和发展
Neituijunsir1 小时前
2024.06.27 校招 实习 内推 面经
c++·算法·面试·车载系统·自动驾驶·汽车·求职招聘
DisonTangor1 小时前
新纪录将圆周率计算到了小数点后202万亿位 用了28块61.44TB SSD
算法·链表·数学建模
计算机周老师1 小时前
java-arraylist 源码分析 1
java·python·算法
dot.Net安全矩阵1 小时前
.NET 漏洞情报 | 某整合管理平台SQL注入
数据库·sql·安全·矩阵·.net
danaaaa1 小时前
算法力扣刷题 三十一【150. 逆波兰表达式求值】
数据结构·c++·算法·leetcode·职场和发展
✿ ༺ ོIT技术༻2 小时前
算法思想总结:优先级队列
算法·leetcode·优先级队列