【leetcode】dp---中等(1)467. 环绕字符串中唯一的子字符串_dp字符_以26字母建立状态(2)474. 一和零_dp_01双背包(3)486. 预测赢家_dp博弈_A比B多的分数

467、把字符串 s 看作是“abcdefghijklmnopqrstuvwxyz”的无限环绕字符串,所以 s 看起来是这样的:"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....". 

现在我们有了另一个字符串 p 。你需要的是找出 s 中有多少个唯一的 p 的非空子串,尤其是当你的输入是字符串 p ,你需要输出字符串 s 中 p 的不同的非空子串的数目。 

注意: p 仅由小写的英文字母组成,p 的大小可能超过 10000。

示例 1:

输入: "zab" 输出: 6 解释: 在字符串 S 中有六个子串“z”、“a”、“b”、“za”、“ab”、“zab”。

示例 2:

输入: "cac" 输出: 2 解释: 字符串 S 中的字符串“cac”只有两个子串“a”、“c”。

特殊情况 ---  输入:“” 输出:0 

 dp[i]:26个字母中第i个字母结尾的子串个数
 新加一个字母,相当于增加以该字母i结尾的子串。该子串的个数为加上i后的子串长度

abcd     i=a时,子串为a;   i=b时,子串为ab,b;   i=c时,新增子串为abc,bc,c;   i=d时,新增子串为abcd,bcd,cd,d;

字符串的题目,可以以26个字母建立dp数组保存状态

// dp[i]:26个字母中第i个字母结尾的子串个数
// 状态转移:1)i,i-1可以连接成一个串;2)i单独成一串开头
// 新加一个字母,相当于增加以该字母i结尾的子串,该子串的个数为加上i后的子串长度

class Solution {
public:
    int findSubstringInWraproundString(string p) {
        if(p == "") return 0;
        vector<int> dp(26, 0);
        int cnt = 1;
        dp[p[0] - 'a'] = 1;
        for(int i = 1; i < p.size(); i++){
            if((p[i-1] - 'a' + 1) % 26 == p[i] - 'a') dp[p[i] - 'a'] = max(dp[p[i] - 'a'], ++cnt);
            else dp[p[i] - 'a'] = max(dp[p[i] - 'a'], 1), cnt = 1;
        }
        int ans = 0;
        for(auto &i:dp) ans += i;
        return ans;
    }
};

结果:

执行用时:8 ms, 在所有 C++ 提交中击败了94.46% 的用户

内存消耗:7.3 MB, 在所有 C++ 提交中击败了75.38% 的用户

 

474、在计算机界中,我们总是追求用有限的资源获取最大的收益。

现在,假设你分别支配着 m 个 0 和 n 个 1。另外,还有一个仅包含 0 和 1 字符串的数组。

你的任务是使用给定的 m 个 0 和 n 个 1 ,找到能拼出存在于数组中的字符串的最大数量。每个 0 和 1 至多被使用一次。

注意:

  1.     给定 0 和 1 的数量都不会超过 100。
  2.     给定字符串数组的长度不会超过 600。

示例 1:

输入: Array = {"10", "0001", "111001", "1", "0"}, m = 5, n = 3
输出: 4

解释: 总共 4 个字符串可以通过 5 个 0 和 3 个 1 拼出,即 "10","0001","1","0" 。

不能直接sort,按照字符串长度来贪心。特殊情况 --- 输入: ["111","1000","1000","1000"]  m =9 n=3 ;输出:3 

01背包

题目转化为:选择Array中若干个物品,把里面的 0 和 1 分别放到两个容量分别为 m 和 n 的背包中,在背包容量允许的情况下,最多可以选择多少个物品。

因为有容量 m 和 n 两个背包,所以是双背包决策。因此,这是一个二维 0 - 1 背包问题

本问题每个字符串的重量w:该字符串0/1的个数,本问题每个字符串的价值v:1

 

  • dp定义:dp[i][j][k] 表示选中前i个字符串使用 j 个 0 和 k 个 1 进行构造字符串的最大数目,设 zero[] 和 one[] 分别保存了 strs 数组中每个 string 的 0 和 1 的个数,对于每个字符串有选或者不选两种决策,背包空间是(m,n)
  • 考虑子问题:dp[i-1][j][k] 表示选中前 i - 1 个字符串使用 j 个 0 和 k 个 1 进行构造的最大数量,dp[i-1][j-zero[i]][k-one[i]] 表示选中前 i-1 个字符串使用 j-zero[i] 个 0 和 k-one[i] 个 1 进行构造的最大数量,而 dp[i][j][k] 恰好由上述两种状态转化而来
  • 状态转移方程:dp[i][j][k] = max(dp[i-1][j][k],dp[i-1][j-zero[i]][k-one[i]])
// 01背包
// dp[i][j][k]:前i个字符串,使用了j个0,k个1,能构成的最大字符串数目

class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int l = strs.size();
        vector<int> zero(l + 1, 0);
        vector<int> ones(l + 1, 0);
        int dp[l+1][m+1][n+1];
        for(int i = 1; i <= l; i++) 
            for(auto &c:strs[i - 1]) 
                if(c == '0') zero[i]++; 
                else ones[i]++;
        for(int i = 0; i <= m; i++)
            for(int j = 0; j <= n; j++)
                dp[0][i][j]=0;
        for(int i = 1; i <= l; i++){
            for(int j = 0; j <= m; j++){
                for(int k = 0; k <= n; k++){
                    if(j < zero[i] || k < ones[i]) dp[i][j][k] = dp[i-1][j][k];
                    else dp[i][j][k] = max(dp[i-1][j][k], dp[i-1][j-zero[i]][k-ones[i]]+1); 
                }
            }
        }                   
        return dp[l][m][n];
    }
};

结果: 

执行用时:320 ms, 在所有 C++ 提交中击败了41.50% 的用户

内存消耗:32.7 MB, 在所有 C++ 提交中击败了20.47% 的用户

 

优化:三维 ------》二维

// dp[j][k]:使用了j个0,k个1,能构成的最大字符串数目
class Solution {
public:
    int findMaxForm(vector<string>& strs, int m, int n) {
        int l = strs.size();
        vector<int> zero(l + 1, 0);
        vector<int> ones(l + 1, 0);
        for(int i = 1; i <= l; i++) 
            for(auto &c:strs[i - 1]) 
                if(c == '0') zero[i]++; 
                else ones[i]++;
        vector<vector<int>> dp(m + 1, vector<int>(n + 1, 0));
        for(int i = 1; i <= l; i++){
            for(int j = m; j >= zero[i]; j--){
                for(int k = n; k >= ones[i]; k--){
                    dp[j][k] = max(dp[j][k], dp[j-zero[i]][k-ones[i]] + 1);
                }
            }
        }
        return dp[m][n];
       
    }
};

结果:

执行用时:416 ms, 在所有 C++ 提交中击败了28.63% 的用户

内存消耗:10 MB, 在所有 C++ 提交中击败了61.67% 的用户

 

486、给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。

给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。

示例 1:

输入:[1, 5, 2]
输出:False
解释:一开始,玩家1可以从1和2中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 False 。

博弈dp解法,空间逐渐优化。三维->二维->一维

 dp[i][j]:玩家1在(i,j)获得的比玩家2多的分数
 状态转移:1)玩家一选择左端,则dp[i+1][j]为玩家2比1多的分数,nums[i] - dp[i+1][j];
                   2)玩家一选择右端,则dp[i][j-1]为玩家2比1多的分数,nums[j] - dp[i][j-1]

// dp[i][j]:玩家1在(i,j)获得的比玩家2多的分数
// 状态转移:1)玩家一选择左端,则dp[i+1][j]为玩家2比1多的分数,nums[i] - dp[i+1][j];
//          2)玩家一选择右端,则dp[i][j-1]为玩家2比1多的分数,nums[j] - dp[i][j-1]
class Solution {
public:
    bool PredictTheWinner(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(n, 0));
        for(int i = 0; i < n; i++) dp[i][i] = nums[i];
        for(int i = n - 2; i >= 0; i--){
            for(int j = i + 1; j < n; j++)
                dp[i][j] = max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1]);
        }
        return dp[0][n-1] >= 0;
    }
};

结果:

执行用时:0 ms, 在所有 C++ 提交中击败了100.00% 的用户

内存消耗:8.1 MB, 在所有 C++ 提交中击败了17.65% 的用户

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值