【LeetCode 热题 100】动态规划 专题(动态规划 ==> 找子问题!)

from: https://leetcode.cn/studyplan/top-100-liked/

70. 爬楼梯

class Solution {
public:
    int climbStairs(int n) {
        // int f[n + 1];
        // memset(f, 0, sizeof f);
        // f[0] = 1;
        // f[1] = 1;

        // for(int i = 2;i <= n;i ++ ){
        //     f[i] = f[i - 1] + f[i - 2];
        // }
        // return f[n];

        // 写法2:优化空间写法
        int a = 1, b = 1, c = 1; // debug: n=1,c 未初始化会随机一个值
        for(int i = 2;i <= n;i ++ ){
            c = a + b;
            a = b, b = c;
        }
        return c;
    }
};

118. 杨辉三角

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> res;
        for(int i = 0;i < numRows;i ++ ){
            vector<int> cur;
            for(int j = 0;j <= i; j ++ ){
                if(j == 0 || j == i) cur.push_back(1);
                else{
                    cur.push_back(res[i - 1][j - 1] + res[i - 1][j]);
                }
            }
            res.push_back(cur);
        }
        return res;
    }
};

198. 打家劫舍(线性DP,维护两个状态:偷/不偷)

class Solution {
public:
    int rob(vector<int>& nums) {
        // 线性DP:

        // f[i][0]: 考虑前i个, 第i家不偷 的最大金额
        // f[i][1]: 考虑前i个, 第i家偷 的最大金额
        // ans = max(f[i][0/1])

        // 划分依据:第i家偷不偷
        // 不偷: f[i][0] = max(f[i - 1][0], f[i - 1][1])
        // 偷:  f[i][1] = max(f[i - 1][0] + nums[i])

        int n = nums.size();
        // int f[n + 1][2];
        // memset(f, 0, sizeof f);

        int a = 0, b = 0;

        for(int i = 1;i <= n;i ++ ){
            // f[i][0] = max(f[i - 1][0], f[i - 1][1]);
            int temp_a = a;
            a = max(a, b);
            // f[i][1] = f[i - 1][0] + nums[i - 1]; // debug: nums[],下标从1开始
            b = temp_a + nums[i - 1];
        }
        // return max(f[n][0], f[n][1]);
        return max(a, b);
    }
};

279. 完全平方数

不需要从1开始枚举, 从j, j * j, 时间复杂度 O ( n ∗ l o g n ) O(n * logn) O(nlogn)

class Solution {
public:
    int numSquares(int n) {
        // f[n] = (f[1] + f[n - 1])  + (f[2] + f[n - 2]) + ... + 
        // 不需要从1开始枚举, 从j, j * j

        int f[n + 1];
        memset(f, 0x3f, sizeof f); // debug:初始化,最少数量
        f[0] = 0;
        f[1] = 1;
        for(int i = 1;i <= n;i ++ ){
            for(int j = 1;j * j <= i;j ++ )
            {
                f[i] = min(f[i], f[i - j * j] + 1); // debug: 最少数量 // debug: f[i - j * j] + 1
            }
        }
        return f[n];
    }
};

322. 零钱兑换(数量无限完全背包,体积j 从小到大枚举)

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        /* 状态表示: 
                集合: f[i][j]:从前i个硬币中选,且总金额恰好是j的硬币数量
                属性: Min
            状态计算:
                f[i][j] = min(f[i-1][j],f[i-1][j - v[i]] + 1)
                边界:f[i][0] = 0
        */
        int n = coins.size();
        vector<int> f(amount + 1,1e9); // 初始化

        f[0] = 0; // 初始化边界
        for(int i=0;i<n;i++)  
            for(int j = 0; j<=amount;j ++)
                if(j >= coins[i])
                    f[j] = min(f[j],f[j - coins[i]] + 1);
        
        if(f[amount] == 1e9) return -1;
        return f[amount];
    }
};

139. 单词拆分【难,字符串划分动态规划,字符串哈希优化!】

算法1:从后往前 动态规划
时间复杂度 O ( l e n ( s ) ∗ l e n ( s ) ∗ l e n ( w o r d D i c t ) = 300 ∗ 300 ∗ 1000 ) O(len(s) * len(s) * len(wordDict) = 300 * 300 * 1000 ) O(len(s)len(s)len(wordDict)=3003001000)

字符串比较也需要O(len(s))的时间,可用 字符串哈希优化!

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        unordered_set<string> dict(wordDict.begin(), wordDict.end()); //构建为set,方便查找

        vector<bool> dp(s.size()+1, false); //dp表示字符之间的隔板,n个字符有n+1个隔板
        dp[0] = true; //dp[0]是s[0]前面的隔板

        for(int i=1; i<=s.size(); ++i){
            for(int j=i; j>=0; --j){
                if(dict.find(s.substr(j,i-j))!=dict.end() && dp[j]){
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.size()];
    }
};

算法2:从前往后 动态规划,更容易理解
动态规划 前向递推 (不一定每次都要后向思考,前向思考更简单!)+ 字符串哈希, O( n ^ 2)

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        // DP优化搜索

        /*
            f[i] : s[1-i] 可以拼接
            
            f[i] = f[k - 1] + wordDict.count(s[k ~ i]);

            如果是哈希表直接判断字符串,也要计算上时间复杂度O(L),L单词长度
            考虑字符串哈希优化
        */

        typedef unsigned long long ULL;
        const int P = 131;

        unordered_set<ULL> S;
        for(auto &c : wordDict)
        {
            ULL h = 0;
            for(auto d : c) h = h * P + d; // h 是 乘P
            S.insert(h);
        }

        int n = s.size();
        s = ' ' + s;
        vector<bool> f(n + 1);
        f[0] = true;
        for(int i = 0;i < n;i ++ )
        {
            if(f[i])
            {
                ULL h = 0;
                for(int j = i + 1;j <= n;j ++ ) // 因为要计算h,所以这里枚举 能扩展到j
                {
                    h = h * P + s[j];
                    if(S.count(h)) f[j] = true;
                }
            }
        }
        return f[n];
    }
};

300. 最长递增子序列【贪心+二分更优!】

算法1:常规动态规划, O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        // O(n ^ 2)

        int n = nums.size();

        vector<int> f(n);

        int ans = 0;
        for(int i = 0;i < n;i ++ ){
            f[i] = 1;
            for(int j = 0;j < i;j ++ ){
                if(nums[i] > nums[j]) f[i] = max(f[i], f[j] + 1);
            }
            ans = max(ans, f[i]);
        }
        return ans;
    }
};

算法2:贪心 + 二分, O ( n ∗ l o g n ) O(n * logn) O(nlogn)

q[i] : 长度为i 的 最小值 q[i]

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        // O(n ^ 2)

        // 优化:核心:在 <nums[i] 的 前i个元素,找到 最大的长度

        int n = nums.size();
        vector<int> q(n + 1);

        int len = 0;
        for(int i = 0;i < n;i ++ ){
            int l = 0, r = len;
            while(l < r){
                int mid = l + r + 1 >> 1;
                if(q[mid] < nums[i]) l = mid;
                else r = mid - 1;
            }
            q[r + 1] = nums[i];
            len = max(len, r + 1);
        }
        return len;
    }
};

152. 乘积最大子数组

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        // 动态规划
        // 维护最大和最小

        /*
            f[i] : 以i结尾,乘积最大的非空连续子数组
            g[i] : 以i结尾,乘积最小的非空连续子数组

            f[i] = max(f[i - 1] * nums[i], g[i - 1] * nums[i]);
            g[i] = min(f[i - 1] * nums[i], g[i - 1] * nums[i]);

            ans = max(f[i], g[i])
        */

        int n = nums.size();
        int f[n], g[n];
        memset(f, -0x3f, sizeof f);
        memset(g, 0x3f, sizeof g);

        int ans;
        f[0] = g[0] = ans = nums[0];

        for(int i = 1;i < n;i ++ ){
            f[i] = max({f[i - 1] * nums[i], g[i - 1] * nums[i], nums[i]});
            g[i] = min({f[i - 1] * nums[i], g[i - 1] * nums[i], nums[i]});

            ans = max(f[i], ans); // debug: ans = max(f[i], ans);
        } 
        return ans;
    }
};

416. 分割等和子集【转化题意,从数组中挑选和为s的子序列,动态规划!】

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        // 子集元素和 = sum / 2
        // 也就是 从 nums中挑选 和为 sum / 2

        int s = 0;
        for(auto c : nums) s += c;
        if(s & 1) return false;

        // f[i][j] 考虑前i个元素,和为j, O(n * s) = O(200 * 20000)
        // 不选i: f[i][j] = f[i - 1][j]
        // 选i:   f[i][j] = f[i - 1][j - nums[i]]

        // 空间优化
        
        // f[j] = f[j - nums[i]] ==> debug: 因为 j - nums[i] < j 是先遍历的,所以是第i层的,
                                // 不符合 f[i - 1][j - nums[i]], 需要倒着遍历!!!
        
        // 先写好状态方程,再考虑代码优化!

        s = s / 2;
    
        int n = nums.size();
        bool f[s + 1];
        memset(f, 0, sizeof f);
        f[0] = 1;

        for(int i = 1; i <= n;i ++ ){
            // for(int j = 0;j <= s;j ++ ){
            for(int j = s;j >= 0 ;j -- ){ // debug: j 倒着遍历
                if(j >= nums[i - 1]) f[j] |= f[j - nums[i - 1]];
            }
        }
        return f[s];
    }
};

32. 最长有效括号【栈,一般都是栈 好做】

class Solution {
public:
    int longestValidParentheses(string s) {
        
        stack<int> stk;
        int res = 0;
        for(int i = 0, start = -1; i < s.size(); i ++ )
        {
            if(s[i] == '(') stk.push(i);
            else if(s[i] == ')')
            {
                if(stk.size())
                {
                    stk.pop();
                    if(stk.size()) res = max(res, i - stk.top());
                    else res = max(res, i - start);
                }else{
                    start = i; // 不合法,直接 start 移到i
                }
            } 
        }
        return res;
    }
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值