【LeetCode每日一题】【2023/1/3】2042. 检查句子中的数字是否递增


2042. 检查句子中的数字是否递增

LeetCode: 2042. 检查句子中的数字是否递增

简单 \color{#00AF9B}{简单} 简单

句子是由若干 token 组成的一个列表,token 间用 单个 空格分隔,句子没有前导或尾随空格。每个 token 要么是一个由数字 0-9 组成的不含前导零的 正整数 ,要么是一个由小写英文字母组成的 单词

  • 示例,"a puppy has 2 eyes 4 legs" 是一个由 7 个 token 组成的句子:"2""4" 是数字,其他像 "puppy" 这样的 tokens 属于单词。

给你一个表示句子的字符串 s ,你需要检查 s 中的 全部 数字是否从左到右严格递增(即,除了最后一个数字,s 中的 每个 数字都严格小于它 右侧 的数字)。

如果满足题目要求,返回 true ,否则,返回 false

示例 1:

在这里插入图片描述

输入:s = "1 box has 3 blue 4 red 6 green and 12 yellow marbles"
输出:true
解释:句子中的数字是:1, 3, 4, 6, 12 。
这些数字是按从左到右严格递增的 1 < 3 < 4 < 6 < 12

示例 2:

输入:s = "hello world 5 x 5"
输出:false
解释:句子中的数字是:5, 5 。这些数字不是严格递增的。

示例 3:

在这里插入图片描述

输入:s = "sunset is at 7 51 pm overnight lows will be in the low 50 and 60 s"
输出:false
解释:s 中的数字是:7, 51, 50, 60 。这些数字不是严格递增的。

示例 4:

输入:s = "4 5 11 26"
输出:true
解释:s 中的数字是:4, 5, 11, 26 。
这些数字是按从左到右严格递增的:4 < 5 < 11 < 26

提示:

  • 3 <= s.length <= 200
  • s 由小写英文字母、空格和数字 09 组成(包含 09
  • s 中数字 token 的数目在 2100 之间(包含 2100
  • s 中的 token 之间由单个空格分隔
  • s 中至少有 两个 数字
  • s 中的每个数字都是一个 小于 100 数,且不含前导零
  • s 不含前导或尾随空格

方法1:直接遍历

直接遍历字符串。每次遇到 数字字符 时,记录该数值为 num ,并且继续往后遍历。如果后面紧跟 数字字符,则更新 num(将之前的 num 乘以10再加本次数字)。

此外,还需要记录最大值 biggest ,用于和每次遍历得到的 num 进行比较。若不符合 严格递增 的条件( num <= biggest ),则直接返回 false

#include <string>
#include <cctype>
using namespace std;

class Solution
{
public:
    bool areNumbersAscending(string s)
    {
        int biggest = 0;
        for (auto it = s.begin(); it != s.end();)
        {
            if (!std::isdigit(*it))
            {
                it++;
                continue;
            }

            int num = 0;
            while (it != s.end() && std::isdigit(*it))
            {
                num = 10 * num + *it - '0';
                it++;
            }
            if (num > biggest)
                biggest = num;
            else
                return false;
        }
        return true;
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)。其中,n 为字符串 s 的长度。在上面的写法中,即使有内外两层循环,但本质上还是对字符串 s一次 遍历。内循环是接着外循环遍历下去的,字符串中的每个字符都只被遍历 一次

  • 空间复杂度: O ( 1 ) O(1) O(1)。只用到了一些变量,占用常数空间。

参考结果

Accepted
98/98 cases passed (4 ms)
Your runtime beats 30.38 % of cpp submissions
Your memory usage beats 40.51 % of cpp submissions (6.2 MB)

写法2:按本题特有条件

实际上,本题给出的条件中,s 中的每个数字都是一个 小于 100 数。也就说明每个数最多只占 2 位。这样的话我们可以把上述代码中的循环去掉。每次遇到 数字字符 时,直接把 下一个 字符“拿进来”。如果下一个字符也是数字字符,就更新 num ;否则直接“扔掉”该字符。

#include <string>
#include <cctype>
using namespace std;

class Solution
{
public:
    bool areNumbersAscending(string s)
    {
        int biggest = 0;
        for (auto it = s.begin(); it != s.end();)
        {
            if (!std::isdigit(*it))
            {
                it++;
                continue;
            }

            int num = *it;
            it++;
            if (std::isdigit(*it))
            {
                num = 10 * num + *it - '0';
                it++;
            }

            if (num > biggest)
                biggest = num;
            else
                return false;
        }
        return true;
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)。其中,n 为字符串 s 的长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)。只用到了一些变量,占用常数空间。

参考结果

Accepted
98/98 cases passed (0 ms)
Your runtime beats 100.00 % of cpp submissions
Your memory usage beats 56.33 % of cpp submissions (6.1 MB)

方法2:栈

在遍历字符串的时候,我们假设一次遍历只能处理一个字符(例如外部设备很慢,一次只能给出一个字符之类)。当我们遇到一个数字字符时,是不知道它处在数的第几位的。例如在一个 空格 之后接收到一个 数字字符 1 ,它有可能就是 1 这个数,但是它也有可能是 十几一百多。因此我们要从前往后记录这些数,再从后往前把每个数乘上 10的i次方 再加起来。

首先,方法1 中的写法实际上也可以看做一个 隐式 。(当然,“隐式栈”一般指“调用栈”,这里只是个比喻。)

while (it != s.end() && std::isdigit(*it))
{
    num = 10 * num + *it - '0';
    it++;
}

num = 10 * num + *it - '0' 这一句我们可以看作把一个个 数字字符 记录在了一个 “栈” 里,这个 “栈” 本身是一个 整型变量 int,我们把每个 数字字符 都存到了这个变量里。

如果要写明一个 的实现的话,可以参考以下代码:

#include <string>
#include <stack>
using namespace std;

class Solution
{
public:
    bool areNumbersAscending(string s)
    {
        s.push_back(' ');

        stack<int> stk;
        int biggest = 0;

        for (const auto &c : s)
        {
            if ('0' <= c && c <= '9')
            {
                stk.emplace(c);
            }
            else
            {
                if (stk.empty())
                    continue;

                int num = 0;
                int multiple = 1;
                while (!stk.empty())
                {
                    num += stk.top() * multiple;
                    multiple *= 10;
                    stk.pop();
                }
                if (num > biggest)
                    biggest = num;
                else
                    return false;
            }
        }
        return true;
    }
};

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)。其中,n 为字符串 s 的长度。

  • 空间复杂度: O ( n ) O(n) O(n)。栈会占用 O ( n ) O(n) O(n)的空间。

参考结果

Accepted
98/98 cases passed (0 ms)
Your runtime beats 100.00 % of cpp submissions
Your memory usage beats 56.33 % of cpp submissions (6.1 MB)

同样地,对于本题每个数最多 2 位的条件,我们也可以把 简化为对一个长度为 2char 数组的维护:

#include <string>
#include <cmath>
#include <cstdint>
using namespace std;

class Solution
{
public:
    bool areNumbersAscending(string s)
    {
        s.push_back(' ');

        char nums[2]{};
        int index = 0;
        int biggest = 0;

        for (const auto &c : s)
        {
            if ('0' <= c && c <= '9')
            {
                nums[index] = c - '0';
                index++;
            }
            else
            {
                int num = 0;
                if (index == 1)
                    num = nums[0];
                else if (index == 2)
                    num = 10 * nums[0] + nums[1];
                else
                    continue;

                if (num > biggest)
                    biggest = num;
                else
                    return false;

                *(uint16_t *)nums = 0;
                index = 0;
            }
        }
        return true;
    }
};

*(uint16_t *)nums = 0 这一句中,由于 nums 是一个长度为 2char 类型数组,而一个 char 类型变量占 1 字节,因此 nums 总占 2 字节,也就是16位。这里把 nums 指针强制转换为一个 16 位的无符号整数指针,再设为 0 ( 0000H ),就是一次性把 nums 的两个元素都设为了 0

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n)。其中,n 为字符串 s 的长度。

  • 空间复杂度: O ( 1 ) O(1) O(1)。只用到了一些变量,占用常数空间。

参考结果

Accepted
98/98 cases passed (0 ms)
Your runtime beats 100.00 % of cpp submissions
Your memory usage beats 17.72 % of cpp submissions (6.3 MB)

方法3:std::stringstream

我们可以使用C++标准库中的 std::stringstream 字符串流类来实现。由于 stringstream>>运算符重载 在向外提取数据时,在空格处可以自动停止,因此对于我们划分 token 来说比较有帮助。同时,如果 >> 右边的参数是一个 数值类型 的变量,它还可以自动将字符串转换为对应的数,并赋值给这个数。

我们首先把字符串 s 中所有字符都插入字符串流中,然后定义一个 整型变量 来接收。当把非 数字字符 (非’0’~‘9’)传给一个数值类型变量时,stringstream 会在状态位中设置 failbit 。此时我们只需要清除状态位,然后调用 get() 成员函数“扔掉”这个字符就行,再继续遍历。

#include <string>
#include <sstream>
using namespace std;

class Solution
{
public:
    bool areNumbersAscending(string s)
    {
        stringstream ss{s};
        int biggest = 0;

        while (ss.good())
        {
            int num = 0;
            ss >> num;
            if (!ss.fail())
            {
                if (num > biggest)
                    biggest = num;
                else
                    return false;
            }
            else
            {
                ss.clear();
                ss.get();
            }
        }
        return true;
    }
};

复杂度分析

  • 时间复杂度:可能为 O ( n ) O(n) O(n)。其中,n 为字符串 s 的长度。

    • cppreference上并未给出 std::stringstream 的构造及个别操作的时间复杂度。
  • 空间复杂度:可能为 O ( n ) O(n) O(n)

    • cppreference上并未给出 std::stringstream 的构造及个别操作的时间复杂度。

参考结果

Accepted
98/98 cases passed (0 ms)
Your runtime beats 100.00 % of cpp submissions
Your memory usage beats 29.12 % of cpp submissions (6.2 MB)

写法2

我们可以换一种方式。上一种是将每个 token 提取到 整型变量 中,然后根据是否成功来继续操作。我们也可以把每个 token 提取到 字符串变量 中。根据题目给出的条件:

每个 token 要么是一个由数字 0-9 组成的不含前导零的 正整数 ,要么是一个由小写英文字母组成的 单词

我们可以判断每个 token 中的任一字符(如第一个字符),只要这个 字符数字字符 ,那么整个 token 都是由 数字字符 组成的。这时,我们调用 std::stoi 函数将字符串转化为 int 即可。

#include <string>
#include <sstream>
using namespace std;

class Solution
{
public:
    bool areNumbersAscending(string s)
    {
        stringstream ss{s};
        string &token = s;
        int biggest = 0;

        while (!ss.eof())
        {
            ss >> token;
            if (std::isdigit(token[0]))
            {
                const int num = std::stoi(s);
                if (num > biggest)
                    biggest = num;
                else
                    return false;
            }
        }
        return true;
    }
};

复杂度分析(同写法1)

  • 时间复杂度:可能为 O ( n ) O(n) O(n)。其中,n 为字符串 s 的长度。

    • cppreference上并未给出 std::stringstream 的构造及个别操作的时间复杂度。
  • 空间复杂度:可能为 O ( n ) O(n) O(n)

    • cppreference上并未给出 std::stringstream 的构造及个别操作的时间复杂度。

参考结果

Accepted
98/98 cases passed (0 ms)
Your runtime beats 100.00 % of cpp submissions
Your memory usage beats 77.22 % of cpp submissions (6.1 MB)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

亡心灵

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

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

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

打赏作者

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

抵扣说明:

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

余额充值