我有一个运行很快的解决方案。不过,我没有进行严格的时间或内存检查。我的解决方案是递归的,尽管我不知道如何使它成为动态的:
- 在数组中找到小于 N 的最大数,将其添加到子集中
递归第 1 步,从 N 中减去刚刚添加的数字
这为您提供了一个可能不完美的解决方案:
如果 N = 18, Array = {12, 9, 8, 5, 4},您将得到子集答案 {12, 5} 而不是 {9, 5, 4}。你可以说这个解决方案中的“差距”是gap = 1
.
对于子集的每个成员m
,您将再次求解,将 N 设置为m + gap
,并将 Array 设置为原始 Array 的成员,不包括子集的所有成员。在我们的示例中,我们将产生另外两个问题:N = 13, Array = {9, 8, 4} 和 N = 6, Array = {9, 8, 4}。
采用上一步提供的最佳解决方案,由差距缩小确定。如果最佳解决方案中的差距小于较大问题中的差距,则将目标数字替换为子集。在我们的例子中,N = 13 可以通过以 12 为目标的 {9, 4} 完美解决,因此我们将 12 替换为 {9, 4},从而得到 {9, 4, 5}。
如果gap=0
对于这个子问题,我们就完成了。
- 如果您没有达到
gap=0
,但确实做了替换,请递归第 4 步。
- 如果您没有在第 4 步中进行更换,那么您就有了最好的解决方案,您就完成了。
我在一个相当丑陋的 C# 中完成了它,但如果你想要代码,我可以稍微清理一下。
编辑:添加代码
我试图将 C# 细节与特定功能隔离开来。没有必要一直对事物进行排序,而且我相信您可以在 ImprovementOnGaps 函数中减少内存使用量。
跑步:
void Main()
{
Problem p = Solvers.GenerateRandomProblem();
Solution imperfectSolution = Solvers.SolveRecursively(p);
Solution bestPossibleSolution = Solvers.ImproveOnGaps(s);
}
class Solution
{
public Problem Problem;
public int[] NumbersUsed;
public int n;
public int[] NumbersUnused;
}
class Problem
{
public int N;
public int[] Array;
}
class Solvers
{
public static Problem GenerateRandomProblem()
{
Random r = new Random();
int N = r.Next(1500000);
int arraySize = r.Next(1, 2000);
int[] array = new int[arraySize];
for(int i = 0; i < arraySize; i++)
array[i] = r.Next(1, 15000);
Problem problem = new Problem
{
N = N,
Array = array
};
return problem;
}
public static Solution SolveRecursively(Problem p)
{
return SolveRecursively( new Solution
{
Problem = p,
n = 0,
NumbersUnused = SortAscending(p.Array),
NumbersUsed = new int[0]
});
}
private static Solution SolveRecursively(Solution s)
{
if(s.n == s.Problem.N)
return s;
for(int i = s.NumbersUnused.Length - 1; i >= 0; i--) //
{
if(s.n + s.NumbersUnused[i] <= s.Problem.N)
{
return SolveRecursively(new Solution
{
n = s.n + s.NumbersUnused[i],
NumbersUnused = SkipIthPosition(s.NumbersUnused, i),
NumbersUsed = AddToSortedArray(s.NumbersUsed, s.NumbersUnused[i]),
Problem = s.Problem
});
}
}
return s;
}
public static Solution ImproveOnGaps(Solution s)
{
if(s.n == s.Problem.N)
return s;
int gap = s.Problem.N - s.n;
List<Problem> newProblems = new List<Problem>();
foreach (int i in s.NumbersUsed)
{
newProblems.Add(new Problem
{
Array = s.NumbersUnused,
N = i + gap
});
}
int newGap = gap;
Solution bestImprovement = null;
foreach (Problem p in newProblems)
{
Solution tempSolution = SolveRecursively(p);
if(tempSolution.Problem.N - tempSolution.n < newGap)
bestImprovement = tempSolution;
}
if(bestImprovement != null)
{
List<int> usedNumbers = s.NumbersUsed.ToList();
usedNumbers.Remove(bestImprovement.Problem.N - gap);
usedNumbers.AddRange(bestImprovement.NumbersUsed);
List<int> unusedNumbers = s.NumbersUnused.ToList();
foreach (int i in bestImprovement.NumbersUsed)
unusedNumbers.Remove(i);
return ImproveOnGaps(new Solution
{
n = usedNumbers.Sum(),
NumbersUnused = unusedNumbers.ToArray(),
NumbersUsed = usedNumbers.ToArray(),
Problem = s.Problem
});
}
return s;
}
private static int[] SortAscending(int[] array)
{
return array.OrderBy(i => i).ToArray();
}
private static int[] SkipIthPosition(int[] array, int i)
{
return array.Take(i)
.Union(array.Skip(i + 1).Take(array.Length - 1 - i))
.ToArray();
}
private static int[] AddToSortedArray(int[] array, int i)
{
return array.Concat(new int[] { i }).OrderBy(d => d).ToArray(),
}
}