我在面试挑战中遇到了这个问题
K毛毛虫正在通过每片毛虫的N片叶子吃东西 所有毛毛虫都以独特的顺序从一片叶子落到另一片叶子 在0号位置的树枝处,落在两片之间的叶子上 每个履带j具有相关的跳跃数Aj。一个 毛茸茸的跳跃数j吃叶子的位置 j的倍数。它将以j,2j,3j ......的顺序进行。直到它 到达叶子的末端,它停止并建立它的茧。特定 一组K元素,我们需要确定数字 未吃的叶子。约束:
1 <= N <= 10 9
1 <= K <= 15
1&lt; = A [i]&lt; = 10 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的另一种方法,可以找到here和here的解释
下面是我的第二种方法的代码
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%正确。
答案 0 :(得分:3)
使用包含 - 排除定理是正确的方法,但是,您的实现似乎太慢。我们可以使用bitmasking技术来获得 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,因此需要进行额外检查。