文章目录
- 2042. 检查句子中的数字是否递增
- 方法1:直接遍历
- 写法2:按本题特有条件
- 方法2:栈
- 方法3:std::stringstream
- 写法2
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
由小写英文字母、空格和数字0
到9
组成(包含0
和9
)s
中数字 token 的数目在2
和100
之间(包含2
和100
)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
位的条件,我们也可以把 栈 简化为对一个长度为 2
的 char
数组的维护:
#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
是一个长度为2
的char
类型数组,而一个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
的构造及个别操作的时间复杂度。
- cppreference上并未给出
-
空间复杂度:可能为 O ( n ) O(n) O(n)。
- cppreference上并未给出
std::stringstream
的构造及个别操作的时间复杂度。
- cppreference上并未给出
参考结果
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
的构造及个别操作的时间复杂度。
- cppreference上并未给出
-
空间复杂度:可能为 O ( n ) O(n) O(n)。
- cppreference上并未给出
std::stringstream
的构造及个别操作的时间复杂度。
- cppreference上并未给出
参考结果
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)