动态规划(dynamic programming : dp)的解题技巧:
动态规划的出发点:枚举不是很理想,枚举没有利用题目的一些性质
代码量不会很大,思考出来就可以写;通过做题 来熟悉动态规划
1、确定动态规划数组dp[]中dp[i]代表什么状态?
2、确定每两个相邻状态之间有什么联系?dp[i] 和 dp[i-1]?
3、dp数组当中的dp[0]要会使用,可以减少边界条件的判定。
70、爬楼梯
思考:用递归做会超时,使用动态规划的方法来做。
class Solution {
public:
int climbStairs(int n) {
vector<int> dp(n+3,0); //n + 3 输入n = 0 n= 1 n = 2 都不用再做特殊处理
dp[1] = 1 ;
dp[2] = 2;
for(int i = 3 ; i<= n ; i++){
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
};
198、打家劫舍
class Solution {
public:
int rob(vector<int>& nums) {
if(nums.size() == 0 ){
return 0;
}
if(nums.size() == 1){
return nums[0];
}
vector<int> dp(nums.size() ,0);
dp[0] = nums[0];
dp[1] = max(nums[0],nums[1]);
for(int i = 2 ;i < nums.size();i++){
dp[i] = max(dp[i-2] + nums[i],dp[i-1] );
}
return dp[nums.size() - 1 ];
}
};
53、最大子序和
思考:
1、假设i状态代表前i个数字组成的连续最大字段和?可以嘛?不行
2、假设状态i表示以nums[i]结尾的子段和?
class Solution {
public:
int maxSubArray(vector<int>& nums) {
if(nums.size() == 1 ){
return nums[0];
}
vector<int> dp(nums.size(),0);//dp[0] 要有对应的值
dp[0] = nums[0];
int max_res = dp[0];
for(int i = 1 ; i < nums.size(); i++){
dp[i] = max(dp[i -1] + nums[i] ,nums[i]);
if(max_res < dp[i]){
max_res = dp[i];
}
}
return max_res;
}
};
322、零钱兑换
思考: 选择贪心?还是dp?在固定面值的情况下,贪心是可以的!但是题目中并没有固定!
状态i : dp[i]表示当前金额下的最小组合张数。
设置边界条件,dp[0] = 0;使得属于面值的金额可以一起运算,不用单独初始化。即当前的面值的张数,等于减去对应的纸币面额后的张数 +1。
题目类似于最基础的1,2题。可以把金额想象成总的台阶数,而钱币面值就是每次攀爬的层数。
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
vector<int> dp(amount+ 1 ,-1);
dp[0] = 0;
for(int i = 1 ; i <= amount; i ++){
for(auto it : coins){
if( i - it >= 0 && dp[i - it] != -1){
if(dp[i] == -1 || dp[i] > dp[i-it] + 1 ){
dp[i] = dp[i - it] + 1 ;
}
}
}
}
return dp[amount];
}
};
120、三角形最短路径和
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
if(triangle.size() == 0){
return 0;
}
vector<vector<int>> dp;
for(int i = 0 ;i < triangle.size();i++){
dp.push_back(vector<int>());
for(int j = 0 ; j < triangle[i].size();j++){
dp[i].push_back(0);
}
}
for(int i = 0 ;i <triangle[triangle.size()-1].size();i++){
dp[triangle.size()-1][i] = triangle[triangle.size()-1][i];
}
for(int i = dp.size() - 2 ;i >= 0; i--){
for(int j = 0 ; j < dp[i].size();j++){
dp[i][j] = min(dp[i+1][j],dp[i+1][j+1]) + triangle[i][j];
}
}
return dp[0][0];
}
};
300、最长上升子序列
思考:两种状态
1、以i个数字组成的数组中找一个最长上升子序列
2、以第i个数字为最长上升子序列最后一个数字(以i个数字结尾!判断是双重循环,要判断i-1个数字的大小,并且当dp[i] < dp[j] +1 ,要更新)
Longest Increasing Subsequence,LIS,最长上升子序列
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0){
return 0;
}
vector<int> dp(nums.size(),1);
int LIS = 1;
for(int i = 1;i<nums.size();i++){
for(int j =0;j <= i -1 ;j++ ){
if(nums[i] > nums[j] && dp[i] < dp[j] + 1 ){
dp[i] = dp[j] + 1;
}
}
if(LIS < dp[i]){
LIS = dp[i];
}
}
return LIS;
}
};
方法2:使用栈
代码:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0){
return 0;
}
vector<int> _stack;
_stack.push_back(nums[0]);
for(int i = 1 ;i < nums.size(); i++){
if(nums[i] > _stack.back()){
_stack.push_back(nums[i]);
}
else{
for(int j = 0; j <_stack.size() ; j++){
if(nums[i] <= _stack[j] )/*key step !*/{
_stack[j] = nums[i];
break;
}
}
}
}
return _stack.size();
}
};
当中为什么是num[i] <= _stack[j],如下如果栈中是{4,10},此时传入4,如果是<的话。那么覆盖的就是10。然后的结果就会有问题。
题目当中说用O(n²)优化到O(nlogn),这里可以因为存入栈中是有序的,所以可以使用二分查找,来替代一次次的从头遍历。
二分查找几个常犯的错误:
1、中间部分是mid = (begin + end )/ 2;
2、mid每次循环是要改变的,所以再循环体之内,每次要重新定义初始化。
3、二分查找,首先检查mid,不在就去搜索左区间,[begin,mid -1 ] ;否则就去搜索右区间,[mid + 1 ,end ],然后来更新begin 和end
4、题目中涉及到插入位置,在左右区间的时候会有。左区间:如果mid == 0 || target > vec[mid -1 ] ,此时就应该index = mid ;右区间: 如果mid == end || target < vector[mid +1 ],此时index = mid +1。
int binary_search_insert(vector<int> &vec,int target){
int index = -1;
int begin = 0;
int end = vec.size() - 1 ;
// int mid = (end - begin)/2 ; 二分查找,mid在循环体之中,每次是变化
//更重要的是!中间部分!是首项加尾项/2!!!!
while(index == -1 ){
int mid = (end + begin)/2;
if(target == vec[mid]){
index = mid;
}
else if(target < vec[mid]){
if(mid == 0 || target > vec[mid - 1]){
index = mid;
}
end = mid -1;
}
else if( target > vec[mid]){
if(mid == vec.size() -1 || target < vec[mid+1]){
index = mid+1;/*一开始写成index = mid */
}
begin = mid +1;
}
}
return index;
}
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
if(nums.size() == 0){
return 0;
}
vector<int> _stack;
_stack.push_back(nums[0]);
for(int i = 1 ;i < nums.size(); i++){
if(nums[i] > _stack.back()){
_stack.push_back(nums[i]);
}
else{
int index = binary_search_insert(_stack,nums[i]);
_stack[index] = nums[i];
}
}
return _stack.size();
}
};
64、最小路径和
思考:地图搜索的题目,比较简单。状态ij表示到达ij这个点的最短路径。收获还有明白了创建固定的二维vector和固定的二维vector的区别。
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
if(grid.size() == 0){
return 0;
}
int row = grid.size();
int col = grid[0].size();
vector<vector<int>> dp(row,vector<int>(col,0));
//不知道尺寸的时候动态创建二维vector
/* vector<vector<int>> dp;
for(int i = 0 ; i< grid.size();i++){
dp.push_back(vector<int>());
for(int j = 0 ;j < grid[0].size();j++){
dp[i].push_back(0);
}
}*/
dp[0][0] = grid[0][0];
/* for(int i = 1; i < row;i++){
dp[i][0] = dp[i-1][0] + grid[i][0];
}*/
for(int j = 1; j < col;j++){
dp[0][j] = dp[0][j-1] + grid[0][j];
}
for(int i = 1;i<row;i++){
dp[i][0] = dp[i-1][0] + grid[i][0];
for(int j = 1 ;j < col;j++){
dp[i][j] = min(dp[i-1][j],dp[i][j-1]) + grid[i][j];
}
}
return dp[row-1][col-1];
}
};
174、地下城游戏
class Solution {
public:
int calculateMinimumHP(vector<vector<int>>& dungeon) {
if(dungeon.size() == 0){
return 0;
}
int row = dungeon.size();
int col = dungeon[0].size();
vector<vector<int>> dp(row,vector<int>(col,0));
dp[row-1][col-1] = max(1,1-dungeon[row-1][col-1]);
for(int j = col -2 ; j >=0 ;j--){
dp[row -1][j] = max(1,dp[row-1][j+1]-dungeon[row-1][j]);
}
for(int i = row- 2 ;i>=0;i--){
dp[i][col -1]=max(1,dp[i+1][col-1] - dungeon[i][col-1]);
for(int j = col -2 ;j >= 0 ;j--){
int dp_min = min(dp[i+1][j],dp[i][j+1]);
dp[i][j] = max(1,dp_min - dungeon[i][j]);
}
}
return dp[0][0];
}
};