LeetCode 91. Decode Ways

本文深入探讨了使用动态规划解决字符串编码问题的方法。通过详细解释两种解码方式,即从前向后和从后向前,作者展示了如何通过动态规划函数dp来计算解码方法的数量。同时,文章提供了C++代码实现,清晰地展示了如何通过遍历字符串并考虑字符之间的组合来求解。此外,还介绍了动态规划方法在实际应用中的优势和注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

There are two ways to solve this problem. One is to solve it front-back, the other is back-front.

I currently only wrote the back-front method:

Suppose we have such a string:    a0, a1, a2.... ai, ai+1, ai+2...an-1

Suppose we have get that from an-1, an-2...ai+2, the ways of decoding such a sequence is dp[i+2]

There are three conditions to consider:

1: if ai == 1, we dont need to care about ai+1, the ways to decode is 1+ dp[i+2];

2: if ai == 2, ai+1 should be smaller than '6', the ways to decode is 1 + dp[i+2].

3: if ai == 2, ai+1 greater than '6', the ways to decode is dp[i+1].

Think that, decode a individual number is 1, but 0 has no way to decode, thus, 0.

So, we can have the dynamic function:  dp[i] = dp[i] + dp[i+2] if(s[i] == 1 || s[i] == 2 && s[i + 1] < '6')

#include <string>
#include <vector>
#include <iostream>
using namespace std;

/*
  A message containing letters from A-Z is being encoded to numbers using the following mapping:
  'A' --> 1
  'B' --> 2
  ....
  'Z' --> 26
  Given an encoded message containing digits, determine the total number of ways to decode it.
  For example:
  Given encoded message "12", it could be decoded as "AB" or "L". 
  The number of ways decoding "12" is 2.
*/
/*
  Analyze: 
  Several example will make this question clear.
  "102" --> "10", "2" -> total method is 1
  "12" --> {"1", "2"}, "12" --> total method is 2.
  "127" --> {"1", "2", "7"}, {"12", "7"} --> total method is 2
  "126" --> {"1", "2", "6"}, {"12", "6"}, {"1", "26"}--> total method is 3.
  There are several cases need special attentions.
  1: "1"/"2" + "0", the char "0" can be interpreted as a decode way.
  2: "2" + "7, 8, 9" can only be interpreted as one way.
*/

int numDecodings(string s) {
  int n = s.size();
  vector<int> dp(n+2, 1);
  for(int i = s.size() - 1; i >= 0; --i) {
    if(s[i] == 0) dp[i] = 0;
    else dp[i] = dp[i + 1];

    if(i + 1 < s.size() && (s[i] == '1' || (s[i] == '2' && s[i+1] <= '6'))) {
      dp[i] += dp[i+2];
    }
  }
  return dp[0];
}



int main(void) {
  cout << "123" << " decode ways: "<< numDecodings("123") << endl;
  cout << "102" << " decode ways: "<< numDecodings("102") << endl;
  cout << "127" << " decode ways: "<< numDecodings("127") << endl;
}


To visualize the problem.... Everytime, we can decode two chars or one chars.


'0' is a special case to case, there are two cases that we might encounter '0'.  However, no matter where we have 0, the corresponding decode way will 0.

#include <string>
#include <vector>
#include <iostream>
using namespace std;
// decode it from back to front.
int decodeWays(string str, int len) {
  if(len == 0 || len == 1) return 1;
  int num = 0;
  if(str[len - 1] > '0') // this line should be commented out! for the case "10000000" == 1
    num = decodeWays(str.substr(0, len - 1), len - 1);
  if(str[len - 2] == '1' || ((str[len - 2] == '2') && str[len - 1] < '6'))
    num += decodeWays(str.substr(0, len - 2), len - 2);
  return num;
}

// dp
int decodeWaysII(string str) {
  int n = str.size();
  vector<int> dp(n + 1, 0);
  dp[0] = dp[1] = 1;
  for(int i = 2; i <= n; ++i) {
    if(str[i - 1] > '0') {
      dp[i] = dp[i-1];
    }
    if(str[i-2] == '1' || (str[i-2] == '2' && str[i-1] < '6'))
      dp[i] += dp[i-2];
  }
  return dp[n];
}

// to simplify more
int decodeWaysIII(string str) {
  int n = str.size();
  if(n <= 1) return 1;
  int prev = 1;
  int curr = 1;
  int sum = 0;
  for(int i = 2; i <= n; ++i) {
    if(str[i-1] == '0') prev = 0;
    if(str[i-2] != '1' && !(str[i-2] == '2' && str[i-1] < '6'))
      curr = 0;
    sum = curr + prev;
    prev = curr;
    curr = sum;
  }
  return curr;
} 


Another ways is more cool! and space efficient!

// In-place ways
int decodeWays(string str) {
  if(str[0] == '0') return 0;
  int prev = 0;
  int curr = 1;
  for(int i = 1; i <= str.size(); ++i) {
    if(s[i-1] == '0') curr = 0;
    if(i < 2 || !(s[i-2] == '1' || (s[i-2] == '2' && s[i-1] <= '6')))
      prev = 0;

    int tmp = curr;
    curr = curr + prev;
    prev = tmp;
  }
  return curr;
}

reference: http://www.acmerblog.com/leetcode-solution-decode-ways-6209.html

Another variation: Asked by Amazon,  return all the decoded strings, better to use backtracking, the time complexity is T(n) = T(n-1) + T(n-2) Thus it is 2^N

#include <iostream>
#include <unordered_map>
#include <vector>
#include <string>
using namespace std;

void decodeWays(string str, int pos, string path, vector<string>& res, unordered_map<string, char>& dict) {
  if(pos > str.size()) return;
  if(pos == str.size()) {
    res.push_back(path);
    return;
  }
  for(int i = 1; i <= 2; ++i) {
    string tmp = str.substr(pos, i);
    if(dict.count(tmp)) {
      decodeWays(str, pos + i, path + dict[tmp], res, dict);
    }
  }
}

vector<string> decodeWays(int input, unordered_map<string, char>& dict) {
  string str = to_string(input);
  if(str.size() == 0) return {};
  vector<string> res;
  string path = "";
  decodeWays(str, 0, path, res, dict);
  return res;
}
int main(void) {
  unordered_map<string, char> dict {
    {"1", 'A'},
    {"2", 'B'},
    {"3", 'C'},
    {"4", 'D'},
    {"5", 'E'},
    {"6", 'F'},
    {"7", 'G'},
    {"8", 'H'},
    {"9", 'I'},
    {"10", 'J'},
    {"11", 'K'},
    {"12", 'M'},
    {"13", 'N'},
    {"14", 'O'},
    {"15", 'P'},
    {"16", 'Q'},
    {"17", 'R'},
    {"18", 'S'},
    {"19", 'T'},
    {"20", 'U'},
    {"21", 'V'},
    {"22", 'W'},
    {"23", 'X'},
    {"24", 'Y'},
    {"25", 'Z'}};
  vector<string> res = decodeWays(123, dict);
  for(int i = 0; i < res.size(); ++i) cout << res[i] << endl;
}


LeetCode 91题是一道非常经典的动态规划问题,题目名为"解码方法"(Decode Ways)。给定一个只包数字的非空,求解有多少种解码方式具体来说给定的字符串由数字字符组成,可以解码成字母。规定字母'A'对应数字1,字母'B对应数字2,以此类推,字母'Z'对应数字26。现在要求计算出给定字符串的所有可能的解码方式数量。 解决这个问题的一种常见方法是使用动态规划。我们可以定义一个长度为n+1的数组dp,其中dp[i]表示字符串的前i个字符的解码方式数量。根据题目要求,我们需要考虑以下几种情况: 1. 如果当前字符为0,那么它不能单独解码,必须与前一个字符组合起来解码。如果前一个字符为1或2,那么可以将当前字符与前一个字符组合起来解码,即dp[i] = dp[i-2];否则,无法解码,返回0。 2. 如果当前字符不为0,那么它可以单独解码成一个字母,即dp[i] = dp[i-1]。同时,如果前一个字符和当前字符组合起来可以解码成一个字母(范围在1到26之间),那么也可以将前两个字符一起解码,即dp[i] += dp[i-2]。 最终,我们可以通过遍历字符串的每个字符,更新dp数组的值,最后返回dp[n]即可得到解码方式的数量。 下面是Java语言的示例代码: ```java public int numDecodings(String s) { int n = s.length(); int[] dp = new int[n + 1]; dp[0] = 1; dp[1] = s.charAt(0) == '0' ? 0 : 1; for (int i = 2; i <= n; i++) { int oneDigit = Integer.parseInt(s.substring(i - 1, i)); int twoDigits = Integer.parseInt(s.substring(i - 2, i)); if (oneDigit >= 1 && oneDigit <= 9) { dp[i] += dp[i - 1]; } if (twoDigits >= 10 && twoDigits <= 26) { dp[i] += dp[i - 2]; } } return dp[n]; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值