代碼隨想錄算法訓練營第三十五天|01背包問題 二維和一維(卡碼網第46題)、416分割等和子集

day35 動態規劃part03

1. 01背包問題 二維 卡碼網第46題

01 背包:有n件物品和一個最多能背重量為w 的背包。第i件物品的重量是weight[i],得到的價值是value[i] 。每件物品只能用一次,求解將哪些物品裝入背包里物品價值總和最大。

動規五部曲分析:具體可看代碼隨想錄完整講解

(1)確定dp數組以及下標的含義

dp[i][j] 表示從下標為[0-i]的物品里任意取,放進容量為j的背包,價值總和最大是多少

(2)確定遞推公式

  • 不放物品i:背包容量為j,里面不放物品i的最大價值是dp[i - 1][j]
  • 放物品i:背包空出物品i的容量后,背包容量為j - weight[i]dp[i - 1][j - weight[i]] 為背包容量為j - weight[i]且不放物品i的最大價值,那么dp[i - 1][j - weight[i]] + value[i] (物品i的價值),就是背包放物品i得到的最大價值

遞歸公式: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

(3)dp數組如何初始化

首先從dp[i][j]的定義出發,如果背包容量j為0的話,即dp[i][0],無論是選取哪些物品,背包價值總和一定為0。

其次,狀態轉移方程 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); 可以看出i 是由 i-1 推導出來,那么i為0的時候就一定要初始化。

dp[0][j],即:i為0,存放編號0的物品的時候,各個容量的背包所能存放的最大價值。

那么很明顯當 j < weight[0]的時候,dp[0][j] 應該是 0,因為背包容量比編號0的物品重量還小。

j >= weight[0]時,dp[0][j] 應該是value[0],因為背包容量放足夠放編號0物品。

for (int j = weight[0]; j <= bagweight; j++) {//其他部分不需要初始化,因為一開始已經都置為0了
dp[0][j] = value[0];
}

(4)確定遍歷順序

先遍歷 物品還是先遍歷背包重量呢?

都可以!! 但是先遍歷物品更好理解

// weight數組的大小 就是物品個數
for(int i = 1; i < weight.size(); i++) { // 遍歷物品for(int j = 0; j <= bagweight; j++) { // 遍歷背包容量if (j < weight[i]) dp[i][j] = dp[i - 1][j];else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}
}

(5)舉例推導dp數組

#include <bits/stdc++.h>
using namespace std;int main() {int n, bagweight;// bagweight代表行李箱空間cin >> n >> bagweight;vector<int> weight(n, 0); // 存儲每件物品所占空間vector<int> value(n, 0);  // 存儲每件物品價值for(int i = 0; i < n; ++i) {cin >> weight[i];}for(int j = 0; j < n; ++j) {cin >> value[j];}// dp數組, dp[i][j]代表行李箱空間為j的情況下,從下標為[0, i]的物品里面任意取,能達到的最大價值vector<vector<int>> dp(weight.size(), vector<int>(bagweight + 1, 0));// 初始化, 因為需要用到dp[i - 1]的值// j < weight[0]已在上方被初始化為0// j >= weight[0]的值就初始化為value[0]for (int j = weight[0]; j <= bagweight; j++) {dp[0][j] = value[0];}for(int i = 1; i < weight.size(); i++) { // 遍歷科研物品for(int j = 0; j <= bagweight; j++) { // 遍歷行李箱容量if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 如果裝不下這個物品,那么就繼承dp[i - 1][j]的值else {dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);}}}cout << dp[n - 1][bagweight] << endl;return 0;
}

2. 01背包問題 一維

(1)確定dp數組的定義

在一維dp數組中,dp[j]表示:容量為j的背包,所背的物品價值可以最大為dp[j]

(2)一維dp數組的遞推公式

二維dp數組的遞推公式為: dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);

一維dp數組,其實就上一層 dp[i-1] 這一層 拷貝的到 dp[i]來。

所以在 上面遞推公式的基礎上,去掉i這個維度就好。

遞推公式為:dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

以下為分析:

dp[j]為 容量為j的背包所背的最大價值。

dp[j]可以通過dp[j - weight[i]]推導出來,dp[j - weight[i]]表示容量為j - weight[i]的背包所背的最大價值。

dp[j - weight[i]] + value[i] 表示 容量為 [j - 物品i重量] 的背包 加上 物品i的價值。(也就是容量為j的背包,放入物品i了之后的價值即:dp[j]

此時dp[j]有兩個選擇,一個是取自己dp[j] 相當于 二維dp數組中的dp[i-1][j],即不放物品i,一個是取dp[j - weight[i]] + value[i],即放物品i,指定是取最大的,畢竟是求最大價值,

所以遞歸公式為:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);

可以看出相對于二維dp數組的寫法,就是把dp[i][j]i的維度去掉了。

(3)一維dp數組如何初始化

dp[j]表示:容量為j的背包,所背的物品價值可以最大為dp[j],那么dp[0]就應該是0,因為背包容量為0所背的物品的最大價值就是0。dp數組除了下標0的位置,初始為0,其他下標都初始為0就可以了。

(4)一維dp數組遍歷順序

代碼如下:

for(int i = 0; i < weight.size(); i++) { // 遍歷物品for(int j = bagWeight; j >= weight[i]; j--) { // 遍歷背包容量dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);}
}

這里大家發現和二維dp的寫法中,遍歷背包的順序是不一樣的!

二維dp遍歷的時候,背包容量是從小到大,而一維dp遍歷的時候,背包是從大到小。

為什么呢?

倒序遍歷是為了保證物品i只被放入一次!。但如果一旦正序遍歷了,那么物品0就會被重復加入多次!

舉一個例子:物品0的重量weight[0] = 1,價值value[0] = 15

如果正序遍歷

dp[1] = dp[1 - weight[0]] + value[0] = 15

dp[2] = dp[2 - weight[0]] + value[0] = 30

此時dp[2]就已經是30了,意味著物品0,被放入了兩次,所以不能正序遍歷。

為什么倒序遍歷,就可以保證物品只放入一次呢?

倒序就是先算dp[2]

dp[2] = dp[2 - weight[0]] + value[0] = 15 (dp數組已經都初始化為0)

dp[1] = dp[1 - weight[0]] + value[0] = 15

所以從后往前循環,每次取得狀態不會和之前取得狀態重合,這樣每種物品就只取一次了。

那為什么二維dp數組遍歷的時候不用倒序呢?

因為對于二維dp,dp[i][j]都是通過上一層即dp[i - 1][j]計算而來,本層的dp[i][j]并不會被覆蓋!

再來看看兩個嵌套for循環的順序,代碼中是先遍歷物品嵌套遍歷背包容量,那可不可以先遍歷背包容量嵌套遍歷物品呢?

不可以!

因為一維dp的寫法,背包容量一定是要倒序遍歷(原因上面已經講了),如果遍歷背包容量放在上一層,那么每個dp[j]就只會放入一個物品,即:背包里只放入了一個物品。

所以一維dp數組的背包在遍歷順序上和二維其實是有很大差異的!,這一點大家一定要注意。

(5)舉例推導dp數組

// 一維dp數組實現
#include <iostream>
#include <vector>
using namespace std;int main() {// 讀取 M 和 Nint M, N;cin >> M >> N;vector<int> costs(M);vector<int> values(M);for (int i = 0; i < M; i++) {cin >> costs[i];}for (int j = 0; j < M; j++) {cin >> values[j];}// 創建一個動態規劃數組dp,初始值為0vector<int> dp(N + 1, 0);// 外層循環遍歷每個類型的研究材料for (int i = 0; i < M; ++i) {// 內層循環從 N 空間逐漸減少到當前研究材料所占空間for (int j = N; j >= costs[i]; --j) {// 考慮當前研究材料選擇和不選擇的情況,選擇最大值dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);}}// 輸出dp[N],即在給定 N 行李空間可以攜帶的研究材料最大價值cout << dp[N] << endl;return 0;
}

3. 416分割等和子集

思路分析

(1)問題轉化

  • 要將數組分成兩個和相等的子集,首先需要計算數組的總和。
  • 如果數組總和為奇數,那么就不可能分成兩個和相等的子集,因為兩個相等的子集的和必須是偶數。
  • 如果數組總和為偶數,我們可以將目標問題轉化為:能否在數組中找到一個子集,其和為總和的一半

(2)動態規劃

  • 設定目標值為 target = sum(nums) / 2
  • 然后,我們要判斷是否存在一個子集,其和為 target。這實際上就是經典的 背包問題
  • 使用動態規劃來判斷能否通過選擇數組中的元素來達到和為 target

動規五部曲:

1、確定 dp 數組以及下標的含義

dp[i]:表示是否存在一個子集,其和為 i

2、確定遞推公式

遞推公式基于是否選擇當前元素來更新 dp 數組。

  1. 不選擇當前元素
    • dp[j] 仍然保持原值,表示沒有選當前元素的情況。
  2. 選擇當前元素
    • 如果當前背包容量 j 大于等于當前元素 nums[i],那么可以通過選擇當前元素來更新 dp[j]。具體而言,dp[j] = dp[j] || dp[j - nums[i]],即:如果 dp[j - nums[i]]true,說明我們可以通過之前的子集加上當前元素nums[i],得到背包容量為 j

3、dp 數組如何初始化

    • 初始時,dp[0] = true,表示和為 0 是一定可以的(即不選任何元素的情況)。
    • 其他所有 dp[j] = false,表示尚未確定是否能組成 j

4、 確定遍歷順序

為了避免重復使用元素,需要倒序遍歷背包容量 j。(類似上述一維的思路一樣)

  • 外層遍歷物品:對于每個元素 nums[i],我們都嘗試更新 dp 數組。
  • 內層遍歷背包容量:背包容量從大到小進行遍歷,確保當前物品 nums[i] 只被使用一次。

遍歷順序:外層遍歷物品(nums[i]),內層遍歷背包容量 j(從大到小)。

倒序遍歷背包容量 j 是為了保證每個物品只被使用一次。

5、舉例推導dp數組

class Solution {
public:bool canPartition(vector<int>& nums) {int sum = accumulate(nums.begin(), nums.end(), 0);// 如果總和是奇數,直接返回falseif (sum % 2 != 0) return false;int target = sum / 2;// 初始化dp數組,dp[i]表示是否可以用數組中的一些元素組成和為ivector<bool> dp(target + 1, false);dp[0] = true; // 和為0是可以通過空子集得到的// 遍歷所有的物品for (int num : nums) {// 從后往前遍歷dp數組,防止重復使用同一個元素for (int j = target; j >= num; --j) {dp[j] = dp[j] || dp[j - num];}}// 返回dp[target],表示是否可以湊成和為target的子集return dp[target];}
};

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/64297.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/64297.shtml
英文地址,請注明出處:http://en.pswp.cn/web/64297.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【Unity3D】ECS入門學習(九)SystemBase

SystemBase&#xff1a;支持主線程或多線程執行篩選實體任務。 主要介紹是內部成員&#xff1a;Entities的各種篩選方法&#xff0c;其內部成員還有EntityManager ForEach方法篩選&#xff0c;傳遞一個有參委托函數進去&#xff0c;參數ref xxx組件類&#xff08;可填多個&…

[Android]init中添加新的command

在Android的init進程中&#xff0c;command是用于定義啟動時要執行的具體命令行指令的關鍵部分。init進程是Android系統啟動的第一個進程&#xff0c;它負責初始化系統的各個組件&#xff0c;并啟動必要的服務。command可以在init.rc文件及其包含的其他.rc文件中找到&#xff0…

STM32F103RCT6學習之五:ADC

1.ADC基礎 ADC&#xff08;Analog-Digital Converter&#xff09;模擬-數字轉換器ADC可以將引腳上連續變化的模擬電壓轉換為內存中存儲的數字變量&#xff0c;建立模擬電路到數字電路的橋梁12位逐次逼近型ADC&#xff0c;1us轉換時間 輸入電壓范圍&#xff1a;0~3.3V&#xff…

strncpy函數和使用案例

strncpy 是 C 語言標準庫函數之一&#xff0c;用于字符串操作。它的功能是將源字符串&#xff08;source&#xff09;中的字符復制到目標字符串&#xff08;destination&#xff09;中&#xff0c;但最多復制 n 個字符。如果源字符串的長度小于 n&#xff0c;則目標字符串剩余的…

實現類似gpt 打字效果

1. css的動畫&#xff08;animation) css中實現動畫有兩種方式&#xff1a;transition過渡動畫、 animation自定義動畫。 具體的可以看MDN鏈接&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/CSS/animation 使用keyframes自定義關鍵幀動畫并未其命名使用自定義動…

微軟遠程桌面APP怎么用

微軟遠程桌面&#xff08;Remote Desktop&#xff09;客戶端&#xff08;RD Client&#xff09;是一款由微軟開發的應用程序&#xff0c;允許用戶通過網絡連接遠程訪問和控制另一臺計算機。同時&#xff0c;微軟遠程桌面RD Client支持多種設備和操作系統&#xff0c;包括Window…

【每日學點鴻蒙知識】grid里面的item支持拖動問題、WebView回調問題、獲取頁面名稱、彈幕效果實現、修改App輸出路徑 |

1、HarmonyOS grid里面的item支持拖動問題&#xff1f; 想要grid里面的item支持拖動,拖出來后可以刪除,下面的代碼就是你們上次給我提供的,正常情況下是可以使用的但是,往下拖的過程中遇到了TextInput后,gridItem的onDragMove就不會走了,我給TextInput設置了draggable(false)后…

SDK 指南

在前端開發中&#xff0c;SDK&#xff08;Software Development Kit&#xff0c;軟件開發工具包&#xff09;是一個用于幫助開發者在特定平臺、框架或技術棧中實現某些功能的工具集。 1. SDK 是什么&#xff1f; SDK 是一種開發工具包&#xff0c;它提供了開發人員實現某些功…

Unity3d UGUI如何優雅的實現Web框架(Vue/Rect)類似數據綁定功能(含源碼)

前言 Unity3d的UGUI系統與Web前端開發中常見的數據綁定和屬性綁定機制有所不同。UGUI是一個相對簡單和基礎的UI系統&#xff0c;并不內置像Web前端&#xff08;例如 Vue.js或React中&#xff09;那樣的雙向數據綁定或自動更新UI的機制。UGUI是一種比較傳統的 UI 系統&#xff…

OptimisticLock

想象你和你的朋友去了一家很受歡迎的餐廳。你們想要點一份特別的菜品——這家餐廳的招牌菜&#xff0c;但因為這道菜非常受歡迎&#xff0c;所以它的狀態可能會隨時變化&#xff08;比如售罄或重新上架&#xff09;。 傳統方式&#xff08;悲觀鎖&#xff09; 通常情況下&…

10分鐘掌握項目管理核心工具:WBS、甘特圖、關鍵路徑法全解析

一、引言 在項目管理的廣闊天地里&#xff0c;猶如一場精心編排的交響樂演奏&#xff0c;每個樂器、每個音符都需精準配合才能奏響美妙樂章。而 WBS&#xff08;工作分解結構&#xff09;、甘特圖、關鍵路徑法無疑是這場交響樂中的關鍵樂章&#xff0c;它們從不同維度為項目管…

TCP 和 UDP 的區別:解析網絡傳輸協議

引言 在計算機網絡的世界中&#xff0c;TCP&#xff08;Transmission Control Protocol&#xff0c;傳輸控制協議&#xff09;和 UDP&#xff08;User Datagram Protocol&#xff0c;用戶數據報協議&#xff09;是兩種極為重要且應用廣泛的傳輸層協議。它們在功能、特性以及適…

代碼思想之快慢路徑

處理業務代碼的過程中&#xff0c;對業務代碼有了一些調整&#xff0c;后續發現這是一種代碼思想 在一段復雜的邏輯里&#xff0c;我把查詢redis操作寫在了前面&#xff0c; 業務邏輯&#xff1a; 如果需要不打壓就退出本次處理 查詢redis拿到商品需要打壓的次數 如果次數 …

Java 溯本求源之基礎(三十一)——泛型

目錄 1. 泛型的定義與基本概念 2. 泛型的優勢 3. 泛型的基本語法 3.1 泛型類 3.2 泛型方法 3.3 泛型接口 4. 泛型的邊界 4.1 上限通配符&#xff08;? extends T&#xff09; 4.2 下限通配符&#xff08;? super T&#xff09; 5. 泛型的類型擦除 6. 泛型的使用場景…

純 HTML+CSS+JS 實現一個炫酷的圣誕樹動畫特效

純 HTMLCSSJS 實現一個炫酷的圣誕樹動畫特效 前言 圣誕節快到了&#xff0c;今天給大家帶來一個簡單但是效果不錯的圣誕樹動畫特效。這個特效完全使用原生 HTML、CSS 和 JavaScript 實現&#xff0c;包含閃爍的星星、隨機彩燈等元素&#xff0c;非常適合節日氣氛&#xff01;…

Maven:Java項目構建與管理的利器

在Java開發領域&#xff0c;Maven無疑是一個舉足輕重的工具。它不僅簡化了項目的構建和依賴管理&#xff0c;還促進了團隊協作和持續集成。本文將深入探討Maven的核心功能、基本配置以及在實際項目中的應用。 Maven簡介 Maven是Apache基金會下的一個開源項目&#xff0c;旨在…

【ES6復習筆記】Promise對象詳解(12)

1. 什么是 Promise&#xff1f; Promise 是 JavaScript 中處理異步操作的一種機制&#xff0c;它可以讓異步操作更加容易管理和控制。Promise 對象代表一個異步操作的最終完成或失敗&#xff0c;并提供了一種方式來處理操作的結果。 2. Promise 的基本語法 Promise 對象有三…

【RAG實戰】語言模型基礎

語言模型賦予了計算機理解和生成人類語言的能力。它結合了統計學原理和深度神經網絡技術&#xff0c;通過對大量的樣本數據進行復雜的概率分布分析來學習語言結構的內在模式和相關性。具體地&#xff0c;語言模型可根據上下文中已出現的詞序列&#xff0c;使用概率推斷來預測接…

【ES6復習筆記】Map(14)

概念 Map 是 JavaScript 中的一種數據結構&#xff0c;它允許你存儲鍵值對&#xff0c;并且可以通過鍵來訪問對應的值。在本教程中&#xff0c;我們將學習如何聲明、添加、刪除、獲取和遍歷 Map 集合。 ES6 提供了 Map 數據結構。它類似于對象&#xff0c;也是鍵值對的集合。…

富芮坤FR800X系列之PWM輸出程序應用設計

文章目錄 前言1.設計背景2.簡介3.如何設計控制調光的接口呢4.硬件設計5.軟件設計5.1.軟件流程圖5.2.軟件代碼 6.小結 前言 版權歸作者所有、未經允許、請勿轉載。 讀者對象&#xff1a; 本文檔主要適用以下工程師&#xff1a; ?嵌入式系統工程師 ?單片機軟件工程師 ?IOT固…