python - 前缀和算法
python - prefix sum algorithm
我正在尝试通过 Codility here(采蘑菇问题)中的示例来理解前缀和概念背后的思想
我的理解是,整个概念是基于简单的 属性,其中求数组的两个位置 A(pos_left, pos_right) 之间所有元素的总和使用第二个数组 P,其中所有元素连续求和,搜索的总和计算为
值(P(pos_right + 1))-值(P(pos_left))。
A 1 2 3 4 5 6
P 0 1 3 6 10 15 21
sum of all elements between A[2] and A[5] = 3+ 4 + 5 = 12
or using the prefix sums" P[5+1] - P[2] = 15 -3 = 12
The problem
There is a street with mushroom at every place represented
by a non-empty vector. Given the initial position of a picker and its
movement range, possible maximum number of mushrooms to collect is
looked for.
看这个例子我不明白循环构造背后的逻辑。任何人都可以澄清这个算法的机制吗?
其次,我发现这个例子中的 positoin 索引非常混乱和繁琐。 "shift" 前缀和开头为零的向量是常见的做法吗? (向量中的元素计数默认从 python 中的 0 开始,这一事实已经引起了一些混乱)。
解决方法
def prefix_sums(A):
n = len(A)
P = [0] * (n + 1)
for k in xrange(1, n + 1):
P[k] = P[k - 1] + A[k - 1]
return P
def count_total(P, x, y):
return P[y + 1] - P[x]
# A mushroom picker is at spot number k on the road and should perform m moves
def mushrooms(A, k, m):
n = len(A)
result = 0
pref = prefix_sums(A)
for p in xrange(min(m, k) + 1): # going left
left_pos = k - p
right_pos = min(n - 1, max(k, k + m - 2 * p))
result = max(result, count_total(pref, left_pos, right_pos))
for p in xrange(min(m + 1, n - k)):
right_pos = k + p
left_pos = max(0, min(k, k - (m - 2 * p)))
result = max(result, count_total(pref, left_pos, right_pos))
return result
我有 运行 一些小数组示例 A= [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
,选择位置 k=5 和范围 m = 3。我不明白创建要检查的范围的逻辑通过两个循环。
我得到以下循环参数
(p=, left_pos=, right_pos=)
loop 1 (0,5,8), (1,4,6),(2,3,5),(3,2,5)
loop 2 (0,2,5), (1,4,6), (2,5,7), (3,5,8)
范围不同。为什么?
调试版本
def mushrooms2(A, k, m):
n = len(A)
result = 0
pref = prefix_sums(A)
l1 =min(m, k) + 1
print 'loop p in xrange(min(m, k) + 1): %d' % l1
for p in xrange(min(m, k) + 1):
print 'p %d' % p
print 'A= %r' % A
print 'pref= %r' % pref
left_pos = k - p
right_pos = min(n - 1, max(k, k + m - 2 * p))
result = max(result, count_total(pref, left_pos, right_pos))
print 'left_pos = k - p= %d' % left_pos
print 'right_pos= min(n-1,max(k,k+m-2*p))= %d' % right_pos
print 'max'
print '(result %d' % result
print 'count_total(pref, left_pos, right_pos)) %r, %r, %r, %r' % (pref,left_pos, right_pos,count_total(pref, left_pos, right_pos))
print 'result= %d' % result
print 'next p'
l2=min(m + 1, n - k)
print 'loop xrange(min(m + 1, n - k)): %d' % l2
for p in xrange(min(m + 1, n - k)):
print 'p %d' % p
print 'A= %r' % A
print 'pref= %r' % pref
right_pos = k + p
left_pos = max(0, min(k, k - (m - 2 * p)))
result = max(result, count_total(pref, left_pos, right_pos))
print 'right_pos = k + p= %d' % right_pos
print 'left_pos = max(0, min(k, k - (m - 2 * p)))= %d' % left_pos
print 'max'
print '(result %d' % result
print 'count_total(pref, left_pos, right_pos)) %r, %r, %r, %r' % (pref,left_pos, right_pos,count_total(pref, left_pos, right_pos))
print 'result= %d' % result
print 'next p'
print 'result %d' % result
return result
认为循环构造违反直觉的不止您一个,因为我也不得不花几分钟时间研究它。这是我想出来的。
现在,link 中的解决方案提供了更多详细信息,最佳策略是以这样一种方式在路径上行走,即只改变方向一次。以这种方式,一个人能够覆盖左右端点的范围,left_pos
和 right_pos
似乎代表。
关于循环的细节,与其根据循环变量(即 p
)来考虑循环,更容易弄清楚循环过程中发生了什么变化,以及如何发生变化p
被使用。否则,一开始弄清楚那些 min 和 max 表达式中的内容似乎有点太奇怪了。
例如,在第一个循环中,不要弄清楚该范围代表什么,而是尝试 left_pos
如何受到不同值 p
的影响。经过一番思考,人们注意到 left_pos
以一种符合可能的左端点的方式发生变化。
具体来说,当p == 0
时,左端点为起始索引(即k
),当p
为min(m, k)
时,则为0(即如果k < m
) 或 (k - m)
。在前一种情况下,这是左端点可以到达的最远距离,因为它会超出道路上有效的点范围。在后一种情况下,移动次数禁止 left_pos
小于 (k - m)
的任何解决方案,因为不可能从 k
到 m 移动中的那些索引。
在第一个循环中对right_pos
的赋值可以用类似的方式解释。 min 语句包括 (n-1)
,这是可以达到的最右边的合法索引,它用于将正确的端点保持在允许的范围内。内部最大语句具有 k
,因为它是 right_pos
的最小可能值。 (即由于 k
是起点)它还有一个表达式 (k + m - 2 * p)
。此表达式表示以下过程:
- 向左走 p 步。
- 改变方向,向右走p步到达起点。
- 用剩下的
(m - 2p)
步向右走。
第二个循环只是第一个循环的反映,您可以通过调整我对第一个循环的解释来简单地解释它。
关于你的第二个问题,我认为移动前缀和数组的索引不是常见的做法。我通常在在线编程竞赛中使用此方法,我对您在 Python 中使用的前缀和数组的实现如下。
def prefix_sums(A):
n = len(A)
P = [0] * n
P[0] = A[0]
for k in xrange(1, n):
P[k] = P[k - 1] + A[k]
return P
def count_total(P, x, y):
return (P[y] - P[x - 1] if x > 0 else P[y])
上述实现背后的基本思想是,在 P[x]
,我们有总和 A[0] + A[1] + ... + A[x]
。
阅读主题后仍然很难理解这个想法,直到我实现了天真的解决方案(这是 codility document 中的第一个)
难以理解的解决方案 #2 只是简单地模仿左右移动,所有这些看起来很奇怪的计算只是为了获得区域的左右限制(因为你真的会在其中移动)。所以每次迭代意味着使用 6 个步骤的一个完整循环。
如果你向左移动然后向右移动
(p=0...M),你有
- 左 0 步,右 6 步(真的 0 和 2 步导致数组外
边界),所以区域的左边界在索引 4 处,右边界在
索引 6
- 向左 1 步,向右 5 步(实际上是 1 和 3),所以左边框
位于索引 3,右边框位于索引 6
- 左2步,右4步(真的是2和4)...继续计算
这是我的 PHP 版本,其中包含过于简化的代码和其他变量以便于理解
function prefix_sums(array $a)
{
$n = count($a);
$p = array_fill(0, $n + 1, 0);
for ($i = 1; $i <= $n; $i++) {
$p[$i] = $p[$i - 1] + $a[$i - 1];
}
return $p;
}
function count_total($p, $x, $y)
{
return $p[$y + 1] - $p[$x];
}
function mushrooms(array $a, int $k, int $m)
{
$n = count($a) - 1;
$max = 0;
$sums = prefix_sums($a);
//start moving to the left and then the right
for ($p = 0; $p < $m; $p++) {
$stepsLeft = $p;
$realStepsLeft = min($k, $stepsLeft);
$leftBorder = $k - $realStepsLeft;
$stepsRight = $m - $stepsLeft;
$realStepsRight = min($n - $leftBorder, $stepsRight);
$rightBorder = $leftBorder + $realStepsRight;
$max = max($max, count_total($sums, $leftBorder, $rightBorder));
}
//moving to the right and then the left
for ($p = 0; $p < $m; $p++) {
$stepsRight = $p;
$realStepsRight = min($p, $n - $k);
$rightBorder = $k + $realStepsRight;
$stepsLeft = $m - $stepsRight;
$realStepsLeft = min(($k + $realStepsRight), $stepsLeft);
$leftBorder = $rightBorder - $realStepsLeft;
$max = max($max, count_total($sums, $leftBorder, $rightBorder));
}
return $max;
}
assert(ASSERT_EXCEPTION, 1);
assert(mushrooms([2, 3, 7, 5, 1, 3, 9], 4, 6) == 25);
echo 'Success';
我正在尝试通过 Codility here(采蘑菇问题)中的示例来理解前缀和概念背后的思想
我的理解是,整个概念是基于简单的 属性,其中求数组的两个位置 A(pos_left, pos_right) 之间所有元素的总和使用第二个数组 P,其中所有元素连续求和,搜索的总和计算为
值(P(pos_right + 1))-值(P(pos_left))。
A 1 2 3 4 5 6
P 0 1 3 6 10 15 21
sum of all elements between A[2] and A[5] = 3+ 4 + 5 = 12
or using the prefix sums" P[5+1] - P[2] = 15 -3 = 12
The problem
There is a street with mushroom at every place represented by a non-empty vector. Given the initial position of a picker and its movement range, possible maximum number of mushrooms to collect is looked for.
看这个例子我不明白循环构造背后的逻辑。任何人都可以澄清这个算法的机制吗?
其次,我发现这个例子中的 positoin 索引非常混乱和繁琐。 "shift" 前缀和开头为零的向量是常见的做法吗? (向量中的元素计数默认从 python 中的 0 开始,这一事实已经引起了一些混乱)。
解决方法
def prefix_sums(A):
n = len(A)
P = [0] * (n + 1)
for k in xrange(1, n + 1):
P[k] = P[k - 1] + A[k - 1]
return P
def count_total(P, x, y):
return P[y + 1] - P[x]
# A mushroom picker is at spot number k on the road and should perform m moves
def mushrooms(A, k, m):
n = len(A)
result = 0
pref = prefix_sums(A)
for p in xrange(min(m, k) + 1): # going left
left_pos = k - p
right_pos = min(n - 1, max(k, k + m - 2 * p))
result = max(result, count_total(pref, left_pos, right_pos))
for p in xrange(min(m + 1, n - k)):
right_pos = k + p
left_pos = max(0, min(k, k - (m - 2 * p)))
result = max(result, count_total(pref, left_pos, right_pos))
return result
我有 运行 一些小数组示例 A= [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
,选择位置 k=5 和范围 m = 3。我不明白创建要检查的范围的逻辑通过两个循环。
我得到以下循环参数
(p=, left_pos=, right_pos=)
loop 1 (0,5,8), (1,4,6),(2,3,5),(3,2,5)
loop 2 (0,2,5), (1,4,6), (2,5,7), (3,5,8)
范围不同。为什么?
调试版本
def mushrooms2(A, k, m):
n = len(A)
result = 0
pref = prefix_sums(A)
l1 =min(m, k) + 1
print 'loop p in xrange(min(m, k) + 1): %d' % l1
for p in xrange(min(m, k) + 1):
print 'p %d' % p
print 'A= %r' % A
print 'pref= %r' % pref
left_pos = k - p
right_pos = min(n - 1, max(k, k + m - 2 * p))
result = max(result, count_total(pref, left_pos, right_pos))
print 'left_pos = k - p= %d' % left_pos
print 'right_pos= min(n-1,max(k,k+m-2*p))= %d' % right_pos
print 'max'
print '(result %d' % result
print 'count_total(pref, left_pos, right_pos)) %r, %r, %r, %r' % (pref,left_pos, right_pos,count_total(pref, left_pos, right_pos))
print 'result= %d' % result
print 'next p'
l2=min(m + 1, n - k)
print 'loop xrange(min(m + 1, n - k)): %d' % l2
for p in xrange(min(m + 1, n - k)):
print 'p %d' % p
print 'A= %r' % A
print 'pref= %r' % pref
right_pos = k + p
left_pos = max(0, min(k, k - (m - 2 * p)))
result = max(result, count_total(pref, left_pos, right_pos))
print 'right_pos = k + p= %d' % right_pos
print 'left_pos = max(0, min(k, k - (m - 2 * p)))= %d' % left_pos
print 'max'
print '(result %d' % result
print 'count_total(pref, left_pos, right_pos)) %r, %r, %r, %r' % (pref,left_pos, right_pos,count_total(pref, left_pos, right_pos))
print 'result= %d' % result
print 'next p'
print 'result %d' % result
return result
认为循环构造违反直觉的不止您一个,因为我也不得不花几分钟时间研究它。这是我想出来的。
现在,link 中的解决方案提供了更多详细信息,最佳策略是以这样一种方式在路径上行走,即只改变方向一次。以这种方式,一个人能够覆盖左右端点的范围,left_pos
和 right_pos
似乎代表。
关于循环的细节,与其根据循环变量(即 p
)来考虑循环,更容易弄清楚循环过程中发生了什么变化,以及如何发生变化p
被使用。否则,一开始弄清楚那些 min 和 max 表达式中的内容似乎有点太奇怪了。
例如,在第一个循环中,不要弄清楚该范围代表什么,而是尝试 left_pos
如何受到不同值 p
的影响。经过一番思考,人们注意到 left_pos
以一种符合可能的左端点的方式发生变化。
具体来说,当p == 0
时,左端点为起始索引(即k
),当p
为min(m, k)
时,则为0(即如果k < m
) 或 (k - m)
。在前一种情况下,这是左端点可以到达的最远距离,因为它会超出道路上有效的点范围。在后一种情况下,移动次数禁止 left_pos
小于 (k - m)
的任何解决方案,因为不可能从 k
到 m 移动中的那些索引。
在第一个循环中对right_pos
的赋值可以用类似的方式解释。 min 语句包括 (n-1)
,这是可以达到的最右边的合法索引,它用于将正确的端点保持在允许的范围内。内部最大语句具有 k
,因为它是 right_pos
的最小可能值。 (即由于 k
是起点)它还有一个表达式 (k + m - 2 * p)
。此表达式表示以下过程:
- 向左走 p 步。
- 改变方向,向右走p步到达起点。
- 用剩下的
(m - 2p)
步向右走。
第二个循环只是第一个循环的反映,您可以通过调整我对第一个循环的解释来简单地解释它。
关于你的第二个问题,我认为移动前缀和数组的索引不是常见的做法。我通常在在线编程竞赛中使用此方法,我对您在 Python 中使用的前缀和数组的实现如下。
def prefix_sums(A):
n = len(A)
P = [0] * n
P[0] = A[0]
for k in xrange(1, n):
P[k] = P[k - 1] + A[k]
return P
def count_total(P, x, y):
return (P[y] - P[x - 1] if x > 0 else P[y])
上述实现背后的基本思想是,在 P[x]
,我们有总和 A[0] + A[1] + ... + A[x]
。
阅读主题后仍然很难理解这个想法,直到我实现了天真的解决方案(这是 codility document 中的第一个)
难以理解的解决方案 #2 只是简单地模仿左右移动,所有这些看起来很奇怪的计算只是为了获得区域的左右限制(因为你真的会在其中移动)。所以每次迭代意味着使用 6 个步骤的一个完整循环。
如果你向左移动然后向右移动 (p=0...M),你有
- 左 0 步,右 6 步(真的 0 和 2 步导致数组外 边界),所以区域的左边界在索引 4 处,右边界在 索引 6
- 向左 1 步,向右 5 步(实际上是 1 和 3),所以左边框 位于索引 3,右边框位于索引 6
- 左2步,右4步(真的是2和4)...继续计算
这是我的 PHP 版本,其中包含过于简化的代码和其他变量以便于理解
function prefix_sums(array $a)
{
$n = count($a);
$p = array_fill(0, $n + 1, 0);
for ($i = 1; $i <= $n; $i++) {
$p[$i] = $p[$i - 1] + $a[$i - 1];
}
return $p;
}
function count_total($p, $x, $y)
{
return $p[$y + 1] - $p[$x];
}
function mushrooms(array $a, int $k, int $m)
{
$n = count($a) - 1;
$max = 0;
$sums = prefix_sums($a);
//start moving to the left and then the right
for ($p = 0; $p < $m; $p++) {
$stepsLeft = $p;
$realStepsLeft = min($k, $stepsLeft);
$leftBorder = $k - $realStepsLeft;
$stepsRight = $m - $stepsLeft;
$realStepsRight = min($n - $leftBorder, $stepsRight);
$rightBorder = $leftBorder + $realStepsRight;
$max = max($max, count_total($sums, $leftBorder, $rightBorder));
}
//moving to the right and then the left
for ($p = 0; $p < $m; $p++) {
$stepsRight = $p;
$realStepsRight = min($p, $n - $k);
$rightBorder = $k + $realStepsRight;
$stepsLeft = $m - $stepsRight;
$realStepsLeft = min(($k + $realStepsRight), $stepsLeft);
$leftBorder = $rightBorder - $realStepsLeft;
$max = max($max, count_total($sums, $leftBorder, $rightBorder));
}
return $max;
}
assert(ASSERT_EXCEPTION, 1);
assert(mushrooms([2, 3, 7, 5, 1, 3, 9], 4, 6) == 25);
echo 'Success';