目录
【案例1】
【题目描述】
【思路解析】
【代码实现】
【案例2】
【题目描述】
【题目描述】
【代码实现】
【案例3】
【题目描述】
【思路解析】
【代码实现】
【案例4】
【题目描述】
先通过遍历数组得到整个数组的最小值和最大值,将【最小值,最大值】这个闭区间划分为n+1个小区间,然后整个数组的n个数字一定会分布在这n+1个小区间中,可能某一个区间会含有多个数,但是这些数中的相邻差值一定小于一个区间的长度,然后因为是n+1个小区间,就一定会有个别区间中没有数字,这些空区间左右的两边相邻数字差一定会大于一个区间的长度。(即这n+1个小区间,因为有空区间,所以给出了一个平凡解(区间长度),所以我们不需要再考虑一定小于平凡解的解,所以我们不必求解相同区间的相邻数字的解)。我们只用记录每个区间的最小值和最大值,然后在不同区间中求解。
/**
* @ProjectName: study3
* @FileName: Ex1
* @author:HWJ
* @Data: 2023/9/16 15:57
* 利用平凡解优化的技巧
*/
public class Ex1 {
public static void main(String[] args) {
}
public static int getMaxDeviation(int[] arr){
if (arr.length < 2) {
return -1; // 只有一个数字的数组,无法得到两个相邻数的差
}
int max = Integer.MIN_VALUE;
int min = Integer.MAX_VALUE;
int len = arr.length;
for (int i = 0; i < len; i++) {
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
if (max == min){ // 满足此条件表示整个数组为常数数组。
return 0;
}
boolean[] hasNum = new boolean[len + 1];
int[] maxs = new int[len + 1];
int[] mins = new int[len + 1];
int index = 0;
for (int i = 0; i < len; i++) {
index = getIndex(len + 1, arr[i], min, max);
maxs[index] = hasNum[index] ? Math.max(maxs[index], arr[i]) : arr[i];
mins[index] = hasNum[index] ? Math.min(mins[index], arr[i]) : arr[i];
hasNum[index] = true;
}
int lastMax = maxs[0];
int res = Integer.MIN_VALUE;
for (int i = 1; i < len + 1; i++) {
if(hasNum[i]){
res = Math.max(mins[i] - lastMax, res);
lastMax = maxs[i];
}
}
return res;
}
public static int getIndex(int len, int num, int min, int max){
return (int) (num - min) * len / (max - min);
}
}
给出n个数字a1,……an,问如何将这个数组划分为多个部分,使得这个划分中异或和等于0的部分尽可能多。问最多是多少。
对于任何一个i位置上的数字,只有两种可能性,(1)它在最优化分情况下能使从k位置到i位置的异或和等于0 ,(2)它在最优划分情况下不能使某一个部分的异或和等于0。所以我们需要解决如果i位置能使最优划分情况下从k位置到i位置的异或和等于0,我们怎么找到这个最近的k位置。我们可以在遍历时记录从0-i位置的异或和,异或和为m,则上一次达到m的位置是k-1。利用这个记录结构我们可以找到那些部分能够使异或和为0,然后再满足不重复的条件下,得到最优划分情况。
import java.util.HashMap;
/**
* @ProjectName: study3
* @FileName: Ex2
* @author:HWJ
* @Data: 2023/9/16 16:31
*/
public class Ex2 {
public static void main(String[] args) {
int[] arr= {3,2,1,4,0,4,0,3,2,1};
System.out.println(getBestDivision(arr));
}
public static int getBestDivision(int[] arr){
int[] division = new int[arr.length];
HashMap map = new HashMap<>();
map.put(0, -1); // 初始化记录表
int xor = 0;
for (int i = 0; i < arr.length; i++) {
xor ^= arr[i];
if (map.containsKey(xor)){
int pre = map.get(xor);
division[i] = pre == -1 ? 1 : Math.max(division[pre] + 1, division[i - 1]);
}
map.put(xor, i);
}
return division[arr.length - 1];
}
}
这道题可以看作使用普通币完成a面值有x种方法,纪念币完成m-a面值有y种方法。然后对于这样使用普通币和纪念币完成m面值的方法为 x*y。所以我们使用两个动态规划分别得到普通币完成0……m面值的方法数,纪念币完成0……m面值的方法数。
对于普通币的动态规划可以进行斜率优化。
1 | 0 | 0 | 1 | 0 |
1 | ||||
1 |
对于上面的表格可以做是普通币动态规划需要填充的表格,对于上面表格dp[i, j]表示使用0……i种,完成 j 面值的方法数。假设有三种货币分别为3,2,1,需要完成的面值为4。第一列根据定义表示为,使用 i种货币完成0面值的方法数,即不使用币,均为1。第一行表示为使用3这个货币,完成 j面值的方法数,然后第二行表示为使用 2和3这两个货币,完成 j面值的方法数,对于这一行任一j位置,它依赖与dp[i-1][j] dp[i-1][j - 3] dp[i-1][j - 6].......对于j - 3依赖dp[i-1][j - 3] dp[i-1][j - 6].......所以我们对于任一j位置可以优化为dp[i][j] = dp[i-1][j ] + dp[i][j - 3] .
/**
* @ProjectName: study3
* @FileName: Ex3
* @author:HWJ
* @Data: 2023/9/16 17:09
*/
public class Ex3 {
public static void main(String[] args) {
int[] arr1 = {2,3,4};
int[] arr2 = {2,3,1};
System.out.println(dpWays(arr1, arr2, 8));
}
public static int dpWays(int[] arr1, int[] arr2, int m){
int[][] dp1 = new int[arr1.length][m + 1];
int[][] dp2 = new int[arr2.length][m + 1];
for (int i = 0; i < arr1.length; i++) {
dp1[i][0] = 1;
}
for (int i = arr1[0]; i < m + 1; i += arr1[0]) {
dp1[0][i] = 1;
}
for (int i = 1; i < arr1.length; i++) {
for (int j = 1; j < m + 1; j++) {
dp1[i][j] = dp1[i - 1][j] + ((j - arr1[i]) >= 0 ? dp1[i][j - arr1[i]] : 0);
}
}
for (int i = 0; i < arr2.length; i++) {
dp2[i][0] = 1;
}
dp2[0][arr2[0]] = 1;
for (int i = 1; i < arr2.length; i++) {
for (int j = 1; j < m + 1; j++) {
dp2[i][j] = dp2[i - 1][j] + ((j - arr2[i]) >= 0 ? dp2[i - 1][j - arr2[i]] : 0);
}
}
int ans = 0;
for (int i = 0; i < m + 1; i++) {
ans += dp1[arr1.length - 1][i] * dp2[arr2.length - 1][m - i];
}
return ans;
}
}