题目地址:
https://leetcode.com/problems/campus-bikes-ii/
在平面直角坐标系里,给定 n n n个工人的坐标和 m m m个自行车的坐标( m ≥ n m ≥ n m≥n并且它们的坐标两两不同),问怎样分配才能使得每个工人都有一个自行车,并且工人与自行车的曼哈顿距离之总和最小。返回那个最小的距离总和。
可以用状态压缩 + 记忆化DFS,用二进制数的二进制位表示每个工人是否已经被分配过了一个自行车。设 f [ k ] [ s ] f[k][s] f[k][s]是还有下标为 k , k + 1 , . . . k,k+1,... k,k+1,...的自行车未分配,且拥车状态为 s s s的情况下,继续分配自行车所带来的曼哈顿距离总和的增加值(即还未被分配车的那些工人贡献的曼哈顿距离之和)。那么题目就是要求 f [ 0 ] [ 0 ] f[0][0] f[0][0]。可以枚举下标为 k k k的车分配给了哪个工人,则遍历 s s s的各个二进制位,看哪个工人还没自行车,分配之,然后进入下一层DFS;当然下标 k k k的车不分配给任何人也是一种选择。关于递归出口,如果待分配的车的数量不够还没车的工人的数量了,则不合法,直接返回 + ∞ +\infty +∞表示当前得不到合法解;如果每个人都有车了,则返回 0 0 0,因为不需要继续分配车了;如果有人没车,但已经没车分配了,则也返回 + ∞ +\infty +∞表示当前得不到合法解;有记忆则调取记忆。注意, f [ k ] [ s ] f[k][s] f[k][s]这个值如果不加记忆化的话,是会被多次计算的,因为不同的分配方式很可能导致相同的 s s s值,然而在 k k k和 s s s都确定的情况下,“至少还增加多少曼哈顿距离”事实上是个定值,不应该被计算多次。代码如下:
import java.util.Arrays;
public class Solution {
public int assignBikes(int[][] workers, int[][] bikes) {
int[][] f = new int[bikes.length][1 << workers.length];
for (int[] row : f) {
Arrays.fill(row, -1);
}
return dfs(0, 0, workers, bikes, f);
}
private int dfs(int pos, int state, int[][] w, int[][] b, int[][] f) {
// 车不够分了
if (b.length - pos < w.length - Integer.bitCount(state)) {
return 0x3f3f3f3f;
}
// 每个人都有车了
if (state == (1 << w.length) - 1) {
return 0;
}
// 有人没车,但车已经用完了
if (pos == b.length) {
return 0x3f3f3f3f;
}
// 有记忆则调取记忆
if (f[pos][state] != -1) {
return f[pos][state];
}
int res = 0x3f3f3f3f;
// 枚举pos这个车分给谁
for (int i = 0; i < w.length; i++) {
if ((state >> i & 1) == 0) {
res = Math.min(res, dis(w, b, i, pos) + dfs(pos + 1, state | (1 << i), w, b, f));
}
}
// 枚举pos这个车浪费掉的情况
res = Math.min(res, dfs(pos + 1, state, w, b, f));
// 回溯前做记忆
f[pos][state] = res;
return res;
}
private int dis(int[][] w, int[][] b, int i, int j) {
return Math.abs(w[i][0] - b[j][0]) + Math.abs(w[i][1] - b[j][1]);
}
}
时间复杂度 O ( m n 2 n ) O(mn2^n) O(mn2n),空间 O ( m 2 n ) O(m2^n) O(m2n)。