题目
给定N件物品,每个物品有重量(w[i])、有价值(v[i]),只能最多选两件商品,重量不超过bag,返回价值最大能是多少?
- 动态规划思想:从左往右遍历模型,当前要或者不要
function process(w, v, index, restNumber, restWeight) {
if (restNumber < 0 || restWeight < 0) {
return -1;
}
// 越界
if (index === w.length) {
return 0;
}
let p1 = process(w, v, index + 1, restNumber, restWeight);
let p2;
let res = process(w, v, index + 1, restNumber - 1, restWeight - w[index]);
// 要当前value
if (res != -1) {
p2 = v[index] + res;
}
return Math.max(p1, p2);
}
- RMQ方式
- 首先对整个数组按照重量进行排序,然后从左往右遍历,假设当前位置重量为n,总重量为m,就是要找重量<=m-n里,价值最大的,查找<=m-n的左边通过二分查找最右的那个,每个位置都遍历一遍就能得出答案
- 查找到最右边界后,相当于知晓了范围,通过RMQ去计算范围内的value最大值,i位置在RMQ中对应i+1,RMQ
public static int max2(int[] w, int[] v, int bag) {
int n = w.length;
int[][] arr = new int[n][2];
for (int i = 0; i < n; i++) {
arr[i][0] = w[i];
arr[i][1] = v[i];
}
// O(N * logN)
Arrays.sort(arr, (a, b) -> (a[0] - b[0]));
// 重量从轻到重,依次标号1、2、3、4....
// 价值依次被构建成了RMQ结构
// O(N * logN)
RMQ rmq = new RMQ(arr);
int ans = 0;
// N * logN
// j 就是 RMQ 中的索引
for (int i = 0, j = 1; i < n && arr[i][0] <= bag; i++, j++) {
// 当前来到0号货物,RMQ结构1号
// 当前来到i号货物,RMQ结构i+1号
// 查询重量的边界,重量 边界 <= bag - 当前货物的重量
// 货物数组中,找到 <= 边界,最右的位置i
// RMQ,位置 i + 1
int right = right(arr, bag - arr[i][0]) + 1;
int rest = 0;
// 查找到的最右边界就是自身
if (right == j) {
rest = rmq.max(1, right - 1);
} else if (right < j) {
// 查找到的最右边界在自身位置左边
rest = rmq.max(1, right);
} else { // right > j
// 查找到的最右边界在自身位置右边就要分两段查找,因为不能包含自身
// 左边到自身-1,自身+1到右边的范围分别查找value最大值然后比较
rest = Math.max(rmq.max(1, j - 1), rmq.max(j + 1, right));
}
ans = Math.max(ans, arr[i][1] + rest);
}
return ans;
}
public static int right(int[][] arr, int limit) {
int l = 0;
int r = arr.length - 1;
int m = 0;
int ans = -1;
while (l <= r) {
m = (l + r) / 2;
if (arr[m][0] <= limit) {
ans = m;
l = m + 1;
} else {
r = m - 1;
}
}
return ans;
}
public static class RMQ {
public int[][] max;
public RMQ(int[][] arr) {
int n = arr.length;
int k = power2(n);
max = new int[n + 1][k + 1];
for (int i = 1; i <= n; i++) {
max[i][0] = arr[i - 1][1];
}
for (int j = 1; (1 << j) <= n; j++) {
for (int i = 1; i + (1 << j) - 1 <= n; i++) {
max[i][j] = Math.max(max[i][j - 1], max[i + (1 << (j - 1))][j - 1]);
}
}
}
public int max(int l, int r) {
if (r < l) {
return 0;
}
int k = power2(r - l + 1);
return Math.max(max[l][k], max[r - (1 << k) + 1][k]);
}
private int power2(int m) {
int ans = 0;
while ((1 << ans) <= (m >> 1)) {
ans++;
}
return ans;
}
}