【问题标题】:How to find the sum in a matrix with dynamic programming如何使用动态规划在矩阵中求和
【发布时间】:2021-06-22 20:16:46
【问题描述】:

请,我想找出每行只有一个值的最大总和。我已经通过蛮力做出了决议,它是O(N ^ 5)。现在我想找到一种动态编程的方法或另一种降低复杂性的方法。

例如:

矩阵:

  100   5   4   3  1

  90   80  70  60  50

  70   69  65  20  10

  60   20  10   5   1

  50   45  15   6   1

5套解决方案:

  1. 100 + 90 + 70 + 60 + 50 = 370

  2. 100 + 90 + 69 + 60 + 50 = 369

  3. 100 + 90 + 70 + 60 + 45 = 365

  4. 100 + 90 + 65 + 60 + 50 = 365

  5. 100 + 90 + 69 + 60 + 45 = 364

总和:1833

蛮力求和的例子:

  for(int i=0; i<matrix[0].size(); i++) {
    for(int j=0; j<matrix[1].size(); j++) {
      for(int k=0; k<matrix[2].size(); k++) {
        for(int l=0; l<matrix[3].size(); l++) {
          for(int x=0; x<matrix[4].size(); x++) {
            sum.push_back(matrix[0][i] + matrix[1][j] + matrix[2][k] + matrix[3][l] + matrix[4][x]);
          }
        }
      }
    }
  }
  
sort(sum.begin(), sum.end(), mySort);

谢谢!

【问题讨论】:

  • 在您的示例中,您有 `2: 100 + 90 + 69 + 60 + 50 = 369` 作为较大的总和之一,但 9069 出现在同一行?
  • 对不起,我做了很多测试用例,结果弄得一团糟。现在它是正确的。谢谢!
  • 矩阵中的行是否总是排序的(开头最高,结尾最低)?
  • 是的,每行开头的最大值,末尾的最小值。
  • 有什么限制? O(n^3) 会工作吗?

标签: algorithm time-complexity dynamic-programming


【解决方案1】:

更新我之前用过贪心算法,对这个问题不起作用。这是一个更通用的解决方案。

假设我们已经找到了总和最高的 m 个组合。下一个最高组合(数字 m+1)必须与其中一个相距 1 步,其中一步定义为在矩阵的其中一行中将焦点向右移动一列。 (任何距离所有顶部 m 组合超过一步的组合不能是 m+1 最高的,因为您可以将其转换为更高的组合,即通过撤消这些步骤之一,即返回到现有组合之一,不在顶部 m。)

对于 m = 1,我们知道“m 最高组合”仅表示取矩阵每一行的第一个元素的组合(假设每个行从高到低排序)。那么我们可以从那里开始工作:

  1. 创建一组候选组合以考虑下一个最高位置。这最初将只保存可能的最高组合(矩阵的第一列)。

  2. 找出总和最高的候选人并将其移至结果中。

  3. 找出与刚刚添加到结果中的组合相距 1 步的所有组合。将所有这些添加到候选组合集中。每轮只会添加其中的 n 个,其中 n 是矩阵中的行数。有些可能与之前确定的候选者重复,应该忽略。

  4. 返回第 2 步。重复直到有 5 个结果。

下面是一些执行此操作的 Python 代码:

m = [
    [100, 5, 4, 3, 1],
    [90, 80, 70, 60, 50],
    [70, 69, 65, 20, 10],
    [60, 20, 10, 5, 1],
    [50, 45, 15, 6, 1]
]
n_cols = len(m[0]) # matrix width

# helper function to calculate the sum for any combination,
# where a "combination" is a list of column indexes for each row
score = lambda combo: sum(m[r][c] for r, c in enumerate(combo))

# define candidate set, initially with single highest combination
# (this set could also store the score for each combination
# to avoid calculating it repeatedly)
candidates = {tuple(0 for row in m)}
results = set()

# get 5 highest-scoring combinations
for i in range(5):
    result = max(candidates, key=score)
    results.add(result)
    candidates.remove(result)  # don't test it again
    # find combinations one step away from latest result
    # and add them to the candidates set
    for j, c in enumerate(result):
        if c+1 >= n_cols:
            continue  # don't step past edge of matrix
        combo = result[:j] + (c+1,) + result[j+1:]
        if combo not in results:
            candidates.add(combo)  # drops dups

# convert from column indexes to actual values
final = [
    [m[r][c] for r, c in enumerate(combo)]
    for combo in results
]
final.sort(key=sum, reverse=True)
print(final)
# [
#     [100, 90, 70, 60, 50]
#     [100, 90, 69, 60, 50], 
#     [100, 90, 70, 60, 45], 
#     [100, 90, 65, 60, 50], 
#     [100, 90, 69, 60, 45], 
# ]

【讨论】:

  • 感谢@Matthias,但答案不正确。预期的答案: [ # [100, 90, 70, 60, 50], # [100, 90, 69, 60, 50], # [100, 90, 65, 60, 50], # [100, 90, 70, 60, 45], # [100, 90, 69, 60, 45] # ]
  • @Jessicadomingues 好点。我已经修改了算法,以考虑距所有先前找到的组合仅一步之遥,而不是距最后找到的组合仅一步之遥。我认为这具有 O(n) 复杂性,其中 n 是矩阵中的行数。
  • 谢谢。我正在努力理解你所做的一切,我不太了解python,我用c / c ++编程,但非常感谢你试图帮助我。
  • 这个想法是您逐步构建一组 k 个最高价值的组合,从最高点向下工作。组合是一个向量,主矩阵中的每一行都有一个列号。组合的值是它引用的单元格的总和。在每一步,您将下一个最高组合添加到集合中。要找到这一点,您只需考虑与结果集中已经存在的组合相距一步的组合。为了加快速度,我保留了所有这些邻居的集合,每当我向结果集添加新组合时,我也会将其邻居添加到候选集。
  • 另外,在 Python 中,enumerate(collection) 为您提供一对值 ival,其中 val 是集合中的一个值,i 是它在其中的索引(位置)集合。并且表达式r[:j] + (x,) + r[j+1:] 连接三个元组以创建一个新元组。这些是r 的第一个j 元素,然后是值x,然后是r 从位置j+1 到末尾的元素。所以这只是创建元组r 的新版本,将j 位置的值替换为x。您可以将元组视为类似于向量、数组或列表。
【解决方案2】:

您可以使用Dijkstra's algorithmO(k*log k) 时间内解决它。图中的一个节点由一个列表表示,该列表具有矩阵相应行中数字的 5 个索引。

例如在矩阵中

100 5  4  3  1
90  80 70 60 50
70  69 65 20 10
60  20 10 5  1
50  45 15 6  1

节点[0, 0, 2, 0, 1]代表数字[100, 90, 65, 60, 45]

初始节点是[0, 0, 0, 0, 0]。每个节点最多有5条出边,5个索引中的1个加1,节点之间的距离是索引数之和的绝对差。

因此对于该矩阵,来自节点[0, 0, 2, 0, 1] 的边领先:

  • [1, 0, 2, 0, 1],距离为 100 - 5 = 95
  • [0, 1, 2, 0, 1],距离为 90 - 80 = 10
  • [0, 0, 3, 0, 1],距离为 65 - 20 = 45
  • [0, 0, 2, 1, 1],距离为 60 - 20 = 40
  • [0, 0, 2, 0, 2],距离为 45 - 15 = 30

通过此设置,您可以使用 Dijkstra 算法来查找与初始节点最近的 k - 1 节点。

【讨论】:

  • 非常感谢,但我有一个疑问:生成所有图形组合的复杂性是多少?小于 O(n ^ 5)?对我来说,这样,问题是 n^5 生成 + k*log k 。
  • @Jessicadomingues “生成”是什么意思?您是否需要花费 n^5 来生成该矩阵?答案中的算法给出了一个矩阵 M 和一个数字 k。它不会生成所有的组合,只是遍历 5*k 条边,访问 k 个节点。
  • 首先非常感谢您的关注。我试图创建图表。第一步是带有0 0 0 0 0 的列表,对吗?下一步我需要查看00001, 00010, 00100, 01000 的费用。接下来是00100,因为成本是1。下一步我需要看到00001, 00010,01000,10000, 00200。现在我得到了00001。接下来,我需要所有以前的节点加上 11000、10100、10010、10001、00002 等。抱歉,我在进行图形建模时遇到了困难。
  • @Jessicadomingues 查看 Matthias Fripp 的最新回答 他基本上实现了这个想法并提供了代码。唯一的区别是他使用线性检查来查找下一个节点而不是优先级队列。所以他的解决方案是 O(k^2),但是如果你使用优先级队列,它将是 O(k log k)。
  • 我正在尝试理解代码。我对 c / c ++ 有更多的技能,但再次感谢所有答案。
【解决方案3】:

如果您只想要最大总和,则在每一行求和最大值。 也就是说,

M = [[100, 5, 4, 3, 1],
 [90, 80, 70, 60, 50],
 [70, 69, 65, 20, 10],
 [60, 20, 10, 5, 1],
 [50, 45, 15, 6, 1]]

sum(max(row) for row in M)

编辑

不必使用动态规划等。
有一个简单的规则:考虑数字与当前数字之间的差异选择下一个数字。

这是一个使用 numpy 的代码。

import numpy as np
M = np.array(M)
M = -np.sort(-M, axis = 1)
k = 3

answer = []
ind = np.zeros(M.shape[0], dtype = int)
for _ in range(k):
    answer.append(sum(M[list(range(M.shape[0])), ind]))
    min_ind = np.argmin(M[list(range(len(ind))), ind] - M[list(range(len(ind))), ind+1])
    ind[min_ind] += 1

结果是[370, 369, 365]

【讨论】:

  • 谢谢,但我需要取最大可能总和的前 k 个组合。
  • 我明白了。检查编辑
  • 非常感谢@GilseungAhn,但是当我尝试使用此代码时,对于 k = 5,答案不正确。答案:[370, 369, 365, 360, 350],预期答案:370, 369, 365, 365, 364。
猜你喜欢
  • 2018-04-09
  • 2013-09-04
  • 1970-01-01
  • 1970-01-01
  • 2023-03-06
  • 1970-01-01
  • 1970-01-01
  • 2015-07-31
  • 1970-01-01
相关资源
最近更新 更多