将一个数组按照总和平均分成均匀的两个数组,并输出.
首先先计算出sum,然后判断sum%2.如果是奇数直接输出-1
应用01背包问题的解决思路.
该问题可以拆分为多个问题来:
1.将一个数组平均拆分为均匀的两个数组
2.将一个数组拆分为两个数组使得两个数组的差值最小
这是问题2:将一个数组拆分为两个数组使得两个数组的差值最小 的完整实现
使用01背包问题可以先求得’‘使得两个数组的差值最小’'的对应子数组的总和是多少
//注意target就是数组总和的一半:sum/2
//dp[i][j]表示从数组的[0,i]下标范围内选取若干个正整数,达到总和为j的最小总和
public static int func(int[] nums,int target){
int[][] dp = new int[nums.length+1][target+1];
for (int i=1;i<=nums.length;i++){
for (int j=1;j<=target;j++){
if (j>=nums[i-1]){//如果大于 就可以进行二选一
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
for (int i=0;i<dp.length;i++){
System.out.println(Arrays.toString(dp[i]));
}
System.out.println(dp[nums.length][target]);
return dp[nums.length][target];
}
通过这个总和继续使用动态规划01背包 求子数组的元素
//注意target就是数组总和的一半:sum/2
//dp[i][j]表示从数组的[0,i]下标范围内选取若干个正整数(可以是0个)
//是否存在一种选取方案使得被选取的正整数的和等于j
//这里的dp[n][target]就是能不能取到
public static boolean[][] func2(int[]nums,int target){
int n = nums.length;
boolean[][] dp = new boolean[n + 1][target + 1];
// 如果不选取任何正整数,则被选取的正整数等于 0。因此对于所有 0≤i<n,都有 dp[i][0]=true
for (int i = 0; i <= n; i++) {
dp[i][0] = true;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= target; j++) {
if (j - nums[i - 1] < 0) {//说明没办法取到j-nums[i-1]这个数
dp[i][j] = dp[i - 1][j];
} else {//只要前面i-1个数能取到j 或者 之前i-1个数能取到j-nums[i-1]即可
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - nums[i - 1]];
}
}
}
for (int i=0;i<dp.length;i++){
System.out.println(Arrays.toString(dp[i]));
}
return dp;
}
通过这个动态规划的结果,可以求子数组的元素.
boolean[][] booleans = func2(nums, ans);
List<Integer> list = new ArrayList<>();
int target = ans;
//寻找路径 从原来的boolean中先找target位置是否有满足的条件,有的话加入list,然后更新target
int n = nums.length;
while (target>0){
for (int i=1;i<=n;i++){
if (booleans[i][target]){
list.add(nums[i-1]);
//System.out.println(nums[i-1]);
target-=nums[i-1];
n=i-1;//之后的n只能从前面的糖果袋中取
break;
}
}
}
完整的代码如下:
package huatielu;/*
@author Oblak
@date 2022/4/14
@description 可以求得:将一个数组拆分为两个数组使得两个数组的差值最小
求出对应的两个数组
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
public class bag01 {
public static void main(String[] args) {
int[] nums = new int[]{1,3,5,7,5,2,5};
int sum = 0;
HashMap<Integer,Integer> map= new HashMap<>();
for (int i:nums){
sum+=i;
map.put(i,map.getOrDefault(i,0)+1);
}
int ans = func(nums, sum / 2);
boolean[][] booleans = func2(nums, ans);
List<Integer> list = new ArrayList<>();
int target = ans;
//寻找路径 从原来的boolean中先找target位置是否有满足的条件,有的话加入list,然后更新target
int n = nums.length;
while (target>0){
for (int i=1;i<=n;i++){
if (booleans[i][target]){
list.add(nums[i-1]);
//System.out.println(nums[i-1]);
target-=nums[i-1];
n=i-1;//之后的n只能从前面的糖果袋中取
break;
}
}
}
for (int i=0;i<list.size();i++){
if (i==0) System.out.print(list.get(0));
else System.out.print(" "+list.get(i));
map.put(list.get(i),map.get(list.get(i))-1);
}
System.out.println();
List<Integer> list2 = new ArrayList<>();
for (int i=0;i<nums.length;i++){
if (map.get(nums[i])>0){
list2.add(nums[i]);
map.put(nums[i],map.get(nums[i])-1);
}
}
for (int i=0;i<list2.size();i++){
if (i==0) System.out.print(list2.get(0));
else System.out.print(" "+list2.get(i));
}
}
public static int func(int[] nums,int target){
int[][] dp = new int[nums.length+1][target+1];
for (int i=1;i<=nums.length;i++){
for (int j=1;j<=target;j++){
if (j>=nums[i-1]){
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
for (int i=0;i<dp.length;i++){
System.out.println(Arrays.toString(dp[i]));
}
System.out.println(dp[nums.length][target]);
return dp[nums.length][target];
}
public static boolean[][] func2(int[]nums,int target){
int n = nums.length;
boolean[][] dp = new boolean[n + 1][target + 1];
// 如果不选取任何正整数,则被选取的正整数等于 0。因此对于所有 0≤i<n,都有 dp[i][0]=true
for (int i = 0; i <= n; i++) {
dp[i][0] = true;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= target; j++) {
if (j - nums[i - 1] < 0) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - nums[i - 1]];
}
}
}
for (int i=0;i<dp.length;i++){
System.out.println(Arrays.toString(dp[i]));
}
return dp;
}
}
对于问题1:将一个数组平均拆分为均匀的两个数组 其实是一个特例 只需要在刚才的代码中添加一些判断即可.
package huatielu;/*
@author Oblak
@date 2022/4/13
@description
*/
import org.junit.Test;
import java.util.*;
public class Main3 {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int[] nums = new int[n];
int sum = 0;
HashMap<Integer,Integer> map = new HashMap<>();
for (int i=0;i<n;i++){
nums[i] = sc.nextInt();
sum+=nums[i];
map.put(nums[i],map.getOrDefault(nums[i],0)+1);
}
if (sum%2==1){
System.out.println(-1);
}else{
boolean[][] booleans = func2(nums, sum / 2);
//如果booleans[i][target]都无法满足 则返回-1
boolean flag = booleans[0][sum/2];
for (int i=1;i<booleans.length;i++){
flag = flag||booleans[i][sum/2];
if (flag) break;
}
if (!flag){
System.out.println(-1);
return;
}
int ans = sum/2;
List<Integer> list = new ArrayList<>();
int target = ans;
//寻找路径 从原来的boolean中先找target位置是否有满足的条件,有的话加入list,然后更新target
while (target>0){
for (int i=1;i<=n;i++){
if (booleans[i][target]){
list.add(nums[i-1]);
//System.out.println(nums[i-1]);
target-=nums[i-1];
n=i-1;//之后的n只能从前面的糖果袋中取
break;
}
}
}
System.out.println(ans);
//从大到小排个序
Collections.sort(list, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (int i=0;i<list.size();i++){
if (i==0) System.out.print(list.get(0));
else System.out.print(" "+list.get(i));
map.put(list.get(i),map.get(list.get(i))-1);
}
System.out.println();
List<Integer> list2 = new ArrayList<>();
for (int i=0;i<nums.length;i++){
if (map.get(nums[i])>0){
list2.add(nums[i]);
map.put(nums[i],map.get(nums[i])-1);
}
}
Collections.sort(list2, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
for (int i=0;i<list2.size();i++){
if (i==0) System.out.print(list2.get(0));
else System.out.print(" "+list2.get(i));
}
}
}
//这个函数可以得到一个数组平均分成两等分的情况 (01背包问题)
public static int[][] func(int[] nums,int target){
int[][] dp = new int[nums.length+1][target+1];
for (int i=1;i<=nums.length;i++){
for (int j=1;j<=target;j++){
if (j>=nums[i-1]){
dp[i][j] = Math.max(dp[i-1][j],dp[i-1][j-nums[i-1]]+nums[i-1]);
}else{
dp[i][j] = dp[i-1][j];
}
}
}
for (int i=0;i<dp.length;i++){
System.out.println(Arrays.toString(dp[i]));
}
System.out.println(dp[nums.length][target]);
return dp;
}
public static boolean[][] func2(int[]nums,int target){
int n = nums.length;
boolean[][] dp = new boolean[n + 1][target + 1];
// 如果不选取任何正整数,则被选取的正整数等于 0。因此对于所有 0≤i<n,都有 dp[i][0]=true
for (int i = 0; i <= n; i++) {
dp[i][0] = true;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= target; j++) {
if (j - nums[i - 1] < 0) {
dp[i][j] = dp[i - 1][j];
} else {
dp[i][j] = dp[i - 1][j] | dp[i - 1][j - nums[i - 1]];
}
}
}
// for (int i=0;i<dp.length;i++){
// System.out.println(Arrays.toString(dp[i]));
// }
return dp;
}
}