Если запрашиваемая сумма не будет такой большой; и все элементы положительны (ноль допускается), и вы не возражаете много о памяти, вы можете использовать следующий подход:
public static IEnumerable<int> GetSumList(this IEnumerable<int> values, int sum) {
List<int>[] sets = new List<int>[sum+1];
sets[0] = new List<int>();
foreach(int xi in values) { //O(N*sum*N) with N the number of items
List<int> f0, f1, f2;
for(int i = sum-xi; i >= 0; i--) { //O(N*sum)
f0 = sets[i];
f1 = sets[i+xi];
if(f0 != null && (f1 == null || f0.Count < f1.Count-1)) {
f2 = new List<int>(f0);//make clone, O(N)
f2.Add(xi);
sets[i+xi] = f2;
}
}
}
return sets[sum];
}
(если не существует такой суммы, null
возвращается; этот алгоритм также вернет самый маленький поднабор, если таковой имеется).
Алгоритм использует динамическое программирование и рассматривает список комплектов. Каждый с отличной суммой . Вы можете сделать это немного быстрее, если вы используете связанный список вместо List<int>
s при расчете временных значений, поскольку это позволяет (совместное) клонирование и вставки в голове в O (1).
Demo (с csharp
интерактивной оболочки):
csharp> List<int> prices= new List<int>(new int[] { 1, 2, 3, 4, 5 });
csharp> prices.GetSumList(4);
{ 4 }
csharp> prices.GetSumList(6);
{ 2, 4 }
csharp> prices.GetSumList(7);
{ 3, 4 }
csharp> prices.GetSumList(8);
{ 3, 5 }
csharp> prices.GetSumList(9);
{ 4, 5 }
csharp> prices.GetSumList(10);
{ 2, 3, 5 }
csharp> prices.GetSumList(11);
{ 2, 4, 5 }
csharp> prices.GetSumList(12);
{ 3, 4, 5 }
csharp> prices.GetSumList(13);
{ 1, 3, 4, 5 }
csharp> prices.GetSumList(14);
{ 2, 3, 4, 5 }
csharp> prices.GetSumList(15);
{ 1, 2, 3, 4, 5 }
csharp> prices.GetSumList(16);
null
Что вы пытались? –
Сначала найдите все перестановки ([это было решено до] (http://stackoverflow.com/questions/15470672/finding-possible-combinations-linq)), затем отфильтруйте перестановки с теми, где сумма равна 4. –
@DStanley : это экспоненциальный подход, вы можете сделать это более эффективным с динамическим программированием ... –