【七十】【算法分析与设计】639. 解码方法 II,264. 丑数 II,32. 最长有效括号,记忆化递归填写dp表,用记忆化递归代替大部分动态规划

639. 解码方法 II

一条包含字母 A-Z 的消息通过以下的方式进行了 编码

'A' -> "1"

'B' -> "2"

...

'Z' -> "26"

解码 一条已编码的消息,所有的数字都必须分组,然后按原来的编码方案反向映射回字母(可能存在多种方式)。例如,"11106" 可以映射为:

  • "AAJF" 对应分组 (1 1 10 6)

  • "KJF" 对应分组 (11 10 6)

注意,像 (1 11 06) 这样的分组是无效的,因为 "06" 不可以映射为 'F' ,因为 "6""06" 不同。

除了 上面描述的数字字母映射方案,编码消息中可能包含 '*' 字符,可以表示从 '1''9' 的任一数字(不包括 '0')。例如,编码字符串 "1*" 可以表示 "11""12""13""14""15""16""17""18""19" 中的任意一条消息。对 "1*" 进行解码,相当于解码该字符串可以表示的任何编码消息。

给你一个字符串 s ,由数字和 '*' 字符组成,返回 解码 该字符串的方法 数目

  • 由于答案数目可能非常大,返回 10(9)7

示例 1:

输入:s = "*" 输出:9 解释:这一条编码消息可以表示 "1"、"2"、"3"、"4"、"5"、"6"、"7"、"8" 或 "9" 中的任意一条。 可以分别解码成字符串 "A"、"B"、"C"、"D"、"E"、"F"、"G"、"H" 和 "I" 。 因此,"*" 总共有 9 种解码方法。

示例 2:

输入:s = "1*" 输出:18 解释:这一条编码消息可以表示 "11"、"12"、"13"、"14"、"15"、"16"、"17"、"18" 或 "19" 中的任意一条。 每种消息都可以由 2 种方法解码(例如,"11" 可以解码成 "AA" 或 "K")。 因此,"1*" 共有 9 * 2 = 18 种解码方法。

示例 3:

输入:s = "2*" 输出:15 解释:这一条编码消息可以表示 "21"、"22"、"23"、"24"、"25"、"26"、"27"、"28" 或 "29" 中的任意一条。 "21"、"22"、"23"、"24"、"25" 和 "26" 由 2 种解码方法,但 "27"、"28" 和 "29" 仅有 1 种解码方法。 因此,"2*" 共有 (6 * 2) + (3 * 1) = 12 + 3 = 15 种解码方法。

提示:

  • 1 <= s.length <= 10(5)

  • s[i]0 - 9 中的一位数字或字符 '*'

1.

定义递归函数,dfs(i)表示[i,n-1]解码的方法数.

如果单独解码i位置的数字符,分三种情况,如果i位置字符是'0',则[i,n-1]解码方法数为0.

如果i位置字符是数字但是不是'0',此时解码方法数是dfs(i+1).

如果i位置字符是'*',解码方法数是9*dfs(i+1).

如果i位置和i+1位置一起解码,分几种情况.

nums+nums

*+nums

nums+*

*+ *

如果是nums+nums.看看是不是小于26,如果小于26,此时解码方法数是dfs(i+2).

如果是*+nums.有两种情况,第一种情况*表示1,此时解码方法数是dfs(i+2).

第二种情况*表示2,此时如果nums小于等于6,此时解码方法数是dfs(i+2).

如果是nums+*.nums必须是1或者2,否则解码方法数是0.nums为1的情况,此时解码方法数是9*dfs(i+2).

nums为2,解码方法数是6*dfs(i+2).

如果*+ *.此时解码方法数是15*dfs(i+2).

2.

不要忘记对MOD取余.

暴力递归

 
class Solution {
public:
    using __ll = long long;   // 定义长整型别名
    string __s;               // 存储输入字符串
    __ll __n;                 // 存储输入字符串长度
    const __ll MOD = 1e9 + 7; // 定义模数

    // 初始化函数
    void solveinit() { __n = __s.size(); }

    // 递归解码函数
    __ll dfs(int __i) {
        if (__i == __n)
            return 1; // 当索引达到字符串长度时,返回1

        __ll __ans = 0; // 定义结果变量

        // 如果当前字符是'0',则返回0
        if (__s[__i] == '0')
            return __ans;

        // 如果当前字符不是'*',则直接递归下一位
        if (__s[__i] != '*')
            __ans += dfs(__i + 1);
        else
            __ans += 9 * dfs(__i + 1); // 如果当前字符是'*',则有9种选择

        // 如果索引已经到达字符串末尾,则返回当前结果对模数取余
        if (__i + 1 >= __n)
            return __ans % MOD;

        // 处理当前字符不为'*'的情况
        if (__s[__i] != '*') {
            if (__s[__i + 1] != '*') {
                // 如果当前字符和下一字符可以组成一个有效数字,则递归下两位
                if ((__s[__i] - '0') * 10 + __s[__i + 1] - '0' <= 26) {
                    __ans += dfs(__i + 2);
                }
            } else {
                // 如果当前字符为1,则有9种选择;如果当前字符为2,则有6种选择
                if (__s[__i] == '1')
                    __ans += 9 * dfs(__i + 2);
                if (__s[__i] == '2')
                    __ans += 6 * dfs(__i + 2);
            }
        } else { // 处理当前字符为'*'的情况
            if (__s[__i + 1] != '*') {
                // 如果下一字符不为'*',则有15种选择
                __ans += dfs(__i + 2); // 当前字符为1时,有15种选择
                if (__s[__i + 1] <= '6')
                    __ans += dfs(__i + 2); // 当前字符为2时,有15种选择
            } else {
                // 如果下一字符为'*',则有15种选择
                __ans += 15 * dfs(__i + 2);
            }
        }

        return __ans % MOD; // 返回当前结果对模数取余
    }

    // 解码函数入口
    int numDecodings(string s) {
        __s = s; // 存储输入字符串

        solveinit(); // 初始化

        return dfs(0); // 调用递归解码函数
    }
};

记忆化递归

1.

将暴力递归改动态规划.使用前看看memory有没有数据.

返回前把数据存入memory中.

 
class Solution {
public:
    // 使用长长整型来存储大数
    using __ll = long long;
    // 存储输入的字符串
    string __s;
    // 存储字符串的长度
    __ll __n;
    // 定义一个大质数作为取模运算的基数
    const __ll MOD = 1e9 + 7;
    // 动态规划数组,用于存储子问题的解
    vector<__ll> __memory;
    // 初始化函数,准备动态规划
    void solveinit() {
        __n = __s.size();
        __memory.clear();
        __memory.resize(__n + 1,-1); // 初始化记忆数组,将所有值设为-1表示未计算
    }
    // 递归函数,用于计算解码方法的数目
    __ll dfs(int __i) {
        if (__i == __n) // 如果已经到达字符串末尾,返回1表示一种解码方式
            return 1;
        // 如果当前位置的解已经计算过,直接返回
        if(__memory[__i]!=-1) return __memory[__i];
        __ll __ans = 0; // 初始化解码方法数目为0
        // 如果当前字符是'0',不能作为有效解码,直接返回0
        if (__s[__i] == '0')
            return __ans;
        // 如果当前字符不是'*',则直接递归计算下一个字符的解码方法数目并加到当前的解码方法数目中
        if (__s[__i] != '*')
            __ans += dfs(__i + 1);
        else
            // 如果当前字符是'*',则它可以代表1到9的任何一个数字,因此递归计算下一个字符的解码方法数目,并乘以9加到当前的解码方法数目中
            __ans += 9 * dfs(__i + 1);
        // 将当前位置的解码方法数目存储到记忆数组中,并取模
        __memory[__i]=__ans%MOD;
        // 如果下一个字符是字符串的最后一个字符,则不需要考虑两位数字的情况,直接返回当前解码方法数目
        if (__i + 1 >= __n)
            return __ans % MOD;
        // 如果当前字符不是'*',则考虑两位数字的情况
        if (__s[__i] != '*') {
            if (__s[__i + 1] != '*') {
                // 如果下一个字符不是'*',则只有当两位数字组成的数在1到26之间时,才是一种有效的解码方式
                if ((__s[__i] - '0') * 10 + __s[__i + 1] - '0' <= 26) {
                    __ans += dfs(__i + 2);
                }
            } else {
                // 如果下一个字符是'*',则根据当前字符的不同,它可以与'*'组成不同的两位数字
                if (__s[__i] == '1')
                    __ans += 9 * dfs(__i + 2); // '1'可以与'*'组成10到19,共9种
                if (__s[__i] == '2')
                    __ans += 6 * dfs(__i + 2); // '2'可以与'*'组成20到26,共6种
            }
        } else {
            // 如果当前字符是'*',则考虑与下一个字符的所有可能组合
            if (__s[__i + 1] != '*') {
                // 如果下一个字符不是'*',则它可以与'*'组成1到26的任何一个数字,但需要递归计算后续的解码方法数目
                __ans += dfs(__i + 2); // '*'可以代表1到9,与下一个数字组成1到26的数,共9种
                if (__s[__i + 1] <= '6')
                    __ans += dfs(__i + 2); // 如果下一个数字小于或等于6,'*'还可以代表2到6,与下一个数字组成11到26的数,共5种
            } else {
                // 如果下一个字符也是'*',则'*'可以代表1到9,与另一个'*'组成11到99的数,共15种
                __ans += 15 * dfs(__i + 2);
            }
        }
        // 将计算出的解码方法数目存储到记忆数组中,并返回当前位置的解码方法数目
        __memory[__i]=__ans%MOD;
        return __ans % MOD;
    }
    // 主函数,接收一个字符串s,返回解码该字符串的方法数目
    int numDecodings(string s) {
        __s = s;
        solveinit(); // 初始化
        return dfs(0); // 从字符串的第一个字符开始递归计算解码方法的数目
    }
};

动态规划

1.

dfs(i)对应dp[i].递归出口对应初始化.

 
class Solution {
public:
    // 使用长长整型来存储大数
    using __ll = long long;
    // 存储输入的字符串
    string __s;
    // 存储字符串的长度
    __ll __n;
    // 定义一个大质数作为取模运算的基数
    const __ll MOD = 1e9 + 7;
    // 动态规划数组,用于存储子问题的解
    vector<__ll> __memory;
    // 动态规划数组,用于从底部向上计算解码方法的数目
    vector<__ll> __dp;
    // 初始化函数,准备动态规划
    void solveinit() {
        __n = __s.size();
        __memory.clear();
        __memory.resize(__n + 1, -1); // 初始化记忆数组,将所有值设为-1表示未计算
        __dp.clear();
        __dp.resize(__n + 1); // 初始化动态规划数组
        __dp[__n] = 1; // 字符串末尾的解码方法数目为1
    }
    // 递归函数,用于计算解码方法的数目
    __ll dfs(int __i) {
        if (__i == __n) // 如果已经到达字符串末尾,返回1表示一种解码方式
            return 1;
        // 如果当前位置的解已经计算过,直接返回
        if (__memory[__i] != -1)
            return __memory[__i];
        __ll __ans = 0; // 初始化解码方法数目为0
        // 如果当前字符是'0',不能作为有效解码,直接返回0
        if (__s[__i] == '0')
            return __ans;
        // 如果当前字符不是'*',则直接递归计算下一个字符的解码方法数目并加到当前的解码方法数目中
        if (__s[__i] != '*')
            __ans += dfs(__i + 1);
        else
            // 如果当前字符是'*',则它可以代表1到9的任何一个数字,因此递归计算下一个字符的解码方法数目,并乘以9加到当前的解码方法数目中
            __ans += 9 * dfs(__i + 1);
        // 将当前位置的解码方法数目存储到记忆数组中,并取模
        __memory[__i] = __ans % MOD;
        if (__i + 1 >= __n)
            return __ans % MOD; // 如果下一个字符是字符串的最后一个字符,则不需要考虑两位数字的情况,直接返回当前解码方法数目

        // 考虑当前字符和下一个字符组成的两位数的解码情况
        if (__s[__i] != '*') {
            if (__s[__i + 1] != '*') {
                // 如果两个字符都不是'*',则只有当它们组成的两位数在1到26之间时,才是一种有效的解码方式
                if ((__s[__i] - '0') * 10 + __s[__i + 1] - '0' <= 26) {
                    __ans += dfs(__i + 2);
                }
            } else {
                // 如果当前字符不是'*',下一个字符是'*',则根据当前字符的不同,它可以与'*'组成不同的两位数字
                if (__s[__i] == '1')
                    __ans += 9 * dfs(__i + 2); // '1'可以与'*'组成10到19,共9种
                if (__s[__i] == '2')
                    __ans += 6 * dfs(__i + 2); // '2'可以与'*'组成20到26,共6种
            }
        } else {
            if (__s[__i + 1] != '*') {
                // 如果当前字符是'*',下一个字符不是'*',则'*'可以代表1到9,与下一个数字组成1到26的数,共9种
                __ans += dfs(__i + 2); // '*“可以代表1到9,与下一个数字组成1到26的数,共9种
                if (__s[__i + 1] <= '6')
                    __ans += dfs(__i + 2); // 如果下一个数字小于或等于6,'*'还可以代表2到6,与下一个数字组成11到26的数,共5种
            } else {
                // 如果两个字符都是'*',则'*'可以代表1到9,与另一个'*'组成11到99的数,共15种
                __ans += 15 * dfs(__i + 2);
            }
        }
        // 将计算出的解码方法数目存储到记忆数组中,并返回当前位置的解码方法数目
        __memory[__i] = __ans % MOD;
        return __ans % MOD;
    }
    // 主函数,接收一个字符串s,返回解码该字符串的方法数目
    int numDecodings(string s) {
        __s = s;
        solveinit(); // 初始化

        // 从底部向上计算解码方法的数目
        for (int __i = __n - 1; __i >= 0; __i--) {
            __ll __ans = 0;
            if (__s[__i] == '0') {
                // 如果当前字符是'0',则不能从该位置解码,解码方法数目为0
                __dp[__i] = __ans;
                continue;
            }

            // 如果当前字符不是'*',则它可以独立解码,解码方法数目等于下一个字符的解码方法数目
            if (__s[__i] != '*')
                __ans += __dp[__i + 1];
            else
                // 如果当前字符是'*',则它可以代表1到9的任何一个数字,解码方法数目等于下一个字符的解码方法数目乘以9
                __ans += 9 * __dp[__i + 1];

            // 如果下一个字符是字符串的最后一个字符,则不需要考虑两位数字的情况
            if (__i + 1 >= __n) {
                __dp[__i] = __ans % MOD;
                continue;
            }

            // 考虑当前字符和下一个字符组成的两位数的解码情况
            if (__s[__i] != '*') {
                if (__s[__i + 1] != '*') {
                    // 如果两个字符都不是'*',则只有当它们组成的两位数在1到26之间时,才是一种有效的解码方式
                    if ((__s[__i] - '0') * 10 + __s[__i + 1] - '0' <= 26) {
                        __ans += __dp[__i + 2];
                    }
                } else {
                    // 如果当前字符不是'*',下一个字符是'*',则根据当前字符的不同,它可以与'*'组成不同的两位数字
                    if (__s[__i] == '1')
                        __ans += 9 * __dp[__i + 2]; // '1'可以与'*'组成10到19,共9种
                    if (__s[__i] == '2')
                        __ans += 6 * __dp[__i + 2]; // '2'可以与'*'组成20到26,共6种
                }
            } else {
                if (__s[__i + 1] != '*') {
                    // 如果当前字符是'*',下一个字符不是'*',则'*'可以代表1到9,与下一个数字组成1到26的数,共9种
                    __ans += __dp[__i + 2]; // '*“可以代表1到9,与下一个数字组成1到26的数,共9种
                    if (__s[__i + 1] <= '6')
                        __ans += __dp[__i + 2]; // 如果下一个数字小于或等于6,'*'还可以代表2到6,与下一个数字组成11到26的数,共5种
                } else {
                    // 如果两个字符都是'*',则'*'可以代表1到9,与另一个'*'组成11到99的数,共15种
                    __ans += 15 * __dp[__i + 2];
                }
            }

            // 将计算出的解码方法数目存储到动态规划数组中,并取模
            __dp[__i] = __ans % MOD;
        }

        // 返回字符串第一个字符的解码方法数目
        return __dp[0];
    }
};

264. 丑数 II

给你一个整数 n ,请你找出并返回第 n丑数

丑数 就是质因子只包含 235 的正整数。

示例 1:

输入:n = 10 输出:12 解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。

示例 2:

输入:n = 1 输出:1 解释:1 通常被视为丑数。

提示:

  • 1 <= n <= 1690

动态规划

 
class Solution {
public:
    // 定义长整型别名
    using _ll=long long;
    // 存储丑数序列的长度
    _ll _n;
    // 动态规划数组,存储前n个丑数
    vector<_ll> _dp;
    // 记录当前第几个丑数是2的倍数、3的倍数和5的倍数
    _ll _cur2, _cur3, _cur5;
    // 初始化函数,准备动态规划
    void solveinit(){
        _dp.clear();
        _dp.resize(_n+1);

        // 1是第一个丑数
        _dp[1]=1;
        // 如果序列长度大于等于2,2是第二个丑数
        if(_n>=2) _dp[2]=2;
        // 如果序列长度大于等于3,3是第三个丑数
        if(_n>=3) _dp[3]=3;
        // 初始化2的倍数、3的倍数和5的倍数的当前索引
        _cur2=2;
        _cur3=2;
        _cur5=1;
    }

    // dfs函数,用于递归计算丑数,但在这段代码中未使用
    void dfs(int _i){
        
    }

    // 主函数,接收一个整数n,返回第n个丑数
    int nthUglyNumber(int n) {
        _n=n; // 赋值n
        solveinit(); // 初始化

        // 从4开始到n,循环计算每个丑数
        for(int i=4;i<=n;i++){
            // 计算当前2的倍数、3的倍数和5的倍数的丑数
            _ll _nums2, _nums3, _nums5;
            _nums2=2*_dp[_cur2];
            _nums3=3*_dp[_cur3];
            _nums5=5*_dp[_cur5];
            // 比较这三个数,选择最小的作为下一个丑数
            if(_nums2<=_nums3 && _nums2<=_nums5){
                _dp[i]=_nums2;
                _cur2++; // 移动2的倍数的索引
            }
            if(_nums3<=_nums2 && _nums3<=_nums5){
                _dp[i]=_nums3;
                _cur3++; // 移动3的倍数的索引
            }
            if(_nums5<=_nums2 && _nums5<=_nums3){
                _dp[i]=_nums5;
                _cur5++; // 移动5的倍数的索引
            }
        }

        // 返回第n个丑数
        return _dp[_n];
    }
};

32. 最长有效括号

给你一个只包含 '('')' 的字符串,找出最长有效(格式正确且连续)括号

子串

的长度。

示例 1:

输入:s = "(()" 输出:2 解释:最长有效括号子串是 "()"

示例 2:

输入:s = ")()())" 输出:4 解释:最长有效括号子串是 "()()"

示例 3:

输入:s = "" 输出:0

提示:

  • 0 <= s.length <= 3 * 10(4)

  • s[i]'('')'

记忆化递归(动态规划递归形式)

1.

用记忆化递归填写dp表.

定义递归表示以i位置结尾的最长括号子串.

利用记忆化递归填写dp表.

 
class Solution {
public:
    // 定义长长整型别名
    using __ll = long long;
    // 存储输入的字符串
    string __s;
    // 动态规划数组,用于存储子问题的解
    vector<__ll> __memory;
    // 存储字符串的长度
    __ll __n;
    // 初始化函数,准备动态规划
    void solveinit() {
        __n = __s.size(); // 赋值字符串长度
        __memory.clear(); // 清空记忆数组
        __memory.resize(__n + 1, -1); // 初始化记忆数组,将所有值设为-1表示未计算
    }
    // 递归函数,用于计算最长有效括号子串的长度
    __ll dfs(int __i) {
        if (__i < 0) // 如果索引小于0,说明超出字符串开头,返回0
            return 0;
        if (__memory[__i] != -1) // 如果当前位置的解已经计算过,直接返回
            return __memory[__i];

        if (__s[__i] == '(') {
            __memory[__i] = 0; // 如果当前字符是'(',不能形成有效括号,长度为0
            return __memory[__i];
        }

        if (__s[__i] == ')') {
            __ll nums = dfs(__i - 1); // 计算左边连续有效括号的长度
            if (__i - 1 - nums >= 0 && __s[__i - 1 - nums] == '(') {
                // 如果左边连续有效括号的外面是一个'(',则可以形成更长的有效括号
                __memory[__i] = nums + 2 + dfs(__i - 1 - nums - 1);
                return __memory[__i];
            }
        }
        return 0; // 如果当前字符是')',但无法形成有效括号,返回0
    }
    // 主函数,接收一个字符串s,返回最长有效括号子串的长度
    int longestValidParentheses(string s) {
        __s = s; // 赋值字符串
        solveinit(); // 初始化

        __ll __ans = 0; // 初始化最长有效括号长度为0
        for (int __i = 0; __i <= __n - 1; __i++) {
            // 遍历字符串,使用dfs计算每个位置后面最长的有效括号长度,并更新最长长度
            __ans = max(__ans, dfs(__i));
        }

        return __ans; // 返回最长有效括号的长度
    }
};

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

  • 13
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

妖精七七_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值