与动态规划相关的算法

时间:2013-06-06 15:34:19

标签: dynamic-programming

我正在尝试解决一个问题,即你会得到一个整数N,即  0< = N< = 150000.您还将获得一个包含整数的数组,数组长度最多为2000.

我希望获得最接近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

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

2 个答案:

答案 0 :(得分:0)

我的解决方案运行得非常快。我没有做严格的计时或内存检查。我的解决方案是递归的,虽然我没有看到如何让它动态:

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

    这为您提供了一个可能不完美的解决方案: 如果N = 18,数组= {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完全由{9,4}解决,其中12对象,所以我们用{9,4}代替12给我们{9,4,5}。

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

  6. 如果您未到达gap=0,但确实做了替换,请在步骤4中进行递归。
  7. 如果您在步骤4中没有进行替换,那么您就拥有了最佳解决方案,那就完成了。
  8. 我是用一个相当丑陋的C#来做的,不过如果你想要代码,我可以把它清理一下。

    编辑:添加了代码

    我试图将C#细节与特定功能联系起来。保持事物整个时间排序是没有必要的,我相信你可以减少ImproveOnGaps函数中的内存使用量。

    运行:

    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(),
        }
    
    
    }
    

答案 1 :(得分:0)

在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