如何在数组中将n 个数字相加,乘以给定范围,并在O(n)时间内反转数组中的范围
How to add, multiply n numbers to a given range in array and also reverse the range in the array in O(n) time?
假设我们有一个数组L[]={4,1,2,6}。我们需要做的是将一个由字符 A、R、M 组成的字符串 S 作为输入,并应用以下算法:
for i from 1 to N do
if ith letter of S is 'R'
reverse L[i...N]
else if ith letter of S is 'A'
add A to all numbers of L[i..N].
else if ith letter of S is 'M'
multiply B to all numbers of L[i..N].
for all number in L[i..N], module them by C.
print L[i]
end
如何优化此算法,使其在 O(n) 时间内运行?数组和字符串的长度为 n。 无论如何,此算法中的任何优化(例如删除仅加法和乘法的循环)都将受到欢迎。
这个答案很长,但恕我直言,我已经以一种相当容易理解的方式写了它,并有相当多的解释,所以请耐心等待我片刻。
假设A
和B
都是整数,则可以O(N)
时间内解决此问题。否则,此时您可以停止阅读。但我认为有必要将算法分解为几个不同的步骤,每个步骤都是O(N)
.所以总体上还是会O(N)
的。
问题中最困难的部分可能是弄清楚如何使此步骤在线性时间内运行:
if ith letter of S is 'R'
reverse L[i...N]
如果我们一直盯着原始算法,我们会确信,即使其他步骤可以在线性时间内实现,这一步也永远无法在线性时间内完成。但事实并非如此。我们怎么做?我想到的方法是从双端队列/deque数据结构中借用一个想法。由于我们知道数组L
有多长,我们只维护 3 个变量,leftmost
,rightmost
和isReversed
。
leftmost
将保存L
数组当前最左边未使用的索引,因此leftmost
初始化为1
,因为我们使用的是您的问题中所述的一个索引数组(术语"未使用"将在后面解释)。rightmost
将保存L
数组当前最右边未使用的索引,因此初始化为N
,长度为L
。isReversed
用于指示数组是否被反转。这初始化为false
。
我们手头的第一个任务是在应用所有reverse
操作后,找出数组L
原始元素的最终顺序。我们甚至不需要反转数组一次即可达到与反转相同的效果。这可以通过遍历输入字符串S
一次来完成,并在所有反向操作后确定数组L
的哪个元素应该位于每个位置。为简单起见,我们创建了一个新的整数数组L'
,它将在应用所有反向操作后保存L
的最终原始元素,并尝试填充L'
。
假设我们在索引i
和S[i] == 'R'
,所以我们设置isReversed = true
来指示我们正在反转子数组[i..N]
。当isReversed == true
时,我们知道子数组[i..N]
被反转,所以L'[i]
的元素应该是最右边的未使用的元素,其索引是rightmost
。因此,我们设定L'[i] = L[rightmost]
,并将rightmost
递减1
(rightmost = rightmost - 1
)。相反,如果isReversed == false
我们不反转子数组[i..N]
,所以L'[i]
处的元素应该是最左边未使用的元素,其索引为leftmost
。因此,我们设置L'[i] = L[leftmost]
,并将leftmost
递增1
(leftmost = leftmost - 1
)。后续reverse
将否定isReversed
的值。
所以当前的算法在C++中看起来像这样(我假设你对C++没问题,因为你的问题的一个标签是C++):
// set up new array L'
int Lprime[N+1];
int leftmost = 1;
int rightmost = N;
bool isReversed = false;
for (int i = 1; i <= N; i++) {
if (S[i] == 'R') {
// negate isReversed
isReversed = !isReversed;
}
if (isReversed) {
Lprime[i] = L[rightmost];
rightmost = rightmost - 1;
} else {
Lprime[i] = L[leftmost];
leftmost = leftmost + 1;
}
}
请验证这是正确的,尽管我相信是这样。
现在我们看看原始算法的其余部分:
else if ith letter of S is 'A'
add A to all numbers of L[i..N].
else if ith letter of S is 'M'
multiply B to all numbers of L[i..N].
for all number in L[i..N], module them by C.
困难的部分似乎是需要通过为每个索引[i..N]
C
来执行模i
。但根据我有限的理解,这是模算术,我们真的不需要在每个i
的子数组[i..N]
上执行它。但不要相信我的话。我对数论的理解非常简陋。
不仅如此,加法和乘法的步骤也可以简化。这里的诀窍是维护 2 个额外的变量,我们称它们为multiplicativeFactor
和additiveConstant
.multiplicativeFactor
用于保持我们需要乘以L'[i]
的常数。这最初是1
.顾名思义,additiveConstant
变量用于存储L'[i]
乘以multiplicativeFactor
后我们需要添加到L'[i]
中的任何常量。additiveConstant
被启动到0
.
为了更具体地了解这一点,让我们设置A = 3
,B = 5
。假设S
是字符串"AMMAAM"
.这意味着以下内容(注意:我们暂时忽略模C
):
- 在索引
1
处,设置L'[1] = L'[1] + 3;
- 在索引
2
处,设置L'[2] = (L'[2] + 3) * 5;
- 在索引
3
处,设置L'[3] = ((L'[3] + 3) * 5) * 5;
- 在索引
4
处,设置L'[4] = (((L'[4] + 3) * 5) * 5) + 3;
- 在索引
5
处,设置L'[5] = ((((L'[5] + 3) * 5) * 5) + 3) + 3
- 在索引
6
处,设置L'[6] = (((((L'[6] + 3) * 5) * 5) + 3) + 3) * 5
请注意,先前字符的效果'A'
,'M'
"延续"(或级联)到L'
的未来元素中。让我们以稍微不同的方式表达这些操作:
L'[1] = L'[1] + 3
L'[2] = 5 * L'[2] + (3 * 5)
L'[3] = 5 * 5 * L'[3] + (3 * 5 * 5)
L'[4] = 5 * 5 * L'[4] + (3 * 5 * 5 + 3)
L'[5] = 5 * 5 * L'[5] + (3 * 5 * 5 + 3 + 3)
L'[6] = 5 * 5 * 5 * L'[6] + (3 * 5 * 5 + 3 + 3) * 5
我们开始看到一些模式。
L'[i]
的乘法因子始终是B
的幂。添加A
对这个乘法因子没有任何影响。乘法因子存储在我们上面描述的multiplicativeConstant
变量中- 每次我们需要将
L'[i]
乘以一个额外的B
时,都需要将所有常量(由A
相加而产生的)乘以B
以获得要添加到L'[i]
的最终常数。这就是上述additiveConstant
变量的目的。 - 在将
additiveConstant
添加到L'[i]
之前,应进行L'[i]
的乘法
因此,每个L'[i]
的最终值可以表示为multiplicativeConstant * L'[i] + additiveConstant;
,算法的第二个主要部分如下所示:
int multiplicativeConstant = 1;
int additiveConstant = 0;
for (int i = 1; i <= N; i++) {
if (S[i] == 'A') {
additiveConstant += A;
} else if (S[i] == 'M') {
multiplicativeConstant *= B;
// need to multiply all the constants by B as well
additiveConstant *= B;
}
Lprime[i] = multiplicativeConstant * Lprime[i] + additiveConstant;
}
有一个警告我没有谈到。multiplicativeConstant
和additiveConstant
的整数溢出,以及中间计算。如果L
是一个int
数组,我们很幸运,因为我们可以将long long
用于所有事情并避免溢出。否则,我们必须小心中间计算不会溢出。
现在modulo C
操作呢?实际上,它们的存在是为了将L'[i]
中的每个值保持在[0..C-1]
范围内。基于我对数论的有限理解,我们可以像这样执行模算术来达到相同的效果:
int multiplicativeConstant = 1;
int additiveConstant = 0;
for (int i = 1; i <= N; i++) {
if (S[i] == 'A') {
additiveConstant = (additiveConstant + (A % C)) % C;
} else if (S[i] == 'M') {
multiplicativeConstant = (multiplicativeConstant * (B % C)) % C;
// need to multiply all the constants by B as well
additiveConstant = (additiveConstant * (B % C)) % C;
}
Lprime[i] = ((multiplicativeConstant * (Lprime[i] % C)) % C + additiveConstant) % C;
}
这解决了multiplicativeConstant
和additiveConstant
变量的溢出问题(但不防止中间计算和其他变量的溢出),并完成了我们的算法。我相信这是正确的,但请自己验证。我无法解释模块化算术的东西,因为我只知道如何使用它,所以你必须自己查找它。附带说明一下,A % C
和B % C
部分可以完成一次,其结果存储在变量中。
最后,把所有东西放在一起:
// set up new array L'
int Lprime[N+1];
int leftmost = 1;
int rightmost = N;
bool isReversed = false;
for (int i = 1; i <= N; i++) {
if (S[i] == 'R') {
// negate isReversed
isReversed = !isReversed;
}
if (isReversed) {
Lprime[i] = L[rightmost];
rightmost = rightmost - 1;
} else {
Lprime[i] = L[leftmost];
leftmost = leftmost - 1;
}
}
int multiplicativeConstant = 1;
int additiveConstant = 0;
// factor out A % C and B % C
int aModC = A % C;
int bModC = B % C;
for (int i = 1; i <= N; i++) {
if (S[i] == 'A') {
additiveConstant = (additiveConstant + aModC) % C;
} else if (S[i] == 'M') {
multiplicativeConstant = (multiplicativeConstant * bModC) % C;
// need to multiply all the constants by B as well
additiveConstant = (additiveConstant * bModC) % C;
}
Lprime[i] = ((multiplicativeConstant * (Lprime[i] % C)) % C + additiveConstant) % C;
}
// print Lprime
这在总体上运行O(N)
时间。
再一次,如果你担心整数溢出,假设L
是一个int
数组,你可以对计算中涉及的所有变量使用long long
,你应该没问题。
- 并行用于C++17中数组索引范围内的循环
- 在函数范围内在堆栈上分配的数组在离开函数时是否总是被释放?
- 基于字节数组生成静态范围整数值
- 在C++中使用变量而不是"#define"来指定数组大小是不是一种糟糕的做法?(C错误:在文件范围内
- 使用基于数组和范围的 For 循环替换一些基本代码行
- C++中循环和 C 样式数组的范围工作
- Excel 不愉快地显示大型 2D 范围公式数组
- 变量未在此范围内声明 数组线性搜索
- 如何使用基于范围的for循环迭代Rapidjson文档(它本身就是一个JSON数组)
- 对于多个查询,查找在 l 到 r 范围内具有相同元素的最长公共子数组
- 基于多维标准::数组的范围
- 从文件中读取选定的行范围并存储到数组中
- 从原始指针(衰减的 C 样式数组)和大小生成范围::视图
- 给定数组范围的选择排序问题
- gcc8.2如何启用警告:数组下标在数组范围之上[-warray-bounds]
- 如何通过迭代通过它插入数组范围
- 输出在我的字符数组范围之外,同时使用 rand()
- C++中数组范围内的有效元素计数
- 访问超出数组范围不会产生任何错误
- 为什么我可以设置数组范围之外的值?