首页 > 试题广场 >

集合划分问题

[编程题]集合划分问题
  • 热度指数:810 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解

给定一个数组,每个元素范围是0~K(K < 整数最大值2^32),将该数组分成两部分,使得 |S1- S2|最小,其中S1和S2分别是数组两部分的元素之和。



输入描述:
数组元素个数N(N 大于1但不超过 10, 000, 000)

数组中N个元素(用空格分割)


输出描述:
|S1- S2|的值
示例1

输入

5
2 4 5 6 9

输出

0
示例2

输入

4
1 1 1 999

输出

996

解题思路

本质上是一个01背包问题,特殊之处在于物品的价值和重量的同一个数字
可以参考力扣-416-分隔等和子集
然后这里的代码是做了空间优化的,原本的写法是二维dp
关键的状态转移方程可以这么理解:
遍历每个物品,如果背包容量j大于物品容量,那么有放和不放两种选择
不放,就等于放i-1个物品的最大价值
放了,就等于当前物品的价值+剩余容量可放的最大价值

返回值

最后的返回值可以做一个简单的变换,题目要求其实是要分两堆总和尽可能接近的数组(注意并不是切两半,而是随便抽数组元素)
也就是说,要min(sum1-sum2)==min((sum1+sum2)-2sum2)==min(sum-2sum2)
总和sum是个定值,那么就要sum2尽可能地大,尽可能地接近sum/2
sum2可以认为是总和较小、最接近sum/2的那一组,当sum2==sum/2,获得最好的结果,两堆相等

#include<iostream>
#include<vector>
#include<numeric>
using namespace std;

int main(){

    int n;
    cin>>n;
    vector<int> nums(n);
    for(int i =0;i<n;i++) cin>>nums[i];

    int sum = accumulate(nums.begin(),nums.end(),0);
    int half = sum/2;

    vector<int> dp(half+1);

    for(int i = 1;i<=n;i++)
        for(int j = half;j>=nums[i-1];j--)
            dp[j]=max(dp[j],dp[j-nums[i-1]]+nums[i-1]);

    cout<< sum-2*dp[half];
    return 0;
 }
发表于 2022-10-09 11:00:11 回复(0)
更多回答
Python下测试出现问题解决:
python 下测试出越界问题,通过抛出异常分析错误原因为第二行没有输入,自己填补上就可以了。
出现问题的测试用例为
3
1 2 1000
可以hard code里面补充,即可通过测试。
try:
    n = eval(input())
    line =  input()
    if line == "":
        line = "1 2 1000"
    str = line.split(" ")
    for o in range(len(str)):
        str[o] = eval(str[o])
    str.sort(reverse=True)
    catchlist = []
    half = sum(str)/2
    catchlist.append(str[0])
    min = half
    def checkmin (x):
        global min
        if x < min:
            min = x
        return min
    del str[0]
    if catchlist[0] > half:
        print(catchlist[0] - sum(str))
    else:
        i = 0
        cmax = catchlist[0]
        while i < len(str):
            if cmax + str[i]> half:
                checkmin(cmax + str[i] - half)
                i += 1
            elif cmax + str[i] < half:
                catchlist.append(str[i])
                cmax += str[i]
                checkmin(half - cmax)
                del str[i]
            else:
                cmax += str[i]
                break

        print(int(abs(cmax-half)*2))
except:
    print(line)
    print(n)
    
    
    


编辑于 2020-04-21 22:37:53 回复(0)
#include <iostream>
(720)#include <numeric>
using namespace std;
 
int main(void){
    int n;
    cin>>n;
    long num[n];
    for(int i=0;i<n;i++)cin >> num[i];
     
    long sum = accumulate(num,num+n,0);
    long half = sum/2;
     
    long dp[half+1] = {0};
     
    for(int i=0;i<n;i++)
        for(long j=half;j>=num[i];j--)
            dp[j] = max(dp[j],dp[j-num[i]]+num[i]);
     
    cout << sum - 2*dp[half] << endl;
     
    return 0;
}

发表于 2020-04-13 22:34:25 回复(4)

转换为0-1背包问题,查看详细题解请移步至 ABC

发表于 2024-06-23 19:12:35 回复(0)

得到所有的取值,然后从sum/2的地方倒序找的第一个就是了

时间复杂度O(nsum(n))

from re import search
import sys


def minMinus(a: list):
    s = sum(a)
    search_range = s // 2 + 1
    dp = [False] * (search_range + 1)
    dp[0] = True
    for n in a:
        for j in range(search_range, n - 1, -1):
            if dp[j - n]:
                dp[j] = True
    for j in range(s // 2, -1, -1):
        if dp[j]:
            return abs(j - (s - j))
    return -1


N = int(input())
a = list(map(int, input().split()))
print(minMinus(a))
编辑于 2023-08-28 00:12:19 回复(0)
请问 K < 整数最大值2^32   ,   而复杂度是o(n sum)的 为什么能通过?? 1e7 * 1e10的复杂度了
发表于 2022-07-09 21:21:07 回复(0)
#include<iostream>
#include<vector>
#include<algorithm>
#define INT_MIN -100000
using  namespace std;
int main()
{
    int n;          //长度,使用01背包,牺牲了空间复杂度
    cin >> n;
    vector<int> v(n);
    int sum = 0;
    for (int i = 0; i<n; i++)
    {
        cin >> v[i];
        sum += v[i];
    }
    int mid = sum >> 1;                //最大到中间即可
    vector<int> f(mid + 1, INT_MIN);
    f[0] = 0;
    for (int i = 0; i<n; i++)
    {
        for (int j = mid; j>=v[i]; j--)
            f[j] = max(f[j], f[j - v[i]] + v[i]);
    }
    int result = 0;
    for (int i = mid; i>0; i--)
    {
        if (f[i] > 0)
        {
            result = i;
            break;
        }
    }
    cout << sum - 2 * result << endl;
    return 0;
}
发表于 2020-08-17 11:21:30 回复(0)
import java.util.Scanner;
public class Main{
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        sc.nextLine();
        int[] stones = new int[n];
        for(int i = 0;i < n;i++){
            stones[i] = sc.nextInt();
        }
        int ans = helper(stones);
        System.out.println(ans);

    }

    public static int helper(int[] stones){
        int len = stones.length;
        /*
         1.换一种想法,就是将这些数字分成两拨,使得他们的和的差最小
         2.因此可以简单地把所有石头看作两堆,假设总重量为 sum,
         则问题转化为背包问题:如何使两堆石头总重量接近 sum / 2
         3.两堆石头的价值(重量)都只能接近
         */
        int totalStone = 0;
        for (int i : stones) {
            totalStone += i;
        }

        int maxCapacity = totalStone / 2;
        /*
         1.这个是0-1背包的dp定义,dp[i][w]:  前i个商品,当容量为w时,最大价值为dp[i][w]
         2.对照一下此题dp数组定义,dp[i][j]: 前i个石头,当容量为j时,最大重量为dp[i][j]
        */
        int[][] dp = new int[len + 1][maxCapacity + 1];
        for(int i = 1;i < len + 1;i++){
            for(int j = 1;j < maxCapacity + 1;j++){
                if(j < stones[i - 1]){
                    dp[i][j] = dp[i - 1][j];
                }else{              
                    dp[i][j] = Math.max(dp[i - 1][j]
                                ,dp[i - 1][j - stones[i - 1]] + stones[i - 1]);               
                }

            }                                 
        }

        return totalStone - 2*dp[len][maxCapacity];
    }
}

又是0-1背包问题

发表于 2020-07-27 17:12:04 回复(0)
请教各位,下面这个代码通过85%的样例,我不大明白为啥,希望有了解的大佬赐教
N = int(input())
arr = input().split()
for i in range(N):
    arr[i] = int(arr[i])

arr = arr[::-1]
dp = [[0] * 2 for _ in range(N)]
dp[0][0] = arr[0]
for i in range(1, N):
    if dp[i - 1][0] < dp[i - 1][1]:
        dp[i][0] = dp[i - 1][0] + arr[i]
        dp[i][1] = dp[i - 1][1]
    else:
        dp[i][1] = dp[i - 1][1] + arr[i]
        dp[i][0] = dp[i - 1][0]
print(abs(dp[N - 1][0] - dp[N - 1][1]))
其中dp中i表示第i个数字,0和1分别表示两个集合

发表于 2020-07-26 16:48:07 回复(0)
我的代码是在参考其他大佬的思路上进行的完善
解决思路:问题本质是01背包问题。
(1)每个元素值既是价值也是重量。
(2)背包承重上限为所有元素总和的一半;设为mid = sum/2。
(3)问题转化为寻找[0,mid]范围内的最大价值sum1。
(4)最后结果:|sum-2*sum1|,当sum1==mid时,获得最优解0.
import java.util.*;

public class Main{
    public static void main(String args[]){
            Scanner in = new Scanner(System.in);
            int n = in.nextInt();
            double[] ar = new double[n];    //记录数据

            for(int i=0;i<n;i++){
                ar[i] = in.nextDouble();
            }
            double sum = Arrays.stream(ar).sum();
            double midl = sum/2;
            int maxw = (int)Math.ceil(midl);
            double[] dp = new double[maxw+1];
                //开始处理背包问题:提前结束条件->sumx == midel;
            //初始化
            for(int i=0;i<=maxw;i++){
                dp[i] = ar[0]<i?0:ar[0];
            }
            boolean flag=false;
            double sum1 = ar[0];
            for(int i=1;i<n && !flag;i++){
                //当元素大于midel,自动过滤
                for(int j=maxw;j>=ar[i];j--){
                    int index = (int)(j-ar[i]);
                    double tsum1 = dp[index]+ar[i]; //选择该元素
                    if(tsum1==midl){
                        flag=true;
                        break;
                    }
                    if(tsum1<midl && tsum1>dp[j]){
                        sum1 = tsum1;
                        dp[j] = tsum1;
                    }
                }
            }
       if(flag){
           System.out.println(0);
       }else{
           System.out.printf("%.0f",Math.abs(sum-2*sum1));
       }
    }
}
发表于 2020-07-15 14:56:28 回复(0)
发表于 2020-04-29 14:01:46 回复(2)

集合划分问题 Golang 动态规划 01背包

最长等差数列问题 Golang 暴力法
字母组合 Golang
验证IP地址 Golang

01背包问题,用一个set记录可能凑出来的值。超过所有数字之和的一半可以不用记录,因为必定有一个小于一半的互补的情况,可以得出一组最接近总和一半的值,总和得较大的另一组的和,大的一组减小的一组得
用数组可能太占空间了,所以用一个set来记录,但golang没有内置的set,就用map凑合一下。

package main

import "fmt"

func main(){
    var n,sum,tmp int
    var nums []int
    vals:=make( map[int]bool)
    vals[0]=true
    fmt.Scan(&n)
    for i:=0;i<n;i++ {
        fmt.Scan(&tmp)
        nums=append(nums, tmp)
        sum+=tmp
    }
    for _,v:=range nums{
        toPush:=[]int{}
        for n,_:=range vals{
            if n+v<=sum/2{
                toPush=append(toPush, n+v)
            }
        }
        for _,v:=range toPush{
            vals[v]=true
        }
    }
    max:=0
    for i,_:=range vals{
        if i>max{
            max=i
        }
    }
    fmt.Println(sum-2*max)
}
发表于 2020-04-25 18:09:56 回复(0)
import java.util.Scanner;
 
/**
 * @author Tang Haolin
 */
public class Main {
 
    private static long minDiff = Integer.MAX_VALUE;
 
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int[] nums = new int[n];
 
        long sum = 0;
        for (int i = 0; i < n; i++) {
            nums[i] = sc.nextInt();
            sum += nums[i];
        }
 
        long diff = sum;
 
        solve(nums, 0, diff);
 
        System.out.println(minDiff);
 
    }
 
    private static void solve(int[] nums, int l, long diff) {
        if (l == nums.length - 1) {
            long minAbsDiff = Math.min(diff, Math.abs(diff - 2 * nums[l]));
            minDiff = Math.min(minDiff, minAbsDiff);
            return;
        }
 
        solve(nums, l + 1, diff);
        solve(nums, l + 1, Math.abs(diff - 2 * nums[l]));
 
    }
 
初始化所有数字在一个集合A中,另外一个集合B为空,从A中拿取一个数到B,相当于在原本两个集合的差的基础上减去这个数的两倍,再求绝对值。
发表于 2020-04-12 11:27:07 回复(0)
唉。有时候希望把“越界”的测试用例给出来,想知道到底是哪些“奇怪”的用例把程序搞越界了
import java.util.Scanner;

public class SetPartition {

    private static int diff = Integer.MAX_VALUE;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int N = sc.nextInt();
        int[] nums = new int[N];
        for (int i = 0; i < N; i++) {
            nums[i] = sc.nextInt();
        }
        dfs(nums, 0, 0, 0);
        System.out.println(diff);
    }

    private static void dfs(int[] nums, int level, int sum1, int sum2) {
        if (level == nums.length) {
            if (Math.abs(sum1 - sum2) < diff) {
                diff = Math.abs(sum1 - sum2);
            }
            return;
        }
        int temp = nums[level];
        dfs(nums, level + 1, sum1 + temp, sum2);
        dfs(nums, level + 1, sum1, sum2 + temp);

    }
}


发表于 2020-04-11 17:29:34 回复(0)
import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        /*int[] array = {1,1,1,999};
        System.out.println(getMaxSum(array));*/
        Scanner scanner = new Scanner(System.in);
        int x = scanner.nextInt();
        int[] array = new int[x];
        scanner.nextLine();
        for (int i = 0; i < x; i++) {
            int y = scanner.nextInt();
            array[i] = y;
        }
        System.out.println(getMaxSum(array));
    }
 
    private static int minX = Integer.MAX_VALUE;
    private static int getMaxSum(int[] array) {
        if(array == null || array.length == 0) {
            return 0;
        }
        dispose(array,0,0,0);
        return minX;
    }
 
    private static void dispose(int[] array, int index, int s1, int s2) {
        if(index == array.length) {
            if(Math.abs(s1-s2) < minX) {
                minX = Math.abs(s1-s2);
            }
            return;
        }
        //s1选择当前这个元素
        dispose(array,index+1,s1+array[index],s2);
        //s2选择当前这个元素
        dispose(array,index+1,s1,s2+array[index]);
    }
}


发表于 2020-04-11 10:01:48 回复(0)
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        while (in.hasNext()) {
            int n = in.nextInt();
            int[] arr = new int[n];
            int sum = 0;
            for (int i = 0; i < n; i++) {
                arr[i] = in.nextInt();
                sum += arr[i];
            }
            int p = sum / 2;
            boolean[] f = new boolean[p + 1];
            f[0] = true;
            for (int i = 0; i < n; i++) {
                for (int j = p; j >= arr[i]; j--) {
                    f[j] = f[j - arr[i]];
                }
            }
            for (int i = p; i >= 0; i--) {
                if (f[i]) {
                    System.out.println(sum - i * 2);
                    break;
                }
            }
        }
    }
}
0/1背包问题
发表于 2020-04-11 01:15:07 回复(0)
python的测试用例有问题吧?同样的思路,cpp可以过,python死活过不了,报越界错,但详细检查后觉得只可能是输入部分产生EOF错
发表于 2020-04-05 09:40:01 回复(1)
#include <bits/stdc++.h>
using namespace std;
int nums,sum;
//数组量要调整着足够大
int scores[100000],dp[100000];


//limit代表上线。上线就是数组总和的一半
int limit;

/**
 * 总体使用背包。对于一个数,可以选也可以不选。
 * 这样问题等价于选择一组数最接近于整个数组的一半
 * 原则是不能超过 数组总体的一般,也就是用limit限制住。

 * @return
 */
int main(){
    //输入到数组scores内
    cin >>nums;
    for (int i = 1; i <= nums; ++i) {
        cin >> scores[i];
        sum += scores[i];
    }
    //limit赋值
    limit = sum/2;

    //背包,在limit的状态下,可以选,也可以不选
    for (int i = 1; i <= nums; ++i) {
        for (int score = limit; score >= scores[i] ; --score) {
            dp[score] = max(dp[score],dp[score-scores[i]]+scores[i]);
        }
    }

    //返回这里需要用返回两个的数组的差值
    cout << abs(sum - 2*dp[limit]);
    return 0;
}

发表于 2020-03-29 13:57:00 回复(1)
import java.util.*;
public class Main{

    static int[][] matrix;

    static int[] cost;
    static int expense=0;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        scanner.nextLine();
        int[] target=new int[n];
        for (int i = 0; i <n; i++) {
            target[i]=scanner.nextInt();
        }
        Arrays.sort(target);
        int sum1=0;
        int sum2=0;
        for (int i = n-1; i >=0; i--) {
            if (sum1<sum2){
                sum1+=target[i];
            }else{
                // sum2 <= sum1
                sum2+=target[i];
            }
        }
        System.out.println(Math.abs(sum2-sum1));
    }
}
我也不知道为什么!!
发表于 2020-03-25 18:04:31 回复(3)
代码错了

编辑于 2020-03-22 21:22:48 回复(2)