剑指Offer刷题记录_Day10

动态规划(中等)

Q1把数字翻译成字符串

给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。

我的第一思路是利用递归思想,将问题根据数字数组的长度和前两个数字来划分成子问题:

  • 当长度为0或者1时,递归函数直接返回1,因为此时不会有多余情况了
  • 当长度大于2时,要根据前两位数进行划分:
    1. 若第一个数字为0或者二位数大于25,则此时只有一种情况,即将第一个数字翻译成一个字母,返回后参数是 后n-1个数字串 可能的情况类型个数
    2. 若二位数<25 ,则可以有两种划分 ,即两个数字作为一个字母,或者一个对应一个字母

显然,递归算法会开辟更多的空间,但时间复杂度较好:

在这里插入图片描述

class Solution {
public:
    int translateNum(int num) {
 
      vector<int> nums;
      int n=0;
      while(num)
      {
         nums.insert(nums.begin(),num - (num/10)*10);
         num = num/10;
         n++;
      } 
      return countNum(nums,0,n);     
    }
    int countNum(vector<int> nums,int a , int n)
      { 
          if(n < 0) return 0;
          else if(n == 1 || n == 0) return 1;
          else {
            if(nums[a]*10+nums[a+1] > 25 || nums[a]==0 ) return  countNum(nums,a+1,n-1);
            else return   countNum(nums,a+1,n-1) + countNum(nums,a+2,n-2);
          };
      }
};

本题最好的方法还是动态规划的方法,主要思想如下:

  • dp[ i ] 表示以i为结尾的数字串的翻译类型个数

  • 转移方程: d p[ i ] = if (i 和 i-1 能组合翻译) dp[ i ] = dp[i-1] + dp[ i - 2]

    ​ else : dp[i] = dp [ i-1]

提升性能的两个tips:

  1. 由于整体顺序是一遍的遍历,因此可以不需要先将数转换为字符串或者一维数组,直接循环取余,自右向左动态规划
  2. 由于每次运算只涉及dp数组中的第 i i -1 i -2 的元素,因此也不需要开辟dp数组,直接用a b c变量进行代替即可
class Solution {
public:
    int translateNum(int num) {
        string str = to_string(num);
        int len = str.size();
        if(len < 2) return len;
        int a,b,c;             //代表dp i-2/i-1/i
        b = 1;
        a = 1;
        for(int i = 2;i <= len;i++){
            if(str[i-2] == '1' || (str[i-2] == '2' && str[i-1] <= '5')) c = a+b;
            else c = b;
        }
        return dp[len];
    }
};

Q2 最长不含重复字符的子字符串

从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

我的第一种思路,使用集合的数据结构,根据字符串进行递推,dp[ i ]表示以 i 为结尾的最长的不重复子字符串:

  • 当某一字符在当前字符集合已经存在,则清空集合,回溯,构造新的集合,计算dp值
  • 当某一字符不存在与当前集合,则加入集合,dp[ i ] = dp[ i-1 ] + 1

缺点:dp和集合 占用空间,回溯浪费大量时间

class Solution {
public:
    int lengthOfLongestSubstring(string s) {

        set<char> sett;
        int n = s.length();
        if(n <= 1) return n;
        int dp[n];
        dp[0] = 1;
        sett.insert(s[0]);
        int max = 1,j;
        for(int i=1;i<n;i++)
          {
             if(sett.count(s[i])) {
                 sett.clear();
                 for( j=i-1;j>=0;j--)
                 {
                     sett.insert(s[j]);
                     if(s[i] == s[j]) break;
                 }
                   dp[i] = i - j;               
             }else{
                 dp[i] = dp[i-1] + 1;
                 sett.insert(s[i]);
                 if(dp[i]>max) max = dp[i];
             }
          }     
        return max;
    }
};

根据题解,其实不需要使用集合,在第一种情况中,只需要向左找到第一个相同的元素求差即可,当然也可以用哈希表来存贮有关信息,减少了向左搜索的过程。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.size();
        if (len == 0) {
            return 0;
        }

        unordered_map<char, int> hash;          //存储每个字符 和 该字符最后出现的位置(每次遍历到相同的字符会更新下标)
        int max_len = 0, sub_len = 0;        
        for(int right=0;right<len;++right){
            int left = hash.find(s[right]) != hash.end() ? hash[s[right]] : -1;      

            hash[s[right]] = right;                      // 更新哈希表(遍历每个字符,都要更新该字符 最后在字符串s中出现的位置)
            sub_len = (sub_len < right - left) ? sub_len + 1 : right - left;         
            max_len = std::max(max_len, sub_len);        // max(dp[j - 1], dp[j])
        }

        return max_len;
    }
};




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值