01背包類問題

文章目錄

  • [模版]01背包
    • 1. 第一問: 背包不一定能裝滿
      • (1) 狀態表示
      • (2) 狀態轉移方程
      • (3) 初始化
      • (4) 填表順序
      • (5) 返回值
    • 2. 第二問: 背包恰好裝滿
    • 3. 空間優化
  • 416.分割等和子集
    • 1. 狀態表示
    • 2. 狀態轉移方程
    • 3. 初始化
    • 4. 填表順序
    • 5. 返回值
  • [494. 目標和](https://leetcode.cn/problems/target-sum/description/)
    • 1. 狀態表示
    • 2. 狀態轉移方程
    • 3. 初始化
    • 4. 填表順序
    • 5. 返回值
  • 1049.最后一塊石頭的重量II
    • 1. 狀態表示
    • 2. 狀態轉移方程
    • 3. 初始化
    • 4. 填表順序
    • 5. 返回值

[模版]01背包

在這里插入圖片描述

1. 第一問: 背包不一定能裝滿

(1) 狀態表示

dp[i][[j]: 從前 i 個物品中挑選, 總體積不超過 j, 所有選法中, 能挑選出來的最大價值.

(2) 狀態轉移方程

根據最后一步的狀況, 分情況討論:

  1. 不選 i 物品
    此時就是在前 i-1 個物品中挑選, 總體積不超過 j, 所有選法中, 能挑選出來的最大價值.

dp[i][j] = dp[i-1][j]

  1. 選 i 物品
    選了 i 物品, 說明最后要加上 w[i], 此時就是在前 i-1 個物品中挑選, 總體積不超過 j - v[i], 所有選法中, 能挑選出來的最大價值.

dp[i][j] = dp[i-1][ j - v[i] ] + w[i]

注:j-v[i]可能不存在, 所以 j-v[i] >= 0
比如, j = 1, 但是 v[i] = 4 了, 此時 j-v[i] 為 負數了, 就是不存在的.

綜上, 狀態轉移方程為:

dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i])

(3) 初始化

根據題意可知, 下標是從 1 開始的, 所以 dp表 會多一行多一列.
橫坐標代表物品數, 縱坐標代表體積.
第0行表示: 從前0個物品中挑選, 總體積不超過 j 的最大價值, 就是為0.
第0列表示: 從前 i 個物品中挑選, 總體積不超過 0 的最大價值, 物品都是有體積的, 這種情況也不存在, 也是為0.

所以可以不用特別的初始化, 默認為0即可.

(4) 填表順序

從上往下

(5) 返回值

根據狀態表示可知, 題目最終要返回的是, 從前 n 個物品中挑選, 總體積不超過 v 的最大價值.
即返回: dp[n][v].

2. 第二問: 背包恰好裝滿

與第一問的討論思路和過程是一模一樣, 狀態轉移方程也一樣, 只有以下幾點有細微的變化:

(1) 狀態表示
dp[i][[j]: 從前 i 個物品中挑選, 總體積恰好等于 j, 所有選法中, 能挑選出來的最大價值.

(2) 判斷狀態方程是否存在
在求每一個狀態時, 從前 i-1 個物品中選,可能會選不出體積恰好等于 j 的物品.
此時可以做一個約定: dp[i][j] = -1時, 表示沒有這種情況.
其實在第一種情況 不選 i 物品時, 是可以不用特判 dp[i-1][j] != -1的. 因為第一種情況不選 i 物品是一定存在的, 如果 dp[i-1][j] = -1, 那么 dp[i][j] = -1, 這是合理的.
但是第二種情況 選 i 物品時一定要特判 dp[i-1][j-v[i]] != -1. 因為第二種情況多加了一個 w[i], 如果 dp[i-1][j-v[i]] 等于 -1, 再加上 w[i], 就會影響最終結果了.

(3) 初始化
第一個格子為0 , 因為正好能湊齊體積為0 的背包, 但是第一行后面的格子都是 -1 , 因為沒有物品, 無法滿足體積大于 0 的情況.

(4) 返回值
最后有可能湊不出體積恰好為 V 的,所以返回之前要特判一下.

代碼實現如下:

#include <iostream>
#include <cstring>
using namespace std;const int N = 1010;
int n, V;
int v[N], w[N];
int dp[1010][1010];int main()
{cin >> n >> V;for(int i = 1; i <= n; i++) //下標從1開始cin >> v[i] >> w[i];//解決第一問for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];if(j - v[i] >= 0)dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);}}cout << dp[n][V] << endl;//解決第二問memset(dp, 0, sizeof(dp));for(int i = 1; i <= V; i++)dp[0][i] = -1;for(int i = 1; i <= n; i++){for(int j = 1; j <= V; j++){dp[i][j] = dp[i-1][j];// 注意要特判if(j - v[i] >= 0 && dp[i-1][j-v[i]] != -1)dp[i][j] = max(dp[i-1][j], dp[i-1][j-v[i]] + w[i]);}}cout << (dp[n][V] == -1 ? 0 : dp[n][V]) << endl;return 0;
}

3. 空間優化

1.利用滾動數組做空間上的優化
2.直接在原始的代碼上修改即可
步驟:

1.把二維dp表中的 i (所有橫坐標)去掉改成一維(不是改成一層循環,還是需要兩層循環,只是把dp表中的 i 去掉).
2.修改 j 的遍歷順序成從右往左.

修改后的代碼如下:

#include <iostream>
#include <cstring>
using namespace std;const int N = 1010;
int n, V;
int v[N], w[N];
int dp[1010];int main()
{cin >> n >> V;for(int i = 1; i <= n; i++) //下標從1開始cin >> v[i] >> w[i];//解決第一問for(int i = 1; i <= n; i++){for(int j = V; j >= 0; j--){dp[j] = dp[j];if(j - v[i] >= 0)dp[j] = max(dp[j], dp[j-v[i]] + w[i]);}}cout << dp[V] << endl;//解決第二問memset(dp, 0, sizeof(dp));for(int i = 1; i <= V; i++)dp[i] = -1;for(int i = 1; i <= n; i++){for(int j = V; j >= 0; j--){dp[j] = dp[j];// 注意要特判if(j - v[i] >= 0 && dp[j-v[i]] != -1)dp[j] = max(dp[j], dp[j-v[i]] + w[i]);}}cout << (dp[V] == -1 ? 0 : dp[V]) << endl;return 0;
}

416.分割等和子集

在這里插入圖片描述

1. 狀態表示

dp[i][j]:從前 i 個數中選,和是否恰好等于 j ,是為true, 不是為false.

2. 狀態轉移方程

和01背包問題一樣, 根據第 i 個位置選或不選,分兩種情況討論:
(1) 不選第 i 個數: dp[i][j] = dp[i-1][j]
(2) 選第 i 個數: dp[i][j] = dp[i-1][j-nums[i]]
只要這兩種選法中有一個能湊成就是符合題意的, 所以可得:
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]
注意: j - nums[i] >= 0.

3. 初始化

為了防止填表時出現越界問題,一般還是多開一行多開一列.
(0, 0) 位置, 從前 0 個數中選,和是否恰好等于 0, 成立, 所以是 true, 第一行的其他位置則為 false.
第一列, 從前 i 個數中選,和是否恰好等于 0, 成立, 所以第一列初始化為 true.

4. 填表順序

從上往下.

5. 返回值

本題可以轉化為: 在數組中選一些數, 讓這些數的和等于 sum / 2.(其中sum是整個數組的和).
所以最終返回: dp[n][sum / 2] 即可.

代碼實現如下:

class Solution {
public:bool canPartition(vector<int>& nums) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;//當和為奇數時,不能等和分if(sum % 2 == 1) return false;vector<vector<bool>>dp(n+1, vector<bool>(sum+1));for(int j = 1; j <= sum; j++) dp[0][j] = false;for(int i = 0; i <= n; i++) dp[i][0] = true;for(int i = 1; i <= n; i++){for(int j = 1; j <= sum; j++){dp[i][j] = dp[i-1][j];if(j - nums[i-1] >= 0)dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];}}return dp[n][sum/2];}
};

空間優化后的代碼如下:

class Solution {
public:bool canPartition(vector<int>& nums) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;if(sum % 2 == 1) return false;vector<bool>dp(vector<bool>(sum+1));for(int j = 1; j <= sum; j++) dp[j] = false;for(int i = 0; i <= n; i++) dp[0] = true;for(int i = 1; i <= n; i++){for(int j = sum; j >= 1; j--){dp[j] = dp[j];if(j - nums[i-1] >= 0)dp[j] = dp[j] || dp[j-nums[i-1]];}}return dp[sum/2];}
};

494. 目標和

在這里插入圖片描述
在這里插入圖片描述
我們先對這道題進行分析:
在添加完±號后會有正數和負數,我們把所有正數和記為a,所有負數和的絕對值記為b,總和記為sum.
根據題意可知: a-b=target, a+b=sum,可得 a = (sum+target)/2

所以原題可轉換為:在數組中選一些數,讓這些數字的和等于a,一共有多少種選法.
這就是一個01背包問題, 下面的分析過程和上一題 [416.分割等和子串] 基本一樣.

1. 狀態表示

dp[i][j]:從前 i 個數中選,使得總和為 j ,一共有多少種選法.

2. 狀態轉移方程

根據i位置的狀態,有兩種情況:
1.i 位置不選,dp[i][j] = dp[i-1][j]
2.i 位置選,dp[i][j] = dp[i-1][j-nums[i]]
注意: j >= nums[i].
綜上,兩種選法的總數:
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]

3. 初始化

依舊是多加一行多加一列.
第一行:數組里沒有元素,要湊成和為0,和為1… 都湊不出, 所以填 0, 但是 dp[0][0] = 1.
第一列:除了第一個位置,其余位置是可以不用特別初始化的.
因為本題的數字有可能是0,第一列表示的是從前 i 個數字中,總和為0的選法,那就會有很多種情況了。
而我們初始化的目的就是避免填表時越界訪問,而選第 i 個位置時,是用第二種情況,這種情況我們有前提條件 j >= nums[i],當 j = 0 時, 要使用那個方程就要滿足 nums[i] = 0, 此時 dp[i][j] = dp[i-1][0], 這使得這種情況填表時只會使用表中的上一個位置,而不是越界訪問。
![在這里插入圖片描述](https://i-blog.csdnimg.cn/direct/04948e2307da45a3a6d4ba01722ca7b6.png

4. 填表順序

從上往下

5. 返回值

根據我們上面的分析可知, 最終返回: dp[n][a] 即可.

代碼實現如下:

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;int a = (sum + target) / 2;if(a < 0 || (sum + target) % 2 == 1) return 0;vector<vector<int>> dp(n+1, vector<int>(a+1));dp[0][0] = 1;for(int i = 1; i <= n; i++){for(int j = 0; j <= a; j++){dp[i][j] = dp[i-1][j];if(j >= nums[i-1])dp[i][j] += dp[i-1][j-nums[i-1]];}}return dp[n][a];}
};

空間優化后的代碼如下:

class Solution {
public:int findTargetSumWays(vector<int>& nums, int target) {int n = nums.size();int sum = 0;for(auto x : nums) sum += x;int a = (sum + target) / 2;if(a < 0 || (sum + target) % 2 == 1) return 0;vector<int> dp(vector<int>(a+1));dp[0] = 1;for(int i = 1; i <= n; i++){for(int j = a; j >= 1; j--){dp[j] = dp[j];if(j >= nums[i-1])dp[j] += dp[j-nums[i-1]];}}return dp[a];}
};

1049.最后一塊石頭的重量II

在這里插入圖片描述
我們先來分析題目:
挑選兩個石頭粉碎,其實就是在任意兩個石頭前添加±號
分析思路同"目標和"一題,全部正數和記為a,負數和絕對值記為b. 要求的就是|a-b|的最小值。
而我們又知道全部數的總和sum, 即a+b=sum.
求|a-b|的最小值可以說成: 把一個數sum拆成兩個數,求這兩個數的差的最小值.
而只有當這兩個數越接近sum/2時,差就越小
綜上分析,本題可轉換為:
在數組中選擇一些數,讓這些數的和盡可能接近sum/2("這些數的和"就是上面的a或b).

本質就是一個01背包問題:
物品 - 數
每個物品的價值 - nums[i]
每個物品體積 - nums[i]
背包體積 - sum/2.
選一些數放進背包中,在不超過背包體積的情況下,里面的最大和是多少.

1. 狀態表示

dp[i][j]:從前i個數中選,總和不超過j,此時的最大和.

2. 狀態轉移方程

根據i位置的狀態,有兩種情況:
1.i 位置不選,dp[i][j] = dp[i-1][j]
2.i 位置選,dp[i][j] = dp[i-1][j-nums[i]] + nums[i]
注意: j >= nums[i].
綜上: dp[i][j] = max(dp[i-1][j] , dp[i-1][j-nums[i]])

3. 初始化

多一行多一列
第一行:背包中沒有石頭,無法湊成總和為0,1,2,3… 初始化為0即可
第一列:同 [目標和] 一題.

4. 填表順序

從上往下

5. 返回值

dp[n][sum/2] 就是上面分析中的 a,則 b = sum-dp[n][sum/2], 所以最小值為 a - b = sum - 2 * dp[n][sum/2].

代碼實現如下:

class Solution 
{
public:int lastStoneWeightII(vector<int>& stones) {int n = stones.size(), sum = 0;for(auto x : stones) sum += x;vector<vector<int>> dp(n+1, vector<int>(sum / 2 + 1));for(int i = 1; i <= n; i++){for(int j = 0; j <= sum / 2; j++){dp[i][j] = dp[i-1][j];if(j >= stones[i-1])dp[i][j] = max(dp[i-1][j], dp[i-1][j-stones[i-1]] + stones[i-1]);}}// dp[n][sum/2]就是上面分析中的a,則b=sum-dp[n][sum/2]// 所以最小值為a-b=sum - 2 * dp[n][sum/2]return sum - 2 * dp[n][sum/2];}
};

空間優化后的代碼:

class Solution 
{
public:int lastStoneWeightII(vector<int>& stones) {int n = stones.size(), sum = 0;for(auto x : stones) sum += x;vector<int> dp(vector<int>(sum / 2 + 1));for(int i = 1; i <= n; i++){for(int j = sum / 2; j >= 1; j--){dp[j] = dp[j];if(j >= stones[i-1])dp[j] = max(dp[j], dp[j-stones[i-1]] + stones[i-1]);}}// dp[n][sum/2]就是上面分析中的a,則b=sum-dp[n][sum/2]// 所以最小值為a - b = sum - 2 * dp[sum/2]return sum - 2 * dp[sum/2];}
};

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

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

相關文章

解鎖 DevOps 新境界 :使用 Flux 進行 GitOps 現場演示 – 自動化您的 Kubernetes 部署

前言 GitOps 是實現持續部署的云原生方式。它的名字來源于標準且占主導地位的版本控制系統 Git。GitOps 的 Git 在某種程度上類似于 Kubernetes 的 etcd&#xff0c;但更進一步&#xff0c;因為 etcd 本身不保存版本歷史記錄。毋庸置疑&#xff0c;任何源代碼管理服務&#xf…

將Docker鏡像變為可執行文件?體驗docker2exe帶來的便捷!

在現代軟件開發中,容器化技術極大地改變了應用程序部署和管理的方式。Docker,作為領先的容器化平臺,已經成為開發者不可或缺的工具。然而,對于不熟悉Docker的用戶來說,接觸和運行Docker鏡像可能會是一個復雜的過程。為了解決這一問題,docker2exe項目應運而生。它提供了一…

IBM BAW(原BPM升級版)使用教程第八講

續前篇&#xff01; 一、流程開發功能模塊使用邏輯和順序 前面我們已經對 流程、用戶界面、公開的自動化服務、服務、事件、團隊、數據、性能、文件各個模塊進行了詳細講解&#xff0c;現在統一進行全面統一講解。 在 IBM Business Automation Workflow (BAW) 中&#xff0c;…

針對共享內存和上述windows消息機制 在C++ 和qt之間的案例 進行詳細舉例說明

針對共享內存和上述windows消息機制 在C++ 和qt之間的案例 進行詳細舉例說明 以下是關于在 C++ 和 Qt 中使用共享內存(QSharedMemory)和 Windows 消息機制(SendMessage / PostMessage)進行跨線程或跨進程通信的詳細示例。 ?? 使用 QSharedMemory 進行進程間通信(Qt 示例…

jetson orin nano super AI模型部署之路(十)使用frp配置內網穿透,隨時隨地ssh到機器

為什么要內網穿透&#xff1f; 我們使用jetson設備時&#xff0c;一般都是在局域網內的電腦去ssh局域網內的jetson設備&#xff0c;但是這種ssh或者VNC僅限于局域網之間的設備。 如果你出差了&#xff0c;或者不在jetson設備的局域網內&#xff0c;想再去ssh或者VNC我們的jet…

VScode密鑰(公鑰,私鑰)實現免密登錄【很細,很全,附帶一些沒免密登錄成功的一些解決方法】

一、 生成SSH密鑰對 ssh-keygen 或者 ssh-keygen -t rsa -b 4096區別&#xff1a;-t rsa可以明確表示生成的是 RSA 類型的密鑰-b參數將密鑰長度設置為 4096 位默認&#xff1a;2048 位密鑰不指定-t參數&#xff0c;ssh -keygen默認也可能生成 RSA 密鑰【確保本機安裝ssh&#…

解釋器和基于規則的系統比較

解釋器&#xff08;Interpreter&#xff09;和基于規則的系統&#xff08;Rule-Based System&#xff09;是兩種不同的軟件架構風格&#xff0c;分別適用于不同的應用場景。它們在設計理念、執行機制和適用領域上有顯著差異。以下是它們的核心對比&#xff1a; 1. 解釋器&#…

DB4S:一個開源跨平臺的SQLite數據庫管理工具

DB Browser for SQLite&#xff08;DB4S&#xff09;是一款開源、跨平臺的 SQLite 數據庫管理工具&#xff0c;用于創建、瀏覽和編輯 SQLite 以及 SQLCipher 數據庫文件。 功能特性 DB4S 提供了一個電子表格風格的數據庫管理界面&#xff0c;以及一個 SQL 查詢工具。DB4S 支持…

printf調試時候正常,運行時打印不出來

問題是在添加了 printf 功能后&#xff0c;程序獨立運行時無法正常打印輸出&#xff0c;而調試模式下正常。這表明問題可能與 printf 的重定向實現、標準庫配置、或編譯器相關設置有關。 解決&#xff1a; 原來是使用 Keil/IAR&#xff0c;printf可能需要啟用 MicroLIB 或正確…

輕松制作高質量視頻,實時生成神器LTX-Video重磅登場!

探索LTX-Video&#xff1a;實時視頻生成跨越新高度 在如今這個視覺內容主導的數字時代&#xff0c;視頻生成成為推動創意表達的關鍵。而今天&#xff0c;我們將帶您深入探索LTX-Video&#xff0c;一個強大的開源項目&#xff0c;致力于通過尖端技術將視頻生成提升到一個全新的…

分布式事務快速入門

分布式事務基本概念 使用分布式事務的場景&#xff1a;分布式場景下的跨數據庫事務 分布式事務誕生的理論&#xff1a;CAP和Base 3種一致性&#xff1a; 強一致性 &#xff1a;系統寫入了什么&#xff0c;讀出來的就是什么。 弱一致性 &#xff1a;不一定可以讀取到最新寫入…

nvme Unable to change power state from D3cold to D0, device inaccessible

有個thinkpad l15 gen4筆記本&#xff0c;使用較少&#xff0c;有一塊三星m2和東芝14t硬盤&#xff0c;想安裝飛牛nas系統作為家庭照片庫&#xff0c;制作飛牛啟動盤&#xff0c;發現安裝飛牛需要全盤格式化&#xff0c;電腦本身的系統還是需要保留的&#xff0c;故想到再安裝一…

Unity Shaders and Effets Cookbook

目錄 作者簡介 審稿人簡介 前言 我是偏偏 Unity Shaders and Effets Cookbook 第一章&#xff1a;Diffuse Shading - 漫反射著色器 第二章&#xff1a;Using Textures for Effects - 著色器紋理特效的應用 第三章&#xff1a;Making Your Game Shine with Specular - 鏡…

部署RocketMQ

部署環境&#xff1a;jdk8以上&#xff0c;Linux系統 下載和安裝指令&#xff1a; wget https://archive.apache.org/dist/rocketmq/4.9.4/rocketmq-all-4.9.4-bin-release.zip 顯示下載成功&#xff1a; --2025-05-10 11:34:46-- https://archive.apache.org/dist/rocketm…

使用FastAPI和React以及MongoDB構建全棧Web應用04 MongoDB快速入門

一、NoSQL 概述 1.1 了解關系數據庫的局限性 Before diving into NoSQL, it’s essential to understand the challenges posed by traditional Relational Database Management Systems (RDBMS). While RDBMS have been the cornerstone of data management for decades, th…

高精度之加減乘除之多解總結(加與減篇)

開篇總述&#xff1a;精度計算的教學比較雜亂&#xff0c;無系統的學習&#xff0c;且存在同法多線的方式進行同一種運算&#xff0c;所以我寫此篇的目的只是為了直指本質&#xff0c;不走教科書方式&#xff0c;步驟冗雜。 一&#xff0c;加法 我在此講兩種方法&#xff1a; …

氣象大模型光伏功率預測中的應用:從短期,超短期,中長期的實現與開源代碼詳解

1. 引言 光伏功率預測對于電力系統調度、能源管理和電網穩定性至關重要。隨著深度學習技術的發展,大模型(如Transformer、LSTM等)在時間序列預測領域展現出強大能力。本文將詳細介紹基于大模型的光伏功率預測方法,涵蓋短期(1-6小時)、超短期(15分鐘-1小時)和中長期(1天-1周…

玩轉Docker(一):基本概念

容器技術是繼大數據和云計算之后又一炙手可熱的技術&#xff0c;而且未來相當一段時間內都會非常流行。 本文將對其基本概念和基本使用做出介紹。包括容器生態系統、容器的原理、怎樣運行第一個容器、容器技術的概念與實踐、Docker鏡像等等 目錄 一. 鳥瞰容器生態系統 1. 容器…

計算機視覺與深度學習 | 基于數字圖像處理的裂縫檢測與識別系統(matlab代碼)

???????????????????????????????? 基于數字圖像處理的裂縫檢測與識別系統 ??????????????????????????**系統架構設計****1. 圖像預處理**目標:消除噪聲+增強裂縫特征**2. 圖像分割**目標:提取裂縫區域**3. 特征…

推薦一款免費開源工程項目管理系統軟件,根據工程項目全過程管理流程開發的OA 辦公系統

在當今的工程項目管理領域&#xff0c;許多企業和團隊面臨著諸多難題。傳統的管理方式往往依賴于人工記錄和分散的工具&#xff0c;導致項目進度難以實時把控&#xff0c;任務分配不夠清晰&#xff0c;合同管理混亂&#xff0c;事件提醒不及時&#xff0c;財務管理缺乏系統性&a…