动态规划学习记录

leecode 96 不同的二叉搜索树

在这里插入图片描述

刚开始一直以为是跟括号生成类似的回溯,于是写了这样的代码:

 void dfs(const int n, int left, int right, int &res){
        if(left + right == n - 1){
            //cout << "l: "<<left<<" r: "<<right<<endl;
            res += 1;
            return;
        }
        dfs(n, left + 1, right, res);
        dfs(n, left, right + 1, res);
    }
    int numTrees(int n) {
        int res = 0;
        dfs(n, 0, 0, res);
        return n ? res + 1 : 0;
    }

后来发现有一种情况怎么都考虑不到,就是从根结点分别左右两边建立二叉树,看了题解官方题目解释,才发现用的是递归。
最近在ACwing学算法基础课,刚好讲到递归,对于动态规划要找到其状态表示,状态表示是一个结合,官解中的G[n]表示程度为n的序列构成二叉树的个数,假设在i点,i左侧的每个点均可能成为其左子数的根节点,右边同理,因此以该节点作为根节点,左右序列可能构成二叉树的笛卡尔积就是该段的笛卡尔积个数。
两重for循环,i 确定的是长度n,j是用来划分长度i

 int numTrees(int n) {
        vector<int> dp(n+1,0);
        dp[0] = dp[1] = 1;
        for(int i=2;i<=n;++i)
            for(int j=0;j<=i-1;++j)
                dp[i] += dp[j]*dp[i-j-1];
        return dp[n];
    }
Leecode 64.最小路径和

在这里插入图片描述

第一眼看很坚定的觉得是一个bfs,后来超时了。
看了题解,是dp,状态转移方程就是我从哪里来。g[i][j]代表[i][j]这个格子是从哪几个可能的格子转移过来的,其属性值表示的是左上角到该点的最短距离。而状态计算则是依据dp的含义进行划分,代码如下:

 int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        for(int i = 0; i < m; i++)
            for(int j = 0; j < n; j++){
                int tmp = grid[i][j];
                if( i && j)grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]);
                else if(i)grid[i][j] = grid[ i - 1][j];
                else if(j)grid[i][j] = grid[i][j - 1];
                if(i || j)grid[i][j] += tmp;
            }
        return grid[m - 1][n - 1];
    }
leecode 198.打家劫舍

这里是用滚动数组代替dp,空间复杂度为O(1),时间为O(n)

class Solution {
public:
    int rob(vector<int>& nums) {
        if (nums.empty()) {
            return 0;
        }
        int size = nums.size();
        if (size == 1) {
            return nums[0];
        }
        int first = nums[0], second = max(nums[0], nums[1]);
        //vector<int>dp;
        for (int i = 2; i < size; i++) {
        	//dp[i] = max(dp[i - 2] + nums[i], dp[i - 1]);
            int temp = second;
            second = max(first + nums[i], second);
            first = temp;
        }
        return second;//dp[nums.size() - 1];
    }
};

leecode 198.打家劫舍II
进阶版本,是一个环形。如果偷窃了第一间就不要最后一间

 return max(maxRob(nums, 0, n - 2), maxRob(nums, 1, n - 1));
 //maxRob与1中的函数相同。

完整代码

class Solution {
public:
    int maxRob(vector<int>& nums, int s, int e){
        int first = nums[s], second = max(nums[s], nums[s+1]);
        for(int i = s + 2; i <= e; i++){
            int temp = second;
            second = max(first + nums[i], second);
            first = temp;
        }
        return second;
    }
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(!n)return 0;
        if(n == 1)return nums[0];
        if(n == 2)return max(nums[0], nums[1]);
        return max(maxRob(nums, 0, n - 2), maxRob(nums, 1, n - 1));
    }
};
Leecode. 279 完全平方数

在这里插入图片描述
按照动规的逻辑来做,有一个公式:
假设当前枚举到 j,那么我们还需要取若干数的平方,构成i - j 2,则可以据此得到状态转移方程:
f [ i ] = 1 + m i n ⌊ i ⌋ j = 1 f [ i − j 2 ] f[i] = 1 + \underset{j = 1}{\overset{\lfloor\sqrt{i} \rfloor}{min}}f[i - j ^2] f[i]=1+j=1mini f[ij2]
(没搞明白为啥要加1)

class Solution {
public:
    int numSquares(int n) {
        vector<int>f(n + 1, 0);
        //vector<vector<int>>dp(0, vector<int>(n));
        for(int i = 1; i <= n; i++){
            //cout <<"i: "<<i<<endl;
            int Min_ = INT_MAX;
            for(int j = 1; j <= i/j; j++){
                //cout <<"f["<<i - j*j<<"]: "<<f[i - j*j] <<" "<< Min_<<endl;
                Min_ = min(Min_, f[i - j*j]);
            }
            f[i] = Min_ + 1;  //没懂为什么要+1啊
        }
        return f[n];     
    }
};
152. 乘积最大子数组

乍看一眼感觉是动规,不死心尝试一下别的,因为太菜又想不出来什么别的做法,终于屈服了。
动态规划真的好难啊,淦!
对于一个乘积数组,我们维护一个数组maxF[n], 其用来表示长度为n的最大子数组的乘积。但是,因为乘积形式,若只维护最大,则会遗漏负负得正的情况,因此需要维护两个数组:
考虑当前位置如果是一个负数的话,那么我们希望以它前一个位置结尾的某个段的积也是个负数,这样就可以负负得正,并且我们希望这个积尽可能「负得更多」,即尽可能小。如果当前位置是一个正数的话,我们更希望以它前一个位置结尾的某个段的积也是个正数,并且希望它尽可能地大。
​于是便得到了如下实现的动态规划转移方程组:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        vector<int>maxF(nums), minF(nums);//复制两个数组
        for(int i = 1; i < nums.size(); i++){
            maxF[i] = max(maxF[i - 1] * nums[i], max(minF[i - 1] * nums[i], nums[i]));
            minF[i] = min(minF[i - 1] * nums[i], min(maxF[i - 1]* nums[i], nums[i]));
        }
        return *max_element(maxF.begin(), maxF.end());
    }
};
时间优化:
class Solution {
public:
    int maxProduct(vector<int>& nums) {
        vector<int>maxdp(nums.size()), mindp(nums.size());
        maxdp[0] = max(nums[0], maxdp[0]);
        mindp[0] = min(nums[0], mindp[0]);
        int ret = 0;

        for(int i = 1; i < nums.size(); i++){
            if(nums[i] >= 0){
                maxdp[i] = max(maxdp[i - 1] * nums[i], nums[i]);
                mindp[i] = min(mindp[i - 1] * nums[i], 0);
            }else {
                maxdp[i] = max(mindp[i - 1] * nums[i], 0);
                mindp[i] = min(mindp[i - 1] * nums[i], nums[i]);
            }
            ret = max(maxdp[i], ret);
        }
        return ret;
    }
};
Leecode 5.最长回文子串

这里的动态转移方程为:
dp[ i ][ j ]其表示的含义为 字符串第 i 位到第 j 位之间的字符串是不是回文子串
其状态转移方程为:
当一个字符串为回文字串时,若给其左右两端加上一个相同的字符,则新的字符串一定也是回文子串。当 s[i] == s[j]时,
d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] dp[i][j] = dp[i + 1][j - 1] dp[i][j]=dp[i+1][j1]
还有一种情况,若字符串的长度小于等于2,又因为 s[i] == s[j] ,所以一定是两个相等的字符串或者单个字符。

class Solution {
public:
    string longestPalindrome(string s) {
        int maxLen = 0, l = -1, r = - 1;
        int n = s.length();
        if(n < 2)return s;
        vector<vector<bool>>dp(n,vector<bool>(n, false));
        for(int end = 0; end < n; end++){
            for(int start = end; start >= 0; start--){
                if(s[start] == s[end] && (end <= start + 2 || dp[start + 1][end - 1])){
                    dp[start][end] = true;
                    if(maxLen < end - start + 1){
                        maxLen = end - start + 1;
                        l = start;
                    }
                }
            }
        }
        return s.substr(l, maxLen);
    }
};

此种解法会被官方解法快个四倍左右

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值