假设我有一个
下的数组array[] = {14,10,20,4}
这个数组可以分为子集{14,10}和{20,4},因为这两个具有相等的总和。
另一个例子可能是
array[] = {5, 5, 15, 5}
这个数组可以分为子集{5,5,5}和{15},因为这两个数组的总和相等。
但是
array[] = {1, 5, 30}
不能分成相同的集合,因为它们永远不会相等。
我们怎么能这样做?
我的镜头
var result = string.Empty;
int highestNumber = input1[0];
int sum = 0;
string set = string.Empty;
foreach (int value in input1)
if (value > highestNumber) highestNumber = value;
for (int i = 0; i < input1.Length; i++)
{
var elements = input1[i];
if (elements > 0)
{
if (elements != highestNumber)
{
sum += elements;
set += "," + elements;
}
}
}
if (sum == highestNumber)
result = "Yes it can be partitioned"
else
result = "No it cannot be";
Console.WriteLine(result);
它不适用于第一个。
如何做这个程序?
答案 0 :(得分:4)
您希望将给定的int数组分区为2个分区,其中分区的总和相等。
方法可能是:
以下是代码:
class Program
{
static void Main(string[] args)
{
var arrays = new int[][] {
new[]{ 1, 12, 10, 2, 23 },
new[] {14,10,20,4},
new[] {5, 5, 15, 5},
new[] {1, 5, 30}
};
foreach (var array in arrays)
{
Console.WriteLine(String.Format("Results for {0}:", string.Join(",", array)));
IEnumerable<IEnumerable<int>> partitions = Partition(array);
if (!partitions.Any())
Console.WriteLine("Cannot be partitioned.");
else
foreach (var item in partitions)
{
Console.WriteLine(string.Join(",", item));
}
Console.WriteLine();
}
Console.ReadKey();
}
static IEnumerable<IEnumerable<int>> Partition(int[] array)
{
var sum = array.Sum();
if ((sum % 2) == 1) return Enumerable.Empty<IEnumerable<int>>();
return Subsequences(array).Where(ss => ss.Sum(item => item) == (sum / 2));
}
// http://stackoverflow.com/a/3750709/201088
private static IEnumerable<IEnumerable<T>> Subsequences<T>(IEnumerable<T> source)
{
if (source.Any())
{
foreach (var comb in Subsequences(source.Skip(1)))
{
yield return comb;
yield return source.Take(1).Concat(comb);
}
}
else
{
yield return Enumerable.Empty<T>();
}
}
}
输出:
Results for 1,12,10,2,23:
12,10,2
1,23
Results for 14,10,20,4:
14,10
20,4
Results for 5,5,15,5:
15
5,5,5
Results for 1,5,30:
Cannot be partitioned.
答案 1 :(得分:1)
我在C#bool[]
中创建了一个基于比特数组的解决方案。在我展示解决方案之前,我首先必须为此bool[]
添加一些扩展方法。这些是通用功能,可以在其他解决方案中使用。它位于这个答案的底部。
正如我所说,它基于bitset。 bitset将具有需要组的数字数组的长度。 bitset将填充零和一。每个位对应一个数字。因此,如果数字数组有5个元素,则bitset也将有5位。
如果位为0
,则该号码将放在group0
中。如果该位为1
,则该号码将被放入group1
。当两个组具有相同的总和时,工作就完成了。
重复该循环直到分析所有组合。 bitset将以数值op 1开头(如果是5个数字:00001
)。它将增加每次迭代(因此下一次交互将为00010
,然后是00011
,然后是00100
等。)因此更改位集的位并重新组合数字。
请注意:我创建此方法时只考虑其功能性。我不介意任何性能问题。当然,这个例程可以更快(即不是每次迭代都创建新列表等),但这会使代码的可读性降低。我尝试使此代码尽可能可读。
static public bool Partition(IList<int> numbers, out IList<int> group0, out IList<int> group1)
{
if (numbers == null)
throw new ArgumentNullException("numbers");
bool[] groupPerNumber = new bool[](numbers.Count);
while (true)
{
groupPerNumber.Increase(); // Increate the numberic value of the array of bits.
if (groupPerNumber.IsEmpty()) // If all bits are zero, all iterations have been done.
break;
// Create the new groups.
group0 = new List<int>();
group1 = new List<int>();
// Fill the groups. The bit in the groups-array determains in which group a number will be place.
for (int index = 0; index < numbers.Count; index++)
{
int number = numbers[index];
if (!groupPerNumber[index])
group0.Add(number);
else
group1.Add(number);
}
// If both sums are equal, exit.
if (group0.Sum() == group1.Sum())
return true;
}
group0 = group1 = null;
return false;
}
布尔扩展数组
static public class BoolArrayExtensions
{
/// <summary>
/// Treats the bits as a number (like Int32) and increases it with one.
/// </summary>
static public void Increase(this bool[] bits)
{
for (int i = 0; i < bits.Length; i++)
{
if (!bits[i])
{
bits[i] = true;
bits.SetAll(0, i - 1, false);
return;
}
bits[i] = false;
}
bits.SetAll(false);
}
/// <summary>
/// Sets the subset of bits from the start position till length with the given value.
/// </summary>
static public void SetAll(this bool[] bits, int start, int length, bool value)
{
while(length-- > 0)
bits[start++] = value;
}
/// <summary>
/// Returns true if all bits in the collection are false.
/// </summary>
static public bool IsEmpty(this bool[] bits)
{
for (int i = 0; i < bits.Length; i++)
if (bits[i])
return false;
return true;
}
}
修改强>
我创建了另一个这样的解决方案并将其放在codereview上。它基本上是相同的功能,但这次优化了表格速度。感兴趣吗?看看:https://codereview.stackexchange.com/questions/25980/performance-devide-group-of-numbers-into-two-groups-of-which-the-sums-are-equal
答案 2 :(得分:0)
计算数组的总和并除以2。现在你知道你的集合所需的标准(总和值)。
根据您的性能要求和预期尺寸,您可以使用简单的回溯算法来消除所有可能性。由于您知道集合必须具有的确切总和,因此可以显着限制搜索。
答案 3 :(得分:-1)
首先,您可以从一个方法开始,以获取列表中项目的组合。通过为数字创建组合然后将其从数字转换为集合的索引,可以轻松解决这个问题:
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IList<T> source, int n)
{
return Combinations(source.Count, n)
.Select(combination => combination.Select(i => source[i]));
}
public static IEnumerable<IEnumerable<int>> Combinations(int n, int p)
{
//this is logically treated as a stack except that when enumerated it's enumerated as the
//reverse of a stack, which is desirable. There is no efficient way to get the reverse iterator
//of System.Collections.Stack so easily. All mutations are to the end of the list.
var stack = Enumerable.Range(0, p).ToList();
while (stack[stack.Count - 1] < n)
{
//the ToList can be removed if previous sub-sequences won't be iterated
//after the outer sequence moves to the next item.
yield return stack.ToList();
int lastValue = stack.Pop();
while (stack.Any() && lastValue + 1 > n - (p - stack.Count))
{
lastValue = stack.Pop();
}
while (stack.Count < p)
{
lastValue += 1;
stack.Add(lastValue);
}
}
}
//simple helper method to get & remove last item from a list
public static T Pop<T>(this List<T> list)
{
T output = list[list.Count - 1];
list.RemoveAt(list.Count - 1);
return output;
}
使用它创建一个方法来获取集合的所有组合,而不仅仅是特定大小的组合是一件简单的事情:
public static IEnumerable<IEnumerable<T>> AllCombinations<T>(this IList<T> source)
{
IEnumerable<IEnumerable<T>> output = Enumerable.Empty<IEnumerable<T>>();
for (int i = 1; i < source.Count; i++)
{
output = output.Concat(Combinations(source, i));
}
return output;
}
现在我们能够获得列表的所有组合,我们可以使用LINQ完成工作:
List<int> list = new List<int>() { 5, 5, 15, 5 };
var groups = list.AllCombinations()
.GroupBy(combination => combination.Sum());
使用它,您现在拥有总和相同值的所有组合组。