【C++代码】罗马数字和阿拉伯数字互转,双指针完成盛最多水的容器,自动机实现字符串转换整数

题目:整数反转

  • 给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 $[−2^{31}, 2^{31} − 1] $,就返回 0。

  • 记 rev 为翻转后的数字,为完成翻转,我们可以重复「弹出」x 的末尾数字,将其「推入」rev 的末尾,直至 x 为 0。

    • class Solution {
      public:
          int reverse(int x) {
              int rev=0;
              while(x!=0){
                  if(rev<INT_MIN/10 || rev > INT_MAX/10){
                      return 0;
                  }
                  int dig=x%10;
                  x/=10;
                  rev = rev*10+dig;
              }
              return rev;
          }
      };
      
    • 时间复杂度:O(log⁡∣x∣)。翻转的次数即 x 十进制的位数。空间复杂度:O(1)。

题目:字符串转换整数 (atoi)

  • 请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。函数 myAtoi(string s) 的算法如下:

    • 读入字符串并丢弃无用的前导空格
    • 检查下一个字符(假设还未到字符末尾)为正还是负号,读取该字符(如果有)。 确定最终结果是负数还是正数。 如果两者都不存在,则假定结果为正。
    • 读入下一个字符,直到到达下一个非数字字符或到达输入的结尾。字符串的其余部分将被忽略。
    • 将前面步骤读入的这些数字转换为整数(即,“123” -> 123, “0032” -> 32)。如果没有读入数字,则整数为 0 。必要时更改符号(从步骤 2 开始)。
    • 如果整数数超过 32 位有符号整数范围 $[−2^{31}, 2^{31} − 1]$ ,需要截断这个整数,使其保持在这个范围内。具体来说,小于 −231 的整数应该被固定为 −231 ,大于 231 − 1 的整数应该被固定为 231 − 1
    • 返回整数作为最终结果。
  • class Solution {
          
          
    public:
        int myAtoi(string s) {
          
          
            unsigned long len = s.length();
            int index = 0;
            while(index<len){
          
          
                if(s[index]!=' '){
          
          
                    break;
                }
                index++;
            }
            if(index==len){
          
          
                return 0;
            }
            int sign=1;
            if(s[index]=='+'){
          
          
                index++;
            }else if(s[index]=='-'){
          
          
                sign = -1;
                index++;
            }
            int res=0;
            while(index<len){
          
          
                char temp=s[index];
                if(temp<'0' || temp>'9'){
          
          
                    break;
                }
                if(res>INT_MAX/10 ||(res==INT_MAX/10 && (temp-'0')>INT_MAX%10)){
          
          
                    return INT_MAX;
                }
                if(res<INT_MIN/10 || (res==INT_MIN/10 && (temp-'0')>-(INT_MIN%10))){
          
          
                    return INT_MIN;
                }
                res = res*10+sign*(temp-'0');
                index++;
            }
            return res;
        }
    };
    
  • 时间复杂度:O(N),这里 N 为字符串的长度;空间复杂度:O(1)。

  • 字符串处理的题目往往涉及复杂的流程以及条件情况,如果直接上手写程序,一不小心就会写出极其臃肿的代码。因此,为了有条理地分析每个输入字符的处理方法,我们可以使用自动机这个概念:我们的程序在每个时刻有一个状态 s,每次从序列中输入一个字符 c,并根据字符 c 转移到下一个状态 s。这样,我们只需要建立一个覆盖所有情况的从 s 与 c 映射到 s 的表格即可解决题目中的问题。本题可以建立如下图所示的自动机:

    • 在这里插入图片描述

    • 另外自动机也需要记录当前已经输入的数字,只要在 s'in_number 时,更新我们输入的数字,即可最终得到输入的数字。

    • class A{
          string state="start";
          unordered_map<string,vector<string>> table={
              {"start",{"start","signed","in_number","end"}},
              {"signed",{"end","end","in_number","end"}},
              {"in_number",{"end","end","in_number","end"}},
              {"end",{"end","end","end","end"}}
          };
          int get_c(char c){
              if(isspace(c)){
                  return 0;
              }
              if(c=='+' || c=='-'){
                  return 1;
              }
              if(isdigit(c)){
                  return 2;
              }
              return 3;
          }
          public:
              int sign=1;
              long long res=0;
              void get(char c){
                  state=table[state][get_c(c)];
                  if(state=="in_number"){
                      res=res*10+c-'0';
                      res=sign==1?min(res,(long long) INT_MAX):min(res,-(long long)INT_MIN);
                  }else if(state == "signed"){
                      sign=c=='+'?1:-1;
                  }
              }
      };
      class Solution {
      public:
          int myAtoi(string s) {
              A tempA;
              for(char c:s){
                  tempA.get(c);
              }
              return tempA.sign*tempA.res;
          }
      };
      
    • 时间复杂度:O(n),其中 n 为字符串的长度。我们只需要依次处理所有的字符,处理每个字符需要的时间为 O(1)。空间复杂度:O(1)。自动机的状态只需要常数空间存储。

题目:回文数

  • 给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

  • class Solution {
    public:
        bool isPalindrome(int x) {
            string temp =to_string(x);
            int left=0,right=temp.size()-1;
            bool flag=true;
            while(left<right){
                if(temp[left]==temp[right]){
                    left++;
                    right--;
                }else{
                    flag=false;
                    break;
                }
            }
            return flag;
        }
    };
    
  • 所有负数都不可能是回文,例如:-123 不是回文,因为 - 不等于 3。所以我们可以对所有负数返回 false。除了 0 以外,所有个位是 0 的数字不可能是回文,因为最高位不等于 0。所以我们可以对所有大于 0 且个位是 0 的数字返回 false。对于数字 1221,如果执行 1221 % 10,我们将得到最后一位数字 1,要得到倒数第二位数字,我们可以先通过除以 10 把最后一位数字从 1221 中移除,1221 / 10 = 122,再求出上一步结果除以 10 的余数,122 % 10 = 2,就可以得到倒数第二位数字。如果我们把最后一位数字乘以 10,再加上倒数第二位数字,1 * 10 + 2 = 12,就得到了我们想要的反转后的数字。如果继续这个过程,我们将得到更多位数的反转数字。由于整个过程我们不断将原始数字除以 10,然后给反转后的数字乘上 10,所以,当原始数字小于或等于反转后的数字时,就意味着我们已经处理了一半位数的数字了

    扫描二维码关注公众号,回复: 17171168 查看本文章
  • class Solution {
    public:
        bool isPalindrome(int x) {
            if(x<0||(x%10 ==0 && x!=0)){
                return false;
            }
            int revert=0;
            while(x>revert){
                revert = revert*10 +x%10;
                x/=10;
            }
            return x==revert ||x==revert/10;
        }
    };
    
  • 时间复杂度:O(log⁡n),对于每次迭代,我们会将输入除以 10,因此时间复杂度为 O(log⁡n)。

题目:正则表达式匹配

  • 给你一个字符串 s 和一个字符规律 p,请你来实现一个支持 '.''*' 的正则表达式匹配。‘.’ 匹配任意单个字符;‘*’ 匹配零个或多个前面的那一个元素。所谓匹配,是要涵盖 整个 字符串 s的,而不是部分字符串。

  • 题目中的匹配是一个「逐步匹配」的过程:我们每次从字符串 p 中取出一个字符或者「字符 + 星号」的组合,并在 s 中进行匹配。对于 p 中一个字符而言,它只能在 s 中匹配一个字符,匹配的方法具有唯一性;而对于 p 中字符 + 星号的组合而言,它可以在 s 中匹配任意自然数个字符,并不具有唯一性。因此我们可以考虑使用动态规划,对匹配的方案进行枚举。

  • 我们用 f[i] [j]表示 s 的前 i 个字符与 p 中的前 j 个字符是否能够匹配。在进行状态转移时,我们考虑 p 的第 j 个字符的匹配情况:

    • 如果 p 的第 j 个字符是一个小写字母,那么我们必须在 s 中匹配一个相同的小写字母,即

    • f [ i ] [ j ] = f [ i − 1 ] [ j − 1 ] , i f   s [ i ] = s [ j ]   e l s e   f a l s e f[i][j]=f[i-1][j-1],if~s[i]=s[j]~else~false f[i][j]=f[i1][j1],if s[i]=s[j] else false

    • 也就是说,如果 s 的第 i 个字符与 p 的第 j 个字符不相同,那么无法进行匹配;否则我们可以匹配两个字符串的最后一个字符,完整的匹配结果取决于两个字符串前面的部分。

    • 如果 p 的第 j 个字符是 *,那么就表示我们可以对 p 的第 j−1 个字符匹配任意自然数次。在匹配 0 次的情况下,我们有

    • f [ i ] [ j ] = f [ i ] [ j − 2 ] f[i][j]=f[i][j-2] f[i][j]=f[i][j2]

    • 也就是我们「浪费」了一个字符 + 星号的组合,没有匹配任何 s 中的字符。

  • class Solution {
    public:
        bool isMatch(string s, string p) {
            int m=s.size();
            int n=p.size();
            auto matchs=[&](int i,int j){
                if(i==0){
                    return false;
                }
                if(p[j-1]=='.'){
                    return true;
                }
                return s[i-1]==p[j-1];
            };
            vector<vector<int>> dp(m+1,vector<int>(n+1));
            dp[0][0]=true;
            for(int i=0;i<=m;i++){
                for(int j=1;j<=n;j++){
                    if(p[j-1]=='*'){
                        dp[i][j] |= dp[i][j-2];
                        if(matchs(i,j-1)){
                            dp[i][j] |= dp[i-1][j];
                        }
                    }else{
                        if(matchs(i,j)){
                            dp[i][j] |= dp[i-1][j-1];
                        }
                    }
                }
            }
            return dp[m][n];
        }
    };
    
  • 已知 dp[i-1] [j-1] 意思就是前面子串都匹配上了,不知道新的一位的情况。 那就分情况考虑,所以对于新的一位 p[j] s[i] 的值不同,要分情况讨论:

    • 考虑最简单的 p[j] == s[i] : dp[i][j] = dp[i-1][j-1], 然后从 p[j] 可能的情况来考虑,让 p[j]=各种能等于的东西
    • p[j] == "." : dp[i][j] = dp[i-1][j-1]
    • p[j] ==" * ":
      • 明白 * 的含义是 匹配零个或多个前面的那一个元素,所以要考虑他前面的元素 p[j-1]。* 跟着他前一个字符走,前一个能匹配上 s[i],* 才能有用,前一个都不能匹配上 s[i],* 也无能为力,只能让前一个字符消失,也就是匹配 0 次前一个字符。 所以按照 p[j-1] 和 s[i] 是否相等,我们分为两种情况:
        • p[j-1] != s[i] : dp[i][j] = dp[i][j-2],比如(ab, abc * )。遇到 * 往前看两个,发现前面 s[i] 的 ab 对 p[j-2] 的 ab 能匹配,虽然后面是 c*,但是可以看做匹配 0 次 c,相当于直接去掉 c ,所以也是 True。注意 (ab, abc*) 是 False。
        • p[j-1] == s[i] or p[j-1] == ".":* 前面那个字符,能匹配 s[i],或者 * 前面那个字符是万能的 .因为 . * 就相当于 . .,那就只要看前面可不可以匹配就行。
  • 如果 p.charAt(j) == s.charAt(i) : dp[i] [j] = dp[i-1] [j-1];

  • 如果 p.charAt(j) == ‘.’ : dp[i] [j] = dp[i-1] [j-1];

  • 如果 p.charAt(j) == ‘*’:

    • 如果 p.charAt(j-1) != s.charAt(i) : dp[i] [j] = dp[i] [j-2] //in this case, a* only counts as empty
    • 如果 p.charAt(i-1) == s.charAt(i) or p.charAt(i-1) == ‘.’:
      • dp[i] [j] = dp[i-1] [j] //in this case, a* counts as multiple a
      • or dp[i] [j] = dp[i] [j-1] // in this case, a* counts as single a
      • or dp[i] [j] = dp[i] [j-2] // in this case, a* counts as empty

题目:盛最多水的容器

  • 给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0)(i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。**说明:**你不能倾斜容器。

  • 设两指针 i , j ,指向的水槽板高度分别为 h[i] , h[j] ,此状态下水槽面积为 S(i,j) 。由于可容纳水的高度由两板中的 短板 决定,因此可得如下 面积公式 :

    • S ( i , j ) = m i n ( h [ i ] , h [ j ] ) ∗ ( j − i ) S(i,j)=min(h[i],h[j])*(j-i) S(i,j)=min(h[i],h[j])(ji)
  • 在每个状态下,无论长板或短板向中间收窄一格,都会导致水槽 底边宽度 −1-1−1 变短:

    • 若向内 移动短板 ,水槽的短板 min(h[i],h[j]) 可能变大,因此下个水槽的面积 可能增大 。
    • 若向内 移动长板 ,水槽的短板 min(h[i],h[j]) 不变或变小,因此下个水槽的面积 一定变小 。
  • 因此,初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出;即可获得最大面积。

    • class Solution {
      public:
          int maxArea(vector<int>& height) {
              int left=0,right=height.size()-1;
              int max_res=0;
              while(left<right){
                  int temp = (right-left)*min(height[left],height[right]);
                  if(temp>max_res){
                      max_res=temp;
                  }
                  if(height[left]<height[right]){
                      left++;
                  }else{
                      right--;
                  }
              }
              return max_res;
          }
      };
      
    • 时间复杂度:O(N),双指针总计最多遍历整个数组一次。空间复杂度:O(1),只需要额外的常数级别的空间。

题目:整数转罗马数字

  • 罗马数字包含以下七种字符: IVXLCDM

    • 字符          数值
      I             1
      V             5
      X             10
      L             50
      C             100
      D             500
      M             1000
      
    • 例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

      • I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
      • X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
      • C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
  • 罗马数字由 7 个不同的单字母符号组成,每个符号对应一个具体的数值。此外,减法规则(如问题描述中所述)给出了额外的 6 个复合符号。这给了我们总共 13 个独特的符号(每个符号由 1 个或 2 个字母组成),如下图所示。

    • 在这里插入图片描述
  • 根据罗马数字的唯一表示法,为了表示一个给定的整数 num,我们寻找不超过 num 的最大符号值,将 num 减去该符号值,然后继续寻找不超过 num 的最大符号值,将该符号拼接在上一个找到的符号之后,循环直至 num 为 000。最后得到的字符串即为 num 的罗马数字表示。

  • 编程时,可以建立一个数值-符号对的列表 valueSymbols,按数值从大到小排列。遍历 valueSymbols 中的每个数值-符号对,若当前数值 value 不超过 num,则从 num 中不断减去 value,直至 num\textit{num}num 小于 value,然后遍历下一个数值-符号对。若遍历中 num 为 0 则跳出循环。

  • const pair<int, string> temp[] = {
        {1000, "M"},
        {900,  "CM"},
        {500,  "D"},
        {400,  "CD"},
        {100,  "C"},
        {90,   "XC"},
        {50,   "L"},
        {40,   "XL"},
        {10,   "X"},
        {9,    "IX"},
        {5,    "V"},
        {4,    "IV"},
        {1,    "I"},
    };
    class Solution {
    public:
        // const pair<int,string> temp[]={
        //     {1000,"M"},
        //     {900,"CM"},
        //     {500,"D"},
        //     {400,"CD"},
        //     {100,"C"},
        //     {90,"XC"},
        //     {50,"L"},
        //     {40,"XL"},
        //     {10,"X"},
        //     {9,"IX"},
        //     {5,"V"},
        //     {4,"IV"},
        //     {1,"I"},
        // }; 
        string intToRoman(int num) {
            string res;
            for(const auto &[val,sym]:temp){
                while(num>=val){
                    num -= val;
                    res += sym;
                }
                if(num==0){
                    break;
                }
            }
            return res;
        }
    };
    
  • 问题是数组范围不能从类内初始化程序中自动推导出来。【error: array bound cannot be deduced from an in-class initializer】。类内初始化器可以被构造函数的成员初始化器列表覆盖。那么,如果构造函数选择用其他东西初始化数组怎么办?在您的情况下,可以使用大括号括起来的初始化程序列表覆盖初始化程序。

  • 时间复杂度:O(1)。由于 valueSymbols 长度是固定的,且这 13 字符中的每个字符的出现次数均不会超过 3,因此循环次数有一个确定的上限。对于本题给出的数据范围,循环次数不会超过 15 次。空间复杂度:O(1)。

题目:罗马数字转整数

  • 罗马数字包含以下七种字符: IVXLCDM。给定一个罗马数字,将其转换成整数。

    • 字符          数值
      I             1
      V             5
      X             10
      L             50
      C             100
      D             500
      M             1000
      
    • 例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II27 写做 XXVII, 即为 XX + V + II 。通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

      • I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
      • X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
      • C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
  • 通常情况下,罗马数字中小的数字在大的数字的右边。若输入的字符串满足该情况,那么可以将每个字符视作一个单独的值,累加每个字符对应的数值即可。若存在小的数字在大的数字的左边的情况,根据规则需要减去小的数字。对于这种情况,我们也可以将每个字符视作一个单独的值,若一个数字右侧的数字比它大,则将该数字的符号取反。

  • class Solution {
          
          
    public:
        unordered_map<char,int> temp={
          
          
            {
          
          'I',1},
            {
          
          'V',5},
            {
          
          'X',10},
            {
          
          'L',50},
            {
          
          'C',100},
            {
          
          'D',500},
            {
          
          'M',1000}
        };
        int romanToInt(string s) {
          
          
            int res=0;
            int len_s=s.length();
            for(int i=0;i<len_s;i++){
          
          
                int val=temp[s[i]];
                if(i<len_s-1 && val<temp[s[i+1]]){
          
          
                    res -= val;
                }else{
          
          
                    res += val;
                }
            }
            return res;
        }
    };
    
  • 时间复杂度:O(n),其中 n 是字符串 s 的长度。空间复杂度:O(1)。

猜你喜欢

转载自blog.csdn.net/weixin_43424450/article/details/134391210