题目
序列中的最大乘积
在下面的1000 位数中,相邻四位数的最大乘积是9∗9∗8∗9=5832。
从下面1000 位数中,找到相邻的13 位数的乘积的最大值。
7316717653133062491922511967442657474235534919493496983520312774506326239578318016984801869478851843858615607891129494954595017379583319528532088055111254069874715852386305071569329096329522744304355766896648950445244523161731856403098711121722383113622298934233803081353362766142828064444866452387493035890729629049156044077239071381051585930796086670172427121883998797908792274921901699720888093776657273330010533678812202354218097512545405947522435258490771167055601360483958644670632441572215539753697817977846174064955149290862569321978468622482839722413756570560574902614079729686524145351004748216637048440319989000889524345065854122758866688116427171479924442928230863465674813919123162824586178664583591245665294765456828489128831426076900422421902267105562632111110937054421750694165896040807198403850962455444362981230987879927244284909188845801561660979191338754992005240636899125607176060588611646710940507754100225698315520005593572972571636269561882670428252483600823257530420752963450
Largest Product in a Series
The four adjacent digits in the 1000-digit number that have the greatest product are 9∗9∗8∗9=5832.
73167176531330624919225119674426574742355349194934⋮71636269561882670428252483600823257530420752963450
Find the thirteen adjacent digits in the 1000-digit number that have the greatest product. What is the value of this product?
解题方法
使用滑动窗口算法来解最大乘积。时间复杂度为 O(N)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| std::uint64_t Product1(const std::string_view& Num, std::size_t size) {
std::uint64_t result = 0;
std::uint64_t temp = 1;
for (std::size_t i = 0; i + size <= Num.size(); ++i) {
temp = 1;
for (std::size_t j = 0; j < size; ++j) {
temp *= Num[i + j] - '0';
}
if (temp > result) {
result = temp;
}
}
return result;
}
|
基于 现代 C++ 的实现
滑动窗口可以考虑使用 现代 C++ 中的范围库、算法库、并行支持库、模板元编程等内容实现。
各个版本
一、使用 std::views::adjacent_transform
和 std::ranges::max
1
2
3
4
5
| template<std::size_t SIZE>
std::uint64_t Product2(const std::string_view Num) {
auto product = [](auto... window) -> std::uint64_t { return (std::uint64_t(window - '0') * ...); };
return std::ranges::max(Num | std::views::adjacent_transform<SIZE>(product));
}
|
二、使用 std::views::slide
、std::views::transform
和 std::ranges::max
1
2
3
4
5
6
7
8
9
| template<std::size_t SIZE>
std::uint64_t Product3(const std::string_view Num) {
auto product = [](auto&& window) -> std::uint64_t {
return [&window] <std::size_t... INDEX>(std::index_sequence<INDEX...>) -> std::uint64_t {
return ((std::uint64_t(window[INDEX] - '0')) * ...);
}(std::make_index_sequence<SIZE>{});
};
return std::ranges::max(Num | std::views::slide(SIZE) | std::views::transform(product));
}
|
三、使用并行 std::for_each
和 std::views::slide
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| template<std::size_t SIZE>
std::uint64_t Product4(const std::string_view Num) {
std::atomic<std::uint64_t> result = 0;
const auto& windows = Num | std::views::slide(SIZE); // 生成滑块
std::for_each(std::execution::par, std::begin(windows), std::end(windows), [&result](auto&& window) {
std::uint64_t product = [&window]<std::size_t... INDEX>(std::index_sequence<INDEX...>) -> std::uint64_t {
return ((std::uint64_t(window[INDEX] - '0')) * ...);
}(std::make_index_sequence<SIZE>{});
std::uint64_t old = result.load();
while (true) {
if (old >= product || result.compare_exchange_weak(old, product)) {
break;
}
}
}
);
return result.load();
}
|
三、使用并行 std::for_each
和 std::views::adjacent
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| template<std::size_t SIZE>
std::uint64_t Product5(const std::string_view Num) {
std::atomic<std::uint64_t> result = 0;
const auto& windows = Num | std::views::adjacent<SIZE>; // 生成滑块
std::for_each(std::execution::par, std::begin(windows), std::end(windows), [&result](auto&& window) {
std::uint64_t product = [&]<std::size_t... INDEX>(std::index_sequence<INDEX...>) -> std::uint64_t {
return ((std::uint64_t(std::get<INDEX>(window) - '0')) * ...);
}(std::make_index_sequence<SIZE>{});
std::uint64_t old = result.load();
while (true) {
if (old >= product || result.compare_exchange_weak(old, product)) {
break;
}
}
}
);
return result.load();
}
|
三、使用并行 std::for_each
、std::views::slide
和 std::reduce
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| std::uint64_t Product6(const std::string_view Num, std::size_t size) {
std::atomic<std::uint64_t> result = 0;
const auto& windows = Num | std::views::slide(size); // 生成滑块
// 此处使用 par 创建多个线程同步处理数据
// 此处调用的 function 不支持矢量化,因此不使用 unseq
std::for_each(std::execution::par, std::begin(windows), std::end(windows), [&result](std::ranges::viewable_range auto&& window) {
// 因为大量的调用 std::reduce,使用 par 会大量性能消耗在线程的创建和销毁上
// 因为此处 reduce 调用的 function 不是简单的二元乘法运算,因此不使用 unseq
// 注:MSVC 的 std::reduce 不支持 unseq
// 注:GCC 中 std::uint64_t 和 1llu 类型不同
std::uint64_t product = std::reduce(std::execution::seq, std::begin(window), std::end(window), std::uint64_t(1llu), []<class T1, class T2>(T1 f, T2 l) -> std::uint64_t {
if constexpr (std::is_same_v<std::remove_cvref_t<T1>, std::uint64_t> && std::is_same_v<std::remove_cvref_t<T2>, std::uint64_t>) {
return f * l;
}
else if constexpr (std::is_same_v<std::remove_cvref_t<T1>, char> && std::is_same_v<std::remove_cvref_t<T2>, char>) {
return static_cast<std::uint64_t>(f - '0') * static_cast<std::uint64_t>(l - '0');
}
else if constexpr (std::is_same_v<std::remove_cvref_t<T1>, std::uint64_t> && std::is_same_v<std::remove_cvref_t<T2>, char>) {
return f * static_cast<std::uint64_t>(l - '0');
}
else if constexpr (std::is_same_v<std::remove_cvref_t<T1>, char> && std::is_same_v<std::remove_cvref_t<T2>, std::uint64_t>) {
return static_cast<std::uint64_t>(f - '0') * l;
}
else {
throw std::runtime_error("The parameter is of unknown type.");
}
});
std::uint64_t old = result.load();
while (true) {
if (old >= product || result.compare_exchange_weak(old, product)) {
break;
}
}
}
);
return result.load();
}
|
速度测试
针对不同长度的数据量进行测试,使用 1'000
字节长度(str_1k
)和 100'000'000
字节长度(str_100m
)的字符串进行测试。
在 Visual Studio 2022 最新版下测试结果如下:
1
2
3
4
5
6
7
8
9
10
11
12
| str_1k Product1: 755116655247360 103us
str_1k Product2: 755116655247360 122us 0.84426
str_1k Product3: 755116655247360 60us 1.71667
str_1k Product4: 755116655247360 313us 0.32907
str_1k Product5: 755116655247360 324us 0.31790
str_1k Product6: 755116655247360 182us 0.56593
str_100m Product1: 431883715732439040 11152558us
str_100m Product2: 431883715732439040 15882062us 0.70221
str_100m Product3: 431883715732439040 8334643us 1.33810
str_100m Product4: 431883715732439040 838828us 13.29541
str_100m Product5: 431883715732439040 1502017us 7.42505
str_100m Product6: 431883715732439040 1269302us 8.78637
|
由测试结果可知,在数据量较少时,仅 Product3
速度会超过纯循环的版本,当数据量提升到 100'000'000
字节时,并行版的函数会有速度提升。
参考链接