394字符串解码;经典动态规划问题:高楼扔鸡蛋+高楼扔鸡蛋进阶

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

示例:

s = "3[a]2[bc]", 返回 "aaabcbc".
s = "3[a2[c]]", 返回 "accaccacc".
s = "2[abc]3[cd]ef", 返回 "abcabccdcdcdef".

class Solution {//栈
public:
    string decodeString(string s) {
        int sSize=s.size();
        stack<pair<int,string>>st;
        string res;
        int cnt=0;
        for(int i=0;i<sSize;++i){
            if(isdigit(s[i])){
                cnt=cnt*10+(s[i]-'0');//cnt重复次数
            }
            else if(s[i]=='['){
                st.push(make_pair(cnt,res));//依此把(下一个要字符串要重复的次数入栈,当前[字符前的所有正确解码的字符串)入栈
                cnt=0;
                res="";
            }
            else if(s[i]==']'){
                pair p=st.top();
                st.pop();
                for(int j=0;j<p.first;++j)//重复cnt次
                    p.second+=res;
                res=p.second;//当前所有正确解码的字符串
            }
            else res+=s[i];
        }
        return res;
    }
};

题目是这样:你面前有一栋从 1 到 NN 层的楼,然后给你 K 个鸡蛋(K 至少为 1)。现在确定这栋楼存在楼层 0 <= F <= N,在这层楼将鸡蛋扔下去,鸡蛋恰好没摔碎(高于 F 的楼层都会碎,低于 F 的楼层都不会碎)。现在问你,最坏情况下,你至少要扔几次鸡蛋,才能确定这个楼层 F 呢?

对动态规划问题,直接套我们以前多次强调的框架即可:这个问题有什么「状态」,有什么「选择」,然后穷举。

「状态」很明显,就是当前拥有的鸡蛋数 K 和需要测试的楼层数 N。随着测试的进行,鸡蛋个数可能减少,楼层的搜索范围会减小,这就是状态的变化。

「选择」其实就是去选择哪层楼扔鸡蛋。回顾刚才的线性扫描和二分思路,二分查找每次选择到楼层区间的中间去扔鸡蛋,而线性扫描选择一层层向上测试。不同的选择会造成状态的转移。

现在明确了「状态」和「选择」,动态规划的基本思路就形成了:肯定是个二维的 dp 数组或者带有两个状态参数的 dp 函数来表示状态转移;外加一个 for 循环来遍历所有选择,择最优的选择更新状态:

# 当前状态为 K 个鸡蛋,面对 N 层楼
# 返回这个状态下的最优结果
def dp(K, N):
    int res
    for 1 <= i <= N:
        res = min(res, 这次在第 i 层楼扔鸡蛋)
    return res

这段伪码还没有展示递归和状态转移,不过大致的算法框架已经完成了。

我们选择在第 i 层楼扔了鸡蛋之后,可能出现两种情况:鸡蛋碎了,鸡蛋没碎。注意,这时候状态转移就来了

如果鸡蛋碎了,那么鸡蛋的个数 K 应该减一,搜索的楼层区间应该从 [1..N] 变为 [1..i-1]i-1 层楼;

如果鸡蛋没碎,那么鸡蛋的个数 K 不变,搜索的楼层区间应该从 [1..N] 变为 [i+1..N]N-i 层楼。

dp[1~K][0~N]:k次扔鸡蛋,n层楼 
vector<vector<int>>dp(K+1,vector<int>(N+1,0));
for(int i=0;i<=N;++i)
    dp[1][i]=i;
for(int k=2;k<=K;++K)
    for(int n=1;n<=N;++n)
        for(int i=1;i<=N;++i)
            res=min(res,1+max(dp[k][n-i],dp[k-1][i-1]));//在第i层扔鸡蛋没碎和碎的情况
return res;

二分优化

dp[1~K][0~N]:k次扔鸡蛋,n层楼 
vector<vector<int>>dp(K+1,vector<int>(N+1,0));
for(int i=0;i<=N;++i)
    dp[1][i]=i;
for(int k=2;k<=K;++K)
    for(int n=1;n<=N;++n){
        int le=1,ri=N;
        while(le<=ri){
            int mid=le+((ri-le)>>1);
            if(dp[k][n-i]>dp[k-1][i-1])
                le=mid+1,res=min(res,1+dp[k][n-i]);
            else
                ri=mid-1,res=min(res,1+dp[k-1][i-1]);
        }
    }
return res;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值