所有知识点都来自互联网,进行总结和梳理,侵权必删。
引用来源:计算机算法与设计分析(第5版)|山景城一姐|力扣动态规划|灵茶山艾府|灵茶山艾府-最长公共子序列|包教包会~最长公共子序列|灵茶山艾府-买卖股票的最佳时机【基础算法精讲 21】
动态规划找到子状态之间的关系很重要!| LeetCode:96.不同的二叉搜索树|
算法考试怕不及格,以及长久以来对算法的恐惧。
因为算法问题(数学一直是自己的噩梦),失去了很多信心。
想要找到好工作。
DP数组!
最小子问题-》初始化!
赋值规则-》遍历方向(从山景城一姐那里悟出来的!)。
Q1:到目前为止解题思路是:我知道这题是动态规划—》用动态规划的几个步骤解题。那么我要怎么在不知情情况下也能选择动态规划呢?
Q2:习题1和2,分别使用了二维和一维的dp数组,那么怎么确定用几维的动态数组呢?
A2:是状态决定的嘛?
从灵茶山艾府老师那里学习到的。(我目前水平听up讲解,只是听个热闹。。思维完全跟不上。。)
11|518. 零钱兑换 II(组合数)
12377. 组合总和 Ⅳ(排列数)
class Solution {
public:
int fib(int n) {
int dp[n+1];
memset(dp, 0, sizeof(dp));
if(n==0){
return 0;
}
if(n==1){
return 1;
}
dp[0] = 0;
dp[1] = 1;
for(int i=2; i<=n; i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
};
这一题,经常在各种算法课堂是听到,自己也做过。但是总是忘记。每一次听人提起,都心虚不已。似乎是一题每个人都得知道的题目。是一题能够把我推离算法远远的题目。
class Solution {
public:
int climbStairs(int n) {
int dp[n+1];
memset(dp, 0, sizeof(dp));
if(n==0){
return 0;
}
if(n==1){
return 1;
}
if(n==2){
return 2;
}
dp[1]=1;
dp[2]=2;
for(int i=3; i<=n; i++){
dp[i] = dp[i-1]+dp[i-2];//到达第i阶只有两种方式,跨1上来的,或者跨2上来的。
}
return dp[n];
}
};
这里拓展一个问题,一次可以上1-m个台阶,问有几种方法?
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n + 1, 0);
dp[0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) { // 把m换成2,就可以AC爬楼梯这道题
if (i - j >= 0) dp[i] += dp[i - j];
}
}
return dp[n];
}
};
class Solution {
public:
int minCostClimbingStairs(vector<int>& cost) {
int n = cost.size();
int dp[n+1];
memset(dp, 0, sizeof(dp));
for(int i=2; i<=n; i++){
dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]);
}
for(int i=0; i<=n; i++){
cout<<dp[i]<<" ";
}
return dp[n];
}
};
class Solution {
public:
int uniquePaths(int m, int n) {
int dp[m][n];
memset(dp, 0, sizeof(dp));
for(int i=0; i<m; i++){
dp[i][0] = 1;
}
for(int i=0; i<n; i++){
dp[0][i] = 1;
}
for(int i=1; i<n; i++){
for(int j=1; j<m; j++){
dp[j][i] = dp[j-1][i] + dp[j][i-1];
}
}
return dp[m-1][n-1];
}
};
非常基础的动态规划问题,初始化条件和更新公式都在题目鲜明地给出了。就是动态顺序记住行列顺序!双重循环,以后还是用r,c比较合适。
与上一题唯一的区别在于有障碍物的话,路线次数直接为0;
class Solution {
public:
int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
int rnum = obstacleGrid.size();
int cnum = obstacleGrid[0].size();
cout<<rnum<<" "<<cnum<<endl;
int dp[rnum+1][cnum+1];
memset(dp, 0, sizeof(dp));
for(int i=0; i<rnum; i++){
if(obstacleGrid[i][0]==1){
break;
}
dp[i][0]=1;
}
for(int i=0; i<cnum; i++){
if(obstacleGrid[0][i]==1){
break;
}
dp[0][i]=1;
}
for(int i=1; i<rnum; i++){
for(int j=1; j<cnum; j++){
if(obstacleGrid[i][j]==1){
dp[i][j]=0;
}else{
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
}
return dp[rnum-1][cnum-1];
}
};
class Solution {
public:
string longestPalindrome(string s) {
//先初始化dp, 0-false, 1-true
int len = s.length();
int dp[len][len];
string res;
memset(dp, 0, sizeof(dp));
for(int i=0;i<len;i++){
dp[i][i] = 1;
res = s.substr(i ,1);
}
for(int i=0;i<len-1;i++){
if(s[i] == s[i+1]){
dp[i][i+1] = 1;
res = s.substr(i ,2);
}
}
//动态赋值条件 dp[i][j] = (s[i] == s[j]) && (dp[i+1][j-1] == 1)
for(int j=2; j<len; j++){
for(int i=0; i<j-2+1; i++){
// cout<
if(s[i] == s[j] && dp[i+1][j-1] == 1){
dp[i][j] = 1;
}
if(dp[i][j] == 1 && abs(j-i+1)>res.length()){
res = s.substr(i, abs(j-i+1));
}
}
}
return res;
}
};
想要小结一下,对于C++的确非常不熟悉,一直想要不要改用Java。可能二刷会用Java吧,毕竟Java才是自己的主力语言。
明明看过一姐的视频后刷题,但是因为最长子串的记录和C++的字符串语法问题,耽搁了很久。
class Solution {
public int rob(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 0);
//初始化
dp[0] = nums[0];
for(int i=1; i<nums.length; i++){
if(i==1){
dp[1] = Math.max(nums[1], dp[i-1]);
}else{
dp[i] = Math.max(dp[i-2]+nums[i], dp[i-1]);
}
}
return dp[nums.length-1];
}
}
比较简单的动态规划问题,很像背包问题,给出的限制引导了赋值规则。
----------------------------------------------------------------------------2023年10月16日----------------------------------------------------------
10min思考,没有合适的思路。找不到合适的更新公式。
子集型回溯思路 :(1)选或者不选?(2)枚举选哪个?
官方题解给出的是枚举选哪个?
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, 0);
dp[0] = 1;
int res = dp[0], ma;
for(int i=1; i<n; i++){
ma = 0;
for(int j=i-1; j>=0; j--){
if(nums[j]<nums[i]&&ma<dp[j]){
ma = dp[j];
}
}
dp[i] = ma + 1;
if(dp[i]>res)
res=dp[i];
}
return res;
}
}
----------------------------------------------------------------------------2023年10月17日----------------------------------------------------------
一拿到题目,感觉和之前做的题目有些没办法套思路。去看了灵茶山艾府的视频,在递推公式和证明的部分没看懂(看了三遍),又去找了其他up主的视频,最后才弄明白。
其实思路还是很简单的,弄懂dp[i][j]表示的状态(s中前i个字母和t中前j个字母,的 最长公共子序列)。
递推公式,dp[i][j],根据s[i]和t[j]相等与否设计了两个递推公式,相等dp[i-1][j-1] +1,不相等,max(dp[i-1][j], dp[i][j-1])。四种状态,选其中一个,两不选,两都选。艾府的视频中介绍并证明了。
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int n1 = text1.length();
int n2 = text2.length();
int dp[n1+1][n2+1];
memset(dp, 0, sizeof(dp));
for(int r=1; r<=n1; r++)
{
for(int c=1; c<=n2; c++){
if(text1[r-1] == text2[c-1]){
dp[r][c] = dp[r-1][c-1] + 1;
}else{
dp[r][c] = max(dp[r-1][c], dp[r][c-1]);
}
}
}
return dp[n1][n2];
}
};
依旧是思考10min出不来,怎么这么菜呀!
灵茶山艾府:状态转移进行分析。
一共两个状态,四个转移。
class Solution {
public int maxProfit(int[] prices) {
int n =prices.length;
int[][] dp = new int[n+1][2];
for(int i=0; i<=n; i++){
Arrays.fill(dp[i], 0);
}
dp[0][0] = 0;
dp[0][1] = Integer.MIN_VALUE;
dp[1][1] = -prices[0];
dp[1][0] = 0;
for(int i=2; i<=n; i++){
dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i-1]);
dp[i][1] = Math.max(dp[i-1][1], dp[i-2][0] - prices[i-1]);
}
// for(int i=0; i<=n; i++){
// System.out.println(dp[i][0]+" "+dp[i][1]);
// }
return Math.max(dp[n][0], dp[n][1]);
}
}
----------------------------------------------------------------------------2023年10月19日----------------------------------------------------------
拿到题目,感觉和不同路径那题的走格子很像。非常明显的动态规划题。
二叉搜索树的构建。左<根<右;
但是我依旧没有想到怎么做,10min过去了,满脑子都是糊里糊涂的回溯。
每次都在纠结用一维数组还是二维数组(不能明确dp数组含义),递推公式是啥?
看了视频才知道dp[i]表示以i个数组成的搜索二叉树个数。这题不在乎每个结点具体数值的!切记!
class Solution {
public:
int numTrees(int n) {
int dp[n+1];
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i=1; i<=n; i++){
for(int j=1; j<=i; j++){
dp[i] += (dp[j-1] * dp[i-j]);//左子树*右子树
}
}
return dp[n];
}
};
class Solution {
public int numSquares(int n) {
int[] dp = new int[n+1];
Arrays.fill(dp, Integer.MAX_VALUE);
dp[0] = 0;
dp[1] = 1;
for(int i=2; i<=n; i++){
int a = (int)Math.sqrt(i);
if(a*a==i){
dp[i] = 1;
}else{
for(int j=1; j<(int)i/2+1; j++){
int temp = dp[i-j] + dp[j];
if(temp<dp[i]){
dp[i] = temp;
}
}
}
}
return dp[n];
}
}
----------------------------------------------------------------------------2023年10月20日----------------------------------------------------------
这题就差把动态规划四个字写在题目里了。子集型的问题,枚举还是选不选呢?
选择枚举法,时间复杂度很容易算出来是O(n^2),超时,代码如下:
class Solution {
public:
long long mostPoints(vector<vector<int>>& questions) {
int n = questions.size();
int dp[n];
memset(dp, 0, sizeof(dp));
int maxAns=0;
dp[0] = questions[0][0];
maxAns = dp[0];
for(int i=1; i<n; i++){
int maxFore = 0;
for(int j=0; j<i; j++){
if(questions[j][1]+j<i&&maxFore<dp[j])
{
maxFore = dp[j];
}
}
dp[i] = maxFore + questions[i][0];
if(dp[i]>maxAns){
maxAns = dp[i];
}
}
return maxAns;
}
};
随后按照官方题解,反向思考,状态转移方程变成
class Solution {
public:
long long mostPoints(vector<vector<int>>& questions) {
int n = questions.size();
long long dp[n];
memset(dp, 0, sizeof(dp));
long long maxAns=0;
dp[n-1] = questions[n-1][0];
maxAns = dp[n-1];
for(int i=n-2; i>=0; i--){
long long temp = questions[i][1]+i+1;
if(temp>n-1){
temp=0;
}else{
temp=dp[temp];
}
dp[i] = max(dp[i+1], questions[i][0]+ temp);
if(dp[i]>maxAns){
maxAns = dp[i];
}
}
for(int i=0; i<n; i++){
cout<<dp[i]<<" ";
}
return maxAns;
}
};
主要花费递归方程的编写上。
class Solution {
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount+1];
Arrays.fill(dp, -1);
dp[0] = 0;
Arrays.sort(coins);
for(int i=0; i<coins.length; i++){
if(coins[i]<=amount)
dp[coins[i]] = 1;
}
for(int i=coins[0]; i<=amount; i++){
if(dp[i]==1){
continue;
}
int temp = Integer.MAX_VALUE, flag=0;
for(int j=0; j<coins.length; j++)
{
int cur = i - coins[j];
if(cur<0)
{
break;
}
if(dp[cur]>0){
flag=1;
if(temp>dp[cur]+1){
temp = dp[cur]+1;
}
dp[i] = temp;
}
}
if(flag==0){
dp[i] = -1;
}
}
for(int i=1; i<=amount; i++){
System.out.print(dp[i]+ " ");
}
return dp[amount];
}
}
官方的代码更加简单:
public class Solution {
public int coinChange(int[] coins, int amount) {
int max = amount + 1;
int[] dp = new int[amount + 1];
Arrays.fill(dp, max);
dp[0] = 0;
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.length; j++) {
if (coins[j] <= i) {
dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
}
}
}
return dp[amount] > amount ? -1 : dp[amount];
}
}
作者:力扣官方题解
链接:https://leetcode.cn/problems/coin-change/solutions/132979/322-ling-qian-dui-huan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
----------------------------------------------------------------------------2023年10月21日----------------------------------------------------------
以为很简单的问题,找不到转移方程。。。看了代码随想录的视频之后才知道,自己懂得知识太少了,只是套了一个浅层的模版,稍微深层一点的知识就会难倒自己。
class Solution {
public:
int change(int amount, vector<int>& coins) {
int dp[amount+1];
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int j=0; j<coins.size(); j++){
for(int i=coins[j]; i<=amount; i++){
dp[i] += dp[i-coins[j]];
}
}
for(int i=1; i<=amount; i++){
cout<<dp[i]<<" ";
}
return dp[amount];
}
};
class Solution {
public int combinationSum4(int[] nums, int target) {
int[] dp = new int[target+1];
Arrays.fill(dp, 0);
dp[0] = 1;
for(int i=0; i<=target; i++){
for(int j=0; j<nums.length; j++){
if(nums[j]<=i)
dp[i] += dp[i-nums[j]];
}
}
return dp[target];
}
}
感觉是0-1问题又像是子集问题。0-1问题,先物品再背包,倒序。
class Solution {
public int findMaxForm(String[] strs, int m, int n) {
int len = strs.length;
int[] ones = new int[len];
int[] zeros = new int[len];
int[][] dp = new int[m+1][n+1];
for(int i=1; i<=m; i++){
Arrays.fill(dp[i], 0);
}
for(int i=0; i<len; i++){
ones[i] = (int) strs[i].chars().filter(c -> c == '1').count();
zeros[i] = (int) strs[i].chars().filter(c -> c == '0').count();
}
for(int s=0; s<len; s++){
for(int i=m; i>=zeros[s]; i--){
for(int j=n; j>=ones[s]; j--){
dp[i][j] = Math.max(dp[i][j], dp[i-zeros[s]][j-ones[s]]+1);
}
}
}
for(int i=0; i<=m; i++){
for(int j=0; j<=n; j++){
System.out.print(dp[i][j] + " ");
}
System.out.print("\n");
}
return dp[m][n];
}
}
----------------------------------------------------------------------------2023年10月22日----------------------------------------------------------