在2D矩阵中排列数字1

时间:2019-05-25 20:14:10

标签: algorithm matrix

给出二维矩阵的行数和列数

最初矩阵的所有元素均为0

给出每行应显示的1的数量

给出每列中应显示的1的数量

确定是否可以形成这样的矩阵。

示例:

Input: r=3 c=2 (no. of rows and columns)
2 1 0 (number of 1's that should be present in each row respectively)
1 2 (number of 1's that should be present in each column respectively)

输出:可能

说明:

1 1
0 1
0 0

我尝试通过检查Ri的总和= Ci的总和来解决这个问题,大约12个小时

但是我想知道

之类的情况是否不可能
3 3
1 3 0
0 2 2

r和c最高为10 ^ 5

有什么想法我应该进一步发展吗?

编辑:添加的约束和输出只能是“可能”或“不可能”。可能的矩阵不需要显示。

现在有人可以帮我吗?

6 个答案:

答案 0 :(得分:2)

提示:一种可能的解决方案是通过创建特殊图形并在其上运行标准最大流量算法来利用最大流量问题。

如果您不熟悉上述问题,则可以开始阅读有关该问题的信息,例如这里https://en.wikipedia.org/wiki/Maximum_flow_problem

如果您对完整的解决方案感兴趣,请发表评论,我将更新答案。但这需要理解上述算法。

解决方案要求

创建一个r+c+2个节点的图形。

节点0是源,节点r+c+1是宿。节点1..r代表行,r+1..r+c代表列。

创建以下边缘:

  • 从源到容量为i=1..r的节点r_i
  • 从节点i=r+1..r+c到容量c_i的汇聚
  • 容量为1的所有节点i=1..rj=r+1..r+c之间

运行最大流量算法,行节点和列节点之间的饱和边定义应放置1的位置。

或者,如果不可能,则最大流量值小于矩阵中预期的流量值。

答案 1 :(得分:1)

(注意:为避免在谈论问题中的实际数字与谈论矩阵中的零之间的混淆,我将改为用空格填充矩阵和X。这显然不会改变问题。)

一些观察:

  • 如果您要填充一行,并且(例如)一列需要另外10个X,另一列需要另外5个X,那么您有时 最好将X放入“ 10”列,然后保存“ 5”列以备后用(因为您以后可能会遇到5行,每行需要2个X),但是您最好 将X放在“ 5”列,然后保存“ 10”列以备后用(因为即使您以后遇到10个都需要X的行,他们也不会介意是否不在同一列中)。因此,我们可以使用某种“贪心”算法:始终将X放在仍需要最多X的列中。 (当然,我们需要确保不要贪婪地将X放在同一列的同一行中!)
  • 由于不需要实际输出可能的矩阵,因此行都是可互换的,列都是可互换的;重要的是还有多少行仍需要1 X,多少行仍需要2 X,依此类推,对于列也是如此。

请记住,这是一种相当简单的方法:

  • (优化。)将所有行的计数相加,将所有列的计数相加,如果总和不匹配,则返回“不可能”。
  • 创建一个长度为 r +1的数组,并在其中填充多少列需要1 X,多少列需要2 X,等等(您可以忽略任何需要0 X的列。)
  • (优化。)为帮助有效访问阵列,请构建堆栈/链接列表/等。非零数组元素的索引,以降序排列(例如,从索引 r 如果不为零,则从索引 r -1开始,等等),因此您可以轻松找到代表要插入X的列的元素。
  • (优化。)为帮助确定何时不能满足某行,还记下需要 any X的总列数,并记下最大数 any 行需要X的X。如果前者小于后者,则返回“不可能”。
  • (优化。)按所需的X数对行进行排序。
  • 对各行进行迭代,从需要最少X的行开始,到需要最多X的行结束,对于每行:
    • 相应地更新阵列。例如,如果一行需要12个X,并且数组看起来像[...,3,8,5],那么您将更新数组以使其看起来像[...,3 + 7 = 10,8+ 5−7 = 6,5−5 = 0]。如果由于用完了X列而无法更新数组,则返回“不可能”。 (请注意:这部分永远不会真正返回“不可能”,因为我们会保留剩余的列数和所需的最大列数,因此我们应该已经已经返回了“不可能”。我只为清楚起见提及此检查。)
    • 更新非零数组元素的索引的堆栈/链接列表。
    • 更新需要 any X的列总数。如果现在小于任何行所需的最大X数,则返回“不可能”。
    • (优化。)如果第一个非零数组元素的索引大于剩余的行数,则返回“不可能”。
  • 如果我们在不返回“不可能”的情况下完成迭代,则返回“可能”。

(注意:我说要从需要最少X的行开始,然后一直到拥有最多X的行的原因是,需要更多X的行可能涉及检查更新数组和元素的更多元素的原因。这不仅需要推迟工作:只需减少X的行就可以帮助“合并”数组,从而减少了不同的列数,从而减少了X的工作量。在非常糟糕的情况下,例如在方矩阵的情况下,每一行都需要一个不同的正数X,而每一列都需要一个不同的正数X,最小到最大顺序意味着您可以在O(1)时间中处理每一行,总体上是线性时间,而最短到最后的顺序意味着每一行所花费的时间与其所需的X数量成正比,例如总体上是二次时间。)

总体而言,这不比O( r + c + n )时间(其中 n 是X的数量);我认为我列出的优化足以确保它更接近O( r + c )时间,但是很难百分百确定。我建议尝试一下,看看它是否足够快达到您的目的。

答案 2 :(得分:1)

我将通过一个例子来说明算法。

假设我们有m行和n列。令rows[i]i0 <= i < m中1的数目, 并且cols[j]j0 <= j < n列中1的数目。

例如,对于m = 3n = 4,我们可以有:rows = {4 2 3}cols = {1 3 2 3}和 解决方案数组为:

    1 3 2 3
  +--------
4 | 1 1 1 1
2 | 0 1 0 1
3 | 0 1 1 1

因为我们只想知道是否存在解决方案,所以rowscols中的值可以按任何顺序排列。每个排列的解决方案就是上述解决方案的行和列的排列。

因此,给定rowscols,按降序对cols进行排序,对rows进行升序。对于我们的示例,我们有cols = {3 3 2 1}rows = {2 3 4},以及同等的问题。

    3 3 2 1
  +--------
2 | 1 1 0 0
3 | 1 1 1 0
4 | 1 1 1 1

我们将cols转换为更适合该算法的形式。 cols告诉我们的是,我们有两个长度为3的1系列,一个长度为2的一系列1和一个长度为1的一系列1,它们将分布在数组的行中。我们重写cols来捕获COLS = {2/3 1/2 1/1},即长度为3的2个序列,长度为2的1个序列和长度为1的1个序列。

因为我们有2个序列,长度为3,所以只有在第一行中可以放入两个1时,解决方案才存在。这是可能的,因为rows[0] = 2。我们实际上并没有在第一行中放置任何1,而是通过减少长度为3的序列的长度来记录将1放置在其中的事实。因此COLS变为:

COLS = {2/2 1/2 1/1}

,然后我们将两个计数合并为长度2的序列,得出:

COLS = {3/2 1/1}

我们现在有了减少的问题:

3 | 1 1 1 0
4 | 1 1 1 1

同样,我们需要从长度为2的序列中放入1s来解决。幸运的是,rows[1] = 3我们可以做到。我们减少3/2的长度,并得到:

COLS = {3/1 1/1} = {4/1}

我们有减少的问题:

4 | 1 1 1 1

这是由长度为1的4个系列解决的,这就是我们剩下的。如果在任何步骤上,COLS中的序列都不能用于满足行数,则无法解决。

每行的一般处理可以描述如下。对于每行r,从COLS的第一个元素开始,根据需要减少count[k]/length[k]的元素COLS的长度,以使{{ 1}}等于count[k]。在rows[r]中消除长度为0的序列,并合并相同长度的序列。

请注意,由于COLS的元素是按长度降序排列的,因此最后一个元素的长度减小后总是小于或等于COLS中的下一个元素(如果存在下一个元素)。

示例2:存在解决方案。

COLS

长度2的1个序列递减以满足rows = {1 3 3}, cols = {2 2 2 1} => COLS = {3/2 1/1} ,而长度2的其他2个序列保持长度2。

rows[0] = 1

长度2的2个序列递减,长度1的1个序列递减。 长度已变为0的序列被删除,长度为1的序列被组合。

rows[0] = 1
COLS = {2/2 1/1 1/1} = {2/2 2/1}

可以满足rows[1] = 3 COLS = {2/1 1/0 1/1} = {2/1 1/1} = {3/1} 的解决方案。

rows[2]

示例3:解决方案不存在。

rows[2] = 3
COLS = {3/0} = {}

空间复杂度

很容易看出它是rows = {0 2 3}, cols = {3 2 0 0} => COLS = {1/3 1/2} rows[0] = 0 COLS = {1/3 1/2} rows[1] = 2 COLS = {1/2 1/1} rows[2] = 3 => impossible to satisfy; no solution.

时间复杂度

我们仅对每一行进行一次迭代。对于每一行O(m + n),我们最多需要迭代一次 i的{​​{1}}个元素。时间复杂度为rows[i] <= n

找到此算法后,我发现以下定理:

  

Havel-Hakimi定理(Havel 1955,Hakimi 1962)指出存在一个矩阵X 为0和1,行总和为a 0 =( a 1 ,a 2 ,…,a n )和列总计b 0 =(b 1 ,b 2 ,…,b m ),使得b i ≥b i + 1 对于每0 n-1,m 总计a 1 =(a 2 ,a 3 ,…,a n )和列总计b 1 =(b 1 −1,b 2 −1,...,b a1 −1,b a1 + 1 ,...,b m )也存在。

来自帖子Finding if binary matrix exists given the row and column sums

这基本上是我的算法所做的,同时尝试优化递减部分,即上述定理中的所有 -1 。现在,我看到了上述定理,我知道我的算法是正确的。尽管如此,我还是通过将蛮力算法与最多50个细胞的阵列进行比较来检验算法的正确性。

这是C#实现。

COLS

答案 3 :(得分:0)

您可以使用蛮力(通过所有2^(r * c)迭代)来解决它,但这将花费很长时间。如果r * c小于64,则可以对64位整数使用按位运算将其加速到一定程度。但是,即使那样,在5亿年的时间里,以每秒1次尝试的速度遍历所有64位的可能性。

一个更明智的选择是一一添加位,并且仅在没有违反约束的情况下才继续放置位。这将消除绝大多数可能性,从而大大加快该过程。在backtracking中查找一般思路。这与通过猜测来解决数独没什么不同:一旦发现您的猜测错了,就将其删除,然后尝试猜测其他数字。

与数独一样,某些策略可以写入代码中,并在应用时会加速。例如,如果行中1的总和与列中1的总和不同,那么就没有解。

如果将打开超过50%的位,则可以解决互补问题(在更新行和列计数时,将所有的全都转换为零,反之亦然)。这两个问题是等价的,因为对一个问题的任何答案也对互补问题有效。

答案 4 :(得分:0)

可以使用Gale-Ryser Theorem在O(n log n)中解决此问题。 (其中n是两个度序列的长度的最大值)。

首先,通过在较小的序列上加0,使两个序列的长度相等,并将其长度设为n。 假设序列为A和B。以非降序对A排序,以非升序对B排序。为B创建另一个前缀和数组P,以使P的第i个元素等于B的第i个元素的和。 现在,将k从1迭代到n,然后检查
Gale-Ryser Theorem

第二个和可以通过二进制搜索B中小于k的最后一个数字的索引然后使用预先计算的P在O(log n)中进行计算。

答案 5 :(得分:0)

从罗伯特·巴伦(RobertBaron)提供的解决方案中得到启发,我试图构建一种新算法。

rows = [int(x)for x in input().split()]
cols = [int (ss) for ss in input().split()]
rows.sort()
cols.sort(reverse=True)
for i in range(len(rows)):
    for j in range(len(cols)):
        if(rows[i]!= 0 and cols[j]!=0):
            rows[i] = rows[i] - 1;
            cols[j]  =cols[j]-1;
print("rows: ",rows)
print("cols: ",cols)
#if there is any non zero value, print NO else print yes
flag = True
for i in range(len(rows)):
    if(rows[i]!=0):
        flag = False
        break

for j in range(len(cols)):
    if(cols[j]!=0):
        flag = False

if(flag):
    print("YES")
else:
    print("NO")

在这里,我以升序对行进行排序,而cols以降序对行进行排序。如果需要放置1,则以后减少特定的行和列! 它适用于此处发布的所有测试用例!休息神知道