程式碼隨想錄day36 || 1049 最後一筐石頭重量||, 494 目標和,474 一和零

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

1049 最後一塊石頭重量||

func lastStoneWeightII(stones []int) int {
	// 本題思路在於要想得到最小差,就要儘可能將石頭分割為兩堆相近的重量,然後轉換為揹包問題
	// dp[i] 表示容量i揹包能裝的石頭總價值,其中重量和價值相等
	// 遞推公式 dp[j] = max(dp[j], dp[j-w(i)] + v[i]) == max(dp[j], dp[j-s[i]]+s[i])
	// 初始化 dp[0] = 0
	// 遍歷順序,先正序物品, 後倒敘揹包,原因在於遞推公式依賴於上一層dp的狀態推導,所以正序可能導致之前的元素被覆蓋出現錯誤
	// print
	var sum, mid int
	for _, v := range stones{
		sum += v
	}
	mid = sum / 2

	var dp = make([]int, mid + 1)
	for i:=0; i<len(stones); i++ {
		for j:=mid; j>0; j-- {
			if j-stones[i] >= 0{
				dp[j] = max(dp[j], dp[j-stones[i]]+stones[i])
			}
		}
	}
	//fmt.Println(dp)
	return sum - 2*dp[mid]  // sum - dp[mid] 代表另一半石頭的重量,然後兩半相減得到的就是最小差
}

494 目標和

var path []int
var res int
var mutil = []int{-1, 1}
func findTargetSumWays(nums []int, target int) int {
	// 思考一下回溯辦法
	//  回溯三部曲,引數以及返回值,終止條件,回溯遍歷過程
	path = make([]int, 0)
	res = 0
	backTracking(nums, target, len(nums))
	return res
}

func backTracking(nums []int, target, length int ) {
	if len(path) == length{
		//fmt.Println(path)
		if sum(path) == target{
			res += 1
		}
		return
	}

	for i:=0; i<len(nums); i++{
		for _, v := range mutil{ // 每一個元素遍歷兩個,分別插入正數負數,結束時回溯
			path = append(path, v*nums[i])
			backTracking(nums[i+1: ], target, length )
			path = path[0: len(path) - 1]
		}
		break // !!!!!這裡剪枝的目的是儘可能保障path長度為len(nums),如果將首個元素去除的話,長度肯定小於len(nums),所以剪枝
	}
}

func sum(n []int) int {
	var s int
	for _, v := range n {
		s += v
	}
	return s
}
// 回溯法超時
// 剪枝之後壓線透過
	執行耗時:1897 ms,擊敗了5.04% 的Go使用者
	記憶體消耗:2.2 MB,擊敗了69.39% 的Go使用者

image

func findTargetSumWays(nums []int, target int) int {
	// 將陣列劃分為正數陣列和負數陣列 推匯出正數陣列 = (sum + target) / 2
	// dp[i][j] 代表儘可能裝滿容量為j的揹包的方法數量
	// 遞推:dp[i][j] = dp[i-1][j] + dp[i-1][j-w(j)] 代表不裝j的方法數 + 裝j的方法數
	// 初始化 dp[1][1] =1
	// 遍歷順序,隨意
	// print
	var sum int
	for _, v := range nums {
		sum += v
	}
	if ( sum + target ) % 2 == 1 || float64(sum) < math.Abs(float64(target)) {
		return 0
	}

	mid := (sum + target) / 2
	var dp = make([][]int, len(nums) + 1)
	for idx, _ := range dp {
		dp[idx] = make([]int, mid + 1)
	}
	dp[0][0] = 1
	for i:=0; i<len(nums); i++ {
		for j:=0; j<=mid; j++{
			//fmt.Println(i+1, j, dp[i][j], dp[i][j-nums[i]])
			dp[i+1][j] = dp[i][j]
			if j - nums[i] >= 0 {
				dp[i+1][j] += dp[i][j-nums[i]]
			}
		}
	}
	//fmt.Println(dp)
	return dp[len(nums)][mid]
}

474 一和零

image

func findMaxForm(strs []string, m int, n int) int {
	// 此處難點在於二維的0-1揹包
	// dp[i][j] 代表儘可能裝滿i個0,j個1的揹包能獲得的最大價值,由於價值等於數量*1 所以也是最大數量
	// 遞推公式 dp[i][j] = max(dp[i][j], dp[i-x][j-y] + 1) // x,y分別代表字元0,1數量
	// 初始化全部為0
	// 遍歷順序,先正序物品,後倒敘揹包,由於是滾動陣列,所以dp陣列依賴的是上一層的dp值,如果正序會覆蓋上一層,所以不能正序
	// print

	var dp = make([][]int, m+1)
	for idx, _ := range dp{
		dp[idx] = make([]int, n+1)
	}

	for _, str := range strs {
		var x, y int
		for i:=0; i<len(str); i++ {
			if str[i] == '0'{
				x++
			}
			if str[i] == '1'{
				y++
			}
		}

		for i:=m; i>=x; i--{
			for j:=n; j>=y; j--{
				dp[i][j] = max(dp[i][j], dp[i-x][j-y] + 1)
			}
		}

	}
	//fmt.Println(dp)
	return dp[m][n]
}
func max (i, j int) int {
	if i>j{
		return i
	}
	return j
}

相關文章