下面的代码通过了 Codility 的所有测试。 OP 添加了一个main 函数以在命令行上使用它。
约束并不像 OP 想象的那么复杂。特别是,永远不会出现需要添加限制,即输入是输出中其他地方看到的某些特定整数集合的元素。每个输入位置都有明确定义的最小值和最大值。
该规则的唯一复杂之处在于,有时最大值是“前一个输入的值”,并且该输入本身有一个范围。但即便如此,所有类似的值都是连续的并且具有相同的范围,因此可以使用基本组合计算可能性的数量,并且这些输入作为一个组独立于其他输入(仅用于设置范围) ,因此该组的可能性可以通过简单的乘法与其他输入位置的可能性相结合。
算法概述
该算法在每个 span 之后通过输出数组更新输入数组的可能数量,这就是我所说的输出中数字的重复。 (您可能会说每个元素都相同的输出的最大子序列。)例如,对于输出0,1,1,2,我们有三个跨度:0、1,1 和2。当一个新的跨度开始时,计算前一个跨度的可能性数量。
这个决定是基于一些观察:
- 对于长度大于 1 的 spans,输入的最小值
允许在第一个位置是输入的任何值
在第二个位置。计算a的可能性的数量
span 是简单的组合,但标准公式
需要知道数字的范围和跨度的长度。
- 每次的值
输出变化(和一个新的跨度存在),这强烈地限制了前一个跨度的值:
- 当输出上升时,唯一可能的原因是之前的输入是新的更高输出的值,而与新的更高输出的位置对应的输入甚至更高。
- 当输出下降时,会建立新的约束,但这些约束有点难以表达。该算法存储 stairs(见下文),以便量化输出下降时施加的约束
这里的目的是限制每个 span 的可能值范围。一旦我们准确地做到了这一点,计算组合的数量就很简单了。
因为编码器回溯希望以 2 种方式输出与输入相关的数字,更小和更接近,我们知道我们可以抛出更大和更远的数字。在输出中出现一个小数字后,该位置之前的更大数字不会对后面的内容产生任何影响。
因此,为了在输出序列减少时限制这些输入范围,我们需要存储 stairs - 原始数组中位置的可能值越来越大的列表。例如,0,2,5,7,2,4 楼梯的构建方式如下:0、0,2、0,2,5、0,2,5,7、0,2、0,2,4。
使用这些边界,我们可以确定第二个2 位置的数字(示例中的最后一个位置旁边)必须在(2,5] 中,因为5 是下一个楼梯。如果输入大于 5,则会在该空间中输出 5 而不是 2。请注意,如果编码数组中的最后一个数字不是4,而是6,我们将退出提前返回@ 987654341@,因为我们知道前面的数字不能大于5。
复杂度是O(n*lg(min(n,m)))。
功能
-
CombinationsWithReplacement - 从 n 数字中计算 number of combinations with replacements 的大小为 k。例如。对于(3, 2),它计数3,3,3,2,3,1,2,2,2,1,1,1,所以返回6它与choose(n - 1 + k, n - 1)相同。
李>
nextBigger - 在一个范围内找到下一个更大的元素。例如。对于4 在子数组1,2,3,4,5 中返回5,在子数组1,3 中返回其参数Max。
-
countSpan (lambda) - 计算 我们刚刚通过 的跨度可以有多少种不同的组合。考虑跨度2,2 为0,2,5,7,2,2,7。
- 当
curr 到达最终位置时,curr 是7,prev 是2 范围内的最后一个2。
- 它计算
prev 范围的最大和最小可能值。此时楼梯由2,5,7 组成,那么最大可能值为5(nextBigger 在2 之后stair 2,5,7)。在此范围内大于 5 的值将输出 5,而不是 2。
- 它计算跨度的最小值(这是跨度中每个元素的最小值),此时为
prev,(记住此时curr等于7和@987654383 @ 到 2)。我们肯定知道,代替最终的2 输出,原始输入必须有7,所以最小值是7。 (这是“输出上升”规则的结果。如果我们有7,7,2 和curr 将是2,那么前一个跨度(7,7)的最小值将是8,即@ 987654393@.
-
它调整组合的数量。对于长度为 L 且具有 n 个可能性范围(1+max-min)的跨度,有 个可能性,k是 L 或 L-1 取决于跨度之后的内容。
- 对于后跟 更大 数字的跨度,例如
2,2,7,k = L - 1 因为2,2 跨度的最后位置必须是 @ 987654396@(跨度后第一个数字的值)。
- 对于后跟 较小 数字的跨度,例如
7,7,2,k = L 因为
7,7 的最后一个元素没有特殊限制。
最后,它调用CombinationsWithReplacement 找出分支的数量(或可能性),计算新的res 部分结果值(我们正在做的模运算中的剩余值),并返回新的@987654401 @value 和 max 供进一步处理。
-
solution - 遍历给定的编码器输出数组。在主循环中,在跨度中,它计算跨度长度,并在跨度边界处通过调用countSpan 更新res,并可能更新楼梯。
- 如果当前跨度包含比前一个更大的数字,则:
- 检查下一个号码的有效性。例如
0,2,5,2,7是无效输入,因为倒数第二个位置不能有7,只有3,或4,或5。
- 它更新了楼梯。当我们只看到
0,2时,楼梯是0,2,但在下一个5之后,楼梯就变成了0,2,5。
- 如果当前跨度包含的数字小于前一个,则:
- 它更新楼梯。当我们只看到
0,2,5时,我们的楼梯是0,2,5,但是在我们看到0,2,5,2之后,楼梯就变成了0,2。
- 在主循环之后,它通过调用
countSpan 和 -1 来计算最后一个跨度,这会触发计算的“输出下降”分支。
-
normalizeMod、extendedEuclidInternal、extendedEuclid、invMod - 这些辅助函数有助于处理模运算。
对于楼梯,我使用存储编码数组,因为楼梯的数量永远不会超过当前位置。
#include <algorithm>
#include <cassert>
#include <vector>
#include <tuple>
const int Modulus = 1'000'000'007;
int CombinationsWithReplacement(int n, int k);
template <class It>
auto nextBigger(It begin, It end, int value, int Max) {
auto maxIt = std::upper_bound(begin, end, value);
auto max = Max;
if (maxIt != end) {
max = *maxIt;
}
return max;
}
auto solution(std::vector<int> &B, const int Max) {
auto res = 1;
const auto size = (int)B.size();
auto spanLength = 1;
auto prev = 0;
// Stairs is the list of numbers which could be smaller than number in the next position
const auto stairsBegin = B.begin();
// This includes first entry (zero) into stairs
// We need to include 0 because we can meet another zero later in encoded array
// and we need to be able to find in stairs
auto stairsEnd = stairsBegin + 1;
auto countSpan = [&](int curr) {
const auto max = nextBigger(stairsBegin, stairsEnd, prev, Max);
// At the moment when we switch from the current span to the next span
// prev is the number from previous span and curr from current.
// E.g. 1,1,7, when we move to the third position cur = 7 and prev = 1.
// Observe that, in this case minimum value possible in place of any of 1's can be at least 2=1+1=prev+1.
// But if we consider 7, then we have even more stringent condition for numbers in place of 1, it is 7
const auto min = std::max(prev + 1, curr);
const bool countLast = prev > curr;
const auto branchesCount = CombinationsWithReplacement(max - min + 1, spanLength - (countLast ? 0 : 1));
return std::make_pair(res * (long long)branchesCount % Modulus, max);
};
for (int i = 1; i < size; ++i) {
const auto curr = B[i];
if (curr == prev) {
++spanLength;
}
else {
int max;
std::tie(res, max) = countSpan(curr);
if (prev < curr) {
if (curr > max) {
// 0,1,5,1,7 - invalid because number in the fourth position lies in [2,5]
// and so in the fifth encoded position we can't something bigger than 5
return 0;
}
// It is time to possibly shrink stairs.
// E.g if we had stairs 0,2,4,9,17 and current value is 5,
// then we no more interested in 9 and 17, and we change stairs to 0,2,4,5.
// That's because any number bigger than 9 or 17 also bigger than 5.
const auto s = std::lower_bound(stairsBegin, stairsEnd, curr);
stairsEnd = s;
*stairsEnd++ = curr;
}
else {
assert(curr < prev);
auto it = std::lower_bound(stairsBegin, stairsEnd, curr);
if (it == stairsEnd || *it != curr) {
// 0,5,1 is invalid sequence because original sequence lloks like this 5,>5,>1
// and there is no 1 in any of the two first positions, so
// it can't appear in the third position of the encoded array
return 0;
}
}
spanLength = 1;
}
prev = curr;
}
res = countSpan(-1).first;
return res;
}
template <class T> T normalizeMod(T a, T m) {
if (a < 0) return a + m;
return a;
}
template <class T> std::pair<T, std::pair<T, T>> extendedEuclidInternal(T a, T b) {
T old_x = 1;
T old_y = 0;
T x = 0;
T y = 1;
while (true) {
T q = a / b;
T t = a - b * q;
if (t == 0) {
break;
}
a = b;
b = t;
t = x; x = old_x - x * q; old_x = t;
t = y; y = old_y - y * q; old_y = t;
}
return std::make_pair(b, std::make_pair(x, y));
}
// Returns gcd and Bezout's coefficients
template <class T> std::pair<T, std::pair<T, T>> extendedEuclid(T a, T b) {
if (a > b) {
if (b == 0) return std::make_pair(a, std::make_pair(1, 0));
return extendedEuclidInternal(a, b);
}
else {
if (a == 0) return std::make_pair(b, std::make_pair(0, 1));
auto p = extendedEuclidInternal(b, a);
std::swap(p.second.first, p.second.second);
return p;
}
}
template <class T> T invMod(T a, T m) {
auto p = extendedEuclid(a, m);
assert(p.first == 1);
return normalizeMod(p.second.first, m);
}
int CombinationsWithReplacement(int n, int k) {
int res = 1;
for (long long i = n; i < n + k; ++i) {
res = res * i % Modulus;
}
int denom = 1;
for (long long i = k; i > 0; --i) {
denom = denom * i % Modulus;
}
res = res * (long long)invMod(denom, Modulus) % Modulus;
return res;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Only the above is needed for the Codility challenge. Below is to run on the command line.
//
// Compile with: gcc -std=gnu++14 -lc++ -lstdc++ array_recovery.cpp
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <string.h>
// Usage: 0 1 2,3, 4 M
// Last arg is M, the max value for an input.
// Remaining args are B (the output of the encoder) separated by commas and/or spaces
// Parentheses and brackets are ignored, so you can use the same input form as Codility's tests: ([1,2,3], M)
int main(int argc, char* argv[]) {
int Max;
std::vector<int> B;
const char* delim = " ,[]()";
if (argc < 2 ) {
printf("Usage: %s M 0 1 2,3, 4... \n", argv[0]);
return 1;
}
for (int i = 1; i < argc; i++) {
char* parse;
parse = strtok(argv[i], delim);
while (parse != NULL)
{
B.push_back(atoi(parse));
parse = strtok (NULL, delim);
}
}
Max = B.back();
B.pop_back();
printf("%d\n", solution(B, Max));
return 0;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
// Only the above is needed for the Codility challenge. Below is to run on the command line.
//
// Compile with: gcc -std=gnu++14 -lc++ -lstdc++ array_recovery.cpp
//
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
#include <string.h>
// Usage: M 0 1 2,3, 4
// first arg is M, the max value for an input.
// remaining args are B (the output of the encoder) separated by commas and/or spaces
int main(int argc, char* argv[]) {
int Max;
std::vector<int> B;
const char* delim = " ,";
if (argc < 3 ) {
printf("Usage: %s M 0 1 2,3, 4... \n", argv[0]);
return 1;
}
Max = atoi(argv[1]);
for (int i = 2; i < argc; i++) {
char* parse;
parse = strtok(argv[i], delim);
while (parse != NULL)
{
B.push_back(atoi(parse));
parse = strtok (NULL, delim);
}
}
printf("%d\n", solution(B, Max));
return 0;
}
我们来看一个例子:
最大 = 5
数组是
0 1 3 0 1 1 3
1
1 2..5
1 3 4..5
1 3 4..5 1
1 3 4..5 1 2..5
1 3 4..5 1 2..5 >=..2(抱歉,麻烦了写作方式)
1 3 4..5 1 3..5 >=..3 4..5
现在数一下:
1 1 2 1 3 2 总计 12。