算法难题:允许所有站在一条线上的人相互通信的最低成本

时间:2015-10-10 07:06:11

标签: algorithm dynamic-programming greedy

我有一个我无法解决的算法设计难题。

这个谜题是这样形成的:有一个数字线上有N个人,每个人可能站在该线上的任何整数上。多人可能站在同一个号码上。对于任何两个人能够相互通信,它们之间的距离应小于K.目标是移动它们,以便每对两个人可以相互通信(可能通过其他人)。换句话说,我们需要移动它们,以便任何相邻的两个人之间的距离小于K.

问题:总移动的最小数量是多少?感觉这就像贪婪的算法族或动态编程。任何提示都表示赞赏!

2 个答案:

答案 0 :(得分:1)

我们可以在O(n)中执行以下操作:

计算将所有人移动到人i右侧的人员i的可接受距离的费用:

costRight(A[i]) = costRight(A[i+1]) + (A[i+1] - A[i] - k + 1) * count of people to the right

K = 3;  A = { 0,  3, 11, 17, 21}
costRight = {32, 28, 10,  2,  0}

计算将人员i左侧的所有人移动到可接受距离的人i的费用:

costLeft(A[i]) = costLeft(A[i-1]) + (A[i] - A[i-1] - k + 1) * count of people to the left

K = 3;  A = { 0,  3, 11, 17, 21}
costLeft  = { 0,  1, 13, 25, 33}
costRight = {32, 28, 10,  2,  0}

既然我们已从两个方向付费,我们可以在O(n)

中执行此操作
minCost = min(costRight + costLeft) for all A[i]
minCost = min(32 + 0, 28 + 1, 13 + 10, 25 + 2, 33 + 0) = 23

但有时候这还不够:

K = 3;  A = { 0,  0,  1,  8,  8}

      carry:     -2  -4       3
costLeft  = { 0,  0,  0, 11, 11}

      carry: -3   5      -2
costRight = { 8,  8,  8,  0,  0}

最佳值既不是11也不是8.通过最大限度的节省来测试当前最佳:

move 1 to 2, cost = 1

K = 3;  A = { 0,  0,  2,  8,  8}

      carry:     -2  -2     -10
costLeft  = { 0,  0,  0, 10, 10}

      carry: -2          -2
costRight = { 6,  6,  6,  0,  0}

minCost = 1 + min(0 + 6, 0 + 6, 0 + 6, 10 + 0, 10 + 0) = 1 + 6 = 7

不太确定如何有效地进行配方化。

答案 1 :(得分:0)

这是一个用Java编写的贪婪算法,但我不知道它是否在每种情况下都提供了最佳解决方案。此外,它更像是一个概念验证,还有一些优化空间。

这是基于两个邻居不得超过K的事实,下一个邻居不得超过2K,依此类推。在每一步中,我们都会移动“最严重违反这些限制”的人。此计算的详细信息在方法calcForce中。

package so;

import java.util.Arrays;

public class Main {

    public static void main(String args[]) {
        int[] position = new int[] {0, 0, 5, 11, 17, 23};
        int k = 5;

        solve(position, k);
    }

    private static void solve(int[] position, int k) {
        if (!sorted(position)) {
            throw new IllegalArgumentException("positions must be sorted");
        }
        int[] force = new int[position.length];
        int steps = 0;
        while (calcForce(position, k, force)) {
            int mp = -1;
            int mv = -1;
            for (int i = 0; i < force.length; i++) {
                if (mv < Math.abs(force[i])) {
                    mv = Math.abs(force[i]);
                    mp = i;
                }
            }
            System.out.printf("move %d to the %s%n", mp, force[mp] > 0 ? "right" : "left");
            if (force[mp] > 0) {
                position[mp]++;
            } else {
                position[mp]--;
            }
            steps++;
        }
        System.out.printf("total: %d steps%n", steps);
    }

    private static boolean calcForce(int[] position, int k, int[] force) {
        boolean commProblem = false;
        Arrays.fill(force, 0);
        for (int i = 0; i < position.length - 1; i++) {
            for (int j = i + 1; j < position.length; j++) {
                int f = position[j] - position[i] - (j - i) * k;
                if (f > 0) {
                    force[i] += f;
                    force[j] -= f;
                    commProblem = true;
                }
            }
        }
        return commProblem;
    }

    private static boolean sorted(int[] position) {
        for (int i = 0; i < position.length - 1; i++) {
            if (position[i] > position[i+1]) {
                return false;
            }
        }
        return true;
    }
}