【问题标题】:StackOverflow error while filling int[2000][2000] with a logic用逻辑填充 int[2000][2000] 时出现 StackOverflow 错误
【发布时间】:2016-12-22 06:07:00
【问题描述】:

我需要用一些逻辑填充int[2000][2000] 矩阵。

我的数组填充代码:

// n: (1 to 2000)
for(int i=0; i<n; i++){
    for(int j=0; j<n; j++){
        uniMatrix[i][j] = (((calc(i * j * i * j)) & 1) == 0) ? 0 : 1;
    }
}

这里:i * j * i * j 是我对获得i*j 的平方的看法。 calc() 是一种用于获取值的方法。然后,我检查calc() 返回的值是偶数还是奇数。如果是偶数,我将0 存储在(i, j) 的矩阵中,否则我将1 存储在其中。

我的calc()函数如下:

private static int calc(int n){

    // value of n=calc(1 | 2 | 3) is known
    if(n < 4) return n;

    // if value is present in HashMap, return it (don't calculate again)
    if(map.containsKey(n)) {
        return map.get(n);
    }

    // calculate the answer
    int answer = 1 * calc(n-1) + 2 * calc(n-2) + 3 * calc(n-3);

    // store it in HashMap so that we don't have to recalculate it
    map.put(n, answer);

    return answer;
}

现在,如果 n 是 13,它会创建一个 [13x13] 矩阵。但是,对于n=14,它会在map.containsKey(n) 处抛出一个StackOverflowError。我需要能够制作 [2000x2000] 矩阵。

我知道问题可能是递归。但是,有没有办法解决这个问题?我可以用 BitSets 做点什么吗(我不知道该怎么做)?

我也可以使用其他数据类型矩阵:String[][]boolean[][]。我不能使用 Java SDK/JDK 之外的库。

编辑:它不是“什么是 StackOverflowError?”的重复,我知道它是什么,我知道它们为什么会发生。我需要帮助来寻找替代方法来防止出现此错误。

【问题讨论】:

  • 所以当i 是1999 并且j 是1999 时,您将拥有一堆i * j * i * j - 不要认为这有点过分?
  • 问题肯定是递归。位集和其他数据类型在这里对您没有帮助。将函数转换为迭代函数。
  • 事实:我们可以将每个 SOE 问题标记为“什么是 SOE”问题的重复项。只是因为它涵盖了“什么”“如何”“为什么”“预防”。对?我需要一个替代方案。我知道它为什么会被抛出(而且我知道我不应该使用递归)。
  • @rupinderjeet 请参阅下面的链接。我认为cal(100) 会给你带来很小的价值,因为你使用了int。如果我误解,请纠正我。 docs.google.com/spreadsheets/d/…
  • 我的速度有点太快了。但是,您只需要知道每个calc(n) 的奇偶校验(奇偶校验)。肯定有一个模式,尽管它比我一开始想象的要复杂。我仍然认为值得尝试找到它。

标签: java recursion matrix stack-overflow


【解决方案1】:

您无法为您需要的值范围计算calc。在i = j = 216 你会得到一个整数溢出(i * j * i * j 的结果变为负数),所以calc 的结果可能是不正确的。更糟糕的是,你的记忆 map 也会爆炸。

好消息是您实际上不需要计算它。 看这个表达式:

uniMatrix[i][j] = (((calc(i * j * i * j)) & 1) == 0) ? 0 : 1;

您不需要计算值,您只需要知道该值是偶数还是奇数。你可以知道,使用简单的数学。 calc 实现的核心是:

int answer = calc(n - 1) + 2 * calc(n - 2) + 3 * calc(n - 3);

而且我们知道前 3 个值是 1、2、3。 事实上,知道它们是奇数、偶数、奇数就足够了。 后面的值可以根据简单的数学计算出来:

  • 任何数乘以 2 都是偶数
  • 将任何数字乘以 3 将保持原来的值(如果是奇数则为奇数,即使是偶数也是如此)
  • 奇数加偶数就是偶数
  • 将偶数添加到任何其他数字将具有其他数字的偶数

现在,让我们看看calc计算的前几个值,并验证判断该值是奇数还是偶数的逻辑:

  • 0 -> 0 : 偶数(给定)
  • 1 -> 1 : 奇数(给定)
  • 2 -> 2 : 偶数(给定)
  • 3 -> 3 : 奇数(给定)
  • 4 -> 10 : 偶数,因为奇数 + 偶数 + 奇数是偶数
  • 5 -> 22 : 偶数,因为偶数 + 偶数 + 偶数是偶数
  • 6 -> 51 : 奇数,因为奇数 + 偶数 + 偶数是奇数
  • 7 -> 125 : 奇数,因为偶数 + 偶数 + 奇数是奇数
  • 8 -> 293 : 奇数,因为偶数 + 偶数 + 奇数是奇数
  • 9 -> 696 : 偶数,因为奇数 + 偶数 + 奇数是偶数
  • 10 -> 1657 : 奇数,因为奇数 + 偶数 + 偶数是奇数
  • 11 -> 3928 : 偶数,因为奇数 + 偶数 + 奇数是偶数
  • 12 -> 9330 : 偶数,因为偶数 + 偶数 + 偶数是偶数
  • 13 -> 22157 : 奇数,因为奇数 + 偶数 + 偶数是奇数
  • 14 -> 52601 : 奇数,因为偶数 + 偶数 + 奇数是奇数
  • 15 -> 124905 : 奇数,因为偶数 + 偶数 + 奇数是奇数
  • 16 -> 296578 : 偶数,因为奇数 + 偶数 + 奇数是偶数

如果您注意到,出现了重复模式:

even odd even even odd odd odd

只有 0 打破了模式,在这种情况下,该值被指定为 0。 因此,您的实现可以这样重写,没有堆栈溢出的风险:

int[] pattern = {0, 1, 0, 0, 1, 1, 1};
for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        long x = (long) i * j * i * j;
        if (x < 2) {
            uniMatrix[i][j] = (int) x;
        } else {
            uniMatrix[i][j] = pattern[(int)((x - 2) % pattern.length)];
        }
    }
}

对于 0,由于它不遵循模式,因此需要特殊处理。对于 1,x - 2 为负数。因为 0 和 1 的正确值是它本身,所以 if (x

【讨论】:

  • 你的回答比我快几秒。
  • @janos 哇,你的回答太棒了。 !!但愿意解释if(x&lt;2) {}else {} 部分吗?为什么 x
  • @Babel 为0,因为不符合规律,需要特殊处理。对于 1,x - 2 为负数。由于 0 和 1 的正确值是其本身,因此 if (x &lt; 2) 分支是处理这些情况的一种简单方法。我希望这能回答你的问题。
【解决方案2】:

我们只对模 2 的结果感兴趣,因此所有计算都可以模 2 完成。然后递归减少到(为 calc 模 2 编写 calc'):

calc'(1) = 1
calc'(2) = 0
calc'(3) = 1
calc'(n) = (calc'(n-1) + calc'(n-3)) % 2

所以我们看到下一个值仅取决于固定距离内的两个先前值,并且由于每个值的数字范围是有限的(只能是 0 或 1),我们知道序列最终必须是周期性的。一个小实验表明周期是 7。

所以模 2 的值是(对于 n >= 1):

calc'(n) = 
     1 if n%7 = 1,
     0 if n%7 = 2,
     1 if n%7 = 3,
     0 if n%7 = 4,
     0 if n%7 = 5,
     1 if n%7 = 6,
     1 if n%7 = 0.

【讨论】:

  • 我接受了 janos 的回答。它更详细,正如你所说,他首先发布。但是,非常感谢您的回答。
猜你喜欢
  • 2021-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-12
相关资源
最近更新 更多