【问题标题】:Hi, I got java.lang.OutOfMemoryError: Java heap space while trying to solve this Algorithm problem, what can be done?嗨,我在尝试解决此算法问题时遇到 java.lang.OutOfMemoryError: Java heap space,该怎么办?
【发布时间】:2020-03-24 14:31:38
【问题描述】:

这个问题类似于subset-sum,我们确定一个数字列表是否有一个产生给定总和的子集,但是在这种情况下,我们被允许从列表p的元素创建一个子集,仅当它等于或大于其他列表 q 中的相应元素。同样,k 不是元素值的总和,而是可以添加的元素数。 因此,如果 k 为 3,我们需要从列表 p 中选择 3 个元素,但这些元素不能小于列表 q 的相应元素。 我是动态编程和背包新手,请帮助我。

public static List<Integer> kthPerson(int k, List<Integer> p, List<Integer> q) {

    List<Integer> q1 = new ArrayList<>();
    q1.addAll(q);
    Collections.sort(q1);

    int maxQ = q1.get(q1.size()-1);

    List<Integer> res = new ArrayList<>();

    int[][] dp = new int[k+1][maxQ+1];
    for(int[] d:dp){
        Arrays.fill(d, 0);
    }

    for (int u = 0; u < maxQ; u++) {
        int count = 0;
        for (int i = 0; i < p.size(); i++){
            if (p.get(i) >= u){
                dp[count][u] = i+1;
                count++;
            }
            if (count == k){
                break;
            }
        }
    }

    for (int s = 0; s < q.size(); s++) {
        res.add(dp[k-1][q.get(s)]);
    }
    return res;
}

/*if you want to test*/
public static void main(String args[]) {



        List<Integer> p = new ArrayList<>();

        p.add(1);
        p.add(4);
        p.add(4);
        p.add(3);
        p.add(1);
        p.add(2);
        p.add(6);

        List<Integer> q = new ArrayList<>();

        q.add(1);
        q.add(2);
        q.add(3);
        q.add(4);
        q.add(5);
        q.add(6);
        q.add(7);

        kthPerson(2, p, q);


    }
 /*you will get 
2
3
3
3
0
0
0. which is desired result but when the input is really large I get the java heap error*/


【问题讨论】:

  • 你将不得不给我们更多的信息和解释,现在的问题还不清楚,如果你搜索knapsack dynamic programing java,你可以阅读你正在尝试做什么。
  • 另外,由于我本能地认为“无休止的递归”是这里的根本原因,您必须提供相关源代码的所有,而不仅仅是@987654323 @函数,它本身似乎不是递归的。 SO 不是调试服务:我们需要精确 的问题,而不是“这里某个地方有错误,所以让我把它扔给你,你只需修复它。”
  • @MikeRobinson 这里没有递归我正在尝试通过在给定列表中将所有可能的值添加到最大值然后简单地在最后得到结果来进行自下而上的动态编程
  • 导致错误的输入到底有多大? java最大堆大小约为2 GB(!),如果人数为250且最大的q为10,000,则可能会崩溃。您会注意到,您实际上并不需要 k+1 行,只需要最后 2 行,这会有所帮助,但时间复杂度仍然很高。
  • 请帮助我们帮助你@theUturn,解释一下你的输出是什么意思?给变量起有意义的名字。你还确定需要动态编程吗?

标签: java algorithm knapsack-problem


【解决方案1】:

事实上,这个问题不需要动态规划。没有一个案例实际上是基于以前的案例。
因此你可以编写这个函数:

public static List<Integer> kthPersonNew(int k, List<Integer> p, List<Integer> q) {
    List<Integer> res = new ArrayList<>();

    for (int limit : q) {
        int count = 0;
        boolean added = false;
//      For each person index
        for (int i = 0; i < p.size(); i++) {
//          If this person is higher than the needed limit - count it
            if (p.get(i) >= limit) {
                count++;
//              If you have counted k persons than add the kth person to res and break
                if (count == k) {
                    res.add(i + 1);
                    added = true;
                    break;
                }
            }
        }
//      In case no one was added than add 0
        if (!added) {
            res.add(0);
        }
    }
    return res;

}

它为您提供的测试用例提供相同的输出。

p = [1, 4, 4, 3, 1, 2, 6]
q = [1, 2, 3, 4, 5, 6, 7]
=>
res = [2, 3, 3, 3, 0, 0, 0]

现在我确定这个程序不会导致java.lang.OutOfMemoryError,因为您只使用一个数组并且它的长度与q 相同。
这应该可以解决您的问题。


编辑:
看起来新算法在内存方面很好但不够快,假设q 总是在增长,我们可以使用它并确保时间复杂度总是小于O(q * k + n)。因为每个不适合当前限制的元素都不会适合未来的限制。

我们需要一个辅助类:

class Element{
    private int index;
    private int value;

    public Element(int index, int value){
        this.index = index;
        this.value = value;
    }

    public int getIndex(){
        return this.index;
    }

    public int getValue(){
        return this.value;
    }

    @Override
    public String toString(){
        return value + " " + index;
    }

    public void print(){
        System.out.println(this);
    }
}

这样我们即使在删除后也能记住旧的变量索引。

还有新功能:

public static List<Integer> kthPersonFast(int k, List<Integer> p, List<Integer> q) {
    List<Integer> res = new ArrayList<>();

    List<Element> elements = new LinkedList<Element>();
    for (int i = 0; i < p.size(); i++) {
        elements.add(new Element(i, p.get(i)));
    }

    for (int limit : q) {
        int count = 0;
        boolean added = false;
//          For each person         
        Iterator<Element> itr = elements.iterator(); 
        while (itr.hasNext()) { 
            Element person = itr.next(); 

//              If this person is higher than the needed limit - count it
            if (person.getValue() >= limit) {
                count++;
//                  If you have counted k persons than add the kth person to res and break
                if (count == k) {
                    res.add(person.getIndex() + 1);
                    added = true;
                    break;
                }
            } else {
//                  If the person didn't fit for that limit it will not fit to any other limit
//                  since we are assuming the limits are always growing
                itr.remove();
            }
        }
//          In case no one was added than add 0
        if (!added) {
            res.add(0);
        }
    }
    return res;
}

您还需要导入:

import java.util.Iterator;
import java.util.LinkedList;

应该是这样的。

祝你好运

【讨论】:

  • 感谢@Yonlif,但这种方法会造成时间限制超出情况..两个数组的大小都可以是 100000
  • 哦,没问题我会降低时限。您应该知道当前版本的时间复杂度与您的算法相同。
  • @theUturn 你需要输出所有这个数组吗?你能分享一下hackerrank链接吗?
  • 链接已过期,不幸的是.. 但我确信我最初应用了相同的直接逻辑,导致 TLE,然后我采用动态编程方法,否则我为什么要这样做?
  • @theUturn 那仍然输出 TLE 吗?
【解决方案2】:

您可能需要查看程序的其他部分,看看是否可以删除未使用的对象。在这种情况下,我们需要查看更多代码。话虽如此,如果您还没有尝试过,您可能会受益于以下内容:

如果您使用的是 64 位 JVM,您可以简单地增加堆大小。

java -Xmx4G -jar MyProgram.jar

https://javarevisited.blogspot.com/2011/05/java-heap-space-memory-size-jvm.html

【讨论】:

  • 这实际上可能有助于提高内存复杂度,但在竞争性编程中,用户无法控制编译,此外,这对保持不变的时间复杂度没有帮助。
猜你喜欢
  • 2018-12-25
  • 2014-06-15
  • 2013-12-20
  • 2013-06-20
  • 2014-02-15
  • 2021-03-21
  • 2012-08-03
相关资源
最近更新 更多