2019 算法面试相关(leetcode)--动态规划之背包问题

2019 iOS面试题大全---全方面剖析面试
2018 iOS面试题---算法相关
1、七种常见的数组排序算法整理(C语言版本)
2、2019 算法面试相关(leetcode)--数组和链表
3、2019 算法面试相关(leetcode)--字符串
4、2019 算法面试相关(leetcode)--栈和队列
5、2019 算法面试相关(leetcode)--优先队列
6、2019 算法面试相关(leetcode)--哈希表
7、2019 算法面试相关(leetcode)--树、二叉树、二叉搜索树
8、2019 算法面试相关(leetcode)--递归与分治
9、2019 算法面试相关(leetcode)--贪心算法
10、2019 算法面试相关(leetcode)--动态规划(Dynamic Programming)
11、2019 算法面试相关(leetcode)--动态规划之背包问题


背包问题(Knapsack problem)是一种组合优化的NP完全问题。问题可以描述为:给定一组物品,每种物品都有自己的重量和价格,在限定的总重量内,我们如何选择,才能使得物品的总价格最高。问题的名称来源于如何选择最合适的物品放置于给定背包中。相似问题经常出现在商业、组合数学,计算复杂性理论、密码学和应用数学等领域中。也可以将背包问题描述为决定性问题,即在总重量不超过W的前提下,总价值是否能达到V?它是在1978年由Merkel和Hellman提出的。
常见的背包问题:01背包、完全背包、多重背包

一、01背包问题

有M个物品,每个物品的重量为W[i],每个物品的价值为V[i]。
现在有一个背包,它所能容纳的重量为N
问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?


每个物品无非是装入背包或者不装入背包,即每个物品的取值无非就是0或者1,故而这题被称为01背包问题。
我们可以用动态规划的思路,在循环过程中比较第i个物品取与不取的大小,取其中大的一个,依次循环即可。
其状态转移方程为:dp[j] = Math.max(V[i] + dp[j - W[i]],dp[j]) ,表示背包已装容量为j时,背包里所装物品的最大总价值
其约束条件是:∑(num[i]*w[i]) <= N,其中num指第i个物品取的数量,取值为0或1,所以我们要有一个嵌套遍历。
需要注意的是,因为每个物品数量只有一个,即最多只能取一次,所以在遍历背包容量N的时候需倒序遍历,这样能保证不会重复添加物品

var zeroOneBag = function(W,V,N){

    let dp = Array(N + 1).fill(0)

    for(let i = 0; i < W.length; i++){
        
        for(let j = N; j >= w[i]; j--){

            dp[j] = Math.max(V[i] + dp[j - W[i]],dp[j]) 
        }
    }

    return dp[N]
}

二、完全背包问题

有M个物品,每种物品有无限个,每个物品的重量为weight[i],每个物品的价值为value[i]。
现在有一个背包,它所能容纳的重量为N
问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?


完全背包问题和01背包问题很像,解决思路也和上边的差不多,区别就在于每种物品不限,即约束条件有变化:∑(num[i]*w[i]) <= N,其中num指第i个物品取的数量,取值为非负整数
所以我们只要正序遍历即可

var completelyBag = function(W,V,N){

    let dp = Array(N + 1).fill(0)

    for(let i = 0; i < W.length; i++){
        
        for(let j = W[i]; j <= N; j++){
            
            dp[j] = Math.max(V[i] + dp[j - W[i]],dp[j])
        }
    }
    return dp[N]
}

这种方法的时间复杂度:O(M*N),空间复杂度:O(N)

  • 因为每个背包数量不限制,这里其实没必要用动态规划,还有一种排序解法:V[i]/W[i]表示每点重量所提供的价值,即价重比。按价重比从大到小排序,然后依次遍历,直至背包不能再塞
    这种方法的时间复杂度则是O(MlogM),空间复杂度是O(M),是要优于动态规划的
/**
 * 物品对象
 *
 * @param {*} w 物品重量
 * @param {*} v 物品价值
 * @param {*} r 物品价重比
 */
function Goods(w,v,r) {
    this.w = w;
    this.v = v;
    this.r = r;
}
var completelyBag2 = function(W,V,N){

    let GoodsArr = []

    for(let i = 0; i < W.length; i++){

        GoodsArr[i] = new Goods(W[i],V[i],V[i]/W[i])
    }

    GoodsArr.sort((a,b) => b.r - a.r)  //将物品按价重比从大到小排序

    let res = 0

    for(let i = 0; i < GoodsArr.length; i++){
        
        let goods = GoodsArr[i]

        res += Math.floor(N/goods.w)*goods.v

        N = N%goods.w

        if(N == 0) return res
    }

    return res
}

三、多重背包问题

有N个物品,每种物品有nums[i]个,每个物品的重量为weight[i],每个物品的价值为value[i]。
现在有一个背包,它所能容纳的重量为V,
问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?


多重背包问题多了一个变量nums[i],但状态转移方程还是和上边差不多的
dp[j] = Math.max(kV[i] + dp[j - kW[i]],dp[j]),k即是指第i个物品取的数量,取值为0-nums[i]
因为不是无限,所以我们还是需要和01背包问题一样,去倒序遍历背包容量N
约束条件:∑(num[i]*w[i]) <= N,其中num指第i个物品取的数量,取值为0-nums[i]
所以我们在遍历的时候就需要再嵌套一层,添加变量:Math.min(~~(j/W[i]),nums[i]) ,表示第i件物品在容量为j的背包里可以带走的最大数量

var multipleBag = function(W,V,N,nums){

    let dp = Array(N + 1).fill(0)

    for(let i = 0; i < W.length; i++){
        
        for(let j = N; j >= w[i]; j--){
            
            let count = Math.min(~~(j/W[i]),nums[i])    //第i件物品在容量为j的背包里可以带走的最大数量

            for(let k = 0; k <= count; k++){
                
                dp[j] = Math.max(k*V[i] + dp[j - k*W[i]],dp[j])
            }
        }
    }

    return dp[N]
 }
w = [5 ,4 ,7 ,2 ,6]

v = [12 ,3 ,10, 3 ,6]

nums = [2,4,1,5,3]

console.log(zeroOneBag(w,v,15))

console.log(completelyBag(w,v,15))

console.log(completelyBag2(w,v,15))

console.log(multipleBag(w,v,15,nums))

输出结果分别是:25,36,36,30


leetcode上并没有背包问题的题目,但有其类似的题目。

一、零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:

输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。


比较类似完全背包问题,只不过这种变成求最少数量。
其状态转移方程为dp[i] = Math.min(dp[i - c] + 1,dp[i]),dp[i]表示总金额为i时所需硬币的最小数量,如果为Infinity即无穷大时则说明无法凑成i;c为硬币数组中的元素,表示如果i比c大,可以使用c这个硬币加上dp[i - c]的数量凑成金额i,取其最少数量即可
和完全背包问题一样,这里也是正序遍历的

var coinChange = function(coins, amount) {
    
    let dp = Array(amount + 1).fill(Infinity)

    dp[0] = 0

    for (const c of coins) {

        for(let i = c; i < dp.length; i++){
        
            dp[i] = Math.min(dp[i - c] + 1,dp[i]) 
        }
    }

    return dp[amount] == Infinity ? -1 : dp[amount]
};
二、 一和零

在计算机界中,我们总是追求用有限的资源获取最大的收益。

现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。

你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。

注意:

给定 0 和 1 的数量都不会超过 100。
给定字符串数组的长度不会超过 600。
示例 1:

输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4

解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。
示例 2:

输入: Array = {"10", "0", "1"}, m = 1, n = 1
输出: 2

解释: 你可以拼出 "10",但之后就没有剩余数字了。更好的选择是拼出 "0" 和 "1" 。


这是一道比较典型的01背包问题。
只不过约束条件变成了m和n两个,那么dp就会是一个二维数组dp[m][n]
其动态转移方程为:dp[i][j] = Math.max(dp[i - zeros][j - ones] + 1,dp[i][j])

var findMaxForm = function(strs, m, n) {
  
    let dp = Array(m + 1)

    for(let i = 0; i < m + 1; i++){
        
        dp[i] = Array(n + 1).fill(0)
    }

    for (const str of strs) {
        
        let zeros = ones = 0

        for(let i = 0; i < str.length; i++){
            
            if(str[i] == '0') zeros++

            else ones++
        }

        for(let i = m; i >= zeros; i--){
            
            for(let j = n; j >= ones; j--){
                
                dp[i][j] = Math.max(dp[i - zeros][j - ones] + 1,dp[i][j])
            }
        }
    }

    return dp[m][n]
};
最后编辑于
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 回溯算法 回溯法:也称为试探法,它并不考虑问题规模的大小,而是从问题的最明显的最小规模开始逐步求解出可能的答案,并...
    fredal阅读 13,640评论 0 89
  • 动态规划(Dynamic Programming) 本文包括: 动态规划定义 状态转移方程 动态规划算法步骤 最长...
    廖少少阅读 3,273评论 0 18
  • 0. 动态规划分析 0.1 动态规划、递归和贪心算法的区别 动态规划就是利用分治思想和解决冗余的办法来处理问题,所...
    dreamsfuture阅读 7,409评论 2 6
  • 动态规划认知 ? 在问题满足最优性原理之后,用动态规划解决问题的核心就在于填表,表填写完毕,最优解也就找到。 ...
    汝其母戏吾乎阅读 1,602评论 0 1
  • 动态规划(Dynamicprogramming)是一种在数学、计算机科学和经济学中使用的,通过把原问题分解为相...
    安然若知阅读 8,754评论 1 8