LeetCode 解码方法(递归、非递归、动态规划)

一条包含字母 A-Z 的消息通过以下方式进行了编码:
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
给定一个只包含数字的非空字符串,请计算解码方法的总数。
示例 1:

输入: "12"
输出: 2
解释: 它可以解码为 "AB"(1 2)或者 "L"(12)。

示例 2:

输入: "226"
输出: 3
解释: 它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

思路分析:一开始吧,我想着用递归进行解码,就是每次判断字符串的前两个字符,看它们能够组合成一个字母的序号,如果能则分两种情况进行解码(两个字符分开,两个字符合为一个),直到字符串为空。
方法一:递归法

class Solution {
public:
	int numDecodings(string &s) {
		if (s == ""){//如果s串为空了
			return 1;
		}
        if (s[0] == '0'){//首字符不能为零,因为零没有字母对应
            return 0;
        }
		else if (s.size() >= 2 && (s[0] - '0') * 10 + (s[1] - '0') <= 26){//如果前两个字母能合在一起
			string s1 = s.substr(1);//将第一个单独作为一个字母的编码
			string s2 = s.substr(2);//将前两个字符作为一个字母的编码
            if (s[1] != '0'){
                return numDecodings(s1) + numDecodings(s2);
            }
            else{
                return numDecodings(s2);
            }
			
		}
		else{//如果前两个字母不能合在一起,只能将首字符解码为一个字母的编码
			string s1 = s.substr(1);
			return numDecodings(s1);
		}
	}
};

LeetCode 解码方法(递归、非递归、动态规划)_第1张图片
尝试进行优化:不难发现,这个递归的算法中存在大量的求子串的语句,当测试字符串比较长时,将消耗大量的时间、空间。

			string s1 = s.substr(1);//将第一个单独作为一个字母的编码
			string s2 = s.substr(2);//将前两个字符作为一个字母的编码

下边将这部分去掉,取而代之的是下标标记法。

class Solution {
public:
	int numDecodings(string &s) {
       return myNumDecodings(s, 0);
	}
    //index表示的是正在解码s串的下标位置
    int myNumDecodings(string &s, int index){
        int strSize = s.size();
		if (index >= strSize){//如果已经解码完成
			return 1;
		}
        if (s[index] == '0'){//(s[index]不能为零,因为零没有字母对应
            return 0;
        }
		else if (index + 1 < strSize && (s[index] - '0') * 10 + (s[index + 1] - '0') <= 26){//如果index 、index + 1两个下标对应的数字能合在一起
            if (s[index] != '0'){
                return myNumDecodings(s, index + 1) + myNumDecodings(s, index + 2);
            }
            else{//第二个字符为0时,前两个字符必须合在一起
                return myNumDecodings(s, index + 2);
            }
			
		}
		else{//如果前两个字母不能合在一起,只能将首字符解码为一个字母的编码
			return myNumDecodings(s, index + 1);
		}
    }
};

LeetCode 解码方法(递归、非递归、动态规划)_第2张图片
递归算法的通病,当测试数据比较大的时候,时间复杂度(递归深度)巨增!
下面尝试将其改写为非递归算法。
方法二:利用stack的辅助,将方法一改写为非递归算法。

class Solution {
public:
	int numDecodings(string &s) {
		int strSize = s.size();
		int result = 0;//保存结果
		stack indexStack;//辅助栈,当遇到两中可解码的情况的时候,进行保存一种情况
		int nowIndex = 0;//正在解码的下标位置
		while (!indexStack.empty() || nowIndex <= strSize){
			if (nowIndex >= strSize || s[nowIndex] == '0'){//如果已经解码完成(此次解码成功),或者遇到了0(说明此次解码失败)
				if (nowIndex >= strSize){
					result += 1;
				}
				if (!indexStack.empty()){
					nowIndex = indexStack.top();//回到上一次的保存的解码现场
					indexStack.pop();
					continue;
				}
				else{
					break;
				}
			}
			if (nowIndex + 1 < strSize && (s[nowIndex] - '0') * 10 + (s[nowIndex + 1] - '0') <= 26){//如果index 、index + 1两个下标对应的数字能合在一起
				if (s[nowIndex + 1] != '0'){
					indexStack.push(nowIndex + 2);//保存解码两个的现场
					nowIndex += 1;//解码一个
				}
				else{//如果s[nowIndex + 1]是零,则两个必须合在一起解码
					nowIndex += 2;
				}
			}
			else {
				nowIndex += 1;//默认解码一个
			}
		}
		return result;
	}
};

LeetCode 解码方法(递归、非递归、动态规划)_第3张图片
有点懵,改成非递归算法,时间复杂度貌似更大了。。。
方法三:动态规划

class Solution {
public:
	int numDecodings(string &s) {
		int strSize = s.size();
		vector dp(strSize + 1, 0);//dp[i]表示解码的方法数
        if(s.size() == 0 || (s.size() == 1 && s[0] == '0')) {
            return 0;
        }
        if(s.size() == 1) {
            return 1;
        }
        dp[0] = 1;
        for(int i = 0; i < strSize; ++i){
            dp[i+1] = (s[i] == '0' ? 0 : dp[i]);//s[i] == '0' 说明此次解码失败,
            if(i > 0 && (s[i-1] == '1' || (s[i-1] == '2' && s[i] <= '6'))){
                dp[i+1] += dp[i-1];//加上一次解码两个字符
            }
        }
        return dp[strSize];
	}
};

LeetCode 解码方法(递归、非递归、动态规划)_第4张图片
动态规划真心快。
方法三的另外一种形式

int numDecodings(string s) {
    if (s.empty() || s[0] == '0') {
        return 0;
    }
    vector dp(s.size() + 1, 1);//全部初始化为1
    for (int i = 2; i < dp.size(); ++i){
        dp[i] = ((s[i-1] == '0') ? 0 : dp[i-1]);
        if (s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6')){
            dp[i] += dp[i-2];
        }
    }
    return dp.back();
}

根据评论区的提醒,这道题也是“爬楼梯”的另一种表述。
和爬楼梯思想完全一致。只考虑当前迈一步,当前迈两步。(映射到这里就是考虑当前位cur,还是考虑前一位prev)

int numDecodings(string s) {
    if (s.empty() || s[0] == '0') {
        return 0;
    }
    if (s.size() == 1){
        return 1;
    }
    int ans = 0, cur = 1, prev = 1;
    for (int i = 1; i < s.size(); ++i){
        ans = 0;
        if (s[i] != '0') {//(只考虑当前位)
            ans += cur;
        } //向前看一位
        if (s[i-1] == '1' || (s[i-1] == '2' && s[i] <= '6')){
            ans += prev;
        } 
        prev = cur;
        cur = ans;
    }
    return ans;
}

你可能感兴趣的:(LeetCode)