题目链接:爬楼梯 - leetcode
题目描述:
假设你正在爬楼梯。需要n
阶你才能到达楼顶。每次你可以爬1
或2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
题目归纳:
不记得是在哪里看到过了,某国部队规定,上楼一定要迈两步台阶以彰显阳刚之气,经查阅,我人民解放军队列条例没这个规定,但我估计现实中他们上楼应该是按两步踏的。不过也有一些研究是说,踏一步和踏两步哪种消耗的卡路里更多,以及这对人的气质的影响,具体可以看这篇文章:People Who Go Up Two Stairs at a Time People Who Go Up Two Stairs at a Time。
解题思路:
解法: 爬楼梯 - leetcode官方题解
class Solution:
def climbStairs(self, n: int) -> int:
# 把这里的楼梯比作数组,从下标0开始跳跃
# nums = [0(start), 1, ..., n] 长度为n+1,每次可以跳1步或者2步,即求从nums[0]跳跃到nums[n]的方法数
dp = [0] * (n+1)
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
题目链接:打家劫舍 - leetcode
题目描述:
你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。
题目归纳:
去沙特阿拉伯170公里的直线城市,这个小偷将成为世界首富。
解题思路:
解法: 爬楼梯 - leetcode官方题解
class Solution:
def rob(self, nums: List[int]) -> int:
# 动态规划
# dp[i]: 第i间获得的最大收益,第i-2间偷&第i间偷,第i-1间不偷
# dp[i] = max(dp[i-2]+nums[i], dp[i-1])
n = len(nums)
if n == 1:
return nums[0]
if n == 2:
return max(nums[0], nums[1])
dp = [0] * n
dp[0] = nums[0]
dp[1] = max(nums[0], nums[1])
for i in range(2, n):
dp[i] = max(dp[i-2]+nums[i], dp[i-1])
return dp[n-1] # dp[n-1]就是一直偷到第n间为止的最高金额
题目链接:单词拆分 - leetcode
题目描述:
给你一个字符串s
和一个字符串列表wordDict
作为字典。如果可以利用字典中出现的一个或多个单词拼接出s
则返回true
。
注意:不要求字典中出现的单词全部都使用,并且字典中的单词可以重复使用。
题目归纳:
解题思路:
解法: 单词拆分 - leetcode官方题解
# 组合的反义词是拆分,反向思考,如果能拆,那么就能组合。
# dp[i]:字符串s的前i个字符组成的字符串s[0,...,i-1],是否能被空格拆分成若干个字典中出现的单词。
# 从前往后计算并考虑转移方程,每次转移的时候,需要枚举包含位置i-1的最后一个单词,看它是否出现在字典中,以及除去这部分的字符串是否合法即可。
# 枚举s[0,...,i-1]中的分割点j,看s[0,...,j-1]组成的字符串s1与s[j,...,i-1]组成的字符串s2是否合法,若都合法,那么按照定义s1+s2也合法
# 由于计算到dp[i]时,已经计算出了dp[0,...,i-1]的值,因此s1是否合法可以直接由dp[j]得知,剩下的只需要再看s2是否合法
# dp[i] = dp[j] && check(s[j..i-1])
# check(s[j..i-1])表示子串s[j..i-1]是否出现在字典中
#
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
wordDictSet = set(wordDict) # 字典进行去重
n = len(s)
dp = [False] * (n+1)
dp[0] = True # 空串为True
for i in range(1, n+1):
for j in range(0, i):
if dp[j] and s[j:i] in wordDictSet:
# dp[j] and s[j:i] in wordDictSet 这句的解释:
# 由于计算到dp[i]时,已经计算出了dp[0,...,i-1]的值,因此s1是否合法可以直接由dp[j]得知,剩下的只需要再看s2是否合法
dp[i] = True
break
return dp[n]
题目链接:零钱兑换 - leetcode
题目描述:
给你一个整数数组coins
,表示不同面额的硬币;以及一个整数amount
,表示总金额。计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回-1
。
你可以认为每种硬币的数量是无限的。
题目归纳:
把平常买菜找钱的想法写成代码。
解题思路:
解法: 零钱兑换 - leetcode官方题解
动态规划
# 一个整数数组coins,表示不同面额的硬币
# 一个整数amount,表示总金额
# 计算并返回,可以凑成amount的所需的最少的硬币个数,如果没有这种组合,返回-1
# 你可以认为硬币的数量是无限的
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
# 动态规划算法
# dp[i] 凑成金额i的最少硬币个数
# 假设在计算出dp[i]前,已经计算出了dp[0] 到 dp[i-1]的答案
# 那么
# dp[i] = min_{j=0,...,n-1}dp[i-c_{j}] + 1
# c_{j} 代表第j枚硬币的面值
dp = [float('inf')] * (amount + 1)
dp[0] = 0
for coin in coins: # 面值
for x in range(coin, amount+1): # 从coin开始计数是为了防止 (x-coin < 0) 越界。
dp[x] = min(dp[x], dp[x-coin] + 1)
if dp[amount] != float('inf'):
return dp[amount]
else:
return -1
题目链接:最长递增子序列 - leetcode
题目描述:
给你一个整数数组nums
,找到其中最长严格递增子序列的长度。子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7]
是数组[0,3,1,6,2,2,7]
的子序列。
题目归纳:
通过这道题,复习下子序列的定义,和数学中的子数列定义类似。
解题思路:
解法: 最长递增子序列 - leetcode官方题解
(1)动态规划。
(2)动态规划+二分查找。
# dp[i]:前i个元素,以第i个数字结尾的最长上升子序列的长度,nums[i]必须被选取
# 从小到大计算dp数组的值
# dp[i] = max(dp[j])+1,其中0<=j <i且num[j] < num[i]
# 最后 return max(dp[i]) 0<= i < n
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
n = len(nums)
if n == 0:
return 0
dp = [0] * n
dp[0] = 1
ans = 1
for i in range(1, n):
dp[i] = 1 # 万一序列全部递减,一个数也还是满足递增子数组的要求
for j in range(0, i):
if (nums[i] > nums[j]):
dp[i] = max(dp[i], dp[j]+1)
ans = max(ans, dp[i]) # 更新答案
return ans