Length = input Long(can be 2550, 2880, 2568, etc)
List<long> = {618, 350, 308, 300, 250, 232, 200, 128}
程序需要一个很长的值,对于那个特定的长值,我们必须从上面的列表中找到可能的组合,当添加时给我一个输入结果(相同的值可以使用两次)。可能存在+/- 30的差异。
最多必须使用最大数字。
Ex:长度= 868 对于这种组合可以是
组合1 = 618 + 250
组合2 = 308 + 232 + 200 + 128
正确组合将组合1
但也应该有不同的组合。
public static void Main(string[] args)
{
//subtotal list
List<int> totals = new List<int>(new int[] { 618, 350, 308, 300, 250, 232, 200, 128 });
// get matches
List<int[]> results = KnapSack.MatchTotal(2682, totals);
// print results
foreach (var result in results)
{
Console.WriteLine(string.Join(",", result));
}
Console.WriteLine("Done.");
}
internal static List<int[]> MatchTotal(int theTotal, List<int> subTotals)
{
List<int[]> results = new List<int[]>();
while (subTotals.Contains(theTotal))
{
results.Add(new int[1] { theTotal });
subTotals.Remove(theTotal);
}
if (subTotals.Count == 0)
return results;
subTotals.Sort();
double mostNegativeNumber = subTotals[0];
if (mostNegativeNumber > 0)
mostNegativeNumber = 0;
if (mostNegativeNumber == 0)
subTotals.RemoveAll(d => d > theTotal);
for (int choose = 0; choose <= subTotals.Count; choose++)
{
IEnumerable<IEnumerable<int>> combos = Combination.Combinations(subTotals.AsEnumerable(), choose);
results.AddRange(from combo in combos where combo.Sum() == theTotal select combo.ToArray());
}
return results;
}
public static class Combination
{
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int choose)
{
return choose == 0 ?
new[] { new T[0] } :
elements.SelectMany((element, i) =>
elements.Skip(i + 1).Combinations(choose - 1).Select(combo => (new[] { element }).Concat(combo)));
}
}
我已经使用了上面的代码,它可以更简化,再次在这里我也得到了独特的价值观。值可以使用任意次数。但最重要的数字必须得到最优先考虑。
我有一个验证来检查总和是否大于输入值。逻辑在那里失败了..
答案 0 :(得分:0)
您显示的算法假定列表按升序排序。如果没有,那么您首先必须在O(nlogn)时间内对列表进行排序,然后执行算法。
此外,它假设您只考虑对的组合,并在第一场比赛时退出。 如果要查找所有组合,则只需输出组合并增加startIndex或减少endIndex,而不是“break”。
此外,您应该检查范围(targetSum - 30到targetSum + 30)而不仅仅是确切的值,因为问题表明允许误差范围。
根据我的说法,这是最好的解决方案,因为它的复杂性是O(nlogn + n),包括排序。
答案 1 :(得分:0)
V4 - 递归方法,在线程上使用堆栈结构而不是堆栈帧
它工作(在VS中测试),但可能存在一些错误。
static int Threshold = 30;
private static Stack<long> RecursiveMethod(long target)
{
Stack<long> Combination = new Stack<long>(establishedValues.Count); //Can grow bigger, as big as (target / min(establishedValues)) values
Stack<int> Index = new Stack<int>(establishedValues.Count); //Can grow bigger
int lowerBound = 0;
int dimensionIndex = lowerBound;
long fail = -1 * Threshold;
while (true)
{
long thisVal = establishedValues[dimensionIndex];
dimensionIndex++;
long afterApplied = target - thisVal;
if (afterApplied < fail)
lowerBound = dimensionIndex;
else
{
target = afterApplied;
Combination.Push(thisVal);
if (target <= Threshold)
return Combination;
Index.Push(dimensionIndex);
dimensionIndex = lowerBound;
}
if (dimensionIndex >= establishedValues.Count)
{
if (Index.Count == 0)
return null; //No possible combinations
dimensionIndex = Index.Pop();
lowerBound = dimensionIndex;
target += Combination.Pop();
}
}
}
可能是V3 - 对有序解决方案尝试每种组合的建议
虽然这不是相关问题的答案,但我认为这是一个很好的方法 - https://stackoverflow.com/a/17258033/887092(否则你可以尝试选择的答案(虽然输出只有2个项目)设置总和,而不是最多n项)) - 它将枚举每个选项,包括相同值的倍数。 V2工作但效率略低于有序解决方案,因为可能会多次尝试同样的失败尝试。
V2 - 随机选择 - 将能够重复使用相同的数字
我喜欢随机使用“智能”,允许计算机暴力破解解决方案。它也很容易分发 - 例如,两个线程之间没有状态依赖性,例如同时尝试。
static int Threshold = 30;
public static List<long> RandomMethod(long Target)
{
List<long> Combinations = new List<long>();
Random rnd = new Random();
//Assuming establishedValues is sorted
int LowerBound = 0;
long runningSum = Target;
while (true)
{
int newLowerBound = FindLowerBound(LowerBound, runningSum);
if (newLowerBound == -1)
{
//No more beneficial values to work with, reset
runningSum = Target;
Combinations.Clear();
LowerBound = 0;
continue;
}
LowerBound = newLowerBound;
int rIndex = rnd.Next(LowerBound, establishedValues.Count);
long val = establishedValues[rIndex];
runningSum -= val;
Combinations.Add(val);
if (Math.Abs(runningSum) <= 30)
return Combinations;
}
}
static int FindLowerBound(int currentLowerBound, long runningSum)
{
//Adjust lower bound, so we're not randomly trying a number that's too high
for (int i = currentLowerBound; i < establishedValues.Count; i++)
{
//Factor in the threshold, because an end aggregate which exceeds by 20 is better than underperforming by 21.
if ((establishedValues[i] - Threshold) < runningSum)
{
return i;
}
}
return -1;
}
V1 - 有序选择 - 将无法重复使用相同的数字
添加这个非常方便的扩展功能(使用二进制算法查找所有组合):
//Make sure you put this in a static class inside System namespace
public static IEnumerable<List<T>> EachCombination<T>(this List<T> allValues)
{
var collection = new List<List<T>>();
for (int counter = 0; counter < (1 << allValues.Count); ++counter)
{
List<T> combination = new List<T>();
for (int i = 0; i < allValues.Count; ++i)
{
if ((counter & (1 << i)) == 0)
combination.Add(allValues[i]);
}
if (combination.Count == 0)
continue;
yield return combination;
}
}
使用功能
static List<long> establishedValues = new List<long>() {618, 350, 308, 300, 250, 232, 200, 128, 180, 118, 155};
//Return is a list of the values which sum to equal the target. Null if not found.
List<long> FindFirstCombination(long target)
{
foreach (var combination in establishedValues.EachCombination())
{
//if (combination.Sum() == target)
if (Math.Abs(combination.Sum() - target) <= 30) //Plus or minus tolerance for difference
return combination;
}
return null; //Or you could throw an exception
}
测试解决方案
var target = 858;
var result = FindFirstCombination(target);
bool success = (result != null && result.Sum() == target);
//TODO: for loop with random selection of numbers from the establishedValues, Sum and test through FindFirstCombination