如何有效地找到数组中三元组和的最小差异?

How to efficiently find the minimal difference of sums of triplets in an array?

本文关键字:和的 三元组 有效地 数组      更新时间:2023-10-16

我有一个数字数组int arr[] = {4, 7, 8, 9, 2, 4, 7, 3, 5};我需要找到 3 个三胞胎(它们不需要是连续的(,其中它们(三胞胎(的总和差最小("最接近的总和"(。

澄清:每个数字可能只在原始数组中出现的次数出现(即{{4, 7, 8}, {9, 2, 4}, {7, **4**, 5}}是不行的,因为 4 在输入中只出现了两次。

您可以假定数组已排序。 三胞胎不必按连续顺序排列。

有什么想法吗?

答案并不简单。我们需要处理"组合"。请阅读此处。结果,我们可以得到大数字,这使得计算变得困难。

一些基础知识。三元组由 3 个值组成。源数组有 9 个值。我们希望得到满足特定条件的三胞胎。

如果我们查看一个有 9 位数字的数字,我们可以通过计算具有 9 个值的数组的所有排列来获得所有可能的三元组,并始终采用索引 0,1,2 和 3,4,5 和 6,7,8。然后我们会自动得到所有三胞胎。但也有许多双胞胎和不明显的不需要的三胞胎。总共 362880 种排列。现在的电脑也是可行的,没有问题。

我们将采用不同的方式,我们将计算实际组合,然后 9 选择 3 = 84。

有许多算法发布,如何生成所有组合,都基于相同的原理。我们将创建一个布尔数组,该数组由 k = 3 个值组成。然后我们构建这个布尔数组的所有排列。每个排列将始终具有 3 个 true 的布尔值。例:

000000111
000001011
000001101
. . .

所以,很容易理解。

对于布尔数组的所有这些排列,我们检查值在哪个位置为 true,并选择具有相同索引的源值。然后我们保证了三胞胎。我们将得到所有三胞胎。为

{ 4, 7, 8, 9, 2, 4, 7, 3, 5 }
-->
000000111 --> 7 3 5
000001011 --> 4 3 5
000001101 --> 4 7 5
. . .

这是一般机制。现在,接下来,我们应该从找到的 3 个三胞胎中选择 84 个不同的三胞胎。

分明手段,没有仓位是双打的。因此,原始数组中的所有位置都必须存在。我们可以通过将所有值与 2 个三元组的所有其他值进行比较来检查区别。而且,与 3 个三胞胎相似。

接下来,我们需要从已经找到的 84 个三元组中选择由 3 个三元组组成的所有组。这又是一种组合。因此,84 选择 3 = 95284 个可能的组。但是,如前所述,一组的3个三胞胎必须是不同的。

如果我们检查这一点,那么只剩下 280 组进行进一步评估。

(9 choose 3) * (6 choose 3) / 3! = 84 * 20 / 6 = 280

首先,我们选择一个三元组。 还剩下 6 个数字。然后我们从剩余的 3 个值中选择 6 个值,然后剩下 3 个值。但是,对于左三元组,我们有所有排列,所以,去掉排列并除以 3!= 6。

因为我们想找到特殊群,它们的和需要满足某个条件,所以我们计算组中所有三胞胎的总和。

对于距离,我们看总和。例如:如果我们有一个包含三元组和总和的组:

2 3 5-->10    7 4 7-->18    4 8 9-->21
10-------18---21
Distance 1: 8    18-10
Distance 2: 3    21-18
Dinstance overall=: 21-10 = 11       

因此,我们只需计算最大总和 - 最小和并调用此距离。我们为所有三胞胎组执行此操作。

然后我们对结果进行排序,最小距离将位于结果数据的开头。例如,我们将得到:

4 7 5-->16    7 8 2-->17    4 9 3-->16
Distance: 1

请注意。为了不与实数混淆,我们尽可能长时间地使用索引计算源数组。对于大多数计算,源数组数据完全无关紧要。只是为了计算总和,我们需要它们。

请参阅下面完整且注释良好的示例代码:

#include <iostream>
#include <algorithm>
#include <set>
#include <iterator>
#include <array>
#include <iomanip>
using Triplet = std::array<int, 3>;
// Constexpr function to calculate the factorial
constexpr unsigned long fact(unsigned int n) {
if (n == 0) return 1; else return n * fact(n - 1);
};
// Constexpr function to calculate n choose k, and here specifically n choose 3
constexpr unsigned long NCR3(int n) {
return fact(n) / ( 6 * fact(n - 3));
};
int main() {
// The source data
int arr[] = { 4, 7, 8, 9, 2, 4, 7, 3, 5 };
// Get the number of source data
constexpr size_t NumberOfSourceValues = sizeof(arr) / sizeof(arr[0]);
// For calculating the combinations, we build a bool array with 3 bools set to true
// and the rund all permutations for these 3 bools. So we will get all combinations
// of a bool array with 3 true values
std::array<bool, NumberOfSourceValues> selectors1{};
static_assert(NumberOfSourceValues > 3, "nError: Not enough source Valuesn");
selectors1[NumberOfSourceValues - 1] = true;
selectors1[NumberOfSourceValues - 2] = true;
selectors1[NumberOfSourceValues - 3] = true;
// This is the result of 9 choose 3. It is 84. We will get that number of combinations
constexpr size_t NumberOfTriplets = NCR3(NumberOfSourceValues);
// Here we will store the 84 (9 choose 3) resulting combinations
static std::array<Triplet, NumberOfTriplets> triplets{}; // Static --> Put on heap
// Counter and index for storing the result
size_t permutationCounter{};
do {
Triplet triplet{};  // Temporary triplet
size_t indexInTriplet{};
// Go through all bool values in our bool array
for (size_t indexInBoolArray{}; indexInBoolArray < NumberOfSourceValues; ++indexInBoolArray)
// If a bool is set in the bool array, then copy the INDEX of our indicesArray
if (selectors1[indexInBoolArray]) triplet[indexInTriplet++] = indexInBoolArray;;
// So, we found 3 indices (Guaranteed, because 3 bools are true always). Copy that to our result
triplets[permutationCounter++] = std::move(triplet);
// Calculate the next permutation
} while (std::next_permutation(selectors1.begin(), selectors1.end()));
// Array for goups of 3 triplets that are distinct (have no already used number)
constexpr size_t NumberOfTripletGoups = NCR3(9) * NCR3(6) / 6;  // 6 = fact(3)
static std::array<std::array<Triplet, 3>, NumberOfTripletGoups> distinctTripletGroups{}; // Static --> Put on heap
// We want to again calculate combinations, n chooes k
// So, we will have an array of 84 bools with the last 3 values true
static std::array<bool, NumberOfTriplets> selectors2{};
static_assert(NumberOfTriplets > 3, "nError: Not enough triplets foundn");
selectors2[NumberOfTriplets - 1] = true;
selectors2[NumberOfTriplets - 2] = true;
selectors2[NumberOfTriplets - 3] = true;
// Please note: this loop will run 84 choose 3: 95384 times
// But we will only use 280 distinct values
size_t groupCounter{};
do {
std::array<Triplet, 3> tripletGroup{};
size_t k{};
for (size_t i{}; i < NumberOfTriplets; ++i) {
if (selectors2[i]) {
tripletGroup[k++] = triplets[i];
}
}
// Check for distinct (not double) values in all 3 triplets of a triplet group.
// Compare every value with all other values
bool distinct = true;
for (size_t ii{}; distinct && (ii < 3); ++ii) for (size_t kk{}; distinct && (kk < 3); ++kk) {
distinct = distinct && (tripletGroup[0][ii] != tripletGroup[1][kk]);
distinct = distinct && (tripletGroup[0][ii] != tripletGroup[2][kk]);
distinct = distinct && (tripletGroup[1][ii] != tripletGroup[2][kk]);
}
// If the triplets are distinct, then we add the triplet group to the result
if (distinct) {
distinctTripletGroups[groupCounter++] = (std::move(tripletGroup));
}
// Next triplet goup selection
} while (std::next_permutation(selectors2.begin(), selectors2.end()));

// Holding the result of our distance calculations
struct DistanceData {
size_t threeTripletsIndex{};        // The index of the triplet group. Is in the begiining 0,1,2,3,4,5
int distanceForThreeTriplets{};     // Distance of Triplets in triplet group
std::array<int, 3> tripletSums{};   // Sums of single triplets in a group
};
static std::array<DistanceData, NumberOfTripletGoups> distanceData{}; // Static --> Put on heap
// Calculate the distance data. Simply subtract the min sum of a triplet from the max sum of a triplet for one triplet-group
for (size_t tripletGroup{}; tripletGroup < distinctTripletGroups.size(); ++tripletGroup) {
for (size_t tripletIndex{}; tripletIndex < 3; ++tripletIndex)
for (size_t indexInTriplet{}; indexInTriplet < 3; ++indexInTriplet)
// Calculate the sum of one single triplet
distanceData[tripletGroup].tripletSums[tripletIndex] += arr[distinctTripletGroups[tripletGroup][tripletIndex][indexInTriplet]];
// Calculate the distannce for the three triplets
distanceData[tripletGroup].distanceForThreeTriplets = std::max(std::max(distanceData[tripletGroup].tripletSums[0], distanceData[tripletGroup].tripletSums[1]), distanceData[tripletGroup].tripletSums[2]) -
std::min(std::min(distanceData[tripletGroup].tripletSums[0], distanceData[tripletGroup].tripletSums[1]), distanceData[tripletGroup].tripletSums[2]);
// And the index (Just neded for sorting later). Is alwyas equal to running loop variable
distanceData[tripletGroup].threeTripletsIndex = tripletGroup;
}
// Sort result with distances, sum, and three-triplet index
std::sort(distanceData.begin(), distanceData.end(), [](const DistanceData& d1, const DistanceData& d2) { return d1.distanceForThreeTriplets < d2.distanceForThreeTriplets; });
// Show pretty printed output to user
for (size_t tripletGroup{}; tripletGroup < distinctTripletGroups.size(); ++tripletGroup) {
// Show the distance for 3 found triplets
std::cout << std::right << std::setw(5) << tripletGroup + 1 << ".  Distance: " << std::setw(2) << distanceData[tripletGroup].distanceForThreeTriplets << 't';
// For each triplet in the set of 3 triplets
for (size_t tripletIndex{}; tripletIndex < 3; ++tripletIndex) {
// For each value of one single triplet
for (size_t indexInTriplet{}; indexInTriplet < 3; ++indexInTriplet) {
std::cout << arr[distinctTripletGroups[distanceData[tripletGroup].threeTripletsIndex][tripletIndex][indexInTriplet]] << " ";
}
// Show the sum of 1 triplet
std::cout << "[" << std::setw(2) << distanceData[tripletGroup].tripletSums[tripletIndex] << "]t";
}
std::cout << "n";
}
return 0;
}

所有数组大小都可以是编译时常量。

不需要动态内存处理。