找出实数列表中的最大区间和
这是一位同事询问编程职位的面试问题。我认为这对于观察受访者的思考非常有用。我很想得到 SO 社区的看法。
给定一个长度为 N 的实数列表,例如 [a_1, a_2, ..., a_N]
,找到存在索引 1 <= i 的最大值 M 的复杂度是多少<= j <= N 使得
a_i + a_{i+1} + ... + a_j = M
?
如果这是一个经典的计算机科学问题,我很抱歉。
Here's an interview questions that a colleague asked for a programming position. I thought this was great for watching the interviewee think it through. I'd love to get responses for how the SO community thinks of it.
Given a list of real numbers of length N, say [a_1, a_2, ..., a_N]
, what is the complexity of finding the maximum value M for which there exist indices 1 <= i <= j <= N such that
a_i + a_{i+1} + ... + a_j = M
?
My apologies if this is a classic CS problem.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
Kadane 算法的复杂度仅为 O(n):
The complexity is just O(n) for Kadane's algorithm:
这是
O(N)
:这个想法是保留自上次重置以来遇到的所有整数的总和。当总和低于零时,即发生重置,即当前间隔中有太多负数而无法使其成为最佳间隔。
It's
O(N)
:The idea is to keep the sum of all integers that have been encountered since last reset. A reset occurs when the sum goes below zero - i.e. there are too many negative numbers in the current interval to make it possibly the best one.
这是一个众所周知的经典问题,在任何算法课程中都是一个令人大开眼界的问题。很难找到更好/更简单的启动器。
您可以找到 n*3-、n*2-、nlogn- 甚至简单的 n 算法。
我在 John Bentley 1986 年的《Programming Pearls》中找到了讨论/解决的问题 -
多年来,我们在 NTNU/Trondheim 的算法课程中一直使用它作为入门课程。
大约 20 年前,我第一次在大约 250 名学生的考试中使用它,其中只有 1 名学生发现了所有 4 个解决方案,见上文。他,Bjørn Olstad,成为特隆赫姆 NTNU 的“有史以来最年轻的教授”,除了领导奥斯陆的 MSFT 搜索部门外,现在仍然保持着这一地位。
Bjørn 还接受了挑战,寻找算法的良好实际应用。你看到一些吗?
This is a classical, well known, problem that is an excellent eye-opener in any algorithm course. It is hard to find a better/simpler starter.
You can find an n*3-, n*2-, nlogn- and even the simple n-algorithm.
I found the problem discussed/solved in John Bentley´s "Programming Pearls" from 1986 -
and did use it for years as a starter in our Algorithm Course at NTNU/Trondheim.
Some 20 years ago I first used it in an examination for about 250 students, where just 1 student did discover all the 4 solutions, see above. He, Bjørn Olstad, became the "youngest professor ever" at NTNU in Trondheim, and has still this status beside heading the MSFT search division in Oslo.
Bjørn also took the challenge to find good practical applications of the algorithm. Do you see some?
试试这个代码..对于数组中至少一个+ve数字来说它可以正常工作..O(n)只使用一个for循环..
Try this code .. it would work fine for at least one +ve number in the array.. O(n) just one for loop used..
这可能是错误的,因为它简单得令人怀疑。
这看起来像 O(n)。
This might be wrong because it's suspiciously simple.
This looks like O(n).
我介入这个古老的话题是为了详细解释 Kadane 算法的工作原理。该算法是在我目前正在上的一堂课上介绍的,但只有一个模糊的解释。这是该算法在 Haskell 中的实现:
现在,由于我们只是想了解该算法,所以让我们撤消命名
newSum
的小优化:这个疯狂的函数
maxCont'
是什么? >?让我们对它应该做什么做一个简单的说明。我们希望满足以下条件,前提是0 ≤ thisSum ≤ maxSum
:其中
maxInit l
是l
初始段的最大和> 和maxCont 是l
的最大连续总和。琐碎但重要的事实:对于所有
l
,maxInit l ≤ maxCont l
。显然,上述规范保证了 maxCont l = maxCont' 0 0 l,这正是我们想要的。我不会尝试直接解释为什么 maxCont' 的最终版本实现上述规范(我真的不知道该怎么做),而是展示如何从中派生它,一次一步地转换规范,直到它成为代码,那么它肯定是正确的。正如所写,本规范没有给出实现:如果maxCont
是根据maxCont'
定义的,如上所述,它将永远循环为maxCont'< /code> 调用
maxCont
使用相同的列表调用maxCont'
。因此,让我们稍微扩展一下它,以暴露我们需要的部分:这还没有解决任何问题,但它暴露了一些东西。让我们使用它。
thisSum + maxInit (x:xs)
是thisSum
或thisSum + x + maxInit xs
。但thisSum ≤ maxSum
是有前提的,所以我们在计算最大值时可以忽略这种可能性。maxCont (x:xs)
是包含或不包含x
的总和。但如果它包含x
,那么它和maxInit (x:xs)
是一样的,前面已经涵盖了,所以我们可以忽略这种可能性,只考虑maxCont (x:xs) = maxCont xs 的情况。所以我们到达了下一个版本:这个版本最终是正确的递归的,但是我们还有很长的路要走才能获得高效的代码,特别是因为那个神话般的 maxInit 太慢了。让我们将其分解为 Java 代码中考虑的三种情况(稍微滥用 Haskell 表示法):
在第一种情况下,我们知道
maxSum
不能是最大值:thisSum+x
更大,并且maxInit xs
始终为正值。在第二种情况下,我们知道thisSum+x+maxInit xs
不可能是最大值:maxCont xs
始终至少与maxInit xs< 一样大/code>,并且
thisSum+x
为负数。所以我们可以消除这些可能性:现在我们几乎没有足够的优势来扭转局面。现在我们已经消除了不可能的情况,我们将添加一些重复的情况,这会将这三种情况恢复到相同的形式,以便我们可以替换
maxCont'
的原始规范。在第一种情况下,我们没有第一项,因此我们需要使用我们知道不会超过其他项的内容。为了保持thisSum ≤ maxSum
的不变式,我们需要使用thisSum+x
。在第二种情况下,我们没有像something+maxInit xs
这样的第二项,但我们知道maxInit xs ≤ maxCont xs
,所以我们可以放心地坚持在0+maxInit xs
中。添加这些额外的正则项会产生以下结果:最后,检查了前提条件后,我们在规范中进行替换,
以将
其修复为真正的语法并附加省略的基本情况产生实际的算法,我们现在已经证明满足该算法规范只要它终止。但每个连续的递归步骤都在较短的列表上运行,因此它确实终止。
为了我的缘故,还有最后一件事要做,那就是更惯用、更灵活地编写最终代码:
I'm intruding on this ancient thread to give a detailed explanation of why Kadane's algorithm works. The algorithm was presented in a class I'm currently taking, but with only a vague explanation. Here's an implementation of the algorithm in Haskell:
Now since we're just trying to understand the algorithm, let's undo the minor optimization of naming
newSum
:What is this crazy function
maxCont'
? Let's come up with a simple specification of what it's supposed to be doing. We want the following to hold, with the precondition that0 ≤ thisSum ≤ maxSum
:where
maxInit l
is the greatest sum of an initial segment ofl
andmaxCont
is the maximum contiguous sum ofl
.Trivial but important fact: for all
l
,maxInit l ≤ maxCont l
. It should be obvious that the above specification guaranteesmaxCont l = maxCont' 0 0 l
, which is what we want. Instead of trying to explain directly why the final version of maxCont' implements the specification above (which I don't really know how to do), I will show how it can be derived from it, transforming the specification one step at a time until it becomes the code, which will then certainly be correct. As written, this specification doesn't give an implementation: ifmaxCont
is defined in terms ofmaxCont'
as described above, it will loop forever asmaxCont'
callsmaxCont
callsmaxCont'
with the same list. So let's expand it out just a bit to expose the pieces we will need:This didn't fix anything yet, but it exposed things. Let's use that.
thisSum + maxInit (x:xs)
is eitherthisSum
orthisSum + x + maxInit xs
. ButthisSum ≤ maxSum
by the precondition, so we can ignore this possibility when calculating the maximum.maxCont (x:xs)
is a sum that either includesx
or doesn't. But if it includesx
, then it's the same asmaxInit (x:xs)
, which is covered by the preceding, so we can ignore that possibility, and only consider the case wheremaxCont (x:xs) = maxCont xs
. So we arrive at the next version:This one, finally, is properly recursive, but we have a ways to go to get to efficient code, especially because that mythical maxInit would be too slow. Let's break it down into the three cases considered in the Java code (abusing Haskell notation a bit):
In the first case, we know that
maxSum
can't be the maximum:thisSum+x
is greater andmaxInit xs
is always positive. In the second case, we know thatthisSum+x+maxInit xs
can't be the maximum:maxCont xs
is always at least as large asmaxInit xs
, andthisSum+x
is negative. So we can eliminate those possibilities:Now we have just barely enough of an edge to twist things around. Now that we've eliminated impossible cases, we're going to add some duplicate cases, which will put these three cases back into the same form so we can substitute in the original specification of
maxCont'
. In the first case, we don't have a first term, so we need to use something that we know won't exceed the other terms. To maintain the invariant thatthisSum ≤ maxSum
, we will need to usethisSum+x
. In the second case, we don't have a second term that looks likesomething+maxInit xs
, but we know thatmaxInit xs ≤ maxCont xs
, so we can safely stick in0+maxInit xs
. Adding these extra terms for regularity yields the following:Finally, having checked the precondition, we substitute in the specification,
to get
Fixing this up into real syntax and tacking on the omitted base case yields the actual algorithm, which we've now proven satisfies the spec as long as it terminates. But each successive recursive step operates on a shorter list, so it does indeed terminate.
There's just one last thing to do, for my sake, which is to write the final code more idiomatically and flexibly:
我已经尝试过并测试过这一点。如果所有数字都是负数,则返回最大的负数。
测试用例:
代码:
I have tried and tested this. In case all numbers are negative, it returns the greatest negative number.
Test cases:
Code:
我会添加一个包含 2 种方法的答案,以不同的方式处理带有或不带有正元素的数组,用 Java 编写。
代码 -
Java
MaxSubSum.java:
MaxSubSumTest.java:
(测试用例,通过
TestNG
)说明:
find()
查找最大子数组,仅包含和为正数的子数组。
行为:
顺便说一句:
findIncludeNonPositive()
查找最大子数组,还包括非正数的子数组。
行为:
顺便说一句:
0
,这不会改变总和。复杂度:
O(n)
O(1)
I would add an answer that contains 2 approaches, that handle array with or without positive elements differently, written in
Java
.Code -
Java
MaxSubSum.java:
MaxSubSumTest.java:
(Test case, via
TestNG
)Explanation:
find()
Find max sub array, only include sub array with positive sum.
Behavior:
BTW:
findIncludeNonPositive()
Find max sub array, also include sub array with non-positive sum.
Behavior:
BTW:
0
at end of sub array, which doesn't change the sum.Complexity:
O(n)
O(1)
我们可以使用最简单的两行代码算法,
它很简单,也能处理所有负面的事情:)
例如 C++
We can just use the easiest 2 lines of code algo,
it's simple and handles all negative as well :)
eg. C++