自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊十五周算法訓(xùn)練營——背包問題

開發(fā) 前端
「背包問?題:給你一個可裝載重量為W的背包和N個物品,每個物品有重量和價(jià)值兩個屬性。其中第i個物品的重量為wt[i],價(jià)值為val[i],現(xiàn)在讓你用這個背包裝物品,最多能裝的價(jià)值是多少?」

今天是十五周算法訓(xùn)練營的第十三周,主要講背包問題專題。(歡迎加入十五周算法訓(xùn)練營,與小伙伴一起卷算法)

「背包問題:給你一個可裝載重量為W的背包和N個物品,每個物品有重量和價(jià)值兩個屬性。其中第i個物品的重量為wt[i],價(jià)值為val[i],現(xiàn)在讓你用這個背包裝物品,最多能裝的價(jià)值是多少?」

0-1背包動態(tài)規(guī)劃思路

明確狀態(tài)和選擇

狀態(tài)有兩個:背包的容量和可選擇的物品

選擇就是:裝進(jìn)背包或者不裝進(jìn)背包

dp數(shù)組的含義

剛才明確了狀態(tài),現(xiàn)在需要用dp數(shù)組把狀態(tài)表達(dá)出來,剛才找到的「狀態(tài)」,有兩個,也就是說我們需要一個二維dp數(shù)組,一維表示可選擇的物品,一維表示背包的容量。

dp[i][w]表示的就是對于[0……i]個物品,當(dāng)前背包容量為w時(shí)的最大價(jià)值

根據(jù)選擇,思考狀態(tài)轉(zhuǎn)移的邏輯

dp[i][w]表示:對于前i個物品,當(dāng)前背包的容量為w時(shí),這種情況下可以裝下的最大價(jià)值是dp[i][w]。

如果你沒有把這第i個物品裝入背包,那么很顯然,最大價(jià)值dp[i][w]應(yīng)該等于dp[i-1][w]。你不裝嘛,那就繼承之前的結(jié)果。

如果你把這第i個物品裝入了背包,那么dp[i][w]應(yīng)該等于dp[i-1][w-wt[i-1]] + val[i-1]。

首先,由于i是從 1 開始的,所以對val和wt的取值是i-1。

明確base case: 此處的base case就是dp[ 0 ][ …… ]和dp[……][0]的時(shí)候,這個時(shí)候沒有物品或者背包沒有容量,此時(shí)價(jià)值為0

背包問題動態(tài)規(guī)劃的結(jié)構(gòu)

for 狀態(tài)1 in 狀態(tài)1的所有取值:
    for 狀態(tài)2 in 狀態(tài)2的所有取值:
        for ...
            dp[狀態(tài)1][狀態(tài)2][...] = 擇優(yōu)(選擇1,選擇2...)

0-1背包解題思路

/**
 * 0-1背包問題解題思路
 * 
 * 給你一個可裝載重量為W的背包和N個物品,每個物品有重量和價(jià)值兩個屬性。其中第i個物品的重量為wt[i],價(jià)值為val[i],現(xiàn)在讓你用這個背包裝物品,最多能裝的價(jià)值是多少?
 */

// 該問題是一個典型的動態(tài)規(guī)劃問題
// 1. 明確狀態(tài)和選擇
// 狀態(tài)就是背包的容量和可選的物品
// 選擇就是要不要裝該物品
// 2. dp數(shù)組含義
// dp[i][w]表示前i個物品、當(dāng)前背包容量為w時(shí),能裝的最大價(jià)值
// 3. 狀態(tài)轉(zhuǎn)移邏輯
// 為了獲取dp[i][w]的時(shí)候需要考慮當(dāng)前要放入物品的重量是否可以放到背包中,即比較當(dāng)前背包容量和要放入物品重量
// 若不能放進(jìn)去:dp[i][w] = dp[i - 1][w]
// 若可以放進(jìn)去,則此時(shí)就需要比較放入和不放入后的價(jià)值dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - wtPresent] + valPresent)

function knapsack(W, N, wt, val) {
    // 定義dp
    const dp = new Array(N + 1);
    for (let i = 0; i < dp.length; i++) {
        dp[i] = (new Array(W + 1)).fill(0);
    }

    // base case
    // 當(dāng)在定義dp的時(shí)候已經(jīng)進(jìn)行了初始化為0,0就是起base case
    for (let i = 1; i < dp.length; i++) {
        for (let w = 1; w < dp[0].length; w++) {
            // 判斷當(dāng)前物品是否可以放到背包中,如果不能放進(jìn)去
            if (w - wt[i - 1] < 0) {
                dp[i][w] = dp[i - 1][w];
            } else {
                // 如果可以放進(jìn)去
                dp[i][w] = Math.max(dp[i - 1][w], dp[i - 1][w - wt[i - 1]] + val[i - 1]);
            }
        }
    }

    // 最終結(jié)果
    return dp[N][W];
}

分割等和子集

給你一個 只包含正整數(shù) 的 非空 數(shù)組 nums 。請你判斷是否可以將這個數(shù)組分割成兩個子集,使得兩個子集的元素和相等。

示例 1:

輸入:nums = [1,5,11,5] 輸出:true 解釋:數(shù)組可以分割成 [1, 5, 5] 和 [11] 。

// 對于經(jīng)典背包問題,是給你一個可裝載重量為W的背包和N個物品,每個物品有重量和價(jià)值兩個屬性。其中第i個物品的重量為wt[i],價(jià)值為val[i],現(xiàn)在讓你用這個背包裝物品,最多能裝的價(jià)值是多少?

// 該問題其實(shí)是經(jīng)典背包問題的變形,可以先對集合求和,得出sum,該問題就可以轉(zhuǎn)換為背包問題:
// 給一個可裝載重量為sum / 2的背包和N個物品,每個物品的重量為nums[i],現(xiàn)在讓你裝物品,是否存在一種裝法,能夠恰好將背包裝滿

// 1. 明確狀態(tài)和選擇
// 狀態(tài)就是背包容量和可選擇的物品
// 選擇就是裝進(jìn)背包和不裝進(jìn)背包
// 2. dp數(shù)組函數(shù)
// dp[i][j] = x表示,對于前i個物品,當(dāng)前背包的容量為j時(shí),若x為true,則說明可以恰好將背包裝滿,若x為false,則說明不能恰好將背包裝滿。
// 3. 狀態(tài)轉(zhuǎn)移邏輯
// 若放不進(jìn)去,dp[i][j] = dp[i - 1][j]
// 若能夠放進(jìn)去dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i - 1]]
// 4. base case
// base case就是:
// (1)有物品但是容量為0,肯定能裝滿,此時(shí)即dp[……][0] = true
// (2)沒有物品但是有容量時(shí),肯定裝不滿,此時(shí)即dp[0][……] = false

function canPartition(nums) {
    let sums = 0;
    nums.forEach(num => sums += num);

    // 如果是奇數(shù),不能被劃分,直接返回false
    if (sums % 2 === 1) {
        return false;
    }

    const numsLen = nums.length;

    const dp = new Array(numsLen + 1);
    for (let i = 0; i < dp.length; i++) {
        dp[i] = (new Array(sums / 2 + 1)).fill(false);
    }

    // base case
    for (let i = 0; i < dp.length; i++) {
        dp[i][0] = true;
    }

    for (let i = 1; i < dp.length; i++) {
        for (let j = 1; j < dp[0].length; 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]];
            }
        }
    }

    // 最終其dp[n][sum / 2]就是起結(jié)果,因?yàn)榇藭r(shí)能找到和的一半,另一半也是和的一半
    return dp[numsLen][sums / 2];
}

零錢兌換II

給你一個整數(shù)數(shù)組 coins 表示不同面額的硬幣,另給一個整數(shù) amount 表示總金額。

請你計(jì)算并返回可以湊成總金額的硬幣組合數(shù)。如果任何硬幣組合都無法湊出總金額,返回 0 。

假設(shè)每一種面額的硬幣有無限個。

題目數(shù)據(jù)保證結(jié)果符合 32 位帶符號整數(shù)。

示例 1:

輸入:amount = 5, coins = [1, 2, 5] 輸出:4 解釋:有四種方式可以湊成總金額: 5=5 5=2+2+1 5=2+1+1+1 5=1+1+1+1+1

// 該問題可轉(zhuǎn)換為有一個背包,最大容量為amount,有一系列物品coins,每個物品的重量為coins[i],每個物品的數(shù)量無限。請問有多少種方法,能夠把背包恰好裝滿?
// 與經(jīng)典背包區(qū)別的是每個物品的數(shù)量是無限的,這也就是完全背包問題。

// 1. 狀態(tài)和選擇
// 狀態(tài):背包的容量和可選擇的物品
// 選擇:放進(jìn)背包和不放進(jìn)背包
// 2. 定義dp
// dp[i][j]:若只使用前i個物品,當(dāng)背包容量為j時(shí),有dp[i][j]種方法可以裝滿背包。換句話說:若只使用coins中的前i個硬幣的面值,若想湊出金額j,有dp[i][j]中湊法
// 3. 狀態(tài)轉(zhuǎn)移邏輯
// 若不能放進(jìn)去:dp[i][j] = dp[i - 1][j]
// 若能夠放進(jìn)去:dp[i][j] = dp[i - 1][j] + dp[i - 1][j - coins[i]]
// 4. base case
// (1)有硬幣,但是目標(biāo)結(jié)果為0,即dp[0……n][0] = 1
// (2)沒有硬幣,即dp[0][0……n] = 0

function change(amount, coins) {
    const n = coins.length;
    const dp = new Array(n + 1);
    for (let i = 0; i < dp.length; i++) {
        dp[i] = (new Array(amount + 1)).fill(0);
    }

    // base case
    for (let i = 0; i < dp.length; i++) {
        dp[i][0] = 1;
    }

    // 遍歷dp
    for (let i = 1; i < dp.length; i++) {
        for (let j = 1; j < dp[0].length; j++) {
            // 如果當(dāng)前硬幣不能放進(jìn)背包
            if (j < coins[i - 1]) {
                dp[i][j] = dp[i - 1][j];
            } else {
                // 能放進(jìn)去,則結(jié)果就是放進(jìn)去與不放進(jìn)去的加和
                // 為什么當(dāng)放進(jìn)去的時(shí)候?yàn)閕,因?yàn)榇藭r(shí)已經(jīng)決定使用coins[i - 1]的值
                dp[i][j] = dp[i - 1][j] + dp[i][j - coins[i - 1]];
            }
        }
    }

    // 目標(biāo)結(jié)果就是[N, amount]
    return dp[n][amount];
}

// 通過觀察發(fā)現(xiàn)dp[i][j]之和dp[i][……]和dp[i - 1][……]相關(guān)
// 則可進(jìn)行狀態(tài)壓縮
function change1(amount, coins) {
    // 定義dp
    const dp = (new Array(amount + 1)).fill(0);

    // base case
    dp[0] = 1;

    // 進(jìn)行遍歷
    for (let i = 0; i < coins.length; i++) {
        for (let j = 1; j < dp.length; j++) {
            if (j >= coins[i]) {
                dp[j] = dp[j] + dp[j - coins[i]];
            }
        }
    }

    return dp[dp.length - 1];
}

const amount = 5;
const coins = [1, 2, 5];

console.log(change1(amount, coins));

最后一塊石頭的重量II

有一堆石頭,用整數(shù)數(shù)組 stones 表示。其中 stones[i] 表示第 i 塊石頭的重量。

每一回合,從中選出任意兩塊石頭,然后將它們一起粉碎。假設(shè)石頭的重量分別為 x 和 y,且 x <= y。那么粉碎的可能結(jié)果如下:

如果 x == y,那么兩塊石頭都會被完全粉碎; 如果 x != y,那么重量為 x 的石頭將會完全粉碎,而重量為 y 的石頭新重量為 y-x。 最后,最多只會剩下一塊 石頭。返回此石頭 最小的可能重量 。如果沒有石頭剩下,就返回 0。

示例 1:

輸入:stones = [2,7,4,1,8,1] 輸出:1 解釋: 組合 2 和 4,得到 2,所以數(shù)組轉(zhuǎn)化為 [2,7,1,8,1], 組合 7 和 8,得到 1,所以數(shù)組轉(zhuǎn)化為 [2,1,1,1], 組合 2 和 1,得到 1,所以數(shù)組轉(zhuǎn)化為 [1,1,1], 組合 1 和 1,得到 0,所以數(shù)組轉(zhuǎn)化為 [1],這就是最優(yōu)值。

// 該問題和分割等和子集問題(416)處理方式類似,就是背包問題
// 1. 狀態(tài)和選擇
// 狀態(tài):背包和當(dāng)前可選物品
// 選擇:是否裝進(jìn)背包
// 2. dp數(shù)組含義
// dp[w][i]表示背包容量為w時(shí),前i個物品,最多能夠裝的物品重量
// 3. 狀態(tài)轉(zhuǎn)移邏輯
// 不能裝進(jìn)去:dp[w][i] = dp[w][i - 1]
// 能夠裝進(jìn)去:dp[w][i] = Math.max(dp[w][i - 1], dp[w - stones[i]][i - 1] + stones[i])
// 4. base case
// 當(dāng)i = 0時(shí),dp[0……w][0] = 0
// 當(dāng)w = 0時(shí),dp[0][……] = 0
function lastStoneWeightII(stones) {
    // 得到總重量
    let sum = 0;
    stones.forEach(stone => {
        sum += stone;
    });

    const weight = Math.floor(sum / 2);

    // 定義dp
    const dp = new Array(weight + 1);
    for (let i = 0; i < dp.length; i++) {
        dp[i] = (new Array(stones.length + 1)).fill(0);
    }

    // base case 在初始化時(shí)已經(jīng)完成

    // 循環(huán)遍歷
    for (let w = 1; w < dp.length; w++) {
        for (let i = 1; i < dp[0].length; i++) {
            // 判斷是否可以裝進(jìn)去
            if (w - stones[i - 1] < 0) {
                dp[w][i] = dp[w][i - 1];
            } else {
                dp[w][i] = Math.max(dp[w][i - 1], dp[w - stones[i - 1]][i - 1] + stones[i - 1]);
            }
        }
    }

    return sum - 2 * dp[weight][stones.length];
}

const stones = [31,26,33,21,40];
console.log(lastStoneWeightII(stones));

目標(biāo)和

給你一個整數(shù)數(shù)組 nums 和一個整數(shù) target 。

向數(shù)組中的每個整數(shù)前添加 '+' 或 '-' ,然后串聯(lián)起所有整數(shù),可以構(gòu)造一個 表達(dá)式 :

例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串聯(lián)起來得到表達(dá)式 "+2-1" 。 返回可以通過上述方法構(gòu)造的、運(yùn)算結(jié)果等于 target 的不同 表達(dá)式 的數(shù)目。

示例 1:

輸入:nums = [1,1,1,1,1], target = 3 輸出:5 解釋:一共有 5 種方法讓最終目標(biāo)和為 3 。 -1 + 1 + 1 + 1 + 1 = 3 +1 - 1 + 1 + 1 + 1 = 3 +1 + 1 - 1 + 1 + 1 = 3 +1 + 1 + 1 - 1 + 1 = 3 +1 + 1 + 1 + 1 - 1 = 3

// 該方法可以用背包解決
// 如何轉(zhuǎn)換為01背包問題呢?
// 假設(shè)加法的總和為x,那么減法對應(yīng)的總和就是sum - x
// 所以我們要求的就是x - (sum - x) = S
// x = (S + sum) / 2
// 此時(shí)問題就轉(zhuǎn)化為:裝滿容量為x背包,有幾種方法

// 1. 狀態(tài)和選擇
// 2. dp數(shù)組含義
// dp[i][w]:前i個物品、背包容量為w時(shí),有幾種方式裝滿
// 3. 狀態(tài)轉(zhuǎn)移邏輯
// 裝不進(jìn)去:dp[i][w] = dp[i - 1][w]
// 能裝進(jìn)去:dp[i][w] = dp[i - 1][w - nums[i]] + dp[i - 1][w]
// 4. base case
// dp[0][0] = 1
function findTargetSumWays(nums, target) {
    // 求和
    const sum = nums.reduce((total, num) => total + num);

    // weight必須大于0且為整數(shù)
    if (target + sum < 0 || (target + sum) % 2 === 1) {
        return 0;
    }
    // 求weight
    const weight = (target + sum) / 2;

    // dp
    const dp = new Array(nums.length + 1);
    for (let i = 0; i < dp.length; i++) {
        dp[i] = (new Array(weight + 1)).fill(0);
    }

    // base case
    dp[0][0] = 1;

    // 循環(huán)
    for (let i = 1; i < dp.length; i++) {
        for (let w = 0; w < dp[i].length; w++) {
            // 不能裝進(jìn)去
            if (w - nums[i - 1] >= 0) {
                dp[i][w] = dp[i - 1][w] + dp[i - 1][w - nums[i - 1]];
            } else {
                dp[i][w] = dp[i - 1][w];
            }
        }
    }

    return dp[nums.length][weight];
}

// 用回溯算法實(shí)現(xiàn)一遍
function findTargetSumWays1(nums, target) {
    let result = 0;
    const backtrack = (index, sum) => {
        // 結(jié)束條件
        if (index === nums.length) {
            if (sum === target) {
                result++;
            }
            return;
        }

        backtrack(index + 1, sum + nums[index]);
        backtrack(index + 1, sum - nums[index]);
    };

    backtrack(0, 0);

    return result;
}

const nums = [0, 0, 0, 1];
const target = 1;

console.log(findTargetSumWays1(nums, target));


責(zé)任編輯:武曉燕 來源: 前端點(diǎn)線面
相關(guān)推薦

2023-07-10 08:01:13

島嶼問題算法

2023-06-05 07:30:51

2023-05-15 07:32:01

算法訓(xùn)練滑動窗口

2023-04-03 07:33:05

數(shù)組排序快速排序法

2023-07-03 08:01:54

2023-04-17 07:33:11

反轉(zhuǎn)鏈表移除鏈表

2023-05-22 07:31:32

Nums快慢指針

2023-05-29 07:31:35

單調(diào)棧數(shù)組循環(huán)

2023-06-13 06:51:15

斐波那契數(shù)算法

2023-06-19 07:31:34

普通動態(tài)規(guī)劃字符串

2021-09-23 10:53:43

數(shù)據(jù)中心

2023-05-08 07:32:03

BFSDFS路徑

2016-08-05 20:21:51

CTO導(dǎo)師技術(shù)

2016-08-05 18:53:25

CTO導(dǎo)師技術(shù)

2021-07-08 20:22:05

AI

2013-04-22 12:58:14

TechExcel敏捷研發(fā)

2016-10-17 13:50:31

2009-04-29 18:12:41

GAUPS培訓(xùn)

2013-07-13 22:38:14

微軟社區(qū)微軟MVPMWW

2016-08-04 13:41:27

CTO訓(xùn)練營,技術(shù)管理
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號