算法20--数组分成长度相等两个数组,使两部分和最接近

给定一个数组,长度为偶数,将数组分成长度相等两部分,使两部分和最接近

先介绍01背包,然后解决长度可以不相等的情况,最后解决该问题

问题0: 01背包问题---k件商品,每件都有重量wi以及价格vi,给定一个袋子容量为W,求袋子中存放商品价值最大的取货方案

令f(k,w)为袋里可用容量为w时,取前k件商品的最大总价值

若已知f(k-1,w),求出f(k,w):第k件商品重量为wk,价格vk

若wk>w,则袋子容量不够,不能取第k件  f(k,w) = f(k-1, w)

若wk<w,则袋子容量足够,若取第k件,有总价值f(k-1,w-wk)+vk ,若不取则f(k-1, w)    有:  f(k,w) = max{f(k-1,w-wk)+vk,f(k-1,w)}

递归方式解决:

def bag(k, w, v, W):
    if k==-1:
        return 0
    if w[k]>W:
        return bag(k-1, w, v, W)
    else:
        return max(bag(k-1,w,v,W), bag(k-1,w,v,W-w[k])+v[k])

临界条件k==-1时,不论背包可用容量如何,没有商品可以取,直接返回0;当k=0时第一件商品,若w[0]>W,则返回bag(-1,W)=0

若w[0]<w则返回max(bag(-1,W),bag(-1,W-w0)+v[0])

非递归方式:

动态规划方法,利用辅助数组dp[k+1][W+1],其中dp[0][:]表示没有商品时总价值    dp[:][0]表示背包容量为0总价值,对于每一种商品,对于每个容量,利用递推式来进行递推求解。

def bag2(k, w, v, W):
    #k为商品总数
    #dp = [[0]*(W+1)]*(k+1)
    #dp = [([0]*(W+1)) for i in range(k+1)]
    dp = [[0 for i in range(W+1)] for i in range(k+1)]
    for i in range(W+1):
        dp[0][i] = 0
    for i in range(k+1):
        dp[i][0] = 0
    print (mat(dp))
    for i in range(1,k+1):
        for j in range(1,W+1):      
            if j<w[i-1]:
                dp[i][j] = dp[i-1][j]
            else:
                dp[i][j] = max(dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1]) 
            print (i,j,w[i-1],dp[i][j],dp[i-1][j],dp[i-1][j-w[i-1]]+v[i-1])
            print (mat(dp))                
    #print (dp)
    return dp[k][W]

python中二维数组的声明方式:

dp = [[0]*(W+1)]*(k+1)  不要轻易使用list*n 后面均是第一个元素的浅拷贝,太坑,这种方式 是错误的。
dp = [([0]*(W+1)) for i in range(k+1)]
dp = [[0 for i in range(W+1)] for i in range(k+1)]

初始化数组,并将第一行以及第一列置为0:

从第二行(第一件商品)开始遍历,每次增大容量值,计算最大价值,当计算到dp[1][2]时,此时容量=2,而第一件商品重量w[0]=2,即可以放入背包中,此时dp[1][2]=3 同理对于第一件商品而言,之后继续增大容量有:

最终的数组:

问题1:将数组分成两部分(个数可以不相等),使其两部分和最接近

假定数组数字之和为sum, 可以转化为在一个数组中挑选一部分数使其最接近sum/2。

利用01背包来解决:

令dp[i][j]表示从前i个数中取任意个数,且这些数之和为j的取法是否存在

dp[:][0]第一列表示从前几个数中取数使得和为0,易知  dp[:][0]=True

dp[0][1:]第一行除去第一个  表示不取数,使得和从1到目标值的取法,易有  dp[0][1:] = False

def splitArray(num):
    n = len(num)
    sum = 0
    for i in range(n):
        sum = sum + num[i]
    target = int(sum/2)
    #dp[i][j]从前i个数中随意挑选一些数  其和是否可以等于j
    dp = [[False for i in range(target+1)] for i in range(n+1)]
    for i in range(n+1):
        dp[i][0] = True
    for i in range(1, target+1):
        dp[0][i] = False
    print (mat(dp))    
    for i in range(1, n+1):
        for j in range(0, target+1):
            if j>=num[i-1]:
                dp[i][j] = dp[i-1][j-num[i-1]] or dp[i-1][j]
            else:
                dp[i][j] = dp[i-1][j]
            print (mat(dp))
    print (mat(dp))
    for j in range(target, 0, -1):
        if dp[n][j]:
            print(sum - 2*j)
            return sum - 2*j       
    return None

接下来对于每一行,依次测试目标值从1逐渐增大是否可以得到

最终结果是最后一行中,最接近目标值的数

输入数组  num=[2,3,4,5,9]

初始化:

计算第一个数可以得到的值:  第一个数为2  从前1个数中可以得到的和只有2,即第三列=true

最终结果:

解法2:

dp[k][s] 表示从前k个数中取任意个数,且这些数之和为s的取法是否存在

外阶段:在前k1个数中进行选择,k1=1,2...n。
内阶段:从这k1个数中任意选出k2个数,k2=1,2...k1。

状态:这k2个数的和为s,s=1,2...sum/2。

决策:决定这k2个数的和有两种决策,一个是这k2个数中包含第k1个数,另一个是不包含第k1个数。

range函数(a,b,index)从[a,b)以步长index递进  注意不包含b

def splitArray2(num):
    n = len(num)
    sum = 0
    for i in range(n):
        sum = sum + num[i]
    target = int(sum/2)
    #dp[i][j]从前i个数中随意挑选一些数  其和是否可以等于j
    dp = [[False for i in range(target+1)] for i in range(n+1)]
    for i in range(n+1):
        dp[i][0] = True
    for i in range(1, target+1):
        dp[0][i] = False
    print (mat(dp))  
    
    #从前i个数中取i个数,可以实现的和s有哪些
    for i in range(1,n+1):
        #print (i)
        for j in range(i, 0, -1):
            for s in range(1, target+1):
                print (i,j,s)
                if s>=num[i-1] and dp[j-1][s-num[i-1]]:
                    dp[j][s] = True
    print (mat(dp))
    
    #从前i个数中取任意个数,可以实现的和s有哪些
    for i in range(2, n+1):
        for s in range(1, target+1):
            if dp[i-1][s]:
                dp[i][s] = True
                
    for s in range(target, 1, -1):
        if dp[n][s]:
            print(sum - 2*s)
            return sum - 2*s       
    return None

基于动态规划数组,第一种解法,在初始化第一行以及第一列之后,然后逐行填充数组,最后返回结果;

解法二也是基于数组,初始化数组之后,首先遍历前i个数中计算取前i个可以的和有哪些,i表示前i个数,j表示遍历这i个数,观察目标值1--target计算哪些值可以计算得到

然后计算从前i个数中取任意个数,可以的和有哪些,对应于矩阵的列,若dp[i][j]==true则dp[i+1][j]也可以得到

初始化:

计算前k个数可以得到的和有哪些:

前k个数去任意个数:

问题2:给定一个数组,长度为偶数,将数组分成长度相等两部分,使两部分和最接近

选出的物体数必须为n/2

dp[k][s]表示从前k个数中取k个数,且k不超过n/2,且这些数之和为s的取法是否存在

def splitArray3(num):
    n = len(num)
    l = int(n/2)
    sum = 0
    for i in range(n):
        sum = sum + num[i]
    target = int(sum/2)
    #dp[i][j]从前i个数中随意挑选一些数  其和是否可以等于j
    dp = [[False for i in range(target+1)] for i in range(n+1)]
    for i in range(n+1):
        dp[i][0] = True
    for i in range(1, target+1):
        dp[0][i] = False
    print (mat(dp))  
    
    #从前i个数中取i个数(i<n),可以实现的和s有哪些
    for i in range(1,n+1):
        #print (i)
        for j in range(min(l,i), 0, -1):
            for s in range(1, target+1):
                print (i,j,s)
                if s>=num[i-1] and dp[j-1][s-num[i-1]]:
                    dp[j][s] = True
    print (mat(dp))
    
    for s in range(target, 0, -1):
        if dp[l][s]:
            print(sum - 2*s)
            return sum - 2*s       
    return None

注意最后寻找要从n/2行寻找,因为每部分的个数为n/2,即必须从数组中取出n/2个数来不断逼近sum/2

输入num=[2,3,0,5]

初始化

最终dp数组:

dp[k][s]代表从前k个数中取k个数,且k不超过n/2,且这些数之和为s的取法是否存在。

  • 5
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值