【问题标题】:Confusion on Time and Space Complexities On Brute Force Code对蛮力代码的时间和空间复杂性的困惑
【发布时间】:2020-04-20 04:31:32
【问题描述】:

我最近正在研究我在 LeetCode 上发现的一个问题的解决方案。问题可以在这里看到: https://leetcode.com/articles/best-time-to-buy-and-sell-stock-ii/#

关于蛮力方法文章详情,我不明白为什么时间复杂度是O(n^n)。我相信它应该是 O(n!)。我也不明白为什么空间复杂度是 O(N),因为似乎没有存储数组。

这是该问题的蛮力解决方案的代码:

class Solution {
    public int maxProfit(int[] prices) {
        return calculate(prices, 0);
    }

    public int calculate(int prices[], int s) {
        if (s >= prices.length)
            return 0;
        int max = 0;
        for (int start = s; start < prices.length; start++) {
            int maxprofit = 0;
            for (int i = start + 1; i < prices.length; i++) {
                if (prices[start] < prices[i]) {
                    int profit = calculate(prices, i + 1) + prices[i] - prices[start];
                    if (profit > maxprofit)
                        maxprofit = profit;
                }
            }
            if (maxprofit > max)
                max = maxprofit;
        }
        return max;
    }
}

【问题讨论】:

  • “我认为应该是 O(n!)”,你能解释一下你为什么这么认为吗?
  • @akuzminykh 外循环是 n,内循环是 n-1。然后外循环变成n-1,内循环变成n-2。以此类推
  • @D-Studios 你注意到递归调用了吗,这就是它成指数的原因
  • @D-Studios 对于这种情况,形式上正确的证明有点困难,你必须写很多东西。但相信我,这是O(n^n)。如果/当我有时间时,我会在这里发布证明。
  • @akuzminykh 我的empirical evidence 另有说明,即 O(2^n),而不是 O(n^n)

标签: java time-complexity brute-force stock space-complexity


【解决方案1】:

让我们尝试经验证据,即计算成本。

最坏的情况是prices 的顺序是递增的,所以if (prices[start] &lt; prices[i]) 始终为真。

“成本”是在嵌套的if 语句中运行代码,加上递归调用的成本。

所以为了强制计算成本,我们将方法更改为:

public static int calculate(int n, int s) {
    int cost = 0;
    for (int start = s; start < n; start++) {
        for (int i = start + 1; i < n; i++) {
            cost++; // "cost" is the work we do here
            cost += calculate(n, i + 1); // which includes the recursive call
        }
    }
    return cost;
}

如果我们使用n 的各种值运行,我们会得到:

n = 1: cost = 0, 2^n = 2, factor = 0.00
n = 2: cost = 1, 2^n = 4, factor = 0.25
n = 4: cost = 7, 2^n = 16, factor = 0.44
n = 8: cost = 127, 2^n = 256, factor = 0.50
n = 16: cost = 32767, 2^n = 65536, factor = 0.50
n = 32: cost = 2147483647, 2^n = 4294967296, factor = 0.50

所以我们可以看到成本大约是2^n / 2,这意味着O(2^n),而不是O(n^n) em>。


为了比较,如果我们删除递归调用,即注释掉那行代码,我们得到:

n = 1: cost = 0, n^2 = 1, factor = 0.00
n = 2: cost = 1, n^2 = 4, factor = 0.25
n = 4: cost = 6, n^2 = 16, factor = 0.38
n = 8: cost = 28, n^2 = 64, factor = 0.44
n = 16: cost = 120, n^2 = 256, factor = 0.47
n = 32: cost = 496, n^2 = 1024, factor = 0.48
n = 64: cost = 2016, n^2 = 4096, factor = 0.49
n = 128: cost = 8128, n^2 = 16384, factor = 0.50
n = 256: cost = 32640, n^2 = 65536, factor = 0.50
n = 512: cost = 130816, n^2 = 262144, factor = 0.50
n = 1024: cost = 523776, n^2 = 1048576, factor = 0.50

所以我们可以看到成本约为n^2 / 2,这意味着 O(n^2),这是我们对嵌套循环的期望。

【讨论】:

  • 空间复杂度如何?为什么是 O(N)?未存储数组。
  • @D-Studios 该代码具有 O(n) 空间复杂度,因为最大递归深度,即调用堆栈的最大深度,与n 成线性关系。
  • 您是如何发现使用 2^n 的。这似乎是一种猜测和检查。
  • @D-Studios 我看了数列:7, 127, 32767, 2147483647, ... 这些对于有二进制数经验的程序员来说是非常容易识别的数字,所以我怀疑 2^n ,计算它和因子,以证实我的怀疑。
  • 你确定它是 2^n 吗?这是因为解决方案指出时间复杂度为 O(n^n)。此外,我最近在摆弄并画了一个关于递归的扩展树。我意识到,对于每一个 n,一个分支都会导致类似 (n-1) 或 (n-2) 等。所以基本上对于树深度中的每个级别,应该有大约 n^ 的那个级别的数量。扩展树似乎有 n 级。然而,尽管我认为这证明了 n^n 是合理的,但我意识到树的每一级中的每个数字都有一个 n^2。时间是否大于 O(n^n)? @安德烈亚斯
猜你喜欢
  • 2023-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-06
  • 2014-12-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多