我在周五的大部分时间里都试图解决这个问题:
从整数集合生成唯一的,未排序的集合列表。
[如果元素在原始集合中重复,则为了构建集合,它应被视为两个单独的元素]
我最终得到了以下内容,从而取得了正确的结果。我想知道是否有更有效的方法。
特别是,我的Shift()方法必须以更有效的形式存在于某处。我对按位操作不太熟悉......但也许它们适用于此处?
List<int[]> Unique(List<int> intList)
{
List<int[]> results = new List<int[]>();
bool[] toAdd = new bool[intList.Count];
toAdd[toAdd.Length - 1] = true;
int totalSets = (int)Math.Pow(2, intList.Count) - 1;
List<int[]> sets = new List<int[]>();
for (int i = 0; i < totalSets; i++)
{
int[] set = new int[toAdd.Count(p => p)];
int c = 0;
for (int j = toAdd.Length - 1; j >= 0; j--)
if (toAdd[j])
set[c++] = intList[j];
Shift(toAdd);
results.Add(set);
}
return results;
}
void Shift(bool[] array)
{
bool doShift = true;
for (int i = array.Length - 1; i >= 0; i--)
{
if (doShift)
array[i] = !array[i];
doShift = (doShift && !array[i]);
}
}
答案 0 :(得分:2)
从根本上说,时间复杂度总是为O(2 ^ n),因此你可以期望最好的是时间的不断改进。也就是说,您的实现可以简化为以下内容:
public static List<int[]> powerSetBit(int[] list) {
if (list == null)
throw new ArgumentNullException("list may not be null.");
if (list.Length > 31)
throw new ArgumentOutOfRangeException("list must have 31 or fewer items.");
List<int[]> results = new List<int[]>();
int count = 1 << list.Length;
for (int i = 0; i < count; i++) {
int subsetSize = 0;
for (int j = 0; j < list.Length && (1 << j) < count; j++)
if ((i & (1 << j)) > 0)
subsetSize++;
int[] subset = new int[subsetSize];
for (int j = 0, k = 0; j < list.Length && (1 << j) < count; j++)
if ((i & (1 << j)) > 0)
subset[k++] = list[j];
results.Add(subset);
}
return results;
}
对于大约20个项目的输入大小,现有实现平均需要771毫秒才能在我的机器上执行,简化的实现平均需要513毫秒才能在我的机器上执行。
如果您的目标是提高实现的可读性,您还可以使用稍慢的递归实现(示例测试用例平均为857毫秒)。
递归解决方案通过观察列表是功率集的一个元素来工作,并且列表中的每个功率集减去其中一个元素也是整个功率集的一部分。为了防止重复的集合,通过使用第二个参数仅考虑遍历树的左侧。
static class Program {
static void Main() {
List<List<int>> lists = powerSet(new List<int>() { 1, 2, 3, 4 });
foreach (List<int> list in lists)
Console.WriteLine("{{{0}}}", String.Join(", ", list));
}
public static List<List<int>> powerSet(List<int> list) {
if (list == null)
throw new ArgumentNullException("list may not be null.");
return powerSet(list, list.Count);
}
public static List<List<int>> powerSet(List<int> list, int j) {
if (list == null)
throw new ArgumentNullException("list may not be null.");
if (j < 0)
throw new ArgumentOutOfRangeException("j must be not be negative.");
List<List<int>> results = new List<List<int>>() { new List<int>(list) };
for (int i = 0; i < j; i++) {
int x = list[i];
list.RemoveAt(i);
results.AddRange(powerSet(list, i));
list.Insert(i, x);
}
return results;
}
}
答案 1 :(得分:0)
由于原始数组中的每个元素即使具有相同的值也被认为是唯一的,因此可以通过这种方式处理问题:对于数组中的每个元素,您有两种选择:包括它还是不包括它在集合中。因此,每个元素使解决方案的数量加倍,从而得到解决方案的总数:2 * 2 * 2 ... * 2 - 1 = 2 ^ n - 1.减1是因为您从解决方案中排除了空集。由于解的数量是2 ^ n - 1,因此算法的复杂度不能优于O(2 ^ n)。您可以做的唯一改进可能是以更紧凑或更容易理解的方式编写算法。
我认为以下代码更直接。我没有测试过,可能存在错误。
// Generate unique sets in the range [k..n) of the input arr. With elements
// from [0..k) already in the prefix.
void Unique(int[] arr, int k, List<int> prefix, List<int[]> solutions)
{
// Got a solution when we reached the end of array.
if (k == arr.Length)
{
if (prefix.Length > 0)
solutions.Add(prefix.ToArray());
return;
}
// Exclude arr[k]
Unique(arr, k + 1, prefix, solutions);
// Include arr[k]
prefix.Add(arr[k]);
Unique(arr, k + 1, prefix, solutions);
prefix.Remove(arr[k]); // Restore the prefix list
}
List<int[]> Unique(int[] arr)
{
List<int[]> solutions = new List<int[]>();
Unique(arr, 0, new List<int>(), List<int[]> solutions);
return solutions;
}