【题目描述】
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为 O ( n ) O(n) O(n) 并且只使用常数级别额外空间的解决方案。
【示例1】
输入:nums = [1,2,0]
输出:3
【示例2】
输入:nums = [3,4,-1,1]
输出:2
【示例3】
输入:nums = [7,8,9,11,12]
输出:1
【提示】
1 ≤ n u m s . l e n g t h ≤ 5 ∗ 1 0 5 1\le nums.length\le 5 * 10^5 1≤nums.length≤5∗105
− 2 31 ≤ n u m s [ i ] ≤ 2 31 − 1 -2^{31}\le nums[i]\le 2^{31} - 1 −231≤nums[i]≤231−1
【分析】
注意本题的时空复杂度,不能用排序以及哈希表之类的做法。
我们可以用置换的方法求解,我们记 n n n 为 n u m s nums nums 的长度,因此答案一定在 1 ∼ n + 1 1\sim n+1 1∼n+1 之间(因为最完美的情况是 n u m num num 中有 1 ∼ n 1\sim n 1∼n)。
我们利用两两交换的方法将 n u m num num 中在 1 ∼ n 1\sim n 1∼n 范围内的数放置到其对应的位置上,也就是 nums[i] = i + 1
。但其中有若干个位置上的数是错误的,每一个错误的位置就代表了一个缺失的正数。以 [3, 4, -1, 1]
为例,置换后的数组应当为 [1, -1, 3, 4]
,我们就可以知道缺失的数为 2 2 2。
注意在交换的过程中应判断两数是否相等防止死循环。
【代码】
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; i++)
// 将当前数置换到其对应的位置num[i]-1
while (nums[i] > 0 && nums[i] <= n && nums[i] != nums[nums[i] - 1])
swap(nums[i], nums[nums[i] - 1]);
for (int i = 0; i < n; i++)
if (nums[i] != i + 1) return i + 1; // 该位置上没有对应的正整数说明缺失了
return n + 1; // 1~n全部没有缺失
}
};
【题目描述】
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
【示例1】
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
【示例2】
输入:height = [4,2,0,3,2,5]
输出:9
【提示】
1 ≤ h e i g h t . l e n g t h ≤ 2 ∗ 1 0 4 1\le height.length\le 2 * 10^4 1≤height.length≤2∗104
0 ≤ h e i g h t [ i ] ≤ 1 0 5 0\le height[i]\le 10^5 0≤height[i]≤105
【分析】
本题需要用到单调栈的思想,先画出示意图:
我们开一个栈记为 stk
,每根柱子的编号已在图中显示,假设柱子的高度依次为:height = [6, 3, 0, 5, 4, 5, 7]
,算法流程如下:
0
入栈(当前栈:[0]
);1
小于栈顶元素,入栈(当前栈:[0, 1]
);2
小于栈顶元素,入栈(当前栈:[0, 1, 2]
);3
大于等于栈顶元素,将小于等于 3
的元素依次出栈,首先出栈 2
,若栈里还有元素,则 res += (3 - 1 - 1) * (min(height[1], height[3]) - height[2]) = 3
(红色区域),其中 1
为栈顶元素;然后出栈 1
,栈不为空,res += (3 - 1 - 0) * (min(height[0], height[3]) - height[1]) = 7
(黄色区域);栈顶元素 0
大于 3
,停止出栈,将 3
入栈(当前栈:[0, 3]
);4
小于栈顶元素,入栈(当前栈:[0, 3, 4]
);5
大于等于栈顶元素,将小于等于 5
的元素依次出栈,首先出栈 4
,栈不为空,res += (5 - 1 - 3) * (min(height[3], height[5]) - height[4]) = 8
(绿色区域);然后出栈 3
,栈不为空,res += (5 - 1 - 0) * (min(height[0], height[5]) - height[3]) = 8
(面积为0);栈顶元素 0
大于 5
,停止出栈,将 5
入栈(当前栈:[0, 5]
);6
大于等于栈顶元素,将小于等于 6
的元素依次出栈,首先出栈 5
,栈不为空,res += (6 - 1 - 0) * (min(height[0], height[6]) - height[5]) = 13
(蓝色区域);然后出栈 0
,栈为空,停止出栈,将 6
入栈(当前栈:[6]
),遍历结束。【代码】
class Solution {
public:
int trap(vector<int>& height) {
stack<int> stk;
int res = 0;
for (int i = 0; i < height.size(); i++)
{
if (stk.empty() || height[i] < height[stk.top()]) { stk.push(i); continue; }
while (stk.size() && height[i] >= height[stk.top()])
{
int t = stk.top(); stk.pop();
if (stk.size()) // 注意判断栈里是否还有元素
res += (i - 1 - stk.top()) * (min(height[stk.top()], height[i]) - height[t]);
}
stk.push(i);
}
return res;
}
};
【题目描述】
给定两个以字符串形式表示的非负整数 num1
和 num2
,返回 num1
和 num2
的乘积,它们的乘积也表示为字符串形式。
注意:不能使用任何内置的 BigInteger
库或直接将输入转换为整数。
【示例1】
输入: num1 = "2", num2 = "3"
输出: "6"
【示例2】
输入: num1 = "123", num2 = "456"
输出: "56088"
【提示】
1 ≤ n u m 1. l e n g t h , n u m 2. l e n g t h ≤ 200 1\le num1.length, num2.length\le 200 1≤num1.length,num2.length≤200
num1
和 num2
只能由数字组成
num1
和 num2
都不包含任何前导零,除了数字0本身
【分析】
使用高精度乘法的处理方式,假设两个数的低到高位的下标分别为 0 ∼ n 0\sim n 0∼n 与 0 ∼ m 0\sim m 0∼m,那么两个数的第 i i i 位与第 j j j 位相乘后的结果将会累加到第 i + j i+j i+j 位上。我们可以遍历两个数先算出每一位上的乘积之和,最后在从低位到高位统一处理一遍进位即可。
【代码】
class Solution {
public:
string multiply(string num1, string num2) {
int n = num1.size(), m = num2.size();
reverse(num1.begin(), num1.end()); // 字符串的最高位是数字的最低位
reverse(num2.begin(), num2.end());
vector<int> mul(n + m);
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
mul[i + j] += (num1[i] - '0') * (num2[j] - '0');
for (int i = 0, t = 0; i < n + m; i++) // 统一处理进位
{
mul[i] += t;
t = mul[i] / 10;
mul[i] %= 10;
}
int k = n + m - 1;
while (k && mul[k] == 0) k--; // 去掉结果的前导0
string res;
while (k >= 0) res += mul[k--] + '0';
return res;
}
};
【题目描述】
给你一个输入字符串(s
)和一个字符模式(p
),请你实现一个支持 '?'
和 '*'
匹配规则的通配符匹配:
'?'
可以匹配任何单个字符。'*'
可以匹配任意字符序列(包括空字符序列)。判定匹配成功的充要条件是:字符模式必须能够完全匹配输入字符串(而不是部分匹配)。
【示例1】
输入:s = "aa", p = "a"
输出:false
解释:"a" 无法匹配 "aa" 整个字符串。
【示例2】
输入:s = "aa", p = "*"
输出:true
解释:'*' 可以匹配任意字符串。
【示例3】
输入:s = "cb", p = "?a"
输出:false
解释:'?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。
【提示】
0 ≤ s . l e n g t h , p . l e n g t h ≤ 2000 0\le s.length, p.length\le 2000 0≤s.length,p.length≤2000
s
仅由小写英文字母组成
p
仅由小写英文字母、'?'
或 '*'
组成
【分析】
本题与第10题的区别在于这边的 *
可以匹配任意字符串,而第10题的 *
表示可以某个字符可以为任意长度。
同样的,我们来分析一下状态表示和状态计算:
bool
类型,表示是否存在一个合法方案。s[i] == p[j]
或者 p[j] == '?'
,且满足 s s s 的前 i − 1 i-1 i−1 个字符和 j j j 的前 j − 1 j-1 j−1 个字符也匹配,那么 s [ i ] s[i] s[i] 和 p [ j ] p[j] p[j] 匹配,即可以写出以下状态转移方程:f[i][j] = f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '?')
*
表示多少个字符,如果表示0个字符,则 s s s 的前 i i i 个字符和 j j j 的前 j − 1 j-1 j−1(此处与第10题有区别)个字符匹配;如果表示1个字符,则 s s s 的前 i − 1 i-1 i−1 个字符和 j j j 的前 j − 1 j-1 j−1 个字符匹配;如果表示2个字符,则 s s s 的前 i − 2 i-2 i−2 个字符和 j j j 的前 j − 1 j-1 j−1 个字符匹配。因此可以写出以下状态转移方程:f[i][j] = f[i][j - 1] || f[i - 1][j - 1] || f[i - 2][j - 1] || ...
f[i - 1][j]
的状态转移方程如下:f[i - 1][j] = f[i - 1][j - 1] || f[i - 2][j - 1] || ...
f[i][j] = f[i][j - 1] || f[i - 1][j]
本题的 f[0][j]
是有意义的,例如一堆 *
可以匹配空串;但是 f[i][0]
是没有意义的。
【代码】
class Solution {
public:
bool isMatch(string s, string p) {
int n = s.size(), m = p.size();
s = ' ' + s, p = ' ' + p;
vector<vector<bool>> f(n + 1, vector<bool>(m + 1));
f[0][0] = true;
for (int i = 0; i <= n; i++)
for (int j = 1; j <= m; j++)
if (p[j] != '*')
f[i][j] = i && f[i - 1][j - 1] && (s[i] == p[j] || p[j] == '?'); // 要特判i不为0防止越界
else
f[i][j] = f[i][j - 1] || i && f[i - 1][j]; // 同样也要特判i不为0
return f[n][m];
}
};
【题目描述】
给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向前跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
【示例1】
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
【示例2】
输入: nums = [2,3,0,1,4]
输出: 2
【提示】
1 ≤ n u m s . l e n g t h ≤ 1 0 4 1\le nums.length\le 10^4 1≤nums.length≤104
0 ≤ n u m s [ i ] ≤ 1000 0\le nums[i]\le 1000 0≤nums[i]≤1000
题目保证可以到达 nums[n - 1]
【分析】
令 f[i]
表示起点跳到 i i i 的最短步数,那么 f[i]
一定是单调递增的,且相邻差值一定不会超过1,假设 i < j i
因此我们可以找出 f[i] == x
的每一段,只需要在 f[i] == x - 1
的那一段中找出能够跳跃到的最远距离的点。可以枚举 i i i,由于 i i i 是单调递增的,如果 j j j 跳不到 i i i 那么更不可能跳到 i + 1 i+1 i+1,因此 j j j 也是单调的,只需要枚举一遍即可。
【代码】
class Solution {
public:
int jump(vector<int>& nums) {
vector<int> f(nums.size());
for (int i = 1, j = 0; i < nums.size(); i++) // f[0]=0,从1开始
{
while (j + nums[j] < i) j++; // 保证一定有解,因此肯定能从某个j跳到i
f[i] = f[j] + 1; // 从最左边的j跳到i
}
return f[nums.size() - 1];
}
};