面试问题:买卖股票以最大限度地提高利润,但要限制一次卖出就不要买入

时间:2019-01-11 18:04:49

标签: algorithm recursion dynamic-programming

经典的面试问题是利润最大化,一次交易,允许n次交易和k次交易来购买股票。

有人问我一个类似的问题,但有一个扭曲的约束: 您可以多次购买股票(每天最多可以购买一个单位),但是在售出股票后就不能购买。

这有一个引理,您只能卖一次。

例如: 70 40 90 110 80 100

选项1:B B B卖出_ _ = 130

选项2:B B B X B Sell = 120

较旧的问题

https://www.geeksforgeeks.org/stock-buy-sell/

https://www.geeksforgeeks.org/maximum-profit-by-buying-and-selling-a-share-at-most-twice/

https://www.geeksforgeeks.org/maximum-profit-by-buying-and-selling-a-share-at-most-k-times/

https://www.geeksforgeeks.org/maximum-profit-after-buying-and-selling-the-stocks-any-number-of-times/

Stackoverflow讨论

maximizing profit for given stock data via DP

Maximizing profit for given stock quotes

Maximum profit by buying and selling a share exactly k times

Best time to Buy and Sell stock modified version

2 个答案:

答案 0 :(得分:3)

可以使用BST在O(n lg n)空间O(n)中解决此问题。

数据结构

class BSTNode{
    BSTNode(val){
        this.val = val
        this.sum = sum
        this.left = this.right = null
        this.count = 1
    }

    insert(val){
        if(val < this.val)
            insert val into the left subtree
        else if(val > this.val)
            insert val into the right subtree

        this.sum += val
        this.count++
    }

    count_sum_nodes_upper_bound(val){
        if(val < this.val)
            return this.left.sum_nodes_lower_bound(val)
        else if(val == this.val)
            return this.left.sum, this.left.count
        else{
            right_sum, right_count = this.right.sum_nodes_lower_bound(val)

            return (this.sum - this.right.sum + right_sum),
                   (this.count - this.right.count + right_count)
        }
    }
}

以上只是正确的BST外观的粗略概述。实际上,您可能想使用平衡树,并检查count_sum_nodes_lower_bound中是否存在子树。上面的代码解释了:

每个BSTNode除了BST的标准属性外,还具有属性sumcount,其中count是子树中的元素数,{ {1}}是其中所有元素的总和。

Insert的工作方式与普通BST中的工作方式相同,只是在每个节点中需要更新相应的sumsum。如果多次插入相同的值,countsum将被更新以反映重复性。

然而,中心部分是方法count,该方法计算给定上限的元素数及其和。对于给定的上限count_sum_nodes_upper_bound,在值b的节点上可能发生三种情况:

  • v:所有相关元素都包含在左子树中
  • b < v:左侧子树是查询结果
  • b == v:左子树和当前节点中的所有值都是该子集的一部分。此外,右侧子树的某些节点也是结果的一部分,我们需要通过递归找到。

搜索

借助此BST,我们现在可以轻松找到上述问题的解决方案:

b > v

上面的代码根据BST中存储的值找到最佳销售日期的索引。在迭代maximize_profit(A){ node = BSTNode(A[0]) max = 0 max_idx = -1 for(i in 1..(A.length - 1)){ sum, count = node.count_sum_nodes_upper_bound(A[i]) gain = A[i] * count - sum if(max < gain){ max = gain max_idx = i } node.insert(A[i]) } return max_idx } 开始时,i将包含node中的所有值。唯一值得购买的股票是价值低于A[0..i - 1]的股票。我们可以使用A[i]查询O(lg n)中这些股票的数量和总和。如果这些股票的总和为count_sum_nodes_upper_bound,而其数量为s,则这些股票的总收益就是所有股票的卖出数量(c)减去每只股票的购买价值。 (A[i] * c

此后,可以通过过滤s(或根据您的需要扩展BST的功能)在O(n)中轻松地购买股票。

答案 1 :(得分:2)

我们还可以在O(n log n)时间和O(n)空间中使用数组,堆栈和二进制搜索来解决此问题。向后迭代,并且在每次迭代时,如果该值大于数组中的最后一个元素,则向数组记录中添加(value, index, 0, 0)(value, index, count, cost));否则,找到第一个较高的元素(使用二进制搜索),增加其数量和成本,并将索引添加到贡献者堆栈的前面。

0  1  2  3   4  5
70 40 90 110 80 100

i:5
  [(100, 5, 0)]
  contributors: []
i:4
  80 < 100
  [(100, 5, 1, 80)]
  contributors: [4]
i:3
  110 > 100
  [(100, 5, 1, 80), (110, 3, 0, 0)]
  contributors: [4]
i:2
  90 < 100
  [(100, 5, 2, 170), (110, 3, 0, 0)]
  contributors: [2, 4]
i:1
  40 < 100
  [(100, 5, 3, 210), (110, 3, 0, 0)]
  contributors: [1, 2, 4]
i:0
  70 < 100
  [(100, 5, 4, 280), (110, 3, 0, 0)]
  contributors: [0, 1, 2, 4]

现在遍历我们单调增加的新记录。在记录的每次迭代中,添加前一个元组的计数和成本,然后在贡献者堆栈中的每个元素的索引大于记录中的当前元素时,对“贡献者”堆栈中的每个元素进行“弹出”计数和成本:

0  1  2  3   4  5
70 40 90 110 80 100

[(100, 5, 4, 280), (110, 3, 0, 0)]

i:0
  best = 4 * 100 - 280 = 120

i:1
  add count and cost:
  (110, 3, 0 + 4, 0 + 280)

  pop count and cost for each
  contributor with a greater index:
  contributors: [0, 1, 2, 4]
  index 4 > 3
  (110, 3, 4 - 1, 280 - 80)
    profit = 3 * 110 - 200 = 130
    best = 130
    contributors: [0, 1, 2]