【问题标题】:Algorithm related to Dynamic Programming动态规划相关算法
【发布时间】:2013-06-06 15:34:19
【问题描述】:

我正在尝试解决一个问题,即你将得到一个整数 N,它是 0

我想得到最接近 N 或完全等于 N 的数组子集的总和。问题指出总和应该完全等于 N,但是如果没有子集可以完全达到 N,所以我们应该带上最接近但小于 N 的。例如:

N = 11 和 Array = { 2 , 3 , 5 , 7 } 在这种情况下输出应该是 10
N = 12 和 Array = { 4 , 6 , 9 } 在这种情况下输出应该是 10
N = 10 和 Array = { 2 , 3 , 3 , 10 } 在这种情况下输出应该是 10

我试图用所有排列来解决这个问题,但它给了我时间限制,因为输入约束很高。我尝试使用动态编程,但二维数组存储给出的内存限制超过了mem[150001][2001]。我尝试在 [150001][2] 中这样做,因为提到了一些关于 DP 的教程,但我做不到。任何帮助将不胜感激。

【问题讨论】:

  • 和我刚刚回答的this question类似。我使用 DP,但不需要二维数组。一维就够了。
  • 当然,您可以将检查限制为组合而不是排列,这将减少您的时间和内存使用量。
  • 您(需要)使用什么语言?或者你需要伪代码?给定的数组总是排序的吗?如果没有,请尝试对其进行排序,这很有帮助:)
  • Java :) 或任何类似的语言
  • 这个问题的第一部分(确定是否存在精确的和)是 NP 完全子集和问题。一般情况的有效解决方案将是一个重大发现,但对于特殊情况有一些很好的算法。请参阅维基百科文章 [en.wikipedia.org/wiki/Subset_sum_problem] 开始。

标签: dynamic-programming


【解决方案1】:

我有一个运行速度非常快的解决方案。不过,我没有进行严格的时间或内存检查。我的解决方案是递归的,虽然我不知道如何使它动态:

  1. 在数组中找到小于 N 的最大数,将其添加到子集中
  2. 递归第 1 步,从 N 中减去刚刚添加的数字

    这给了你一个可能不完美的解决方案: 如果 N = 18, Array = {12, 9, 8, 5, 4},您将得到子集答案 {12, 5} 而不是 {9, 5, 4}。你可以说这个解决方案中的“差距”是gap = 1

  3. 对于子集的每个成员 m,您将再次求解,将 N 设置为 m + gap,并将 Array 设置为原始 Array 的成员,不包括子集的所有成员。在我们的示例中,我们将产生另外两个问题:N = 13, Array = {9, 8, 4} 和 N = 6, Array = {9, 8, 4}。

  4. 采用上一步提供的最佳解决方案,由差距缩小确定。如果最佳解决方案中的差距小于较大问题中的差距,则将目标数字替换为子集。在我们的例子中,N = 13 可以通过以 12 为目标的 {9, 4} 完美解决,因此我们将 12 替换为 {9, 4},从而得到 {9, 4, 5}。

  5. 如果gap=0 处理这个子问题,我们就完成了。

  6. 如果您没有联系到 gap=0,但确实进行了替换,请递归第 4 步。
  7. 如果您没有在第 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(),
    }


}

【讨论】:

  • 我跟踪了它,我发现它在我的示例中运行良好,将尝试它是否适用于所有情况并且不会超过时间限制,如果您可以发布您的代码,我会谢谢,谢谢
  • 我发现了一个缺陷:N = 51, Array = {25, 24, 18, 17, 16}。当正确答案是 {18, 17, 16} 时,我的算法返回 {25, 24}。不知道有什么办法。
【解决方案2】:

在 WumpusQ 发布的链接的帮助下,我想我得到了一些有用的东西。基本上我使用链接中的 DP 方法,然后从 N 开始向后查找有效总和并返回遇到的第一个。 (在 Python 中)

from collections import defaultdict

def dpFunc(N, Array):
  # determine range of possible values
  minSum = reduce(lambda x, y: x+y, [x for x in Array if x < 0], 0)
  maxSum = reduce(lambda x, y: x+y, [x for x in Array if x > 0], 0)
  # Initialize
  Q = defaultdict(lambda: False)
  for s in xrange(minSum, maxSum + 1):
    Q[(0,s)] = (Array[0] == s)
    for i in xrange(1, len(Array)):
      Q[(i,s)] = Q[(i-1,s)] or (Array[i] == s) \
          or Q[(i-1,s-Array[i])]
  for s in xrange(N, minSum -1, -1):
    if (Q[(len(Array)-1,s)]):
      return s

【讨论】:

  • 感谢您的帮助,但不幸的是我对 python 不熟悉:/
猜你喜欢
  • 2012-03-26
  • 2013-11-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-28
相关资源
最近更新 更多