LeetCode(四)动态规划专题(1)

LeetCode 53. Maximum Subarray

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int last = 0, res = INT_MIN;
        for(auto x : nums)
        {
            int t = x;
            if(last > 0) t += last;
            res = max(res, t);
            last = t;
        }
        return res;
    }
};

LeetCode 300. Longest Increasing Subsequence

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。

进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

/* O(n log n)

q[i] 表示当前长度为i的最长上升子序列,结尾最小是多少

q[i] 是严格单调递增(单调队列?) 

对于当前数x,在q里二分出第一个大于等于x的q[i], q[i] = x; 

*/
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int tt = 0; //结尾 队列下标的下一个元素 tt = 1 有一个元素 q[0] 为第一个元素
        vector<int> q(nums.size());
        for(auto x : nums)
        {
            if(!tt || x > q[tt - 1]) q[tt++] = x;
            else //二分
            {
                int l = 0, r = tt - 1;
                while(l < r)
                {
                    int mid = l + r >> 1;
                    if(q[mid] >= x) r = mid;
                    else l = mid + 1;
                }
                q[r] = x;
            }            
        }
        return tt;
    }
};

LeetCode 72. Edit Distance

给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

示例 1:

输入: word1 = “horse”, word2 = “ros”
输出: 3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

示例 2:

输入: word1 = “intention”, word2 = “execution”
输出: 5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)

/*
状态表示:f[i][j]让word1的前i个字母和word2的前j个字母匹配,所需的最少操作次数

状态转移:
    if word1[i] == word2[j]:
        f[i][j] = f[i -1][j - 1]
    else:
        f[i][j] = min(f[i][j], f[i - 1][j - 1] + 1); //替换操作
        f[i][j] = min(f[i][j], f[i][j - 1] + 1); //添加word1操作
        f[i][j] = min(f[i][j], f[i - 1][j] + 1); //添加word2操作
        f[i][j] = min(f[i][j], f[i - 1][j] + 1);//删除word1操作 与添加word2操作一样
        f[i][j] = min(f[i][j], f[i][j - 1] + 1);//删除word2操作 与添加word1操作一样
        
边界问题:
    f[i][0] = i;//word2 没有 只有word1 操作i次
    f[0][j] = j;//word1 没有 只有word2 操作j次
    
*/
class Solution {
public:
    int minDistance(string word1, string word2) {
        int n = word1.size(), m = word2.size(); //求长度
        vector<vector<int>> f(n + 1, vector<int>(m + 1, 0)); //二维数组初始化
        for(int i = 0; i <= n; i ++) f[i][0] = i;//初始化0到n+1长度
        for(int j = 0; j <= m; j ++) f[0][j] = j;
        for(int i = 1; i <= n; i ++) //动态规划的下标从1开始
            for(int j = 1; j <= m; j ++)
            {
                f[i][j] = f[i - 1][j - 1] + (word1[i - 1] != word2[j - 1]);//合并if操作
                f[i][j] = min(f[i][j], f[i][j - 1] + 1); //添加word1操作或删除word2操作
                f[i][j] = min(f[i][j], f[i - 1][j] + 1);//添加word2操作或删除word1操作
            }
        return f[n][m];
    }
};

LeetCode 121. Best Time to Buy and Sell Stock

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。

注意你不能在买入股票前卖出股票。

示例 1:

输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int res = 0, pre_min = INT_MAX;
        for(auto x : prices)
        {
            res = max(res, x - pre_min);
            pre_min = min(pre_min, x);
        }
        return res;
    }
};

LeetCode 122. Best Time to Buy and Sell Stock II

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int res = 0;
        for(int i = 1; i < prices.size(); i++)
            if(prices[i] > prices[i - 1])
                res += prices[i] - prices[i - 1];
        return res;
    }    
};

LeetCode 123. Best Time to Buy and Sell Stock III

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [3,3,5,0,0,3,1,4]
输出: 6
解释: 在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。

示例 2:

输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例 3:

输入: [7,6,4,3,1]
输出: 0
解释: 在这个情况下, 没有交易完成, 所以最大利润为 0。

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<int> f(n + 1, 0);
        int pre_min = INT_MAX / 2;
        for(int i = 1; i <= n; i++)
        {
            f[i] = prices[i - 1] - pre_min;
            f[i] = max(f[i], f[i - 1]);
            pre_min = min(pre_min, prices[i - 1]);
        }
        int res = 0;
        int sum_max = INT_MIN / 2;
        for(int i = n; i > 0; i--)
        {
            int t = sum_max - prices[i - 1];
            res = max(res, f[i - 1] + t);
            sum_max = max(sum_max, prices[i - 1]);
        }
        return res;
    }
};

LeetCode 188. Best Time to Buy and Sell Stock IV

给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你最多可以完成 k 笔交易。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: [2,4,1], k = 2
输出: 2
解释: 在第 1 天 (股票价格 = 2) 的时候买入,在第 2 天 (股票价格 = 4) 的时候卖出,这笔交易所能获得利润 = 4-2 = 2 。

示例 2:

输入: [3,2,6,5,0,3], k = 2
输出: 7
解释: 在第 2 天 (股票价格 = 2) 的时候买入,在第 3 天 (股票价格 = 6) 的时候卖出, 这笔交易所能获得利润 = 6-2 = 4 。
随后,在第 5 天 (股票价格 = 0) 的时候买入,在第 6 天 (股票价格 = 3) 的时候卖出, 这笔交易所能获得利润 = 3-0 = 3 。

/*
    f[i]表示最多进行i次交易的最大收益; 
    f[i][k] = max(f[i - 1][k], g[ i- 1][k] + prices[i]);
    g[i]表示最多进行i-1次交易,且再一次买入后的最大收益;
    g[i][k] = max(g[i - 1][k], f[i - 1][k - 1] - prices[i]);
*/
class Solution {
public:
    int maxProfit(int k, vector<int>& prices) {
        int n = prices.size();
        if(k >= n / 2)
        {
            int res = 0;
            for(int i = 1; i < n; i ++) res += max(0, prices[i] - prices[i - 1]);
                return res;
        }
        int f[k + 1], g[k + 1];
        for(int i = 0; i <= k; i ++) f[i] = 0, g[i] = INT_MIN;
        for(auto cur : prices)
            for(int i = k; i; i--)
            {
                f[i] = max(f[i], g[i] + cur);
                g[i] = max(g[i], f[i - 1] - cur);
            }
        return f[k];
    }
};

LeetCode 309. Best Time to Buy and Sell Stock with Cooldown

给定一个整数数组,其中第 i 个元素代表了第 i 天的股票价格 。​

设计一个算法计算出最大利润。在满足以下约束条件下,你可以尽可能地完成更多的交易(多次买卖一支股票):

你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
卖出股票后,你无法在第二天买入股票 (即冷冻期为 1 天)。

示例:

输入: [1,2,3,0,2]
输出: 3
解释: 对应的交易状态为: [买入, 卖出, 冷冻期, 买入, 卖出]

/*
    f[i][0] 前i天持有收益
    f[i][1] 前i天卖完收益
    f[i][2] 第i天没有进行交易,并把所有股票卖出的收益, i - 1交易
*/
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        vector<vector<int>> f(n + 1, vector<int>(3));
        f[0][0] = INT_MIN, f[0][1] = f[0][2] = 0;
        for(int i = 1; i <= n; i++)
        {
            f[i][0] = max(f[i - 1][0], f[i - 1][2] - prices[i - 1]);
            f[i][1] = f[i - 1][0] + prices[i - 1];
            f[i][2] = max(f[i - 1][1], f[i - 1][2]);
        }
        return max(f[n][1], f[n][2]);
    }
};

LeetCode 198. House Robber

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [1,2,3,1]
输出: 4
解释: 偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

示例 2:

输入: [2,7,9,3,1]
输出: 12
解释: 偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
偷窃到的最高金额 = 2 + 9 + 1 = 12 。

class Solution {
public:
    /*
        f[i] 第i天拿的收益是多少
        f[i] = nums[i] + g[i - 1];
        g[i] 第i天不拿的收益是多少
        g[i] = max(f[i - 1], g[i - 1]);
    */
    int rob(vector<int>& nums) {
        int n = nums.size();
        vector<int> f(n + 1, 0), g(n + 1, 0);
        for(int i = 1; i <= n; i++)
        {
            f[i] = nums[i - 1] + g[i - 1];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        return max(f[n], g[n]);
    }
};

LeetCode 213. House Robber II

你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

示例 1:

输入: [2,3,2]
输出: 3
解释: 你不能先偷窃 1 号房屋(金额 = 2),然后偷窃 3 号房屋(金额 = 2), 因为他们是相邻的。

示例 2:

输入: [1,2,3,1]
输出: 4
解释: 你可以先偷窃 1 号房屋(金额 = 1),然后偷窃 3 号房屋(金额 = 3)。
偷窃到的最高金额 = 1 + 3 = 4 。

class Solution {
public:
    /*
        f[i] 第i天取了的总收益
        g[i] 第i天没取的总收益
    */
    
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 1) return nums[0];
        vector<int>f(n + 1, 0), g(n + 1, 0);
        for(int i = 2; i <= n; i++) //第一个没取
        {
            f[i] = g[i - 1] + nums[i - 1];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        int res = max(f[n], g[n]);
        for(int i = 1; i <= n; i ++) //第一个取了
        {
            f[i] = g[i - 1] + nums[i - 1];
            g[i] = max(f[i - 1], g[i - 1]);
        }
        return max(res, g[n]);
    }
};

LeetCode 312. Burst Balloons

有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。

现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。

求所能获得硬币的最大数量。

说明:
你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。
0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100

示例:

输入: [3,1,5,8]
输出: 167
解释: nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
coins = 315 + 358 + 138 + 181 = 167

class Solution {
public:
    vector<vector<int>>f;
    vector<int> nums;
    
    int dp(int x, int y)
    {
        if(f[x][y] != -1) return f[x][y]; //当前气球已戳破
        f[x][y] = 0;
        for(int i = x + 1; i < y; i++)
            f[x][y] = max(f[x][y], dp(x, i) + dp(i, y) + nums[x] * nums[i] * nums[y]);
        return f[x][y];
    }
    
    int maxCoins(vector<int>& _nums) {
        int n = _nums.size();
        f = vector<vector<int>>(n + 2, vector<int>(n + 2, -1));//初始化
        nums = vector<int>(n + 2);
        nums[0] = nums[n + 1] = 1;//哨兵
        for(int i = 0; i < n; i++) nums[i + 1] = _nums[i];//读入_nuns
        return dp(0, n + 1);
        
    }
};

LeetCode 96. Unique Binary Search Trees

给定一个整数 n,求以 1 … n 为节点组成的二叉搜索树有多少种?

示例:

输入: 3
输出: 5
解释:
给定 n = 3, 一共有 5 种不同结构的二叉搜索树:

   1         3     3      2      1
    \       /     /      / \      \
     3     2     1      1   3      2
    /     /       \                 \
   2     1         2                 3
class Solution {
public:
    
    /*
    状态表示:f[n] 表示 n个节点的二叉搜索树共有多少种。
    状态转移:左子树可以有 0,1,…n−1 个节点,对应的右子树有 n−1,n−2,…,0 个节点,f[n] 是所有这些情况的加和
    */
    int numTrees(int n) {
        vector<int>f(n + 1);
        f[0] = 1;
        for(int i = 1; i <= n; i++)
        {
            f[i] = 0;
            for(int j = 1; j <= i; j++)
                f[i] += f[j - 1] * f[i - j];
        }
        return f[n];
    }
};

LeetCode 140. Word Break II

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。

说明:
分隔时可以重复使用字典中的单词。
你可以假设字典中没有重复的单词。

示例 1:

输入:
s = “catsanddog”
wordDict = [“cat”, “cats”, “and”, “sand”, “dog”]
输出:
[
“cats and dog”,
“cat sand dog”
]

示例 2:

输入:
s = “pineapplepenapple”
wordDict = [“apple”, “pen”, “applepen”, “pine”, “pineapple”]
输出:
[
“pine apple pen apple”,
“pineapple pen apple”,
“pine applepen apple”
]
解释: 注意你可以重复使用字典中的单词。

示例 3:

输入:
s = “catsandog”
wordDict = [“cats”, “dog”, “sand”, “and”, “cat”]
输出:
[]

class Solution {
public:
    /*
    1.按照Word Break 的动态规划思路,求出f数组,f[i]表示[0, i]是否可以被完整分割。
    2.根据f数组,从位置n开始进行DFS,枚举所有可能分割的方式。枚举时,只需要判断f[i]是否可能从f[j]转移过来的,若可能,则递归到位置j。
    */        
    vector<string> ans;
    unordered_map<string, int>dict;
    
    void dfs(vector<bool>&f, string &s, string path, int u)
    {
        if(!u)
        {
            ans.push_back(path.substr(0, path.size() - 1));
            return;
        }
        
        for(int i = 0; i < u; i ++)
            if(dict[s.substr(i, u -  i)] && f[i])
                dfs(f, s, s.substr(i, u - i) + " " + path, i);
    }
    
    vector<string> wordBreak(string s, vector<string>& wordDict) {
        for(auto &word : wordDict) dict[word] = 1;
        int n = s.size();
        vector<bool> f(n + 1, true);
        for(int i = 1; i <= n; i++)
        {
            f[i] = false;
            for(int j = 0; j < i; j ++)
                if(dict[s.substr(j, i - j)] && f[j])
                {
                    f[i] = true;
                    break;
                }
        }
        dfs(f, s, "", n);
        return ans;
        
    }
};  

LeetCode 10. Regular Expression Matching

给定一个字符串 (s) 和一个字符模式 §。实现支持 ‘.’ 和 ‘*’ 的正则表达式匹配。

‘.’ 匹配任意单个字符。
‘*’ 匹配零个或多个前面的元素。

匹配应该覆盖整个字符串 (s) ,而不是部分字符串。

说明:
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母,以及字符 . 和 *。

示例 1:

输入:
s = “aa”
p = “a”
输出: false
解释: “a” 无法匹配 “aa” 整个字符串。

示例 2:

输入:
s = “aa”
p = “a*”
输出: true
解释: ‘*’ 代表可匹配零个或多个前面的元素, 即可以匹配 ‘a’ 。因此, 重复 ‘a’ 一次, 字符串可变为 “aa”。

示例 3:

输入:
s = “ab”
p = “."
输出: true
解释: ".
” 表示可匹配零个或多个(’*’)任意字符(’.’)。

示例 4:

输入:
s = “aab”
p = “cab”
输出: true
解释: ‘c’ 可以不被重复, ‘a’ 可以被重复一次。因此可以匹配字符串 “aab”。

示例 5:

输入:
s = “mississippi”
p = “misisp*.”
输出: false

class Solution {
public:
    /*
    状态表示:f[i][j]表示p从j开始到结尾,是否能匹配s从i开始到结尾
    状态转移:
    如果p[j+1]不是通配符'*',则f[i][j]是真,当且仅当s[i]可以和p[j]匹配,且f[i+1][j+1]是真;
    如果p[j+1]是通配符'*',则下面的情况只要有一种满足,f[i][j]就是真;
        1. f[i][j+2]是真;
        2. s[i]可以和p[j]匹配,且f[i+1][j]是真;
    */
    vector<vector<int>> f;
    int n, m;
    bool isMatch(string s, string p) {
        n = s.size();
        m = p.size();
        f = vector<vector<int>>(n + 1, vector<int>(m + 1, -1));//初始化
        return dp(0, 0, s, p);        
    }
    
    bool dp(int x, int y, string &s, string &p)
    {
        if(f[x][y] != -1) return f[x][y];//计算过 直接返回
        if(y == m)
            return f[x][y] = x == n;
        bool first_match = x < n && (s[x] == p[y] || p[y] == '.');
        bool ans;
        if(y + 1 < m && p[y + 1] == '*')
            ans = dp(x, y + 2, s, p) || first_match && dp(x + 1, y, s, p);
        else
            ans = first_match && dp(x + 1, y + 1, s, p);
        return f[x][y] = ans;
    }
};   
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值