老师在讲完语言模型之前,貌似是助教插入了一堂习题课044-047,专门讲了下动态规划。关于算法我自己是有体会的(直接不会或者觉得回了过两又忘了)。
动态规划:很重要的是找出初始数据以及状态转移。
题目:53. Maximum Subarray
Given an integer array nums, find the contiguous subarray (containing at least one number) which has the largest sum and return its sum.
Example 1:Input: nums = [-2,1,-3,4,-1,2,1,-5,4]
Output: 6
Explanation: [4,-1,2,1] has the largest sum = 6.
们用 ai 代表 nums[i],用 f(i) 代表以第 i 个数结尾的「连续子数组的最大和」
f(i)=max{f(i−1)+ai,ai}
还是官网的case更清晰。
class Solution {
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = nums[0];
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
}
题目 300. Longest Increasing Subsequence
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
什么是最长上升子序列?请你在其中求出一段不断严格上升的部分,它不一定要连续。
这个怎么体现出划分子问题呢?要找出上面的8个数的最长子序列,你可以先找前7个数里面的最长子序列,同理,你又必须得找前6个数里面的最长子序列,直到子序列为1。
定义 dp[i]为考虑前 ii个元素,以第 i 个数字结尾的最长上升子序列的长度。
状态转移方程:dp[i] = max(dp[i],dp[j]+1)
还有个门槛,就是知道了状态转移方法,代码不一定写的出来。
感觉老师讲的有些奇怪,好像leetcode上题解更好理解。贴一下官网的动态规划,加个注释
public static int lengthOfLIS(int[] nums) {
if (nums.length == 0) { //为空直接返回0
return 0;
}
int[] dp = new int[nums.length];
dp[0] = 1; //coner case:1 最长子序列也是1
int result =1;
for(int i=1;i< nums.length;i++ )// 循环遍历第i个数字
{
dp[i]=1;
for(int j=0;j<i;j++){ // 循环遍历第i个数字
if(nums[i]>nums[j]){ // 如果比第j个数比第i个数小:存在递增关系
dp[i] = Math.max(dp[i],dp[j]+1); //状态转移方程:得出第i个数的最长上升子序列
}
}
result = Math.max(result,dp[i]); //得到d[i] 中最大得数
}
return result;
}
时间复杂度:O(n2),有更优解:二分查找方法时间复杂度:O(nlogn)
322. 零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
你可以认为每种硬币的数量是无限的。
示例 1:
输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1
示例 2:输入:coins = [2], amount = 3
输出:-1
示例 3:输入:coins = [1], amount = 0
输出:0
这个题目老师讲的不一样,就是出于直觉可能是贪心算法而不是动态规划,但是老师说这是货币面值设计的时候规避了这种问题。
贪心算法:上面第一个例子可以优先选择最大面额的5,再选2,再选1 这种也可以。
但是如果我们换一组钞票面值,比如 1, 5, 11,我们要凑出15的时候, 贪心策略就会出错:
15 = 11 * 1 + 1 * 4 (贪心策略)
而实际情况下,正确选择是5*3.
那么动态规划的状态转移过程是怎么分析出来的?
我们用f(n)来表示“凑出n所需的最少钞票数量”。 回到刚才的case:
如果我们取了11,付出的代价等于f(4)加上自己这一张钞票11。
依次类推,可以知道:如果我们用5来凑出15,代价就是f(10) + 1 = 2 + 1 = 3 。
都算一边,就可以找出代价最小的那个。
f(n)=min{f(n-1),f(n-5),f(n-11)}+1
官网的解法没看懂。贴一下官网下面大神ikaruga的思路:(暴力+递归)这个很厉害,比DP还要快。
因为单纯的递归,下面这种思路:会超出限制,
public void findWay(int[] coins,int amount,int count){
if(amount < 0){
return;
}
if(amount == 0){
res = Math.min(res,count);
}
for(int i = 0;i < coins.length;i++){
findWay(coins,amount-coins[i],count+1);
}
}
作者:sugar666
链接:https://leetcode-cn.com/problems/coin-change/solution/javadi-gui-ji-yi-hua-sou-suo-dong-tai-gui-hua-by-s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
通常的优化思路,做缓存,这种就不用重复计算,少了不必要的递归。
再看看大神的思路,感觉比老师的DP讲的还要好。
优先丢大硬币进去尝试,也没必要一个一个丢,可以用乘法算一下最多能丢几个
k = amount / coins[c_index] 计算最大能投几个
amount - k * coins[c_index] 减去扔了 k 个硬币
count + k 加 k 个硬币如果因为丢多了导致最后无法凑出总额,再回溯减少大硬币数量
作者:ikaruga
链接:leetcode-cn.com/problems/co…
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
还有经典问题,背包问题,不在整理了,觉得第三哪个零钱问题。很值得学习。现在还没太懂。