如何有效地计算帕斯卡三角形中的一行?

Posted

技术标签:

【中文标题】如何有效地计算帕斯卡三角形中的一行?【英文标题】:How to efficiently calculate a row in pascal's triangle? 【发布时间】:2013-03-12 21:31:32 【问题描述】:

我有兴趣找到帕斯卡三角形的第 n 行(不是特定元素,而是整行本身)。最有效的方法是什么?

我想到了通过总结上面行中的相应元素来构造三角形的传统方法:

1 + 2 + .. + n = O(n^2)

另一种方法是使用特定元素的组合公式:

c(n, k) = n! / (k!(n-k)!)

对于行中的每个元素,我猜前一种方法会花费更多时间,具体取决于计算组合的方式。有什么想法吗?

【问题讨论】:

你提出的第一种方法是数学废话,所以肯定是第二种。 @PieterGeerkens 其实我希望能低于这两种方法 第二种算法是O(n),因为有n个元素,可能你没有比这更快的了。 @MarcB 你是什么意思?你说的是第一种方法吗? @ZiyaoWei 你可能是对的,但我还没有看到背后的直觉。当您知道C(n,k-1) 等时,可能有一种简单的方法来计算C(n,k) 【参考方案1】:
>>> def pascal(n):
...   line = [1]
...   for k in range(n):
...     line.append(line[k] * (n-k) / (k+1))
...   return line
... 
>>> pascal(9)
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]

这使用以下身份:

C(n,k+1) = C(n,k) * (n-k) / (k+1)

因此您可以从C(n,0) = 1 开始,然后使用此标识计算行的其余部分,每次将前一个元素乘以(n-k) / (k+1)

【讨论】:

您能详细说明一下答案吗?结果似乎是正确的,我猜复杂度是 O(n),但这是如何工作的? @H2CO3:这是我写答案的最有效方式;-) 这里有一个更冗长的解释en.wikipedia.org/wiki/… 把它翻译成 JS 不起作用...``` const pascal = n => let line = [1]; for (k in _.range(n)) line.push(line[k] * (n-k) / (k+1)); 返回线; ; ```有人知道为什么吗? @NimaMehanian JS for-in 循环迭代属性名称,它们是字符串;它们不迭代索引,因此绝对不是值。改用 for-of 循​​环: const pascal = n => const line = [1]; for (const k of L.range(n)) line.push(line[k] * (n-k) / (k+1)); 返回线; ;【参考方案2】:

单行可以计算如下:

First compute 1.               -> N choose 0
Then N/1                       -> N choose 1
Then N*(N-1)/1*2               -> N choose 2
Then N*(N-1)*(N-2)/1*2*3       -> N choose 3
.....

请注意,您可以从前一个值计算下一个值,只需乘以一个数字,然后除以另一个数字。

这可以在一个循环中完成。示例 python。

def comb_row(n):
   r = 0
   num = n
   cur = 1
   yield cur
   while r <= n:
      r += 1  
      cur = (cur* num)/r
      yield cur
      num -= 1

【讨论】:

您说的是我在问题中提到的第二种方法。我在您的回答中没有看到任何与效率相关的内容。 @gokcehan:没有。你检查过代码吗?它与您选择的答案基本相同! 既然我理解了算法,我认为这也是正确的答案。抱歉,你收到了我的 +1 @gokcehan:不用担心。我本来可以更清楚(并且已经编辑了帖子以使其如此)。【参考方案3】:

最有效的方法是:

std::vector<int> pascal_row(int n)
    std::vector<int> row(n+1);
    row[0] = 1; //First element is always 1
    for(int i=1; i<n/2+1; i++) //Progress up, until reaching the middle value
        row[i] = row[i-1] * (n-i+1)/i;
    
    for(int i=n/2+1; i<=n; i++) //Copy the inverse of the first part
        row[i] = row[n-i];
    
    return row;

【讨论】:

行必须有n+1个元素,所以最后一个for应该有i&lt;=n条件。 我真的不记得算法了。但我想你是对的,因为row.resize(n+1). 你为什么要把row[i-1](n-i+1)/i相乘? 每个值都是前一个乘以 (n-i) 再除以 i。这来自阶乘扩展,每个项都与前一项非常相似,在阶乘的下部有 1 个额外/1 个项。因此,对于第 10 行(如果算上第 0 行,则为 9)首先是 10/1,然后是 9/2,然后是 8/3……等等。产生 1,10,45,120,210,252 您可能应该将变量增加到“uint64_t”,以便为第 30 行中的数字提供足够大的范围。【参考方案4】:

这是一个在 go-lang 中实现的快速示例,它从行的外边缘计算,并一直工作到中间,通过一次计算分配两个值...

package main

import "fmt"

func calcRow(n int) []int 
    // row always has n + 1 elements
    row := make( []int, n + 1, n + 1 )

    // set the edges
    row[0], row[n] = 1, 1

    // calculate values for the next n-1 columns
    for i := 0; i < int(n / 2) ; i++ 
        x := row[ i ] * (n - i) / (i + 1)

        row[ i + 1 ], row[ n - 1 - i ] = x, x
    

    return row


func main() 
    for n := 0; n < 20; n++ 
        fmt.Printf("n = %d, row = %v\n", n, calcRow( n ))
    

20 次迭代的输出运行大约需要 1/4 毫秒...

n = 0, row = [1]
n = 1, row = [1 1]
n = 2, row = [1 2 1]
n = 3, row = [1 3 3 1]
n = 4, row = [1 4 6 4 1]
n = 5, row = [1 5 10 10 5 1]
n = 6, row = [1 6 15 20 15 6 1]
n = 7, row = [1 7 21 35 35 21 7 1]
n = 8, row = [1 8 28 56 70 56 28 8 1]
n = 9, row = [1 9 36 84 126 126 84 36 9 1]
n = 10, row = [1 10 45 120 210 252 210 120 45 10 1]
n = 11, row = [1 11 55 165 330 462 462 330 165 55 11 1]
n = 12, row = [1 12 66 220 495 792 924 792 495 220 66 12 1]
n = 13, row = [1 13 78 286 715 1287 1716 1716 1287 715 286 78 13 1]
n = 14, row = [1 14 91 364 1001 2002 3003 3432 3003 2002 1001 364 91 14 1]
n = 15, row = [1 15 105 455 1365 3003 5005 6435 6435 5005 3003 1365 455 105 15 1]
n = 16, row = [1 16 120 560 1820 4368 8008 11440 12870 11440 8008 4368 1820 560 120 16 1]
n = 17, row = [1 17 136 680 2380 6188 12376 19448 24310 24310 19448 12376 6188 2380 680 136 17 1]
n = 18, row = [1 18 153 816 3060 8568 18564 31824 43758 48620 43758 31824 18564 8568 3060 816 153 18 1]
n = 19, row = [1 19 171 969 3876 11628 27132 50388 75582 92378 92378 75582 50388 27132 11628 3876 969 171 19 1]

【讨论】:

为什么row[i](n - i) / (i + 1) 相乘。这是二项式身份吗?【参考方案5】:

一种简单的计算方法是注意下一行的元素可以计算为前一行中两个连续元素的总和。

[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]

例如6 = 5 + 115 = 5 + 101 = 1 + 020 = 10 + 10。这给出了一个简单的算法来计算前一行的下一行。

def pascal(n):
    row = [1]
    for x in xrange(n):
        row = [l + r for l, r in zip(row + [0], [0] + row)]
    # print row
    return row

print pascal(10)

【讨论】:

【参考方案6】:

在 Scala 编程中:我会像这样简单地做到这一点:

def pascal(c: Int, r: Int): Int = c match 
    case 0 => 1
    case `c` if c >= r => 1
    case _ => pascal(c-1, r-1)+pascal(c, r-1)

我会在里面这样称呼它:

for (row <- 0 to 10) 
    for (col <- 0 to row)
        print(pascal(col, row) + " ")
    println()

导致:

. 1 1 1 1 2 1 1 3 3 1 1 4 6 4 1 1 5 10 10 5 1 1 6 15 20 15 6 1 1 7 21 35 35 21 7 1 1 8 28 56 70 56 28 8 1 1 9 36 84 126 126 84 36 9 1 1 10 45 120 210 252 210 120 45 10 1

一步一步解释:

第 1 步:我们确保如果我们的列是第一个列,我们总是返回图 1。

第 2 步: 由于每 X 行有 X 列。所以我们这么说;最后一列X大于等于第X行,则返回数字1。

第 3 步: 否则,我们得到当前列之前的列和当前列之前的行的重复帕斯卡之和;以及该列和当前行之前的行的帕斯卡。

祝你好运。

【讨论】:

想评论一下计算行 n 所需的时间和内存吗?这与之前介绍的方法相比如何?如果乘法的时间是加法的 31 倍,会有什么变化吗?【参考方案7】:

让我在 Shane 的 excellent work 的基础上构建 R 解决方案。 (谢谢你,Shane!。他生成三角形的代码:

pascalTriangle <- function(h) 
  lapply(0:h, function(i) choose(i, 0:i)) 

这将允许将三角形存储为列表。然后我们可以索引任何需要的行。但请在索引时添加1!例如,我将抓住最后一行:

pt_with_24_rows <- pascalTriangle(24)
row_24 <- pt_with_24_rows[25] # add one
row_24[[1]] # prints the row

所以,最后,假装我有一个Galton Board 的问题。我面临着找出聚集在中心的豆子百分比的任意挑战:比如说,10 到 15 箱(25 箱)。

sum(row_24[[1]][10:15])/sum(row_24[[1]]) 

结果是 0.7704771。一切顺利!

【讨论】:

【参考方案8】:

计算帕斯卡三角形中一行的最有效方法是通过卷积。首先我们选择第二行 (1,1) 作为内核,然后为了得到下一行,我们只需要将当前行与内核进行卷积即可。

所以内核与第二行的卷积得到第三行[1 1]*[1 1] = [1 2 1],与第三行的卷积得到第四行[1 2 1]*[1 1] = [1 3 3 1],依此类推

这是 julia-lang 中的一个函数(非常类似于 matlab):

function binomRow(n::Int64)
baseVector = [1] #the first row is equal to 1. 
kernel = [1,1]   #This is the second row and a kernel. 
row = zeros(n)
for i = 1 : n
    row = baseVector 
    baseVector = conv(baseVector, kernel) #convoltion with kernel
end
return row::ArrayInt64,1
end

【讨论】:

没有调试它为什么,但是在 binoRow(59) 之上它开始产生负数并在 binomRow(66) 之上抛出ERROR: InexactError()【参考方案9】:

在 Ruby 中,以下代码将打印出您想要的帕斯卡三角形的特定行:

def row(n)
  pascal = [1]
  if n < 1
    p pascal
    return pascal
  else
    n.times do |num|
      nextNum = ((n - num)/(num.to_f + 1)) * pascal[num]
      pascal << nextNum.to_i
    end
  end
  p pascal
end

调用row(0) 返回[1]row(5) 返回[1, 5, 10, 10, 5, 1]

【讨论】:

【参考方案10】:

这是使用 VBA 动态设计帕斯卡三角形的另一种最佳且简单的方法。

`1
11
121
1331
14641`

`Sub pascal()
Dim book As Excel.Workbook
Dim sht As Worksheet
Set book = ThisWorkbook
Set sht = book.Worksheets("sheet1")
a = InputBox("Enter the Number", "Fill")
For i = 1 To a
    For k = 1 To i
        If i >= 2 And k >= 2 Then
            sht.Cells(i, k).Value = sht.Cells(i - 1, k - 1) + sht.Cell(i-  1, k)
        Else
            sht.Cells(i, k).Value = 1
        End If
    Next k
Next i
End Sub`

【讨论】:

【参考方案11】:

我用的是 Ti-84 Plus CE

第6行中->的使用是储值按钮

Forloop syntax is 
:For(variable, beginning, end [, increment])
:Commands
:End

nCr syntax is 
:valueA nCr valueB

列表索引从 1 开始,所以我将它设置为 R+1

N= row
R= column

PROGRAM: PASCAL
:ClrHome
:ClrList L1
:Disp "ROW
:Input N
:For(R,0,N,1)
:N nCr R–>L1(R+1)
:End
:Disp L1

这是我在编程中能想到的最快方法(使用 ti 84),但如果您的意思是能够使用笔和纸计算行,那么只需画出三角形,因为做因式很痛苦!

【讨论】:

感谢您花时间回答 SO 中的问题。请格式化您的答案以使其更清晰。使用反引号 (`) 格式化一行中的代码。如果您希望代码跨越多行,请使用 (```)。请参考:meta.stackexchange.com/a/22189 获取详细的格式化帮助。【参考方案12】:

这是 Python 中的 O(n) 空间复杂度解决方案:

def generate_pascal_nth_row(n):
    result=[1]*n
    for i in range(n):
        previous_res = result.copy()
        for j in range(1,i):
            result[j] = previous_res[j-1] + previous_res[j]
    return result

print(generate_pascal_nth_row(6))

【讨论】:

这个 O(n) 怎么样?我数了 2 个循环。如果您认为这是因为您的第二个循环并非每次都进入 n,那么我认为这是不正确的。观察:1 + 2 + .. + n = n*(n + 1)/2 仍然是 n^2 我提到了 O(n) 个“空间”。你是对的时间复杂度是 O(n^2)【参考方案13】:

查找第 n 行 -

int res[] = new int[n+1];
res[0] = 1;
for(int i = 1; i <= n; i++)
   for(int j = i; j > 0; j++)
      res[j] += res[j-1];

【讨论】:

【参考方案14】:
class Solution
    public:
    
    int comb(int n,int r)
        long long c=1;
        for(int i=1;i<=r;i++)    //calculates n!/(n-r)!
            c=((c*n))/i;  n--;
        
        return c;
    

    vector<int> getRow(int n)   
        vector<int> v;
        for (int i = 0; i < n; ++i)
            v.push_back(comb(n,i));
        return v;
    
;

在 leet 代码https://leetcode.com/submissions/detail/406399031/ 上的提交速度超过 100%

【讨论】:

以上是关于如何有效地计算帕斯卡三角形中的一行?的主要内容,如果未能解决你的问题,请参考以下文章

Java中的递归nCr组合

经典算法-帕斯卡三角形(杨辉三角形)

C语言中的帕斯卡三角形与组合

C ++:如何产生帕斯卡三角形的第'n'行? [关闭]

巴斯卡三角形理解

C++编程:杨辉三角