【问题标题】:figure out Uneaten Leaves algorithm bug找出 Uneaten Leaves 算法错误
【发布时间】:2016-02-02 08:14:16
【问题描述】:

我在面试挑战中遇到了这个问题

K 条毛毛虫在 N 片叶子中觅食,每条毛毛虫 以独特的顺序从一片叶子落到另一片叶子,所有毛毛虫开始 在位置 0 的树枝上,落在位置之间的叶子上 1 和 N。每条毛虫 j 都有一个相关的跳跃编号 Aj。一种 跳跃数 j 的毛毛虫在以下位置吃树叶 j的倍数。它将按照 j、2j、3j 的顺序进行。直到它 到达叶子的末端,它会停下来并筑茧。给定 一组 A 的 K 个元素,我们需要确定个数 没吃的叶子。

约束:

1 9

1

1 9

输入格式:

N = 未吃的叶子数。

K = 毛毛虫的数量。

A = 整数数组。 跳转数字输出:

整数 nu。没吃完的叶子

示例输入:

10
3
2
4
5

输出:

4

说明:

[2, 4, 5] 是 3 成员的跳跃数字集。吃掉所有 2、4 和 5 倍数的叶子。只剩下 4 片叶子,编号为 1,3,7,9。

解决这个问题的简单方法是使用一个包含所有 N 个数字的 布尔 数组,然后遍历每条毛毛虫并记住它吃过的叶子。

int uneatenusingNaive(int N, vector<int> A)
{
    int eaten = 0;
    vector<bool>seen(N+1, false);
    for (int i = 0; i < A.size(); i++)
    {
        long Ai = A[i];
        long j = A[i];
        while (j <= N && j>0)
        {
            if (!seen[j])
            {
                seen[j] = true;
                eaten++;
            }
            j += Ai;
        }
    }
    return N - eaten;
}

这种方法通过了 10 个测试用例中的 8 个,并为 2 个用例给出错误答案。

另一种使用Inclusion Exclusion principle的方法,可以在herehere找到它的解释
下面是我的第二种方法的代码

 int gcd(int a, int b)
    {
        if (b == 0)
            return a;
        return gcd(b, a%b);
    }
    int lcm(int i, int j)
    {
        return i*j / gcd(i, j);
    }
    
    vector<vector<int>> mixStr(vector<vector<int>> & mix, vector<int>& A, unordered_map<int, int> & maxStart)
    {
        vector<vector<int>> res;
        if (mix.size() == 0)
        {
            for (int i = 0; i < A.size(); i++)
            {
                vector<int> tmp;
                tmp.push_back(A[i]);
                res.push_back(tmp);
            }
            return res;
        }
        
        
        for (int i = 0; i<mix.size(); i++)
        {
            int currSlotSize = mix[i].size();
            int currSlotMax = mix[i][currSlotSize - 1];
            
            for (int j = maxStart[currSlotMax]; j < A.size(); j++)
            {
                vector<int> tmp(mix[i]);
                tmp.push_back(A[j]);
                res.push_back(tmp);
            }
        }
        return res;
    }
    int uneatenLeavs(int N, int k, vector<int> A)
    {
        int i = 0;
        vector<vector<int>> mix;
        bool sign = true;
        int res = N;
        sort(A.begin(), A.end());
        unordered_map<int,int> maxStart;
        for (int i = 0; i < A.size(); i++)
        {
            maxStart[A[i]] = i + 1;
        }
        int eaten = 0;
        
    
        while (mix.size() != 1)
        {   
            
            mix = mixStr(mix, A, maxStart);
            for (int j = 0; j < mix.size(); j++)
            {
                int _lcm = mix[j][0];
                for (int s = 1; s < mix[j].size(); s++)
                {
                    _lcm = lcm(mix[j][s], _lcm);
                }
                if (sign)
                {
                    res -= N / _lcm;
                }
                else
                {
                    res += N / _lcm;
                }
            }
            sign = !sign;
            i++;
        }
        return res;
    }

这种方法只通过了一个 1/10 的测试用例。对于其余的测试用例超出时间限制和错误答案。

问题:
我在第一种或第二种方法中缺少什么是 100% 正确的。

【问题讨论】:

  • 您提到第一种方法通过 8/10。你能提供它成功/失败的例子吗?
  • @shapiroyaacov 不幸的是,测试用例被隐藏了。

标签: c++ algorithm discrete-mathematics


【解决方案1】:

使用包含-排除定理是正确的方法,但是,您的实现似乎太慢了。我们可以使用位掩码技术来获得 O(K*2^K) 的时间复杂度。

看看这个:

long result = 0;

for(int i = 1; i < 1 << K; i++){
     long lcm = 1;
     for(int j = 0; j < K; j++)
        if(((1<<j) & i) != 0) //if bit j is set, compute new LCM after including A[j]
           lcm *= A[j]/gcd(lcm, A[j]);
     if(number of bit set in i is odd)
        result += N/lcm;
     else
        result -= N/lcm; 
}

对于您的第一种方法,O(N*K) 时间复杂度算法,N = 10^9 和 K = 15,它会太慢,并且可能导致超出内存限制/超过时限。

注意lcm可以大于N,所以需要额外检查。

【讨论】:

  • 感谢您的回答,但我不太明白这一行 lcm*= lcm*A[j]/gcd(lcm, A[j]);对于第一种方法,它给出了错误的答案。如果是内存或时间限制,在线评委会再给它一条消息。
  • @MahmoudEmam 是计算A的子集的最小公倍数,所以我们知道lcm of (a,b) = a*b / gcd(a,b)lcm(a, b , c) = lcm(lcm(a,b), c),所以要计算lcm of (a, b, c, d ...) = lcm(lcm(lcm(a, b), c) , d)...
  • @MahmoudEmam 你的第一种方法看起来不错,所以,可能是因为某些特定于语言的问题。
  • 非常感谢我知道了,使用比特来生成电力集是非常有趣的技巧。
  • 我认为 lcm*= lcmA[j]/gcd(lcm, A[j]);应该是 lcm *= A[j]/gcd(lcm, A[j]);或 lcm = lcm A[j]/gcd(lcm, A[j]);
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-10-13
  • 1970-01-01
  • 2017-11-25
  • 1970-01-01
  • 2016-01-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多