动态规划解题算法
1、最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度
示例
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4
方法一:典型的动态规划,dp[i] 表示到第 i 位时最长上升子序列的长度,为了更新 dp[i],需要找到以 num[i] 为最后一个元素的所有上升子序列,求出最长的长度
状态转移方程为:p[i] = max(dp[j]) + 1
, 其中 0 < j < i
, 且 nums[i] > nums[j]
,若不存在 nums[i] > nums[j]
, 其中0 < j < i
, 则 dp[i] = 1
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums:
return 0
n = len(nums)
dp = [1]*len(nums)
for i in range(1,n):
cout = 0
for j in range(i):
if nums[j]<nums[i]:
cout = max(dp[j],cout)
if cout:
dp[i] = cout+1
return max(dp)
C++ 写法
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if(n==0){
return 0;
}
vector<int>dp(n,1);
int resmax = 1;
for(int i = 1;i<n;++i){
int temp = 0;
for(int j = 0;j<i;++j){
if(nums[j]<nums[i]){
temp = max(temp,dp[j]);
}
}
if(temp){
dp[i] = temp + 1;
}
resmax = max(resmax,dp[i]);
}
return resmax;
}
方法二:上面时间复杂度为O(N2),利用二分查找可以优化时间复杂度为 O(N*log2N)
def lengthOfLIS(self, nums: List[int]) -> int:
dp = [0] * len(nums)
res = 0
for num in nums:
left, right = 0, res
while left < right:
mid = (left + right) // 2
# 如果要求非严格递增,将此行 '<' 改为 '<=' 即可
if dp[mid] < num:
left = mid + 1
else:
right = mid
dp[left] = num
if right == res:
res += 1
return res
2、零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount,编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1
示例1
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例2
输入: coins = [2], amount = 3
输出: -1
错误思路:先将 coins 排序,然后依次取出coins 最后一个数字和amount 比较,如果大于amount 则弹出最后一个元素,如果小于amount 则将最后一个元素添加到返回列表中,最后看amount 是否为0,如果等于 0 则返回列表长度,否则返回 -1
正确思路:利用动态规划,从 1 到 amount 这个范围,需要一个一个去判断它最小的个数,dp[i] 表示凑成总金额 i 需要的最少硬币个数
如例 1 中 coins=[1,2,5],dp[1] , dp[2] 和 dp[5] 都等于 1,因为用相应金额的一枚硬币就能组成 i
对于其他的 i ,例如 i = 11
dp[11] = min(dp[11-1], dp[11-2], dp[11-5])+1
dp[i] 只有这两种更新方式 ,有可能 amount 无法用 coins 里的硬币构成,那么,初始化每个 dp[i] 的时候都为 amount,若 dp[amount] > amount 返回 -1
C++ 版本
int coinChange(vector<int>& coins, int amount) {
vector<int>dp(amount+1);
sort(coins.begin(),coins.end());
unordered_map<int,int>um;
for(auto&e: coins){
um[e] = 1;
}
for(int i = 1;i<amount+1;++i){
if(um[i]){
dp[i] = 1;
continue;
}
int min_val = amount;
for(int j = 0;j<coins.size();++j){
if(i>=coins[j]){
min_val = min(min_val,dp[i-coins[j]]);
}
}
dp[i] = min_val+1;
}
if(dp[amount]>amount){
return -1;
}
return dp[amount];
}
方法二:
int coinChange(vector<int>& v, int aim,int n) {
vector<int>dp(aim+1);
// dp[i] 表示到达 i 目标值的最小货币个数
for(int i = 1;i<aim+1;++i){
int min_val = aim;
for(int j = 0;j < n;++j){
if(i >= v[j]){
min_val = min(min_val,dp[i-v[j]]);