diff --git a/problems/0139.单词拆分.md b/problems/0139.单词拆分.md index 230942ef..742fe06c 100644 --- a/problems/0139.单词拆分.md +++ b/problems/0139.单词拆分.md @@ -337,10 +337,53 @@ class Solution { Python: +回溯 +```python +class Solution: + def backtracking(self, s: str, wordSet: set[str], startIndex: int) -> bool: + # 边界情况:已经遍历到字符串末尾,返回True + if startIndex >= len(s): + return True + + # 遍历所有可能的拆分位置 + for i in range(startIndex, len(s)): + word = s[startIndex:i + 1] # 截取子串 + if word in wordSet and self.backtracking(s, wordSet, i + 1): + # 如果截取的子串在字典中,并且后续部分也可以被拆分成单词,返回True + return True + + # 无法进行有效拆分,返回False + return False + + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + wordSet = set(wordDict) # 转换为哈希集合,提高查找效率 + return self.backtracking(s, wordSet, 0) + +``` +DP(版本一) +```python +class Solution: + def wordBreak(self, s: str, wordDict: List[str]) -> bool: + wordSet = set(wordDict) + n = len(s) + dp = [False] * (n + 1) # dp[i] 表示字符串的前 i 个字符是否可以被拆分成单词 + dp[0] = True # 初始状态,空字符串可以被拆分成单词 + + for i in range(1, n + 1): # 遍历背包 + for j in range(i): # 遍历单词 + if dp[j] and s[j:i] in wordSet: + dp[i] = True # 如果 s[0:j] 可以被拆分成单词,并且 s[j:i] 在单词集合中存在,则 s[0:i] 可以被拆分成单词 + break + + return dp[n] + + +``` +DP(版本二) + ```python class Solution: def wordBreak(self, s: str, wordDict: List[str]) -> bool: - '''排列''' dp = [False]*(len(s) + 1) dp[0] = True # 遍历背包 @@ -351,17 +394,6 @@ class Solution: dp[j] = dp[j] or (dp[j - len(word)] and word == s[j - len(word):j]) return dp[len(s)] ``` -```python -class Solution: # 和视频中写法一致(和最上面C++写法一致) - def wordBreak(self, s: str, wordDict: List[str]) -> bool: - dp = [False]*(len(s)+1) - dp[0]=True - for j in range(1,len(s)+1): - for i in range(j): - word = s[i:j] - if word in wordDict and dp[i]: dp[j]=True - return dp[-1] -``` diff --git a/problems/0198.打家劫舍.md b/problems/0198.打家劫舍.md index 7c8ba4db..bed7a4ea 100644 --- a/problems/0198.打家劫舍.md +++ b/problems/0198.打家劫舍.md @@ -195,30 +195,65 @@ class Solution { ``` Python: + +1维DP ```python class Solution: def rob(self, nums: List[int]) -> int: - if len(nums) == 0: + if len(nums) == 0: # 如果没有房屋,返回0 return 0 - if len(nums) == 1: + if len(nums) == 1: # 如果只有一个房屋,返回其金额 return nums[0] + + # 创建一个动态规划数组,用于存储最大金额 dp = [0] * len(nums) - dp[0] = nums[0] - dp[1] = max(nums[0], nums[1]) + dp[0] = nums[0] # 将dp的第一个元素设置为第一个房屋的金额 + dp[1] = max(nums[0], nums[1]) # 将dp的第二个元素设置为第一二个房屋中的金额较大者 + + # 遍历剩余的房屋 for i in range(2, len(nums)): - dp[i] = max(dp[i-2]+nums[i], dp[i-1]) - return dp[-1] + # 对于每个房屋,选择抢劫当前房屋和抢劫前一个房屋的最大金额 + dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]) + + return dp[-1] # 返回最后一个房屋中可抢劫的最大金额 ``` +2维DP ```python -class Solution: # 二维dp数组写法 +class Solution: def rob(self, nums: List[int]) -> int: - dp = [[0,0] for _ in range(len(nums))] - dp[0][1] = nums[0] - for i in range(1,len(nums)): - dp[i][0] = max(dp[i-1][1],dp[i-1][0]) - dp[i][1] = dp[i-1][0]+nums[i] - print(dp) - return max(dp[-1]) + if not nums: # 如果没有房屋,返回0 + return 0 + + n = len(nums) + dp = [[0, 0] for _ in range(n)] # 创建二维动态规划数组,dp[i][0]表示不抢劫第i个房屋的最大金额,dp[i][1]表示抢劫第i个房屋的最大金额 + + dp[0][1] = nums[0] # 抢劫第一个房屋的最大金额为第一个房屋的金额 + + for i in range(1, n): + dp[i][0] = max(dp[i-1][0], dp[i-1][1]) # 不抢劫第i个房屋,最大金额为前一个房屋抢劫和不抢劫的最大值 + dp[i][1] = dp[i-1][0] + nums[i] # 抢劫第i个房屋,最大金额为前一个房屋不抢劫的最大金额加上当前房屋的金额 + + return max(dp[n-1][0], dp[n-1][1]) # 返回最后一个房屋中可抢劫的最大金额 + +``` +优化版 +```python +class Solution: + def rob(self, nums: List[int]) -> int: + if not nums: # 如果没有房屋,返回0 + return 0 + + prev_max = 0 # 上一个房屋的最大金额 + curr_max = 0 # 当前房屋的最大金额 + + for num in nums: + temp = curr_max # 临时变量保存当前房屋的最大金额 + curr_max = max(prev_max + num, curr_max) # 更新当前房屋的最大金额 + prev_max = temp # 更新上一个房屋的最大金额 + + return curr_max # 返回最后一个房屋中可抢劫的最大金额 + + ``` Go: ```Go diff --git a/problems/0213.打家劫舍II.md b/problems/0213.打家劫舍II.md index 6395f3a8..3f532a41 100644 --- a/problems/0213.打家劫舍II.md +++ b/problems/0213.打家劫舍II.md @@ -130,40 +130,93 @@ class Solution { ``` Python: + ```Python class Solution: def rob(self, nums: List[int]) -> int: - #在198入门级的打家劫舍问题上分两种情况考虑 - #一是不偷第一间房,二是不偷最后一间房 - if len(nums)==1:#题目中提示nums.length>=1,所以不需要考虑len(nums)==0的情况 + if len(nums) == 0: + return 0 + if len(nums) == 1: return nums[0] - val1=self.roblist(nums[1:])#不偷第一间房 - val2=self.roblist(nums[:-1])#不偷最后一间房 - return max(val1,val2) + + result1 = self.robRange(nums, 0, len(nums) - 2) # 情况二 + result2 = self.robRange(nums, 1, len(nums) - 1) # 情况三 + return max(result1, result2) + # 198.打家劫舍的逻辑 + def robRange(self, nums: List[int], start: int, end: int) -> int: + if end == start: + return nums[start] + + prev_max = nums[start] + curr_max = max(nums[start], nums[start + 1]) + + for i in range(start + 2, end + 1): + temp = curr_max + curr_max = max(prev_max + nums[i], curr_max) + prev_max = temp + + return curr_max - def roblist(self,nums): - l=len(nums) - dp=[0]*l - dp[0]=nums[0] - for i in range(1,l): - if i==1: - dp[i]=max(dp[i-1],nums[i]) - else: - dp[i]=max(dp[i-1],dp[i-2]+nums[i]) - return dp[-1] ``` +2维DP ```python -class Solution: # 二维dp数组写法 +class Solution: def rob(self, nums: List[int]) -> int: - if len(nums)<3: return max(nums) - return max(self.default(nums[:-1]),self.default(nums[1:])) - def default(self,nums): - dp = [[0,0] for _ in range(len(nums))] + if len(nums) < 3: + return max(nums) + + # 情况二:不抢劫第一个房屋 + result1 = self.robRange(nums[:-1]) + + # 情况三:不抢劫最后一个房屋 + result2 = self.robRange(nums[1:]) + + return max(result1, result2) + + def robRange(self, nums): + dp = [[0, 0] for _ in range(len(nums))] dp[0][1] = nums[0] - for i in range(1,len(nums)): - dp[i][0] = max(dp[i-1]) - dp[i][1] = dp[i-1][0] + nums[i] + + for i in range(1, len(nums)): + dp[i][0] = max(dp[i - 1]) + dp[i][1] = dp[i - 1][0] + nums[i] + return max(dp[-1]) + + + +``` + +优化版 +```python +class Solution: + def rob(self, nums: List[int]) -> int: + if not nums: # 如果没有房屋,返回0 + return 0 + + if len(nums) == 1: # 如果只有一个房屋,返回该房屋的金额 + return nums[0] + + # 情况二:不抢劫第一个房屋 + prev_max = 0 # 上一个房屋的最大金额 + curr_max = 0 # 当前房屋的最大金额 + for num in nums[1:]: + temp = curr_max # 临时变量保存当前房屋的最大金额 + curr_max = max(prev_max + num, curr_max) # 更新当前房屋的最大金额 + prev_max = temp # 更新上一个房屋的最大金额 + result1 = curr_max + + # 情况三:不抢劫最后一个房屋 + prev_max = 0 # 上一个房屋的最大金额 + curr_max = 0 # 当前房屋的最大金额 + for num in nums[:-1]: + temp = curr_max # 临时变量保存当前房屋的最大金额 + curr_max = max(prev_max + num, curr_max) # 更新当前房屋的最大金额 + prev_max = temp # 更新上一个房屋的最大金额 + result2 = curr_max + + return max(result1, result2) + ``` Go: diff --git a/problems/0279.完全平方数.md b/problems/0279.完全平方数.md index f5b23d26..80b69f0c 100644 --- a/problems/0279.完全平方数.md +++ b/problems/0279.完全平方数.md @@ -217,36 +217,61 @@ class Solution { Python: +先遍历物品, 再遍历背包 ```python class Solution: def numSquares(self, n: int) -> int: - '''版本一,先遍历背包, 再遍历物品''' - # 初始化 - nums = [i**2 for i in range(1, n + 1) if i**2 <= n] - dp = [10**4]*(n + 1) + dp = [float('inf')] * (n + 1) dp[0] = 0 - # 遍历背包 - for j in range(1, n + 1): - # 遍历物品 - for num in nums: - if j >= num: - dp[j] = min(dp[j], dp[j - num] + 1) - return dp[n] - - def numSquares1(self, n: int) -> int: - '''版本二, 先遍历物品, 再遍历背包''' - # 初始化 - nums = [i**2 for i in range(1, n + 1) if i**2 <= n] - dp = [10**4]*(n + 1) - dp[0] = 0 - # 遍历物品 - for num in nums: - # 遍历背包 - for j in range(num, n + 1): - dp[j] = min(dp[j], dp[j - num] + 1) - return dp[n] -``` + for i in range(1, n + 1): # 遍历背包 + for j in range(1, int(i ** 0.5) + 1): # 遍历物品 + # 更新凑成数字 i 所需的最少完全平方数数量 + dp[i] = min(dp[i], dp[i - j * j] + 1) + + return dp[n] + +``` +先遍历背包, 再遍历物品 +```python +class Solution: + def numSquares(self, n: int) -> int: + dp = [float('inf')] * (n + 1) + dp[0] = 0 + + for i in range(1, int(n ** 0.5) + 1): # 遍历物品 + for j in range(i * i, n + 1): # 遍历背包 + # 更新凑成数字 j 所需的最少完全平方数数量 + dp[j] = min(dp[j - i * i] + 1, dp[j]) + + return dp[n] + + +``` +其他版本 +```python +class Solution: + def numSquares(self, n: int) -> int: + # 创建动态规划数组,初始值为最大值 + dp = [float('inf')] * (n + 1) + # 初始化已知情况 + dp[0] = 0 + + # 遍历背包容量 + for i in range(1, n + 1): + # 遍历完全平方数作为物品 + j = 1 + while j * j <= i: + # 更新最少完全平方数的数量 + dp[i] = min(dp[i], dp[i - j * j] + 1) + j += 1 + + # 返回结果 + return dp[n] + + + +``` Go: ```go // 版本一,先遍历物品, 再遍历背包 diff --git a/problems/0322.零钱兑换.md b/problems/0322.零钱兑换.md index 0e3947da..67b1a6d9 100644 --- a/problems/0322.零钱兑换.md +++ b/problems/0322.零钱兑换.md @@ -217,36 +217,76 @@ class Solution { Python: + +先遍历物品 后遍历背包 ```python class Solution: def coinChange(self, coins: List[int], amount: int) -> int: - '''版本一''' - # 初始化 - dp = [float("inf")]*(amount + 1) - dp[0] = 0 - # 遍历物品 - for coin in coins: - # 遍历背包 - for j in range(coin, amount + 1): - dp[j] = min(dp[j], dp[j - coin] + 1) - return dp[amount] if dp[amount] != float("inf") else -1 + dp = [float('inf')] * (amount + 1) # 创建动态规划数组,初始值为正无穷大 + dp[0] = 0 # 初始化背包容量为0时的最小硬币数量为0 + + for coin in coins: # 遍历硬币列表,相当于遍历物品 + for i in range(coin, amount + 1): # 遍历背包容量 + if dp[i - coin] != float('inf'): # 如果dp[i - coin]不是初始值,则进行状态转移 + dp[i] = min(dp[i - coin] + 1, dp[i]) # 更新最小硬币数量 + + if dp[amount] == float('inf'): # 如果最终背包容量的最小硬币数量仍为正无穷大,表示无解 + return -1 + return dp[amount] # 返回背包容量为amount时的最小硬币数量 - def coinChange1(self, coins: List[int], amount: int) -> int: - '''版本二''' - # 初始化 - dp = [float("inf")]*(amount + 1) - dp[0] = 0 - # 遍历物品 - for j in range(1, amount + 1): - # 遍历背包 - for coin in coins: - if j >= coin: - dp[j] = min(dp[j], dp[j - coin] + 1) - return dp[amount] if dp[amount] != float("inf") else -1 ``` +先遍历背包 后遍历物品 +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [float('inf')] * (amount + 1) # 创建动态规划数组,初始值为正无穷大 + dp[0] = 0 # 初始化背包容量为0时的最小硬币数量为0 + + for i in range(1, amount + 1): # 遍历背包容量 + for j in range(len(coins)): # 遍历硬币列表,相当于遍历物品 + if i - coins[j] >= 0 and dp[i - coins[j]] != float('inf'): # 如果dp[i - coins[j]]不是初始值,则进行状态转移 + dp[i] = min(dp[i - coins[j]] + 1, dp[i]) # 更新最小硬币数量 + + if dp[amount] == float('inf'): # 如果最终背包容量的最小硬币数量仍为正无穷大,表示无解 + return -1 + return dp[amount] # 返回背包容量为amount时的最小硬币数量 + +``` +先遍历物品 后遍历背包(优化版) +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + + for coin in coins: + for i in range(coin, amount + 1): # 进行优化,从能装得下的背包开始计算,则不需要进行比较 + # 更新凑成金额 i 所需的最少硬币数量 + dp[i] = min(dp[i], dp[i - coin] + 1) + + return dp[amount] if dp[amount] != float('inf') else -1 +``` +先遍历背包 后遍历物品(优化版) +```python +class Solution: + def coinChange(self, coins: List[int], amount: int) -> int: + dp = [float('inf')] * (amount + 1) + dp[0] = 0 + + for i in range(1, amount + 1): # 遍历背包容量 + for coin in coins: # 遍历物品 + if i - coin >= 0: + # 更新凑成金额 i 所需的最少硬币数量 + dp[i] = min(dp[i], dp[i - coin] + 1) + + return dp[amount] if dp[amount] != float('inf') else -1 + + + +``` Go: ```go diff --git a/problems/0377.组合总和Ⅳ.md b/problems/0377.组合总和Ⅳ.md index ee659723..b6b15386 100644 --- a/problems/0377.组合总和Ⅳ.md +++ b/problems/0377.组合总和Ⅳ.md @@ -176,21 +176,37 @@ class Solution { Python: + +卡哥版 ```python class Solution: - def combinationSum4(self, nums, target): + def combinationSum4(self, nums: List[int], target: int) -> int: dp = [0] * (target + 1) dp[0] = 1 + for i in range(1, target + 1): # 遍历背包 + for j in range(len(nums)): # 遍历物品 + if i - nums[j] >= 0: + dp[i] += dp[i - nums[j]] + return dp[target] - for i in range(1, target+1): - for j in nums: - if i >= j: - dp[i] += dp[i - j] - - return dp[-1] ``` +优化版 + +```python +class Solution: + def combinationSum4(self, nums: List[int], target: int) -> int: + dp = [0] * (target + 1) # 创建动态规划数组,用于存储组合总数 + dp[0] = 1 # 初始化背包容量为0时的组合总数为1 + + for i in range(1, target + 1): # 遍历背包容量 + for j in nums: # 遍历物品列表 + if i >= j: # 当背包容量大于等于当前物品重量时 + dp[i] += dp[i - j] # 更新组合总数 + + return dp[-1] # 返回背包容量为target时的组合总数 +``` Go: ```go func combinationSum4(nums []int, target int) int { diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md index 54d90612..abdefc9f 100644 --- a/problems/0416.分割等和子集.md +++ b/problems/0416.分割等和子集.md @@ -355,62 +355,85 @@ class Solution { ``` ### Python: +卡哥版 ```python -# 一维度数组解法 class Solution: def canPartition(self, nums: List[int]) -> bool: - target = sum(nums) - if target % 2 == 1: return False - target //= 2 - dp = [0] * (target + 1) - for i in range(len(nums)): - for j in range(target, nums[i] - 1, -1): - dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]) - return target == dp[target] + _sum = 0 + + # dp[i]中的i表示背包内总和 + # 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200 + # 总和不会大于20000,背包最大只需要其中一半,所以10001大小就可以了 + dp = [0] * 10001 + for num in nums: + _sum += num + # 也可以使用内置函数一步求和 + # _sum = sum(nums) + if _sum % 2 == 1: + return False + target = _sum // 2 + + # 开始 0-1背包 + for num in nums: + for j in range(target, num - 1, -1): # 每一个元素一定是不可重复放入,所以从大到小遍历 + dp[j] = max(dp[j], dp[j - num] + num) + + # 集合中的元素正好可以凑成总和target + if dp[target] == target: + return True + return False + ``` - +二维DP版 ```python -# 二维度数组解法 class Solution: def canPartition(self, nums: List[int]) -> bool: - target = sum(nums) - nums = sorted(nums) + + total_sum = sum(nums) - # 做最初的判断 - if target % 2 != 0: + if total_sum % 2 != 0: return False - # 找到 target value 可以认为这个是背包的体积 - target = target // 2 + target_sum = total_sum // 2 + dp = [[False] * (target_sum + 1) for _ in range(len(nums) + 1)] - row = len(nums) - col = target + 1 + # 初始化第一行(空子集可以得到和为0) + for i in range(len(nums) + 1): + dp[i][0] = True - # 定义 dp table - dp = [[0 for _ in range(col)] for _ in range(row)] - - # 初始 dp value - for i in range(row): - dp[i][0] = 0 - - for j in range(1, target): - if nums[0] <= j: - dp[0][j] = nums[0] - - # 遍历 先遍历物品再遍历背包 - for i in range(1, row): - - cur_weight = nums[i] - cur_value = nums[i] - - for j in range(1, col): - if cur_weight > j: + for i in range(1, len(nums) + 1): + for j in range(1, target_sum + 1): + if j < nums[i - 1]: + # 当前数字大于目标和时,无法使用该数字 dp[i][j] = dp[i - 1][j] else: - dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cur_weight] + cur_value) - - # 输出结果 - return dp[-1][col - 1] == target + # 当前数字小于等于目标和时,可以选择使用或不使用该数字 + dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]] + + return dp[len(nums)][target_sum] + +``` +一维DP版 +```python +class Solution: + def canPartition(self, nums: List[int]) -> bool: + + total_sum = sum(nums) + + if total_sum % 2 != 0: + return False + + target_sum = total_sum // 2 + dp = [False] * (target_sum + 1) + dp[0] = True + + for num in nums: + # 从target_sum逆序迭代到num,步长为-1 + for i in range(target_sum, num - 1, -1): + dp[i] = dp[i] or dp[i - num] + return dp[target_sum] + + ``` ### Go: diff --git a/problems/0474.一和零.md b/problems/0474.一和零.md index 6a178a25..145f64aa 100644 --- a/problems/0474.一和零.md +++ b/problems/0474.一和零.md @@ -210,19 +210,35 @@ class Solution { ``` ### Python +DP(版本一) ```python class Solution: def findMaxForm(self, strs: List[str], m: int, n: int) -> int: - dp = [[0] * (n + 1) for _ in range(m + 1)] # 默认初始化0 + dp = [[0] * (n + 1) for _ in range(m + 1)] # 创建二维动态规划数组,初始化为0 + for s in strs: # 遍历物品 + zeroNum = s.count('0') # 统计0的个数 + oneNum = len(s) - zeroNum # 统计1的个数 + for i in range(m, zeroNum - 1, -1): # 遍历背包容量且从后向前遍历 + for j in range(n, oneNum - 1, -1): + dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1) # 状态转移方程 + return dp[m][n] + +``` +DP(版本二) +```python +class Solution: + def findMaxForm(self, strs: List[str], m: int, n: int) -> int: + dp = [[0] * (n + 1) for _ in range(m + 1)] # 创建二维动态规划数组,初始化为0 # 遍历物品 - for str in strs: - ones = str.count('1') - zeros = str.count('0') - # 遍历背包容量且从后向前遍历! + for s in strs: + ones = s.count('1') # 统计字符串中1的个数 + zeros = s.count('0') # 统计字符串中0的个数 + # 遍历背包容量且从后向前遍历 for i in range(m, zeros - 1, -1): for j in range(n, ones - 1, -1): - dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1) + dp[i][j] = max(dp[i][j], dp[i - zeros][j - ones] + 1) # 状态转移方程 return dp[m][n] + ``` ### Go diff --git a/problems/0494.目标和.md b/problems/0494.目标和.md index c9f88892..32931e6b 100644 --- a/problems/0494.目标和.md +++ b/problems/0494.目标和.md @@ -293,19 +293,84 @@ class Solution { ``` ### Python +回溯版 +```python +class Solution: + + + def backtracking(self, candidates, target, total, startIndex, path, result): + if total == target: + result.append(path[:]) # 将当前路径的副本添加到结果中 + # 如果 sum + candidates[i] > target,则停止遍历 + for i in range(startIndex, len(candidates)): + if total + candidates[i] > target: + break + total += candidates[i] + path.append(candidates[i]) + self.backtracking(candidates, target, total, i + 1, path, result) + total -= candidates[i] + path.pop() + + def findTargetSumWays(self, nums: List[int], target: int) -> int: + total = sum(nums) + if target > total: + return 0 # 此时没有方案 + if (target + total) % 2 != 0: + return 0 # 此时没有方案,两个整数相加时要注意数值溢出的问题 + bagSize = (target + total) // 2 # 转化为组合总和问题,bagSize就是目标和 + + # 以下是回溯法代码 + result = [] + nums.sort() # 需要对nums进行排序 + self.backtracking(nums, bagSize, 0, 0, [], result) + return len(result) + +``` +二维DP ```python class Solution: def findTargetSumWays(self, nums: List[int], target: int) -> int: - sumValue = sum(nums) - #注意边界条件为 target>sumValue or target<-sumValue or (sumValue + target) % 2 == 1 - if abs(target) > sumValue or (sumValue + target) % 2 == 1: return 0 - bagSize = (sumValue + target) // 2 - dp = [0] * (bagSize + 1) - dp[0] = 1 - for i in range(len(nums)): - for j in range(bagSize, nums[i] - 1, -1): - dp[j] += dp[j - nums[i]] - return dp[bagSize] + total_sum = sum(nums) # 计算nums的总和 + if abs(target) > total_sum: + return 0 # 此时没有方案 + if (target + total_sum) % 2 == 1: + return 0 # 此时没有方案 + target_sum = (target + total_sum) // 2 # 目标和 + + # 创建二维动态规划数组,行表示选取的元素数量,列表示累加和 + dp = [[0] * (target_sum + 1) for _ in range(len(nums) + 1)] + + # 初始化状态 + dp[0][0] = 1 + + # 动态规划过程 + for i in range(1, len(nums) + 1): + for j in range(target_sum + 1): + dp[i][j] = dp[i - 1][j] # 不选取当前元素 + if j >= nums[i - 1]: + dp[i][j] += dp[i - 1][j - nums[i - 1]] # 选取当前元素 + + return dp[len(nums)][target_sum] # 返回达到目标和的方案数 + + +``` +一维DP +```python +class Solution: + def findTargetSumWays(self, nums: List[int], target: int) -> int: + total_sum = sum(nums) # 计算nums的总和 + if abs(target) > total_sum: + return 0 # 此时没有方案 + if (target + total_sum) % 2 == 1: + return 0 # 此时没有方案 + target_sum = (target + total_sum) // 2 # 目标和 + dp = [0] * (target_sum + 1) # 创建动态规划数组,初始化为0 + dp[0] = 1 # 当目标和为0时,只有一种方案,即什么都不选 + for num in nums: + for j in range(target_sum, num - 1, -1): + dp[j] += dp[j - num] # 状态转移方程,累加不同选择方式的数量 + return dp[target_sum] # 返回达到目标和的方案数 + ``` ### Go diff --git a/problems/1049.最后一块石头的重量II.md b/problems/1049.最后一块石头的重量II.md index a8150b1f..a978b802 100644 --- a/problems/1049.最后一块石头的重量II.md +++ b/problems/1049.最后一块石头的重量II.md @@ -224,18 +224,79 @@ class Solution { ``` ### Python: +卡哥版 ```python class Solution: def lastStoneWeightII(self, stones: List[int]) -> int: - sumweight = sum(stones) - target = sumweight // 2 - dp = [0] * (target + 1) - for i in range(len(stones)): - for j in range(target, stones[i] - 1, -1): - dp[j] = max(dp[j], dp[j - stones[i]] + stones[i]) - return sumweight - 2 * dp[target] -``` + dp = [0] * 15001 + total_sum = sum(stones) + target = total_sum // 2 + for stone in stones: # 遍历物品 + for j in range(target, stone - 1, -1): # 遍历背包 + dp[j] = max(dp[j], dp[j - stone] + stone) + + return total_sum - dp[target] - dp[target] + +``` +二维DP版 +```python +class Solution: + def lastStoneWeightII(self, stones: List[int]) -> int: + total_sum = sum(stones) + target = total_sum // 2 + + # 创建二维dp数组,行数为石头的数量加1,列数为target加1 + # dp[i][j]表示前i个石头能否组成总重量为j + dp = [[False] * (target + 1) for _ in range(len(stones) + 1)] + + # 初始化第一列,表示总重量为0时,前i个石头都能组成 + for i in range(len(stones) + 1): + dp[i][0] = True + + for i in range(1, len(stones) + 1): + for j in range(1, target + 1): + # 如果当前石头重量大于当前目标重量j,则无法选择该石头 + if stones[i - 1] > j: + dp[i][j] = dp[i - 1][j] + else: + # 可选择该石头或不选择该石头 + dp[i][j] = dp[i - 1][j] or dp[i - 1][j - stones[i - 1]] + + # 找到最大的重量i,使得dp[len(stones)][i]为True + # 返回总重量减去两倍的最接近总重量一半的重量 + for i in range(target, -1, -1): + if dp[len(stones)][i]: + return total_sum - 2 * i + + return 0 + + +``` +一维DP版 +```python +class Solution: + def lastStoneWeightII(self, stones): + total_sum = sum(stones) + target = total_sum // 2 + dp = [False] * (target + 1) + dp[0] = True + + for stone in stones: + for j in range(target, stone - 1, -1): + # 判断当前重量是否可以通过选择之前的石头得到或选择当前石头和之前的石头得到 + dp[j] = dp[j] or dp[j - stone] + + for i in range(target, -1, -1): + if dp[i]: + # 返回剩余石头的重量,即总重量减去两倍的最接近总重量一半的重量 + return total_sum - 2 * i + + return 0 + + + +``` ### Go: ```go func lastStoneWeightII(stones []int) int { diff --git a/problems/背包理论基础01背包-1.md b/problems/背包理论基础01背包-1.md index c2525248..9df2fc4c 100644 --- a/problems/背包理论基础01背包-1.md +++ b/problems/背包理论基础01背包-1.md @@ -339,38 +339,63 @@ public class BagProblem { ``` ### python - +无参数版 ```python -def test_2_wei_bag_problem1(bag_size, weight, value) -> int: - rows, cols = len(weight), bag_size + 1 - dp = [[0 for _ in range(cols)] for _ in range(rows)] +def test_2_wei_bag_problem1(): + weight = [1, 3, 4] + value = [15, 20, 30] + bagweight = 4 - # 初始化dp数组. - for i in range(rows): - dp[i][0] = 0 - first_item_weight, first_item_value = weight[0], value[0] - for j in range(1, cols): - if first_item_weight <= j: - dp[0][j] = first_item_value + # 二维数组 + dp = [[0] * (bagweight + 1) for _ in range(len(weight))] - # 更新dp数组: 先遍历物品, 再遍历背包. - for i in range(1, len(weight)): - cur_weight, cur_val = weight[i], value[i] - for j in range(1, cols): - if cur_weight > j: # 说明背包装不下当前物品. - dp[i][j] = dp[i - 1][j] # 所以不装当前物品. - else: - # 定义dp数组: dp[i][j] 前i个物品里,放进容量为j的背包,价值总和最大是多少。 - dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - cur_weight]+ cur_val) + # 初始化 + for j in range(weight[0], bagweight + 1): + dp[0][j] = value[0] - print(dp) + # weight数组的大小就是物品个数 + for i in range(1, len(weight)): # 遍历物品 + for j in range(bagweight + 1): # 遍历背包容量 + 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]) + print(dp[len(weight) - 1][bagweight]) + +test_2_wei_bag_problem1() + +``` +有参数版 +```python +def test_2_wei_bag_problem1(weight, value, bagweight): + # 二维数组 + dp = [[0] * (bagweight + 1) for _ in range(len(weight))] + + # 初始化 + for j in range(weight[0], bagweight + 1): + dp[0][j] = value[0] + + # weight数组的大小就是物品个数 + for i in range(1, len(weight)): # 遍历物品 + for j in range(bagweight + 1): # 遍历背包容量 + 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]) + + return dp[len(weight) - 1][bagweight] if __name__ == "__main__": - bag_size = 4 - weight = [1, 3, 4] - value = [15, 20, 30] - test_2_wei_bag_problem1(bag_size, weight, value) + + weight = [1, 3, 4] + value = [15, 20, 30] + bagweight = 4 + + result = test_2_wei_bag_problem1(weight, value, bagweight) + print(result) + + ``` diff --git a/problems/背包理论基础01背包-2.md b/problems/背包理论基础01背包-2.md index 3b798334..d481e044 100644 --- a/problems/背包理论基础01背包-2.md +++ b/problems/背包理论基础01背包-2.md @@ -246,25 +246,46 @@ int main() { ### Python +无参版 ```python def test_1_wei_bag_problem(): weight = [1, 3, 4] value = [15, 20, 30] - bag_weight = 4 - # 初始化: 全为0 - dp = [0] * (bag_weight + 1) + bagWeight = 4 - # 先遍历物品, 再遍历背包容量 - for i in range(len(weight)): - for j in range(bag_weight, weight[i] - 1, -1): - # 递归公式 + # 初始化 + dp = [0] * (bagWeight + 1) + for i in range(len(weight)): # 遍历物品 + for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - print(dp) + print(dp[bagWeight]) + test_1_wei_bag_problem() ``` +有参版 +```python +def test_1_wei_bag_problem(weight, value, bagWeight): + # 初始化 + dp = [0] * (bagWeight + 1) + for i in range(len(weight)): # 遍历物品 + for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + return dp[bagWeight] + + +if __name__ == "__main__": + + weight = [1, 3, 4] + value = [15, 20, 30] + bagweight = 4 + + result = test_1_wei_bag_problem(weight, value, bagweight) + print(result) + +``` ### Go ```go func test_1_wei_bag_problem(weight, value []int, bagWeight int) int { diff --git a/problems/背包问题理论基础多重背包.md b/problems/背包问题理论基础多重背包.md index af10dab7..1a856bf5 100644 --- a/problems/背包问题理论基础多重背包.md +++ b/problems/背包问题理论基础多重背包.md @@ -194,55 +194,127 @@ public void testMultiPack2(){ Python: +改变物品数量为01背包格式(无参版) ```python -def test_multi_pack1(): - '''版本一:改变物品数量为01背包格式''' +def test_multi_pack(): weight = [1, 3, 4] value = [15, 20, 30] nums = [2, 3, 2] - bag_weight = 10 + bagWeight = 10 + + # 将数量大于1的物品展开 for i in range(len(nums)): - # 将物品展开数量为1 while nums[i] > 1: weight.append(weight[i]) value.append(value[i]) nums[i] -= 1 - - dp = [0]*(bag_weight + 1) - # 遍历物品 - for i in range(len(weight)): - # 遍历背包 - for j in range(bag_weight, weight[i] - 1, -1): + + dp = [0] * (bagWeight + 1) + for i in range(len(weight)): # 遍历物品 + for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - - print(" ".join(map(str, dp))) + for j in range(bagWeight + 1): + print(dp[j], end=" ") + print() -def test_multi_pack2(): - '''版本:改变遍历个数''' - weight = [1, 3, 4] - value = [15, 20, 30] - nums = [2, 3, 2] - bag_weight = 10 - - dp = [0]*(bag_weight + 1) - for i in range(len(weight)): - for j in range(bag_weight, weight[i] - 1, -1): - # 以上是01背包,加上遍历个数 - for k in range(1, nums[i] + 1): - if j - k*weight[i] >= 0: - dp[j] = max(dp[j], dp[j - k*weight[i]] + k*value[i]) - - print(" ".join(map(str, dp))) + print(dp[bagWeight]) -if __name__ == '__main__': - test_multi_pack1() - test_multi_pack2() +test_multi_pack() + ``` +改变遍历个数(无参版) +```python +def test_multi_pack(): + weight = [1, 3, 4] + value = [15, 20, 30] + nums = [2, 3, 2] + bagWeight = 10 + dp = [0] * (bagWeight + 1) + + for i in range(len(weight)): # 遍历物品 + for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 + # 以上为01背包,然后加一个遍历个数 + for k in range(1, nums[i] + 1): # 遍历个数 + if j - k * weight[i] >= 0: + dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]) + + # 打印一下dp数组 + for j in range(bagWeight + 1): + print(dp[j], end=" ") + print() + + print(dp[bagWeight]) +test_multi_pack() + +``` + + +改变物品数量为01背包格式(有参版) +```python +def test_multi_pack(weight, value, nums, bagWeight): + # 将数量大于1的物品展开 + for i in range(len(nums)): + while nums[i] > 1: + weight.append(weight[i]) + value.append(value[i]) + nums[i] -= 1 + + dp = [0] * (bagWeight + 1) + for i in range(len(weight)): # 遍历物品 + for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + for j in range(bagWeight + 1): + print(dp[j], end=" ") + print() + + print(dp[bagWeight]) + + + + +if __name__ == "__main__": + weight = [1, 3, 4] + value = [15, 20, 30] + nums = [2, 3, 2] + bagWeight = 10 + test_multi_pack(weight, value, nums, bagWeight) +``` + + +改变遍历个数(有参版) +```python +def test_multi_pack(weight, value, nums, bagWeight): + dp = [0] * (bagWeight + 1) + + for i in range(len(weight)): # 遍历物品 + for j in range(bagWeight, weight[i] - 1, -1): # 遍历背包容量 + # 以上为01背包,然后加一个遍历个数 + for k in range(1, nums[i] + 1): # 遍历个数 + if j - k * weight[i] >= 0: + dp[j] = max(dp[j], dp[j - k * weight[i]] + k * value[i]) + + # 使用 join 函数打印 dp 数组 + print(' '.join(str(dp[j]) for j in range(bagWeight + 1))) + + print(dp[bagWeight]) + + + + + +if __name__ == "__main__": + weight = [1, 3, 4] + value = [15, 20, 30] + nums = [2, 3, 2] + bagWeight = 10 + test_multi_pack(weight, value, nums, bagWeight) + +``` Go: ```go diff --git a/problems/背包问题理论基础完全背包.md b/problems/背包问题理论基础完全背包.md index e927aa20..a559a512 100644 --- a/problems/背包问题理论基础完全背包.md +++ b/problems/背包问题理论基础完全背包.md @@ -223,43 +223,81 @@ private static void testCompletePackAnotherWay(){ Python: + + +先遍历物品,再遍历背包(无参版) ```python -# 先遍历物品,再遍历背包 -def test_complete_pack1(): +def test_CompletePack(): weight = [1, 3, 4] value = [15, 20, 30] - bag_weight = 4 - - dp = [0]*(bag_weight + 1) - - for i in range(len(weight)): - for j in range(weight[i], bag_weight + 1): + bagWeight = 4 + dp = [0] * (bagWeight + 1) + for i in range(len(weight)): # 遍历物品 + for j in range(weight[i], bagWeight + 1): # 遍历背包容量 dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - - print(dp[bag_weight]) + print(dp[bagWeight]) -# 先遍历背包,再遍历物品 -def test_complete_pack2(): - weight = [1, 3, 4] - value = [15, 20, 30] - bag_weight = 4 +test_CompletePack() - dp = [0]*(bag_weight + 1) - - for j in range(bag_weight + 1): - for i in range(len(weight)): - if j >= weight[i]: dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) - - print(dp[bag_weight]) - - -if __name__ == '__main__': - test_complete_pack1() - test_complete_pack2() ``` +先遍历物品,再遍历背包(有参版) +```python +def test_CompletePack(weight, value, bagWeight): + dp = [0] * (bagWeight + 1) + for i in range(len(weight)): # 遍历物品 + for j in range(weight[i], bagWeight + 1): # 遍历背包容量 + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + return dp[bagWeight] + +if __name__ == "__main__": + weight = [1, 3, 4] + value = [15, 20, 30] + bagWeight = 4 + result = test_CompletePack(weight, value, bagWeight) + print(result) + +``` +先遍历背包,再遍历物品(无参版) +```python +def test_CompletePack(): + weight = [1, 3, 4] + value = [15, 20, 30] + bagWeight = 4 + + dp = [0] * (bagWeight + 1) + + for j in range(bagWeight + 1): # 遍历背包容量 + for i in range(len(weight)): # 遍历物品 + if j - weight[i] >= 0: + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + + print(dp[bagWeight]) + +test_CompletePack() +``` + +先遍历背包,再遍历物品(有参版) +```python +def test_CompletePack(weight, value, bagWeight): + dp = [0] * (bagWeight + 1) + for j in range(bagWeight + 1): # 遍历背包容量 + for i in range(len(weight)): # 遍历物品 + if j - weight[i] >= 0: + dp[j] = max(dp[j], dp[j - weight[i]] + value[i]) + return dp[bagWeight] + + +if __name__ == "__main__": + weight = [1, 3, 4] + value = [15, 20, 30] + bagWeight = 4 + result = test_CompletePack(weight, value, bagWeight) + print(result) + +``` Go: ```go