DeepSeek 四边形不等式优化DP的Java实现
·
四边形不等式优化DP的Java实现
一、四边形不等式基础概念
1.1 定义
四边形不等式是动态规划优化的一种重要技巧,主要用于优化区间DP的时间复杂度。对于形如:
dp[i][j] = min_{i≤k<j} {dp[i][k] + dp[k+1][j] + w(i,j)}
的状态转移方程,如果权函数w满足四边形不等式,则可以将时间复杂度从O(n³)优化到O(n²)。
1.2 四边形不等式条件
对于任意a≤b≤c≤d,有:
- 区间单调性:w(a,d) ≥ w(b,c)
- 四边形不等式:w(a,d) + w(b,c) ≥ w(a,c) + w(b,d)
二、核心Java实现
2.1 四边形不等式优化模板
import java.util.*;
public class QuadrilateralInequality {
/**
* 四边形不等式优化的区间DP模板
* 适用于:dp[i][j] = min_{i≤k<j} {dp[i][k] + dp[k+1][j] + w(i,j)}
*/
public static int quadInequalityDP(int n, int[] arr) {
// dp[i][j]: 区间[i,j]的最优值
int[][] dp = new int[n+2][n+2];
// s[i][j]: 记录最优决策点
int[][] s = new int[n+2][n+2];
// 前缀和用于快速计算w(i,j)
int[] prefixSum = new int[n+2];
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + arr[i-1];
}
// 初始化:长度为1的区间
for (int i = 1; i <= n; i++) {
dp[i][i] = 0;
s[i][i] = i;
}
// 枚举区间长度
for (int len = 2; len <= n; len++) {
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
dp[i][j] = Integer.MAX_VALUE;
// 四边形不等式优化:缩小k的枚举范围
int lower = s[i][j-1];
int upper = (len == 2) ? i : Math.min(s[i+1][j], j-1);
for (int k = lower; k <= upper; k++) {
int temp = dp[i][k] + dp[k+1][j]
+ (prefixSum[j] - prefixSum[i-1]);
if (temp < dp[i][j]) {
dp[i][j] = temp;
s[i][j] = k;
}
}
}
}
return dp[1][n];
}
/**
* 验证权函数是否满足四边形不等式
*/
public static boolean verifyQuadInequality(int n, Function<Integer, Integer> w) {
for (int a = 1; a <= n; a++) {
for (int b = a; b <= n; b++) {
for (int c = b; c <= n; c++) {
for (int d = c; d <= n; d++) {
int left = w.apply(a, d) + w.apply(b, c);
int right = w.apply(a, c) + w.apply(b, d);
if (left < right) {
return false;
}
}
}
}
}
return true;
}
/**
* 验证区间单调性
*/
public static boolean verifyMonotonicity(int n, Function<Integer, Integer> w) {
for (int a = 1; a <= n; a++) {
for (int b = a; b <= n; b++) {
for (int c = b; c <= n; c++) {
for (int d = c; d <= n; d++) {
if (w.apply(a, d) < w.apply(b, c)) {
return false;
}
}
}
}
}
return true;
}
// 函数式接口定义
interface Function<T, R> {
R apply(T a, T b);
}
}
三、经典问题实现
3.1 石子合并问题
public class StoneMerge {
/**
* 最小代价的石子合并
*/
public static int minStoneMergeCost(int[] stones) {
int n = stones.length;
int[] prefixSum = new int[n+1];
// 计算前缀和
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + stones[i-1];
}
// dp[i][j]: 合并第i到第j堆石子的最小代价
int[][] dp = new int[n+2][n+2];
// s[i][j]: 最优决策点
int[][] s = new int[n+2][n+2];
// 初始化
for (int i = 1; i <= n; i++) {
dp[i][i] = 0;
s[i][i] = i;
}
// 区间DP + 四边形不等式优化
for (int len = 2; len <= n; len++) {
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
dp[i][j] = Integer.MAX_VALUE;
// 四边形不等式优化范围
int start = Math.max(i, s[i][j-1]);
int end = Math.min(j-1, len == 2 ? i : s[i+1][j]);
for (int k = start; k <= end; k++) {
int cost = dp[i][k] + dp[k+1][j]
+ prefixSum[j] - prefixSum[i-1];
if (cost < dp[i][j]) {
dp[i][j] = cost;
s[i][j] = k;
}
}
}
}
return dp[1][n];
}
/**
* 最大代价的石子合并(证明思路相同)
*/
public static int maxStoneMergeCost(int[] stones) {
int n = stones.length;
int[] prefixSum = new int[n+1];
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + stones[i-1];
}
int[][] dp = new int[n+2][n+2];
int[][] s = new int[n+2][n+2];
for (int i = 1; i <= n; i++) {
dp[i][i] = 0;
s[i][i] = i;
}
for (int len = 2; len <= n; len++) {
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
dp[i][j] = Integer.MIN_VALUE;
// 对于最大代价,同样适用四边形不等式
int start = i;
int end = j-1;
// 或者也可以使用决策单调性优化
for (int k = i; k < j; k++) {
int cost = dp[i][k] + dp[k+1][j]
+ prefixSum[j] - prefixSum[i-1];
if (cost > dp[i][j]) {
dp[i][j] = cost;
s[i][j] = k;
}
}
}
}
return dp[1][n];
}
}
3.2 最优二叉搜索树
public class OptimalBST {
/**
* 构造最优二叉搜索树
* keys: 关键字
* freq: 关键字频率
* dummyFreq: 伪关键字频率
*/
public static int optimalBSTCost(int[] keys, int[] freq, int[] dummyFreq) {
int n = keys.length;
// dp[i][j]: 包含关键字i到j的最优二叉搜索树代价
int[][] dp = new int[n+2][n+2];
// w[i][j]: 关键字i到j的频率和
int[][] w = new int[n+2][n+2];
// root[i][j]: 记录根节点
int[][] root = new int[n+2][n+2];
// 初始化
for (int i = 1; i <= n+1; i++) {
dp[i][i-1] = dummyFreq[i-1];
w[i][i-1] = dummyFreq[i-1];
}
// 计算频率和
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
w[i][j] = w[i][j-1] + freq[j-1] + dummyFreq[j];
}
}
// 动态规划 + 四边形不等式优化
for (int len = 1; len <= n; len++) {
for (int i = 1; i <= n - len + 1; i++) {
int j = i + len - 1;
dp[i][j] = Integer.MAX_VALUE;
// 四边形不等式优化范围
int lower = root[i][j-1];
int upper = (len == 1) ? i : root[i+1][j];
for (int r = lower; r <= upper; r++) {
int cost = dp[i][r-1] + dp[r+1][j] + w[i][j];
if (cost < dp[i][j]) {
dp[i][j] = cost;
root[i][j] = r;
}
}
}
}
return dp[1][n];
}
/**
* 重建最优二叉搜索树结构
*/
public static void constructBST(int[][] root, int i, int j, List<Integer> result) {
if (i > j) {
result.add(-1); // 表示空节点
return;
}
int r = root[i][j];
result.add(r);
constructBST(root, i, r-1, result);
constructBST(root, r+1, j, result);
}
}
3.3 邮局选址问题
public class PostOffice {
/**
* 邮局选址问题
* positions: 房屋位置(已排序)
* k: 邮局数量
*/
public static int minPostOfficeDistance(int[] positions, int k) {
int n = positions.length;
// dist[i][j]: 在[i,j]区间建一个邮局的最小距离和
int[][] dist = new int[n+1][n+1];
// dp[i][j]: 前i个房屋建j个邮局的最小距离
int[][] dp = new int[n+1][k+1];
// s[i][j]: 最优决策点
int[][] s = new int[n+1][k+1];
// 计算dist[i][j]
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
int mid = (i + j) / 2;
dist[i][j] = 0;
for (int t = i; t <= j; t++) {
dist[i][j] += Math.abs(positions[t-1] - positions[mid-1]);
}
}
}
// 初始化
for (int i = 1; i <= n; i++) {
dp[i][1] = dist[1][i];
}
// 四边形不等式优化
for (int j = 2; j <= k; j++) {
s[n+1][j] = n; // 初始化边界
for (int i = n; i >= 1; i--) {
dp[i][j] = Integer.MAX_VALUE;
// 四边形不等式优化范围
int lower = (j == 2) ? 1 : Math.max(j-1, s[i][j-1]);
int upper = Math.min(i, s[i+1][j]);
for (int t = lower; t <= upper; t++) {
int cost = dp[t-1][j-1] + dist[t][i];
if (cost < dp[i][j]) {
dp[i][j] = cost;
s[i][j] = t;
}
}
}
}
return dp[n][k];
}
/**
* 验证邮局问题的四边形不等式性质
*/
public static boolean verifyPostOfficeQuad(int n, Function<Integer, Integer> w) {
// 对于邮局问题,dist[i][j]满足四边形不等式
for (int i = 1; i <= n; i++) {
for (int j = i; j <= n; j++) {
for (int k = j; k <= n; k++) {
for (int l = k; l <= n; l++) {
if (w.apply(i, l) + w.apply(j, k)
< w.apply(i, k) + w.apply(j, l)) {
return false;
}
}
}
}
}
return true;
}
}
四、高级应用与变种
4.1 一维四边形不等式优化
public class OneDQuadOptimization {
/**
* 一维DP的四边形不等式优化
* dp[i] = min_{0≤j<i} {dp[j] + w(j,i)}
* 适用于决策单调性
*/
public static int oneDOptimization(int n, Function<Integer, Integer> w) {
int[] dp = new int[n+1];
int[] opt = new int[n+1]; // 最优决策点
// 单调队列优化
Deque<Integer> deque = new ArrayDeque<>();
deque.addLast(0);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
// 移除过时的决策
while (deque.size() >= 2 &&
getTransitionPoint(deque.getFirst(), deque.get(1), i, w) <= i) {
deque.removeFirst();
}
int bestJ = deque.getFirst();
dp[i] = dp[bestJ] + w.apply(bestJ, i);
opt[i] = bestJ;
// 维护决策单调性队列
while (!deque.isEmpty()) {
int last = deque.getLast();
if (dp[last] + w.apply(last, i) >= dp[i] + w.apply(i, i)) {
deque.removeLast();
} else {
break;
}
}
if (deque.isEmpty()) {
deque.addLast(i);
} else {
int left = deque.getLast();
int right = n;
// 二分查找决策点i优于last的点
while (left < right) {
int mid = (left + right) / 2;
if (dp[last] + w.apply(last, mid) >= dp[i] + w.apply(i, mid)) {
right = mid;
} else {
left = mid + 1;
}
}
if (left <= n) {
deque.addLast(i);
}
}
}
return dp[n];
}
private static int getTransitionPoint(int j1, int j2, int i,
Function<Integer, Integer> w) {
int left = i, right = Integer.MAX_VALUE;
while (left < right) {
int mid = left + (right - left) / 2;
int cost1 = w.apply(j1, mid);
int cost2 = w.apply(j2, mid);
if (cost1 >= cost2) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
}
4.2 二维四边形不等式优化
public class TwoDQuadOptimization {
/**
* 二维四边形不等式优化
* dp[i][j] = min_{i≤k<j} {dp[i][k] + dp[k+1][j] + w(i,j)}
* 使用四边形不等式+决策单调性优化
*/
public static int twoDOptimization(int n, int[] values) {
int[][] dp = new int[n+2][n+2];
int[][] s = new int[n+2][n+2];
// 前缀和
int[] prefixSum = new int[n+2];
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + values[i-1];
}
// 初始化
for (int i = 1; i <= n; i++) {
dp[i][i] = 0;
s[i][i] = i;
}
// 四边形不等式优化版本
for (int len = 2; len <= n; len++) {
for (int i = 1; i + len - 1 <= n; i++) {
int j = i + len - 1;
dp[i][j] = Integer.MAX_VALUE;
// 四边形不等式关键优化
int lower = Math.max(i, s[i][j-1]);
int upper = Math.min(j-1, s[i+1][j]);
for (int k = lower; k <= upper; k++) {
int cost = dp[i][k] + dp[k+1][j]
+ prefixSum[j] - prefixSum[i-1];
if (cost < dp[i][j]) {
dp[i][j] = cost;
s[i][j] = k;
}
}
// 证明四边形不等式性质
if (i > 1 && j < n) {
// 验证四边形不等式
if (!verifyQuadCondition(i, j, dp, prefixSum)) {
System.err.println("四边形不等式不满足!");
}
}
}
}
return dp[1][n];
}
/**
* 验证四边形不等式条件
*/
private static boolean verifyQuadCondition(int i, int j, int[][] dp, int[] prefixSum) {
for (int a = i; a <= j; a++) {
for (int b = a; b <= j; b++) {
for (int c = b; c <= j; c++) {
for (int d = c; d <= j; d++) {
int left = dp[a][d] + dp[b][c];
int right = dp[a][c] + dp[b][d];
// 需要加上权函数
int w_ad = prefixSum[d] - prefixSum[a-1];
int w_bc = prefixSum[c] - prefixSum[b-1];
int w_ac = prefixSum[c] - prefixSum[a-1];
int w_bd = prefixSum[d] - prefixSum[b-1];
if (w_ad + w_bc < w_ac + w_bd) {
return false;
}
}
}
}
}
return true;
}
/**
* 四边形不等式的记忆化搜索版本
*/
public static int memoizedQuadOptimization(int n, int[] values) {
int[][] dp = new int[n+2][n+2];
int[][] s = new int[n+2][n+2];
boolean[][] visited = new boolean[n+2][n+2];
int[] prefixSum = new int[n+2];
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + values[i-1];
}
return memoizedSolve(1, n, dp, s, visited, prefixSum);
}
private static int memoizedSolve(int i, int j, int[][] dp, int[][] s,
boolean[][] visited, int[] prefixSum) {
if (i >= j) return 0;
if (visited[i][j]) return dp[i][j];
visited[i][j] = true;
dp[i][j] = Integer.MAX_VALUE;
// 四边形不等式优化
int lower = s[i][j-1];
int upper = (i == j-1) ? i : s[i+1][j];
if (lower == 0) lower = i;
if (upper == 0) upper = j-1;
for (int k = lower; k <= upper; k++) {
int leftCost = memoizedSolve(i, k, dp, s, visited, prefixSum);
int rightCost = memoizedSolve(k+1, j, dp, s, visited, prefixSum);
int totalCost = leftCost + rightCost + (prefixSum[j] - prefixSum[i-1]);
if (totalCost < dp[i][j]) {
dp[i][j] = totalCost;
s[i][j] = k;
}
}
return dp[i][j];
}
}
五、面试常见问题与解答
5.1 理论问题
Q1:什么是四边形不等式?
/**
* 四边形不等式定义:
* 对于任意a≤b≤c≤d,权函数w满足:
* 1. 区间单调性:w(a,d) ≥ w(b,c)
* 2. 四边形不等式:w(a,d) + w(b,c) ≥ w(a,c) + w(b,d)
*
* 如果满足这两个条件,则对应的DP可以使用四边形不等式优化。
*/
Q2:四边形不等式优化能降低多少时间复杂度?
/**
* 时间复杂度对比:
* - 未优化:O(n³)
* - 四边形不等式优化后:O(n²)
*
* 优化原理:决策点具有单调性,缩小了决策点的搜索范围。
*/
5.2 实现细节问题
Q3:如何验证四边形不等式是否成立?
public class QuadVerification {
/**
* 通用验证方法
*/
public static boolean verifyQuadInequality(int n, QuadFunction w) {
for (int a = 1; a <= n; a++) {
for (int b = a; b <= n; b++) {
for (int c = b; c <= n; c++) {
for (int d = c; d <= n; d++) {
// 验证四边形不等式
if (w.apply(a, d) + w.apply(b, c)
< w.apply(a, c) + w.apply(b, d)) {
return false;
}
// 验证区间单调性
if (w.apply(a, d) < w.apply(b, c)) {
return false;
}
}
}
}
}
return true;
}
interface QuadFunction {
int apply(int a, int b);
}
}
Q4:四边形不等式优化的核心代码是什么?
/**
* 核心优化代码片段:
*
* for (int len = 2; len <= n; len++) {
* for (int i = 1; i + len - 1 <= n; i++) {
* int j = i + len - 1;
*
* // 关键优化:使用s[i][j-1]和s[i+1][j]缩小搜索范围
* int lower = s[i][j-1];
* int upper = (len == 2) ? i : s[i+1][j];
*
* for (int k = lower; k <= upper; k++) {
* // 状态转移
* }
* }
* }
*/
5.3 实际应用问题
Q5:哪些经典问题可以用四边形不等式优化?
/**
* 适用四边形不等式优化的问题:
* 1. 石子合并问题
* 2. 最优二叉搜索树
* 3. 邮局选址问题
* 4. 序列分割问题
* 5. 矩阵链乘法(特殊情况)
* 6. 括号匹配最大化问题
*/
六、完整测试用例
public class QuadInequalityTest {
public static void main(String[] args) {
System.out.println("=== 四边形不等式测试 ===");
// 测试1:石子合并问题
int[] stones1 = {1, 2, 3, 4, 5};
int result1 = StoneMerge.minStoneMergeCost(stones1);
System.out.println("石子合并最小代价: " + result1);
// 测试2:最优二叉搜索树
int[] keys = {10, 12, 20};
int[] freq = {34, 8, 50};
int[] dummyFreq = {0, 10, 10, 10};
int result2 = OptimalBST.optimalBSTCost(keys, freq, dummyFreq);
System.out.println("最优二叉搜索树代价: " + result2);
// 测试3:邮局选址问题
int[] positions = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int k = 3;
int result3 = PostOffice.minPostOfficeDistance(positions, k);
System.out.println("邮局选址最小距离: " + result3);
// 测试4:四边形不等式验证
boolean verified = verifyExampleQuadInequality();
System.out.println("四边形不等式验证: " + (verified ? "通过" : "失败"));
}
private static boolean verifyExampleQuadInequality() {
// 测试w(i,j) = (j-i+1)²是否满足四边形不等式
QuadInequality.Function<Integer, Integer> w = (a, b) -> {
int len = b - a + 1;
return len * len;
};
return QuadInequality.verifyQuadInequality(5, w);
}
}
七、性能优化技巧
7.1 循环优化
public class PerformanceTips {
/**
* 优化循环顺序
*/
public static void optimizedLoop(int n, int[] values) {
int[][] dp = new int[n+2][n+2];
int[][] s = new int[n+2][n+2];
int[] prefixSum = new int[n+2];
// 预处理前缀和
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + values[i-1];
}
// 优化1:按照区间长度递增的顺序计算
for (int len = 1; len <= n; len++) {
for (int i = 1; i <= n - len + 1; i++) {
int j = i + len - 1;
if (len == 1) {
dp[i][j] = 0;
s[i][j] = i;
continue;
}
dp[i][j] = Integer.MAX_VALUE;
// 优化2:利用四边形不等式缩小搜索范围
int lower = (len == 2) ? i : Math.max(s[i][j-1], i);
int upper = Math.min(s[i+1][j], j-1);
for (int k = lower; k <= upper; k++) {
int cost = dp[i][k] + dp[k+1][j]
+ prefixSum[j] - prefixSum[i-1];
if (cost < dp[i][j]) {
dp[i][j] = cost;
s[i][j] = k;
}
}
}
}
}
/**
* 使用滚动数组优化空间
*/
public static int spaceOptimized(int n, int[] values) {
int[][] dp = new int[2][n+2];
int[][] s = new int[2][n+2];
int[] prefixSum = new int[n+2];
for (int i = 1; i <= n; i++) {
prefixSum[i] = prefixSum[i-1] + values[i-1];
}
// 使用滚动数组
for (int len = 2; len <= n; len++) {
for (int i = 1; i <= n - len + 1; i++) {
int j = i + len - 1;
dp[len%2][i] = Integer.MAX_VALUE;
// ... 四边形不等式优化代码
}
}
return dp[n%2][1];
}
}
八、总结与面试要点
8.1 关键要点
- 四边形不等式条件:必须满足区间单调性和四边形不等式
- 决策单调性:最优决策点s[i][j]满足单调性
- 时间复杂度:从O(n³)优化到O(n²)
- 适用问题:区间DP、分割问题、优化问题
8.2 面试回答模板
/**
* 四边形不等式面试回答模板:
*
* 1. 定义:四边形不等式是一种优化区间DP的方法...
* 2. 条件:需要满足两个条件...
* 3. 优化原理:利用决策点的单调性缩小搜索范围...
* 4. 实现:使用s[i][j]记录最优决策点...
* 5. 复杂度:时间复杂度从O(n³)降到O(n²)...
* 6. 应用:石子合并、最优二叉搜索树等问题...
*/
8.3 常见错误与调试
public class CommonErrors {
/**
* 常见错误1:忘记初始化s数组
*/
public static void error1() {
int[][] s = new int[n+2][n+2];
// 错误:未初始化s[i][i]
// 正确:s[i][i] = i;
}
/**
* 常见错误2:四边形不等式条件不满足
*/
public static void error2() {
// 错误:使用不满足四边形不等式的权函数
// 正确:先验证四边形不等式条件
}
/**
* 常见错误3:边界条件处理错误
*/
public static void error3() {
// 错误:len==2时upper的取值
// 正确:upper = (len == 2) ? i : s[i+1][j]
}
/**
* 调试方法:验证四边形不等式
*/
public static void debugMethod() {
// 在小规模数据上验证四边形不等式
// 打印决策点观察单调性
// 对比优化前后的计算结果
}
}
这份完整的四边形不等式Java实现涵盖了理论、实现、优化、测试和面试各个方面,可以帮助你全面掌握这一重要算法优化技巧。
更多推荐



所有评论(0)