【问题标题】:Dynamic Programing approach for a subset sum子集和的动态规划方法
【发布时间】:2015-05-06 05:09:14
【问题描述】:

给定以下输入

10 4 3 5 5 7

在哪里

10 = Total Score

4 = 4 players

3 = Score by player 1

5 = Score by player 2

5 = Score by player 3

7 = Score by player 4

我要打印综合得分加到总分中的球员,这样输出就可以 1 4 因为玩家 1 + 玩家 4 得分 = 3 + 7 -> 10 或者输出可以是 2 3 因为玩家 2 + 玩家 3 得分 = 5 + 5 -> 10

所以它与子集和问题非常相似。我对动态编程比较陌生,但是在获得有关 stackoverflow 的帮助并在线阅读动态编程教程并在线观看了过去 3 天的一些视频之后。到目前为止,我提供了以下代码。

class Test
{
    public static void main (String[] args) throws java.lang.Exception
    {
        int[] test = {3,5,5,7};
        getSolution(test,4,10);
    }

    //pass total score, #of players (size) and the actual scores by each player(arr)
    public static int getSolution(int[] arr,int size, int total){


        int W = total;
        int n = size;
        int[][] myArray = new int[W+1][size+1];

        for(int i = 0; i<size+1; i++)
        {
            myArray[i][0] = 1;
        }
        for(int j =1; j<W+1; j++)
        {
            myArray[0][j] = 0;
        }

        for(int i =1; i<size+1; i++)
        {
            for(int x=1; x<W+1; x++)
            {
                if(arr[i] < x)
                {
                    myArray[i][x] = myArray[i-1][x];
                }
                else
                {
                    myArray[i][x] = myArray[i-1][x-arr[i]];
                }
            }
        }

        return myArray[n][W];
    }

}

由于某种原因,我没有得到预期的结果。在过去的 7 个多小时里,我一直试图在这个问题中找到错误,但没有成功。如果有人可以帮助解决问题以获得所需的结果,我将不胜感激。

另外请原谅我的英语不是我的母语。

更新 我也不需要打印所有可能的等于分数的组合。我可以打印任何等于分数的组合,就可以了。

【问题讨论】:

  • main 调用的大小为 3,而不是 4。这是有意的吗?
  • 第一:你的英语很棒!其次,我们是否受限于只考虑成对的学生分数? IE。如果输入是10 2 3 4 1 4,我们可以结合2 3 4 1得到10吗? (4 名学生)
  • 错误@NathanTuggy 哎呀刚刚意识到......我会马上修复。
  • @AndyG 谢谢!是的,您可以根据需要组合任意数量的玩家的分数,只要他们加起来就是总分
  • @user2733436:恐怕你的问题子集总和。最坏情况精确算法仍然具有指数时间复杂度。 IE。作为初学者,您最好尝试生成每个可能的学生子集,然后评估该子集的总和。

标签: java algorithm recursion dynamic-programming subset


【解决方案1】:

我可能会尝试一些事情。

首先,您传入一个包含 4 个值的数组,但后来您说只有三个玩家。我认为这给你带来了一些困难。

对于我能想到的最快的编程方式,我可以试试这个。

public static void getSolution(int[] array, int desiredTotal) {
    for (int firstIndex = 0; firstIndex < array.size - 1; ++firstIndex) {
        getSolutionWith(array, desiredTotal, firstIndex);
    }
}

public static void getSolutionWith(int[] array, int desiredTotal, int firstIndex) {
    int lookFor = desiredTotal - array[firstIndex];
    for (int secondIndex = firstIndex + 1; secondIndex < array.size; ++secondIndex) {
        if (array[secondIndex] == lookFor) {
            System.out.printf("%d %d\n", firstIndex + 1, secondIndex + 1);
        }
    }
}

我没有测试过这段代码,所以它可能并不完美。基本上,您从 0 位置(第 1 个人)开始,然后查看其他所有人,看看第一个人的价值 + 第二个人的价值是否等于您的总和。如果是这样,您打印它们。

【讨论】:

  • 这仅在可以使用大小为 2 的子集发现解决方案时才有效。遵循 OP 的 cmets,任何大小的子集都是可能的。 (所以,这是实际的子集和问题)
  • 我刚刚更新了我的问题,子集可以是任何大小,而且我不需要打印等于分数的所有可能组合我可以打印任何等于分数的组合。最高总分也可以是 1000,仅此而已。
  • 然后让lower方法递归,传入到目前为止的总和。
【解决方案2】:

你的问题出在这部分代码

            if(arr[i] < x)
            {
                myArray[i][x] = myArray[i-1][x];
            }
            else
            {
                myArray[i][x] = myArray[i-1][x-arr[i]];
            }

你有两种情况

  1. (if 内)我们已经找到了一个集合,在这种情况下你需要将之前的结果带到下一个。
  2. (inside else) 减去结果后变为假,但前一个结果为真。所以你需要携带这个结果。

为什么? [3, 34, 4, 12, 5, 2]

不要忘记 DP 具有 Optimal Substructure 属性的部分。因为,求总和是 9,我们必须找到它之前的所有总和,即 1 到 8。这正是您通过声明 W+1 行所做的。因此,当我们计算总和为 7 时,对于前三个值,我们有一个结果 [3,34,4],我们需要将该结果带到下一个级别。

所以你需要修改之前的代码,到这个

           myArray[i][x] = myArray[i-1][x];//carrying previous result
            if(x>=arr[i] )
            {
                if (myArray[i][x]==1){
                    myArray[i][x]=1; 
                }
                else{
                    myArray[i][x] = myArray[i-1][x-arr[i]];
                }
            }

您还有数组索引问题。您的 ix 都从 1 开始,您从不考虑索引 0 实际上是您的第一个玩家。你需要取arr[i-1]的值

所以进一步的更新将如下所示,

        myArray[i][x] = myArray[i-1][x];//carrying previous result
                if(x>=arr[i-1] )
                {
                    if (myArray[i][x]==1){
                        myArray[i][x]=1; 
                    }
                    else{
                        myArray[i][x] = myArray[i-1][x-arr[i-1]];
                    }
                }

所以最终的程序会是这样的

    public boolean findSolution(int[] scores, int total) {
    int W = total;
    int players = scores.length;

    boolean[][] myArray = new boolean[players + 1][total + 1];

    for (int player = 0; player <= players; player++) {
        myArray[player][0] = true;
    }
    for (int score = 1; score < total; score++) {
        myArray[0][score] = false;
    }
    for (int player = 1; player <= players; player++) {
        for (int score = 1; score <= total; score++) {
            myArray[player][score] = myArray[player - 1][score];
            if (score >= scores[player - 1]) {
                myArray[player][score] = myArray[player - 1][score
                        - scores[player - 1]]
                        || myArray[player][score];
            }
        }
    }
    return myArray[players][W];

}

现在要打印结果,请查看矩阵中的真实值。找出设置了哪些值以及设置的时间应该不难。打印这些索引以获得结果。

【讨论】:

  • 我知道你可能没有太多时间,但如果你能展示一下如何打印的话。因为我试图遍历 myArray 但我只打印布尔值而不是我想打印适当的索引值。它会帮助很多,因为它会完全解决这个问题。
【解决方案3】:

这是一个超级简单的解决方案,它只是在您的输入数组上生成一个幂集,然后遍历每个集以查看总和是否满足给定的总数。我和code already available on StackOverflow一起破解了它。

O(2n) 在时间和空间上。毛。

您可以使用Set 的想法将所有索引存储到您的数组中,然后生成这些索引的所有排列,然后使用每组索引然后返回到您的数组并获取值。

输入

  • 目标:10
  • 值:[3, 5, 5, 7]

代码:

import java.util.*;
import java.lang.*;
import java.io.*;

class SubsetSum
{
    public static <T> Set<Set<T>> powerSet(Set<T> originalSet)
    {
        Set<Set<T>> sets = new HashSet<Set<T>>();
        if (originalSet.isEmpty()) 
        {
            sets.add(new HashSet<T>());
            return sets;
        }
        List<T> list = new ArrayList<T>(originalSet);
        T head = list.get(0);
        Set<T> rest = new HashSet<T>(list.subList(1, list.size())); 
        for (Set<T> set : powerSet(rest))
        {
            Set<T> newSet = new HashSet<T>();
            newSet.add(head);
            newSet.addAll(set);
            sets.add(newSet);
            sets.add(set);
        }       
        return sets;
    }

    public static void main(String[] args)
    {
        Set<Integer> mySet = new HashSet<Integer>();
        int[] arr={3, 5, 5, 7};
        int target = 10;
        int numVals = 4;
        for(int i=0;i<numVals;++i)
        {
            mySet.add(i);
        }
        System.out.println("Solutions: ");
        for (Set<Integer> s : powerSet(mySet)) 
        {
            int sum = 0;
            for (Integer e : s)
            {
                sum += arr[e];
            }
            if (sum == target)
            {
                String soln = "[ ";
                for (Integer e : s)
                {
                    soln += arr[e];
                    soln += " ";
                }
                soln += "]";

                System.out.println(soln);
            }
        }
    }
}

输出

解决方案:
[ 5 5 ]
[ 3 7 ]

Live Demo

一旦你理解了这一点,也许你已经准备好开始分支定界或近似方法了。

【讨论】:

  • 感谢您提供现场演示和解决方案,我将立即对其进行测试,非常感谢
  • 我已接受您的回答。再次感谢,如果您能帮我打印 minaz 发布的逻辑中的索引,我也将不胜感激,因为这对我来说最有意义。给您带来的麻烦,我深表歉意。过去 3-4 天我一直在处理这个问题,真的很想完成它。
  • @user2733436:简短的回答是你的逻辑错了,minhaz 的也是。它不能解决所有情况,那何必呢?
  • 你的意思是它只有在整数相对较小的情况下才能解决?
  • @user2733436:我的意思是他所做的修复是不完整的,但这并不重要,因为这种方法本身就有缺陷。该算法是 O(n^2) 但不可能在这样的时间内评估所有必要的子集。即使最大总和为 10。此外,对于负分数,它会非常糟糕,这可能会或可能不会发生在您的情况下。
【解决方案4】:
public List<Integer> findSubsetWithSum(int[] score, int totalScore)
{
    int players = score.length;

    int[] cameFrom = new int[totalScore+1];
    int[] pickedPlayer = new int[totalScore+1];
    for (int s = 0; s <= totalScore; s++)
    {
        cameFrom[s] = -1;
        pickedPlayer[s] = -1;
    }
    cameFrom[0] = 0;
    for (int p = 0; p < players; p++)
    {
        for (int s = score[p]; s <= totalScore; s++)
        {
            if (cameFrom[s - score[p]] >= 0)
            {
                cameFrom[s] = s - score[p];
                pickedPlayer[s] = p + 1;
            }
        }
    }
    List<Integer> picked = new ArrayList<Integer>();
    for (int s = totalScore; s > 0 && cameFrom[s] >= 0; s = cameFrom[s])
    {
        picked.add(pickedPlayer[s]);
    }
    return picked;
}

【讨论】:

  • 嗨,我刚刚尝试了您的解决方案,实际上它没有返回任何内容..我正在尝试在这里进行测试...ideone.com/fork/qIt9Y9
  • 呸。我在编辑时删除了一个重要的行;初始化cameFrom 的第一个单元格。现在它已经修复了。
猜你喜欢
  • 1970-01-01
  • 2012-06-28
  • 1970-01-01
  • 2015-03-24
  • 2018-06-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多