Here's a one-pass solution that improves on liuzhidong's and J.S. Sebastian's solutions by using only O(1) space:
def fillcount(elevations):
start = 0
end = len(elevations) - 1
count = 0
leftmax = 0
rightmax = 0
while start < end:
while start < end and elevations[start] <= elevations[start + 1]:
start += 1
else:
leftmax = elevations[start]
while end > start and elevations[end] <= elevations[end - 1]:
end -= 1
else:
rightmax = elevations[end]
if leftmax < rightmax:
start += 1
while start < end and elevations[start] <= leftmax:
count += leftmax - elevations[start]
start += 1
else:
end -= 1
while end > start and elevations[end] <= rightmax:
count += rightmax - elevations[end]
end -= 1
return count
I tested it against this simpler two-pass solution:
def fillcount_twopass(elevations):
global_max = max(range(len(elevations)), key=lambda x: elevations[x])
count = 0
local_max = 0
for i in xrange(0, global_max):
if elevations[i] > local_max:
local_max = elevations[i]
else:
count += local_max - elevations[i]
local_max = 0
for i in xrange(len(elevations) - 1, global_max, -1):
if elevations[i] > local_max:
local_max = elevations[i]
else:
count += local_max - elevations[i]
return count
The two-pass solution is based on the following logic:
- Find the maximum peak for the whole graph -- the global maximum.
- Scan from the left side towards the global maximum peak. Keep track of the left maximum. Every cell that is a) below or at the same level as the left maximum, b) to the right of the left maximum, and c) to the left of the global maximum will hold water. When the left maximum increases, it has no effect on the earlier columns, but the later columns now hold water at this new maximum level.
- Do the same from the right, in reverse.
This is similar to what Rémi suggested, but uses the global maximum to provide an anchor, which simplifies things.
The one-pass solution is partially based on ideas from Mark Tolonen. It improves on the above by observing that we can do both the left and right pass simultaneously, without knowing the global maximum. That's because the current maximum on either side is either greater than, lower than, or equal to the maximum on the other side. The lower maximum will always be lower than the global maximum, even if we don't yet know what the global maximum is, so we can proceed from there to the next local maximum on that side. The algorithm in full detail:
- Start with pointers at the
start
and the end
of the list, and initialize left_max
, right_max
, and count
to 0
.
- Scan right, incrementing
start
until you reach a left maximum. Then scan left, decrementing end
until you reach a right maximum.
- From the lower maximum, continue scanning until you reach a column greater than the local maximum, counting fillable cells along the way and adding them to
count
.
- Repeat steps 2 and 3, ending when
start
and end
coincide.
Note that for our purposes, a local maximum is simply any point that is preceded by an ascent (and possibly a plateau), and followed by a descent. Local maxima below the highest local maximum encountered so far are only encountered in step 3, where they have no effect.
This last solution can process ten million datapoints in 3 seconds:
>>> rands = [random.randrange(0, 1000000) for i in xrange(10000000)]
>>> %timeit fillcount(rands)
1 loops, best of 3: 3.3 s per loop
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…