程式碼隨想錄day35 || 416 分割等和子集

周公瑾55發表於2024-08-20

揹包問題

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


// pake
//
//	@Description:
//	@param weights: 物品i對應重量
//	@param value: 物品i對應價值
//	@param n: 揹包容量
//	@return int
func pake(weights, value []int, n int) {
	// dp 五部曲
	// 確定dp陣列以及下標的含義: dp[i][j] 代表容量為j的揹包裝前i個物品獲得的最大價值
	// 遞推公式 dp[i][j] = max(dp[i-1][j], dp[i-1][j-w(i)]+v(i))
	// 初始化,揹包容量為0初始化為0,物品0 初始化小於w(0)的揹包價值為0. 大於初始化為v(0)
	// 遍歷,先物品後背包
	// 列印

	var dp = make([][]int, len(weights))
	for i := 0; i < len(dp); i++ {
		dp[i] = make([]int, n+1)
	}

	// dp[i][0] 初始化為0,dp[0][j] 看情況

	for i := 0; i < len(dp); i++ {
		for j := 0; j < len(dp[0]); j++ {
			if j == 0 {
				dp[i][j] = 0
			} else if i == 0 {
				if j >= weights[i] {
					dp[i][j] = value[i]
				} else {
					dp[i][j] = 0
				}
			} else {
				var v1, v2 int
				v1 = dp[i-1][j]
				if j-weights[i] >= 0 {
					v2 = dp[i-1][j-weights[i]] + value[i]
				}
				fmt.Println(v1, v2)
				dp[i][j] = max(v1, v2)
			}

		}
	}
	fmt.Println(dp)
}

// pake
//
//	@Description:
//	@param weights: 物品i對應重量
//	@param value: 物品i對應價值
//	@param n: 揹包容量
//	@return int
func pake(weights, value []int, n int) {
	// dp 五部曲
	// 確定dp陣列以及下標的含義: dp[j] 代表容量為j的揹包裝物品能夠獲得的最大價值
	// 遞推公式 dp[j] = max(dp[j], dp[j-w(i)]+v(i))
	// 初始化,揹包容量為0初始化為0,其他也初始化為0
	// 遍歷,先物品後背包,但是揹包要反序遍歷,因為遞推公式依賴的是上一層迴圈的前一個元素,如果在遍歷過程中修改了上一層的之前元素,那麼之後的遞推就是錯誤的, 可能導致一個物品多次使用的情況,
	// 列印

	var dp = make([]int, n+1)

	for i := 0; i < len(weights); i++ {
		for j := n; j > 0; j-- {
			var v int
			if j-weights[i] >= 0 {
				v = dp[j-weights[i]] + value[i]
			}

			dp[j] = max(dp[j], v)
		}
	}
	fmt.Println(dp)
}

416 分割等和子集

func canPartition(nums []int) bool {
	// 本題主要難點在於,能否想到,透過將target = sum/2 ,並且填滿target容量的揹包從而轉換成01揹包問題
	// 證明,01揹包元素不重複,如果dp[target] == target, 使用了m個元素,和為target=sum/2,那麼剩下元素和也為sum/2,從而出現兩個子集和相同

	// dp陣列以及下標,dp[j] 表示容量j能裝的最大價值,本題陣列可以看作重量=價值
	// 遞推公式 dp[j] = max(dp[j], dp[j-w[j]]+v[j]) = max(dp[j], dp[j-nums[j]]+nums[j])
	// 初始化 全部0
	// 遍歷順序,外層物品正序,內層揹包倒敘
	// print
	var sum, target int
	for _, v := range nums {
		sum += v
	}
	if sum % 2 == 1 { // 和為奇數無法等量劃分
		return false
	}

	target = sum / 2
	dp := make([]int, target + 1)
	for i:=0; i<len(nums); i++{
		for j:=target; j>=nums[i]; j-- { //j>nums[i] 表示空間至少要比i所佔用空間大,不然就無法放入,當然也可以在內部判斷剪枝
			dp[j] = max(dp[j], dp[j-nums[i]] + nums[i])
		}
	}

	fmt.Println(dp)
	if dp[target] == target{
		return true
	}
	return false
}

func max (i, j int )int {
	if i>j{
		return i
	}
	return j
}

相關文章