如何有效地找到从每个值到下一个较低/较高值的距离?
Posted
技术标签:
【中文标题】如何有效地找到从每个值到下一个较低/较高值的距离?【英文标题】:How can I efficiently find distances from each value to the next lower/higher value? 【发布时间】:2022-01-21 06:45:49 【问题描述】:我会告诉你我正在使用什么结构,请随时推荐任何更改,如 numpy 数组或其他东西。
无论如何,我所拥有的是与股票价格相对应的 500 万个连续条目的列表。
然后我还有 2 个列表,每个列表的长度相同 - 500 万个条目。这些列表对应于预期的“上限”和预期的“下限”,我预计库存将从序列中的那个点达到。
我要做的是遍历下限列表中的所有 500 万个条目,并按顺序记录价格最终达到下限所需的时间。然后我想对上限列表做同样的事情。
以下是仅包含 10 个条目的股票价格表的潜在解决方案示例:
prices = [15,16,18,22,23,17,15,19,15,18]
upper_limits = [17,18,21,23,25,22,18,21,18,20]
lower_limits = [14,15,16,18,19,15,13,17,14,16]
solved_upper = [2,1,1,1,x,x,1,x,1,x]
solved_lower = [x,5,4,2,1,1,x,1,x,x]
#I think I got this right? Anyways as you can see, the solved lists simply show
#how many entries we have to look at until we find a value that is >= to it for upper, or <= to it
#for lower
所以问题是,对于大量的条目,如何合理快速地解决这个问题? (实际上,我有 10 个上限列表和 10 个下限列表......所以需要更高的效率)
【问题讨论】:
我试着写一个标题来描述你的问题。有什么不准确的地方吗? ...也就是说,开放式问题(例如寻求解决问题的最佳算法)在这里通常不被视为主题——Stack Overflow 专注于狭隘的、具体的问题(理想情况下)可以接受规范的答案,并且很少有可能证明算法是最好的选择。我并没有对自己投反对票/近距离投票,但不要感到惊讶。 @CharlesDuffy 我认为“询问可以为此使用什么数据结构”是一个非常合理的编程问题。 【参考方案1】:我要清楚效率。用真实的数据对象替换 Dictionary 对象可能是个好主意。
首先,我们需要将您的时间序列变成可搜索的树。
def make_tree (series, i=None, j=None):
if i is None:
i = 0
if j is None:
j = len(series) - 1
if i == j:
return
"min_i": i,
"max_i": i,
"min_value": series[i],
"max_value": series[i],
"left": None,
"right": None
else:
mid = (i + j) // 2
left = make_tree(series, i, mid)
right = make_tree(series, mid+1, j)
return
"min_i": i,
"max_i": j,
"min_value": min(left['min_value'], right['min_value']),
"max_value": max(left['max_value'], right['max_value']),
"left": left,
"right": right
接下来我们需要函数来搜索那棵树:
def find_next_after_at_least(tree, min_i, min_value):
if tree['max_i'] <= min_i or tree['max_value'] < min_value:
return None
elif tree['min_i'] == tree['max_i']:
return tree['min_i'] - min_i
else:
answer = find_next_after_at_least(tree['left'], min_i, min_value)
if answer is None:
answer = find_next_after_at_least(tree['right'], min_i, min_value)
return answer
def find_next_after_at_most(tree, min_i, max_value):
if tree['max_i'] <= min_i or max_value < tree['min_value']:
return None
elif tree['min_i'] == tree['max_i']:
return tree['min_i'] - min_i
else:
answer = find_next_after_at_most(tree['left'], min_i, max_value)
if answer is None:
answer = find_next_after_at_most(tree['right'], min_i, max_value)
return answer
现在您的搜索可以轻松编写:
def solve_upper(tree, limits):
return [
find_next_after_at_least(tree, i, limits[i])
for i in range(len(limits))
]
def solve_lower(tree, limits):
return [
find_next_after_at_most(tree, i, limits[i])
for i in range(len(limits))
]
现在你的示例问题:
t = make_tree([15,16,18,22,23,17,15,19,15,18])
print(solve_upper(t, [17,18,21,23,25,22,18,21,18,20]))
print(solve_lower(t, [14,15,16,18,19,15,13,17,14,16]))
【讨论】:
【参考方案2】:您可以使用类似于“单调队列”的数据结构有效地解决这个问题(在 O(N log N) 时间内)。你可以用谷歌搜索,但通常的用例与你的完全不同,所以我只解释一下。 (奇怪的是,这是我一周内在这里看到的第三个问题,答案需要这样的结构。)
在您的情况下,您将从价格数组的末尾开始工作,将每个价格添加到单调队列的前面。每次你输入一个价格,其他一些可能会被丢弃,因此队列只保存比之前所有的都大的项目。这些是唯一可能成为“下一个更高价格”的项目。它们在队列中也是单调递增的,因此您可以使用二分搜索找到第一个 >= 目标。由于您需要知道下一个较高值的索引,因此您可以存储索引而不是值本身。
这就解决了上限问题。下限是类似的,但队列是单调递减的。
在python中是这样的:
def solve_upper(prices, limits):
solved = [0]*len(prices)
q = [0]*len(prices)
qstart = len(q)
for i in range(len(prices)-1, -1, -1):
price = prices[i]
while qstart < len(q) and prices[q[qstart]] <= price:
# the price at the start of q needs to be discarded, since
# it isn't greater than the new one
qstart += 1
# prepend the new price
qstart -= 1
q[qstart] = i
limit = limits[i]
# binary search to find the first price >= limit
minpos = qstart
maxpos = len(q)
while minpos < maxpos:
testpos = minpos + (maxpos - minpos)//2
if prices[q[testpos]] < limit:
# too low
minpos = testpos+1
else:
# high enough
maxpos = testpos
if minpos < len(q):
solved[i] = q[minpos]-i
else:
solved[i] = None
return solved
def solve_lower(prices, limits):
solved = [0]*len(prices)
q = [0]*len(prices)
qstart = len(q)
for i in range(len(prices)-1, -1, -1):
price = prices[i]
while qstart < len(q) and prices[q[qstart]] >= price:
# the price at the start of q needs to be discarded, since
# it isn't less than the new one
qstart += 1
# prepend the new price
qstart -= 1
q[qstart] = i
limit = limits[i]
# binary search to find the first price <= limit
minpos = qstart
maxpos = len(q)
while minpos < maxpos:
testpos = minpos + (maxpos - minpos)//2
if prices[q[testpos]] > limit:
# too low
minpos = testpos+1
else:
# high enough
maxpos = testpos
if minpos < len(q):
solved[i] = q[minpos]-i
else:
solved[i] = None
return solved
prices = [15,16,18,22,23,17,15,19,15,18]
upper_limits = [17,18,21,23,25,22,18,21,18,20]
lower_limits = [14,15,16,18,19,15,13,17,14,16]
print(solve_upper(prices, upper_limits))
print(solve_lower(prices, lower_limits))
输出:
[2, 1, 1, 1, None, None, 1, None, 1, None]
[None, 5, 4, 2, 1, 1, None, 1, None, None]
注意:如果您将此答案与 @btilly 的答案进行对比,请在评论中包含结果!
【讨论】:
以上是关于如何有效地找到从每个值到下一个较低/较高值的距离?的主要内容,如果未能解决你的问题,请参考以下文章