动态规划(一)

  • 最长上升子序列(一)
  • 最长上升子序列(二)
  • 最长公共子序列(一)
  • 填充数组
  • 打家劫舍(一)
  • 打家劫舍(二)
  • 打家劫舍(三)
  • 目标和

最长上升子序列(一)

牛客链接:NC163 最长上升子序列(一)

定义长度为n的ans数组,用以保存以arr[i]结尾的最长严格上升子序列的长度。在每次获得最长序列的时候进行比较从而得到最终结果。结果满足复杂度要求。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** 给定数组的最长严格上升子序列的长度。* @param arr int整型vector 给定的数组* @return int整型*/int LIS(vector<int>& arr) {// write code hereif(arr.size()==0)return 0;int res = 0;vector<int> ans(arr.size(),1);for(int i = 1; i < arr.size();++i){for(int j =0; j<i; ++j){if(arr[i]>arr[j]){ans[i] = max(ans[i],ans[j]+1);}}res = max(ans[i] ,res);}return res;}
};

最长上升子序列(二)

牛客链接:NC164 最长上升子序列(二)

和上题目目的相同,只是更严格的要求了时间和空间复杂度。这里使用动态规划+二分的思路。
arr[i]用来存储尽可能小的值,因为小的值在后续中,更可能寻找到大的值从而使得长度增加。
遍历每个a[ i ],如果严格大于arr的最后一个数,则加入arr;
否则,找到找到大于等于他的最小的一个数,进行替换。
最终获得的arr的长度就是答案。

//实现1:手写二分
class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** 该数组最长严格上升子序列的长度* @param a int整型vector 给定的数组* @return int整型*/int LIS(vector<int>& a) {// write code hereint n=a.size();if(n==0)return 0;vector<int> arr;arr.push_back(a[0]);for(int i =1;i<n;++i){if(a[i]>arr.back()){arr.push_back(a[i]);}else{int l=0, r = arr.size()-1,mid;while(l<r){        //寻找>=的位置     也可以直接使用lower_bound() 替代mid = (l+r)>>1;if(arr[mid]>=a[i])r = mid;elsel = mid+1;}arr[l] = a[i];  //更新长度为l的子序列的最小值}}return arr.size();}
};//实现2:调用函数
class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** 该数组最长严格上升子序列的长度* @param a int整型vector 给定的数组* @return int整型*/int LIS(vector<int>& a) {// write code herevector<int> arr;for(int i : a){vector<int>::iterator it = lower_bound(arr.begin(),arr.end(),i);if(it == arr.end())arr.push_back(i);else*it = i;}return arr.size();}
};

最长公共子序列(一)

牛客链接:NC165 最长公共子序列(一)


题解一:满足要求1

定义ans[][]二维数组,其中ans[i][j] 表示s1中第i个字符和s2中第j个字符为结尾的最长公共子序列。如果 s1[i-1]==s2[j-1],结果则为ans[i-1][j-1]+1,否则最大值在ans[i-1][j], ans[i][j-1]中产生。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** s1和s2最长公共子序列的长度* @param s1 string字符串 * @param s2 string字符串 * @return int整型*/int LCS(string s1, string s2) {// write code herevector<vector<int>> ans(s1.size()+1,vector<int>(s2.size()+1,0));for(int i = 1;i<=s1.size();++i)for(int j = 1; j<=s2.size(); ++j){if(s1[i-1] == s2[j-1])ans[i][j] = ans[i-1][j-1] + 1;else ans[i][j] = max(ans[i-1][j], ans[i][j-1]);}return ans[s1.size()][s2.size()];}
};

解法2:进阶版

解法1中,建立的arr数组,其实每次都只是用了当前行和之前一行的数据,因此可以使用两个一维数组代替。
b数组用于保存上次的结果,a数组用于计算当前行的结果。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** s1和s2最长公共子序列的长度* @param s1 string字符串 * @param s2 string字符串 * @return int整型*/int LCS(string s1, string s2) {// write code hereint n = min(s1.size(),s2.size());vector<int> a(n+1,0);vector<int> b(n+1,0);if(s1.size()<s2.size())swap(s1,s2);for(int i = 1; i<=s1.size();++i){for(int j = 1; j<=s2.size(); ++j){if(s1[i-1] == s2[j-1])a[j] = b[j-1] + 1;elsea[j] = max(a[j-1],b[j]);}swap(a, b);           //交换a,b a =  vector<int>(n+1,0); //初始化a为全0的数组}return b[n];}
};

填充数组

牛客链接:NC173 填充数组

对于数组中的每一段0,都可以是一个子问题:填充数为i,填充范围为j个数
因此这里建立一个dp[i][j]二维数组,其中i表示待填充数的个数,j表示待填充位置的候选范围个数。

  1. 待填充个数为0时,dp[0][j] = 0;
  2. 候选范围为0时候,dp[i][0] = 0;
  3. 待填充个数为1时,dp[1][j] = j;
  4. 对于dp[i][j],可以拓展为与dp[i-1][j]和dp[i][j]的关系。 dp[i][j] = dp[i-1][j] + dp[i][j-1]:当前第i个数填充j个范围中最大数时,候选方案为 dp[i-1][j] ,当第i个数填充(0 - j-1)范围的时候,结果就是dp[i][j-1];
class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param a int整型vector * @param k int整型 * @return int整型*/const int MOD = 1e9+7;int FillArray(vector<int>& a, int k) {// write code here int n = a.size();vector<vector<int>> dp(n+1,vector<int>(k+1,0));for(int i = 0; i <= k; ++i){dp[1][i] = i; }for(int  i =2;i<=n; ++i){for(int j = 1;j <=k; ++j){dp[i][j] = (dp[i][j-1] + dp[i-1][j])%MOD; }}int i=0;long long res = 1;while(i<n){    //计算每一段的方案数,累乘  while(i<n&&a[i]!=0){i++;}if(i==n)break;int left=i;               //左区间  左端第一个为0的数int low=i>0?a[i-1]:1;     //候选的范围的低值while(i<n&&a[i]==0){i++;}int right=i;         //右区间    右端最后一个为0的数int high=i<n?a[i]:k;    //候选的范围的高值   当为n的时候,表示最后一段到结尾的为0,就要考虑极限值。res=(res*dp[right-left][high-low+1])%MOD;          //累乘}return res;}
};

打家劫舍(一)

牛客链接:NC176 打家劫舍(一)

解法1

定义数组dp,其中dp[i]表示一共有i家能够偷到的最大值金额。其中dp[i] = max(dp[i-1],dp[i-2]+nums[i-1]); 一共有i家能够偷到的最大金额需要考虑有i-1家和i-2家的情况。这两种情况有可能相同,但是i-2家的时候可以选择投第i家,i-1家不一定,有可能i-1家被偷了,那么第i家就不能偷了。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @return int整型*/int rob(vector<int>& nums) {// write code herevector<int> dp(nums.size()+1,0);dp[1] = nums[0];for(int i =2;i<=nums.size();++i){dp[i] = max(dp[i-1],dp[i-2]+nums[i-1]);}return dp[nums.size()];}
};

解法2

定义二维数组dp,其中dp[i][0]表示第i家不偷的情况能够获得的最大值,dp[i][1]表示偷第i家能够获得的最大值。其中dp[i][0] = max(dp[i-1][0],dp[i-1][1]),dp[i][1] = dp[i-1][0] + nums[i];

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @return int整型*/int rob(vector<int>& nums) {// write code herevector<vector<int>> dp(nums.size(),vector<int>(2,0));dp[0][0] = 0;dp[0][1] = nums[0];for(int i =1;i<nums.size(); ++i){dp[i][0] = max(dp[i-1][0],dp[i-1][1]);  //不偷这家dp[i][1] = dp[i-1][0] + nums[i];        //偷这家}return max(dp[nums.size()-1][0],dp[nums.size()-1][1]);}
};

优化:上述方法2每次考虑当前住户的时候其实只用到之前的一家的信息,所以可以使用两个变量来替代数组。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @return int整型*/int rob(vector<int>& nums) {// write code hereint last_notsteal = 0;int last_steal = nums[0];int temp;for(int i =1;i<nums.size(); ++i){temp = last_notsteal;last_notsteal = max(last_notsteal,last_steal);  //不偷这家last_steal = temp + nums[i];        //偷这家}return max(last_notsteal,last_steal);}
};

打家劫舍(二)

牛客链接:NC177 打家劫舍(二)

解法1

和(一)中一样,这里多了一个考虑环形的设计,导致我们需要考虑两种情况:

  1. 第一家偷,那就不能考虑最后一家,最后一家只能不偷,那么结果就是dp[n-1];
  2. 第一家不偷,那么初始化dp[1] = 0,最终结果为dp[n],取决于第n家偷不偷产生的最大值。
class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @return int整型*/int rob(vector<int>& nums) {// write code herevector<int> dp(nums.size()+1,0);dp[1] = nums[0];for(int i =2;i<nums.size();++i){dp[i] = max(dp[i-1],dp[i-2]+nums[i-1]);}int ans = dp[nums.size()-1];dp[1] = 0;for(int i =2;i<=nums.size();++i){dp[i] = max(dp[i-1],dp[i-2]+nums[i-1]);}ans = max(ans,dp[nums.size()]);return ans;}
};

解法2

同(一),使用两个变量来保存偷与不偷,考虑不同的初始化。只要第一家和最后一家不同时考虑。 考虑第一家到第n-1家和第2家到第n家两种情况最大值,必定不会出现头尾都偷的情况。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @return int整型*/int rob(vector<int>& nums) {// write code hereint last_notsteal = 0;int last_steal = nums[0];int temp;for(int i =1;i<nums.size()-1; ++i){temp = last_notsteal;last_notsteal = max(last_notsteal,last_steal);  //不偷这家last_steal = temp + nums[i];        //偷这家}int res = max(last_notsteal,last_steal);last_notsteal = 0;last_steal = nums[1];for(int i =2;i<nums.size(); ++i){temp = last_notsteal;last_notsteal = max(last_notsteal,last_steal);  //不偷这家last_steal = temp + nums[i];        //偷这家}res = max(res,max(last_notsteal,last_steal));return res;}
};

打家劫舍(三)

牛客链接:NC178 打家劫舍(三)

解法1:DFS

会超时。每次递归每个树的结点,考虑选择偷与不偷当前结点。最终取最大值就是结果,

/*** struct TreeNode {*  int val;*   struct TreeNode *left;* struct TreeNode *right;*    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* };*/
class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param root TreeNode类 * @return int整型*/int dfs(TreeNode* r,bool pick = true){ // 当前是否可选if(r == NULL)return 0;// 当前不选int ans = dfs(r->left) + dfs(r->right);// 选择当前if(pick){ans = max(ans, r->val + dfs(r->left,false) + dfs(r->right,false));}return ans;}int rob(TreeNode* root) {return dfs(root);}
};

解法2

利用深度遍历从最后一层到根判断是否偷该结点。
当偷该节点的时候,两个子节点都不偷。
当不偷该节点的时候,有四种可能:

  1. 两个根节点都不偷
  2. 两个根节点都偷
  3. 偷左节点,不偷右节点
  4. 偷右节点,不偷左节点
/*** struct TreeNode {*  int val;*   struct TreeNode *left;* struct TreeNode *right;*    TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* };*/
class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param root TreeNode类 * @return int整型*/pair<int, int> dfs(TreeNode* root){      //其中first表示不偷,second表示偷if(root == nullptr)return {0,0};pair<int, int> left,right;left = dfs(root->left);right = dfs(root->right);//不偷的情况有四种int no_steal = max(left.second+right.second,max(left.first+right.first,\max(left.second+right.first,left.first+right.second)));int steal = left.first + right.first + root->val;return {no_steal,steal};}int rob(TreeNode* root) {// write code herepair<int, int> ans = dfs(root);return max(ans.first, ans.second);}
};

目标和

牛客链接:NC243 目标和

解法1:DFS

对于数组中每个元素都进行正负号的判断,最终数组到达尾部,且target为0时就是一个候选选项。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @param target int整型 * @return int整型*/int dfs(vector<int>& nums, int target){if(target == 0 && nums.size() == 0)return 1;if(nums.size() == 0)return 0;int ans = 0;vector<int> temp(nums.begin()+1,nums.end());ans += dfs(temp,  target+nums[0]);ans += dfs(temp,  target-nums[0]);return ans;}int findTargetSumWays(vector<int>& nums, int target) {// write code hereif(nums.size() == 0)return 0;int ans = 0;vector<int> temp(nums.begin()+1,nums.end());ans += dfs(temp,  target+nums[0]);ans += dfs(temp,  target-nums[0]);return ans;}
};

解法2:动态规划

我们定义整个数组分为两部分一部分为加上去的总值a,一部分为减去的总值b;其中a+b=sum,a-b = target; 所以2*a = sum+target(偶数);
题目就变成了从n个元素数组中选择n个数和为a的次数,这就形成了01完全背包问题。

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @param target int整型 * @return int整型*/int findTargetSumWays(vector<int>& nums, int target) {// write code hereif(nums.size() == 0)return 0;int sum = 0;for(auto num: nums)sum+=num;if((sum+target)%2==1)return 0;int v = (sum+target)/2;vector<vector<int>> dp(nums.size()+1,vector<int>(v+1,0));dp[0][0] = 1;for(int i =1; i<=nums.size(); ++i){for(int j =0; j<=v; ++j){dp[i][j] = dp[i-1][j];if(j>=nums[i-1])dp[i][j] +=  dp[i-1][j-nums[i-1]];}}return dp[nums.size()][v];}
};

每行元素之和上一行有关,可使用滑动数组的方式去掉第一个维度

class Solution {public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可** * @param nums int整型vector * @param target int整型 * @return int整型*/int findTargetSumWays(vector<int>& nums, int target) {// write code hereif(nums.size() == 0)return 0;int sum = 0;for(auto num: nums)sum+=num;if((sum+target)%2==1)return 0;int v = (sum+target)/2;vector<int> dp(v+1,0);dp[0]= 1;for(int i =0; i<nums.size(); ++i){for(int j = v; j>=nums[i]; --j){dp[j] +=  dp[j-nums[i]];}}return dp[v];}
};

C++算法集锦:动态规划(一)相关推荐

  1. 机器阅读理解算法集锦

    机器阅读理解算法集锦   机器阅读理解(Machine Reading Comprehension, MRC) 是一项基于文本的问答任务(Text-QA),也是非常重要和经典的自然语言处理任务之一.机 ...

  2. matlab 算法集锦

    算法集锦 决策树-划分点 function [n,h]=huafendian1(x) %n返回增益 %h返回划分点 %假设0代表第一类 %假设1代表第二类 %输入x第一列为属性,第二列为用于学习的分类 ...

  3. 数据结构常见算法集锦

    数据结构经典算法集锦 第2章 线性表 KMP算法 //获得next数组 void GetNext(char *t, int next[MAX]) {int i = 1, j = 0;next[1] = ...

  4. 数据结构经典算法集锦

    数据结构经典算法集锦 第2章 线性表 KMP算法 //获得next数组 void GetNext(char *t, int next[MAX]) {int i = 1, j = 0;next[1] = ...

  5. STL经典算法集锦之排列(next_permutation/prev_permutation

    STL经典算法集锦之排列(next_permutation/prev_permutation) 来自:CSDN博客推荐文章 | 时间:2012-05-07 14:54:09 原文链接: http:// ...

  6. Bellman-Ford 算法 和 动态规划

    Floyd算法: 状态: d[k][i][j]定义:"只能使用第1号到第k号点作为中间媒介时,点i到点j之间的最短路径长度." 动态转移方程: d[k][i][j]=min(d[k ...

  7. 五大经典算法之动态规划

    一.概念起源   动态规划,又名DP算法(取自其Dynamic Programming的缩写),最初是运筹学的一个分支,是用来求解决策过程最优化的数学方法. 二.基本思想   把 多阶段过程 转化为一 ...

  8. Java入门算法(动态规划篇2:01背包精讲)

    本专栏已参加蓄力计划,感谢读者支持❤ 往期文章 一. Java入门算法(贪心篇)丨蓄力计划 二. Java入门算法(暴力篇)丨蓄力计划 三. Java入门算法(排序篇)丨蓄力计划 四. Java入门算 ...

  9. NOI入门级:算法之动态规划

    糖糖讲动态规划算法,找零钱完全背包问题,LeetCode 322 糖糖讲动态规划算法,找零钱完全背包问题,LeetCode 322_哔哩哔哩_bilibili 程序员面试再也不怕动态规划了,看动画,学 ...

  10. 【算法】动态规划笔记

    [算法]动态规划笔记 动态规划: 将一个复杂的问题分解成若干个子问题,通过综合子问题的最优解来得到原问题的最优解 动态规划会将每个求解过的子问题的解记录下来,这样下一次碰到同样的子问题时,就可以直接使 ...

最新文章

  1. 看漫画学python pdf下载_看漫画还能学Python❓❓❓| 0基础小白福音
  2. SAP Spartacus界面注册用户获取OAuth Access Token的方式
  3. 【推荐系统】那些年, 引用量超1000的经典推荐系统论文
  4. kubernetest pod为ContainerCreating、ImagePullBackOff状态 怎么办
  5. 阶段3 2.Spring_10.Spring中事务控制_5 spring事务控制的代码准备
  6. JSONObject.fromObject()
  7. CTOD安悦系统目录转移软件
  8. 图片加载之前显示 加载中或者加载中图片
  9. 滚滚长江东逝水(三国演义插曲)铃声 滚滚长江东逝水(三国演义插...
  10. 便利店牵手京东到家,多元零售矩阵走向成熟化
  11. 计算机二级考风考纪主题班会,2021年我国计算机二级考试基础概述.doc
  12. 艾美捷胆固醇肉豆蔻酸酯说明书和相关研究
  13. 考研——生命轨迹中一段无法抹去的铿锵岁月
  14. word里面怎么在框里打勾
  15. 基于C++的数据结构-1
  16. Apache PHP 的gzip压缩输出的实现方法
  17. python第三方库文件传输_慢步学习,python库文件概述,再来点第三方库文件安装的干货...
  18. Unity游戏开发中的向量运算-点乘和叉乘
  19. Centos7静默安装Oracle12c
  20. ESP8266-Arduino编程实例-TDS(溶解固体总量)水质传感器驱动

热门文章

  1. C++中int a和int a的区别
  2. linux amd显卡双屏,显示器的合理利用 ati显卡双屏详细设置【图文】
  3. Python爬虫:scrapy框架请求参数meta、headers、cookies一探究竟
  4. 测绘专业c语言程序,2017年武汉大学测绘遥感信息工程国家重点实验室968C语言程序设计[专业硕士]考研导师圈点必考题汇编...
  5. 刘磨叽恶狠狠地瞪了了张s
  6. 书信:欲寄彩笺兼尺素,山长水阔知何处
  7. TensorFlow相关的精彩问答
  8. 前端项目更换鼠标样式
  9. 免费好用的IPv6之一个有趣的需求——浅谈Openwrt的VLAN
  10. Windows系统又被盯上,出现最新MSHTML漏洞