如何在DP表中实现这种递归算法?

时间:2014-01-16 10:57:53

标签: algorithm recursion dynamic-programming

考虑以下问题:

我们有一条长度为L的渡轮。这艘渡轮用于在两条河岸之间运输车辆。渡轮有两条车道,可以容纳车辆。每辆车的长度为l_i。可以容纳每个车道中的车辆,使得它们不超过渡轮的长度。给定长度为l_1, l_2, ..., l_n且长度为L的渡轮的车辆队列,找到可容纳的最大车辆数量。

请记住,车辆只能根据其在队列中的位置进入渡轮。例如,如果队列中的车辆长度为700, 600,前方车辆较大,车道剩余空间为0, 400,则无法加载较短的车辆而不是较长的车辆。

我能够使用递归关系解决这个问题。让solve(L1, L2, l_i, l_i+1, ..., l_n)返回长度为l_i, l_i+1, ..., l_n的车辆的最大数量,这些车辆可以容纳在两条车道中长度为L1, L2的渡轮中。

function solve(L1, L2, l_i, l_i+1, ..., l_n) {
    if (L1 - l_i > 0 and L2 - l_i > 0) {
       return 1 + max(solve(L1 - l_i, L2, l_i+1, ..., l_n), solve(L1, L2 - l_i, l_i+1, ..., l_n))
    } else if (L1 - l_i > 0) {
       return 1 + solve(L1 - l_i, L2, l_i+1, ..., l_n)
    } else if (L2 - l_i > 0) {
       return 1 + solve(L1, L2 - l_i, l_i+1, ..., l_n)
    } else {
       return 0
    }

}

基本上,该算法计算将车辆放置在渡轮中的最佳方式,并且在每次重复时,它从两个车道的长度中减去队列中当前车辆的长度并将其传递给自身,并计算最大值两种策略。

正如您所看到的,递归调用非常快速地构建,并且程序效率不高。如何使用动态编程方式实现相同的算法?

编辑:约束是,L在1到10,000之间,每辆车的长度在100到3000之间。

3 个答案:

答案 0 :(得分:1)

通常在DP中,您将创建一个包含所有可能状态的数组,如答案Pham中所做的那样。根据OP的评论,在这种情况下这是不可行的。因此,最好将一组可能的状态保持到当前的汽车状态。

让我们创建一个状态类(我用Java做这个,希望没问题)。我想保持简洁,所以我不使用getter,只使用公共字段。

class State {
    public int lane1;
    public int lane2;        

    public State(int lane1, int lane2) {
        this.lane1 = lane1;
        this.lane2 = lane2;
    }        

    public boolean equals(Object o) {
        if (o instanceof State) {
            State other = (State) o;
            return lane1 == other.lane1 && lane2 == other.lane2;
        } else {
            return false;
        }            
    }

    public int hashCode() {
        return 31 * lane1 + lane2;
    }
}

需要equals和hashcode实现,因为我想将State个对象放在一个hashset中,我们想要值等价而不是引用等价。

通过这些状态,你可以绕过汽车。在考虑汽车i时,我们需要了解汽车i-1之前的所有可能状态,将汽车i添加到两个航线并存储两个结果状态(如果可行)。在代码中:

public int findMaxCars(int L, int[] len) {        
    Set<State> previousStates = new HashSet<>();

    // the only possible state when there are no cars on the ferry:
    // both lanes have L remaining space
    previousStates.add(new State(L, L));

    for (int i = 0; i < len.length; i++) {
        Set<State> nextStates = new HashSet<>();

        for (State s : previousStates) {
            if (s.lane1 - len[i] >= 0) // can fit car i in lane1
                nextStates.add(new State(s.lane1 - len[i], s.lane2));
            if (s.lane2 - len[i] >= 0) // can fit car i in lane2
                nextStates.add(new State(s.lane1, s.lane2 - len[i]));
        }

        if (nextStates.isEmpty()) {
            // we couldn't fit car i anywhere, so the answer is: at most i cars
            return i;
        } else {
            previousStates = nextStates;
        }
    }
    // finished the loop, so all cars fit
    return len.length;
}

答案 1 :(得分:1)

在您接受i车辆的情况下,计算所有可能的渡轮配置。要处理汽车i+1,新配置的集合是上一步的配置,新车在一个车道或另一个车道(如果可能)。

单个配置可以是其中一个车道的长度(因为在处理i车之后,您将知道两个车道的长度之和为l1 + l2 + ... + l_i。

因此,您可以保留的表可以是一组长度。或者如果你想要一个“表”,那么你可以使用一个真/假值数组并在适当的位置更新(小心),这是在这段代码中采用的方法。

def solve(L, car_lengths):
    confs = [True] + [False] * L
    S = 0
    for i, c in enumerate(car_lengths):
        S += c
        for j in xrange(L, -1, -1):
            if confs[j]:
                if S - j > L:
                    confs[j] = False
                if j + c <= L and S - j - c <= L:
                    confs[j + c] = True
        if not any(confs):
            return i


print solve(9, (5, 2, 3, 4, 5, 5, 3, 4, 8))

由于就地更新,代码有点棘手:如果confs[j]为1,那么这意味着可以在第一个车道上配置j辆汽车的前一辆车。然后,如果添加新车(长度为c),可能的配置再次为j(将车辆添加到第二车道),j+c(将车辆添加到第一车道)。由于我们已经设置了conf[j],因此代码会测试是否无法将汽车添加到第二条车道并在这种情况下清除conf[j]

这是使用相同方法但使用集合的不那么棘手的代码。

def solve(L, car_lengths):
    confs = set([0])
    S = 0
    for i, c in enumerate(car_lengths):
        S += c
        confs |= set(j + c for j in confs)
        confs = set(j for j in confs if j <= L and S - j <= L)
        if not confs:
            return i


print solve(9, (5, 2, 3, 4, 5, 5, 3, 4, 8))

答案 2 :(得分:0)

对于你的解决方案,观察一次只有一个值,我们可以简单地将函数修改为

function solve(int L1,int L2, index, int [] length){//index : what is the current index in the array length we are referring to
}

哪个会做同样的事情。因此,我们可以轻松提出 DP状态DP[L1][L2][index]

另一件事是你可以要么选择考虑当前的索引值,要么通过这样做来继续下一个索引

if(DP[L1][L2][index] != -1){//Need to initialize the DP table first
    return DP[L1][L2][index];
}
int max = 0;
if(L1 >= length[index]){
    max = 1 + solve(L1 - length[index],L2,index + 1, length);
}
if(L2 >= length[index]){
    max = 1 + max(max,solve(L1 ,L2 - length[index],index + 1, length));
}
max = max(max,solve(L1,L2,index + 1, length);

最后观察是L1和L2的顺序并不重要,因此可以用min,max,(L1和L2之间的最小值,L1和L2之间的最大值)代替,这有助于减少 DP的步数。