【问题标题】:Need help on an algorithm (stack overflow) [closed]需要算法帮助(堆栈溢出)[关闭]
【发布时间】:2017-08-18 15:08:12
【问题描述】:

这是我的一个朋友给我的一个挑战。我设法提出了一个递归算法,它适用于小输入,但是我得到大值的分段错误。我想这是因为堆栈溢出。我用C语言来解决这个问题。

给你一个包含 n 个数字的数组。找到并打印子集的最大长度,使得对于构成该子集的任意两个数字,数字之和不能被 k 整除。

输入在第一行包含 2 个数字 n 和 k,在下一​​行包含 n 个数字 a[i],这样:

1 <= n <= 10^5
0 <= a[i] <= 10^9
1 <= k <= 100

# Example input:
4 3
1 7 4 2

# Output:
3

解释:(1 7 4) 1 + 7 = 8; 1 + 4 = 5; 7 + 4 = 11; 都不能被 3 整除。

我的解决方案基于以下想法:对于数组中的所有数字,如果它可以被 k 整除,请检查与其他数字的总和。如果我们找到匹配项,则创建 2 个数组,一个不包括总和的第一项,一个不包括第二项,这样我们就可以从子集中排除这些对。然后对他们俩的第一个数组做同样的事情。如果我们检查了数组中的所有元素,则将解决方案设置为数组的长度,并继续将“求解器”应用于长度大于已找到解决方案的数组。该算法适用于 n

#include <stdio.h>

int n, k;

int * deleteElement(int * a, int n, int j){
    int *c = (int*) malloc((n-1) * sizeof(int));
    int k = 0;
    for(int i = 0; i < n; i++){
        if(i == j) continue;
        c[k] = a[i];
        k++;
    }
    return c;
}

int sol = 0;

void solver(int *a, int n, int *sol){
    int *b, *c;
    if(n <= *sol) return;
    for(int i = 0; i <  n-1; i++){
        for(int j = i + 1; j < n; j++){
             if((a[i] + a[j]) % k == 0){
                  c = deleteElement(a, n, i);
                  b = deleteElement(a, n, j);
                  solver(c, n-1, sol);
                  solver(b, n-1, sol);
                  return; 
             }
         }
     }
     *sol = n;
}

int main(){
    scanf("%d", &n);
    scanf("%d", &k);
    int a[n];
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    solver(a, n, &sol);
    printf("%d\n", sol);
    return 0;
}

【问题讨论】:

  • 您是否专门寻找相同算法的非递归实现?或者您是否愿意接受完全不同的算法以更有效的方式解决同一问题?
  • 我提到我希望看到任何解决方案,我只是好奇如何将其转换为迭代算法。在处理由于堆栈溢出导致的分段错误时,这似乎是我通常具备的一项重要技能。
  • 哦,对不起,我现在看到那是在代码转储之前的最后一句话中(“我希望看到任何解决方案,但是我更希望看到如何将此算法转换为迭代一”)。问题是这是两个非常不同的问题。
  • 好的,我已尝试将其限制为一个问题,如果需要进一步编辑,请通知我。
  • 有点OT:subset是连续的还是集合的一部分,换句话说,对于输入1 7 4 27 2是不是一个子集?

标签: c arrays algorithm recursion segmentation-fault


【解决方案1】:

您可以使用迭代来摆脱两个递归调用之一,但这对堆栈空间没有帮助,因为它们具有相同的深度 - 一个调用与 2 一样糟糕。

编写一个完全迭代的算法来实际测试所有有效集是很容易的,但这仍然是一个指数时间算法。无论如何,这将使您免于堆栈溢出,但运行时间方式 太长。由于该算法也很烂,我不想写它。

解决此问题的合理线性时间方法是:

  1. 计算地图 MODCOUNTS,其中 MODCOUNTS[m] = 元素数 x 使得 x%k == m
  2. 由于任何有效子集只能有一个元素可被 k 整除,如果 MODCOUNTS[0] > 1,则设置 MODCOUNTS[0]=1
  3. 同样,如果 k 是偶数,并且 MODCOUNTS[k/2] > 1,则设置 MODCOUNTS[k/2]=1
  4. 现在,将 MODCOUNTS 中的所有值相加,但在以下情况下省略值 MODCOUNTS[i]:
    • i > 0, i*2
    • i*2 > k AND MODCOUNTS[i]

规则 4 反映了这样一个事实,即有效子集不能包含任何元素 x 和 y,使得 (x+y)%k = 0,对于我们在规则 2 和 3 中没有处理的情况。最大的有效子集包括 MODCOUNTS[i] 中的所有元素,或 MODCOUNTS[ki] 中的所有元素,但不包括两者中的元素。

如果你使用像哈希表这样的稀疏数据结构来实现MODCOUNTS,那么整个事情需要O(N)时间。

【讨论】:

  • 非常感谢,非常棒的解决方案,最初不适用于所有情况,因为我制作了长度为 n 的地图,当它应该是 k 时,现在它通过了所有情况。你能推荐我一种更好地找到像你这样的高效算法的方法吗?
  • 恐怕需要来自实践、经验和研究的直觉。第一部分是了解何时停止寻找更好的解决方案,通过比较您的算法需要多长时间(简单)和解决您的问题的算法应该需要多长时间(困难)。然后你必须熟悉很多聪明的算法及其技巧。
猜你喜欢
  • 2021-11-12
  • 2010-10-27
  • 1970-01-01
  • 2012-06-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多