题目目录
- Leetcode41. 缺失的第一个正数
- Leetcode42. 接雨水
- Leetcode43. 字符串相乘
- Leetcode44. 通配符匹配
- Leetcode45. 跳跃游戏 II
- Leetcode46. 全排列
- Leetcode47. 全排列 II
- Leetcode48. 旋转图像
- Leetcode49. 字母异位词分组
- Leetcode50. Pow(x, n)
- Leetcode51. N 皇后
- Leetcode52. N 皇后 II
- Leetcode53. 最大子数组和
- Leetcode54. 螺旋矩阵
- Leetcode55. 跳跃游戏
- Leetcode56. 合并区间
- Leetcode57. 插入区间
- Leetcode58. 最后一个单词的长度
- Leetcode59. 螺旋矩阵 II
- Leetcode60. 排列序列
Leetcode41. 缺失的第一个正数
思路: 要求找到缺失的最小整数。因为最大长度为int(nums.size())
,所以对于小于等于0以及大于这个最大长度的值,我们可以把他们全部设为int(nums.size())+1
,作为一个无效数值。
然后对于每个有效数值
n
u
m
[
i
]
num[i]
num[i]而言,数组中总有一个下标
j
=
n
u
m
[
i
]
−
1
j=num[i]-1
j=num[i]−1,那我们不妨把每个数字映射到这个下标所对应的数位上来。由于在前面,我们把所有的值都置为了整数,那我们可以将有映射值的下标
j
j
j上的数字
n
u
m
[
j
]
num[j]
num[j]置为负数,表示数组中有等于
j
+
1
j+1
j+1的数字。
最后查看第一个不为负数的下标,将其输出。
代码:
class Solution {
public:
int firstMissingPositive(vector<int>& nums) {
// 最大答案
int mx = int(nums.size()) + 1, tmp;
// 不合法的值置为最大答案
for(int & num : nums) if(num <= 0 || num > mx) num = mx;
// 遍历每个数
for(int & num : nums){
// tmp为当前数字(正数形式)
tmp = num < 0 ? -num : num;
// 如果合法且存在, 那么更改对应下标的数字
if(tmp < mx && nums[tmp - 1] > 0) nums[tmp - 1] *= -1;
}
// 查找第一个不满足的位置
for(int i = 0;i < nums.size();i++) if(nums[i] > 0) return i + 1;
return mx;
}
};
Leetcode42. 接雨水
思路: 前缀数组+后缀数组。对于每个位置而言,当前位置能接的水量取决于在这个位置左侧最高挡板高度,以及这个位置右侧最高挡板高的的最小值(短板效应)。那我们的目的就是找到这两个最大值,那就用一个前缀数组维护从头到第
i
i
i个位置的最大值,用一个后缀数组
a
f
t
[
i
]
aft[i]
aft[i]维护从尾部开始到头部的最大值,那么对于每个位置而言,这两个数求最小值就是对于当前位置的短板高度,而这个高度与当前位置高度差值就是能存水的值。
由于前缀仅需简单处理下最大值,这里就直接用单个数存储到当前位置的最大值。
代码:
class Solution {
public:
// 存放后缀最大值
int aft[20004];
int trap(vector<int>& height) {
// 初始化从后到第i位的最大值
for(int i = height.size() - 1;i >= 0;i--) aft[i] = max(aft[i + 1], height[i]);
// 答案, 前缀最大值
int ans = 0, pre = 0;
for(int i = 0;i < height.size();i++){
// 更新前缀最大值
pre = max(pre, height[i]);
ans += min(pre, aft[i]) - height[i];
}
return ans;
}
};
Leetcode43. 字符串相乘
思路: 模拟一个大数乘法。我们小学学过竖式乘法,那我们就是要模拟这个情况。由于数字正常情况下,从左到右是从高位到低位,那我们为了计算方便,我们将其翻转,使其从低位到高位。然后每次取num2最低位,从num1最低位开始一个一个乘过去,处理一下进位问题。
代码:
class Solution {
public:
string multiply(string num1, string num2) {
// 翻转,使其从低位到高位
std::reverse(num1.begin(), num1.end());
std::reverse(num2.begin(), num2.end());
// 记录答案
string ans = "";
for(int i = 0;i < num2.length();i++){
int tmp = 0;
for(int j = 0;j < num1.length();j++){
// 如果ans长度不够, 那往高位加0
if(ans.length() <= i + j) ans = ans + "0";
// 计算到当前位置的值, 值为进位值+乘法所得积+当前位置本来的值
tmp += (num2[i] - '0') * (num1[j] - '0') + ans[i + j] - '0';
// 获得当前位置变化成的值
ans[i + j] = (char)(tmp % 10 + '0');
// 获得进位值
tmp /= 10;
}
// 处理最后有进位值没加入答案的情况
if(tmp){
if(ans.length() <= i + num1.length()) ans = ans + "0";
ans[i + num1.length()] = (char)(tmp + '0');
}
}
std::reverse(ans.begin(), ans.end());
// 去除前导零
int l = 0;
while(l < ans.length() && ans[l] == '0') ++l;
// 全0的时候保留一个0
l = min(l, (int)ans.length() - 1);
return ans.substr(l, ans.length() - l);
}
};
Leetcode44. 通配符匹配
思路: dp。
我们设置一个dp数组,dp[i][j]
代表s.substr(0,i)
与p.substr(0,j)
匹配情况。然后对于每种s[i]和p[j]匹配情况分类讨论。
s[i]==p[j]
:
s中当前位置的字符与p中当前位置字符相同,相当于在s中不存在s[i]
,在p中不存在p[j]
的情况,则dp[i][j]=dp[i-1][j-1]
。p[j]=='?'
:
当前情况相当于上一种情况,即dp[i][j]=dp[i-1][j-1]
。p[j]=='*'
:
3.1 当前位置在s中应匹配的字符为空,则当前匹配情况与不存在p[j]
的时候相同,则dp[i][j]=dp[i][j-1]
。
3.2 匹配当前字符,相当于在s中不存在s[i]
,在p中不存在p[j]
的情况,则dp[i][j]=dp[i-1][j-1]
。
3.3 当前一个'*'
匹配多个字符的情况,相当于s中不存在s[i]
的情况,则dp[i][j]=dp[i-1][j]
。
需要特别判断当p字符串开头为‘*’的情况。对于p开头为‘*’的时候,两字符串匹配情况相当于删除任意个‘*’,所以当p[i - 1] == '*'
的时候dp[0][i] = true
。
代码:
class Solution {
public:
bool dp[2004][2004];
bool isMatch(string s, string p) {
// 两字符串都为空的时候为匹配
dp[0][0] = true;
// 处理p开头为*的情况
for(int i = 1;i <= p.length() && p[i - 1] == '*';i++) dp[0][i] = true;
for(int i = 0;i < s.length();i++){
for(int j = 0;j < p.length();j++){
// 两字符匹配与当前p为?的情况相同
if(s[i] == p[j] || p[j] == '?') dp[i + 1][j + 1] |= dp[i][j];
// 当前p为* 包含三种情况
else if(p[j] == '*') dp[i + 1][j + 1] |= dp[i][j] | dp[i + 1][j] | dp[i][j + 1];
}
}
return dp[s.length()][p.length()];
}
};
Leetcode45. 跳跃游戏 II
思路: 对于每一次移动而言,都有一段可以移动到的新区间,我们将这个新区间用[l,r]表示,那么在下一次移动中,新的区间为
[
m
a
x
(
m
i
n
(
i
+
n
u
m
s
[
i
]
)
,
r
+
1
)
,
m
a
x
(
i
+
n
u
m
s
[
i
]
)
]
,
l
≤
i
≤
r
[max(min(i+nums[i]), r+1),max(i+nums[i])], l \le i \le r
[max(min(i+nums[i]),r+1),max(i+nums[i])],l≤i≤r。那我们只要每次维护他的lr,在新移动的时候维护新的左右边界即可。时间复杂度
O
(
N
)
O(N)
O(N)。
代码:
class Solution {
public:
int jump(vector<int>& nums) {
// l区间左指针 r区间右指针 ans答案
int l = 0,r = 0, ans = 0, tmp;
while(r < nums.size() - 1){
tmp = r;
// 维护右指针新位置
for(int i = l;i <= r;i++) tmp = max(tmp, nums[i] + i);
// 更新左右指针
l = r + 1, r = tmp;
++ans;
}
return ans;
}
};
Leetcode46. 全排列
思路: C++ STL中有个函数next_permutation()
,他可以使容器内的值变成其下一个全排列并返回true
,否则返回false
。
代码:
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> ans;
// 先排序, 将当前容器内的值成为最小全排列
sort(nums.begin(), nums.end());
do{
ans.push_back(nums);
}while(next_permutation(nums.begin(), nums.end()));
return ans;
}
};
Leetcode47. 全排列 II
思路: 与上一题相同,因为next_permutation()
,他可以使容器内的值变成其下一个全排列,下一个全排列严格大于上一个,所以代码相同。
代码:
class Solution {
public:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> ans;
sort(nums.begin(), nums.end());
do{
ans.push_back(nums);
}while(next_permutation(nums.begin(), nums.end()));
return ans;
}
};
Leetcode48. 旋转图像
思路: 顺时针旋转 90 度相当于先水平翻转,然后沿副对角线翻转。
代码:
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size() - 1;
// 水平翻转
for(int i = 0;i <= n;i++){
for(int j = 0;(j << 1) < n;j++) swap(matrix[i][j], matrix[i][n - j]);
}
// 沿副对角线翻转
for(int i = 0;i <= n;i++){
for(int j = 0;j < n - i;j++) swap(matrix[i][j], matrix[n - j][n - i]);
}
}
};
Leetcode49. 字母异位词分组
思路: 哈希。对于每个字符串分组而言,根据他们所包含的每个字符个数分组。所以我们只需要将每个字符串26个字母分别多少个给拆分出来就能得到这个字符串的特征值。对于特征值相同性判断而言,我们可以将这个特征值进行哈希(就是把他看成一个26位的,HS进制的数字),哈希值相同的就是相同特征值的字符串。
代码:
class Solution {
public:
long long HS = 1331, mod = 1000000007;
int times[26], ans, cnt = 0;
map<int,int> mp;
// 存储答案
vector<vector<string>> result;
// 空Vector
vector<string> newVt;
vector<vector<string>> groupAnagrams(vector<string>& strs) {
for(int i = 0;i < strs.size();i++){
// 初始化每个字符出现次数
memset(times, 0, sizeof(times));
ans = 0;
// 计算每个字符出现次数
for(int j = 0;j < strs[i].length();j++) times[strs[i][j] - 'a']++;
// 计算出现次数哈希值
for(int j = 0;j < 26;j++) ans = (ans * HS % mod + times[j]) % mod;
// 如果没出现过这个哈希值, 那就新开一类
if(mp[ans] == 0) mp[ans] = ++cnt, result.push_back(newVt);
// 将字符串放到对应类别中
result[mp[ans] - 1].push_back(strs[i]);
}
return result;
}
};
Leetcode50. Pow(x, n)
思路: 浮点数快速幂。要判断阶数为负数、初始为0的情况。将n转为long long
,避免在计算过程中溢出。
代码:
class Solution {
public:
double myPow(double x, int n) {
if(x == 0) return 0;
double ans = 1;
int flag = 0;
long long m = n;
// 判断阶数为负的情况
if(m < 0) flag = 1, m = -m;
// 正常快速幂
while(m){
if(m & 1) ans = ans * x;
x *= x;
m >>= 1;
}
if(flag) ans = 1.0 / ans;
return ans;
}
};
Leetcode51. N 皇后
思路: dfs。对于每一行/列/主对角线/副对角线而言,必定有且只有一个Q。那我们只需要维护每一行/列/主对角线/副对角线的Q的个数,然后枚举每一行的Q的位置,并判断是否合法即可。
代码:
class Solution {
public:
vector<string> ans;
vector<vector<string>> result;
// 每一行/列/主对角线/副对角线是否有Q, 有为1
int row[10], col[10], dia1[19], dia2[19];
void dfs(int x,int n){
// 如果已经使每行有合法情况, 则把当前布局存储到答案
if(x >= n){
result.push_back(ans);
return;
}
for(int i = 0;i < n;i++){
// 判断当前位置是否合法
if(row[x] || col[i] || dia1[n-1-x+i] || dia2[x+i]) continue;
// 合法的时候将当前位置放置Q
ans[x][i] = 'Q';
// 更新所在行/列/主对角线/副对角线存在Q的情况
row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 1;
// 查找下一行
dfs(x + 1, n);
// 回溯
ans[x][i] = '.';
row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 0;
}
}
vector<vector<string>> solveNQueens(int n) {
string s = "";
// 初始化棋盘
for(int i = 0;i < n;i++) s = s + '.';
for(int i = 0;i < n;i++) ans.push_back(s);
dfs(0, n);
return result;
}
};
Leetcode52. N 皇后 II
思路: 与上一题一样,将存储答案的步骤改为result++
即可。
代码:
class Solution {
public:
int row[10], col[10], dia1[19], dia2[19], result;
vector<string> ans;
void dfs(int x,int n){
// 如果已经使每行有合法情况, 则把当前布局存储到答案
if(x >= n){
++result;
return;
}
for(int i = 0;i < n;i++){
// 判断当前位置是否合法
if(row[x] || col[i] || dia1[n-1-x+i] || dia2[x+i]) continue;
// 合法的时候将当前位置放置Q
ans[x][i] = 'Q';
// 更新所在行/列/主对角线/副对角线存在Q的情况
row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 1;
// 查找下一行
dfs(x + 1, n);
// 回溯
ans[x][i] = '.';
row[x] = col[i] = dia1[n-1-x+i] = dia2[x+i] = 0;
}
}
int totalNQueens(int n) {
string s = "";
// 初始化棋盘
for(int i = 0;i < n;i++) s = s + '.';
for(int i = 0;i < n;i++) ans.push_back(s);
dfs(0, n);
return result;
}
};
Leetcode53. 最大子数组和
思路:
代码:
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum = 0, mn = 0, ans = -1e9;
for(int i = 0;i < nums.size();i++){
sum += nums[i];
ans = max(ans, sum - mn);
mn = min(mn, sum);
}
return ans;
}
};
Leetcode54. 螺旋矩阵
思路: 一共四种前进方式,右下左上,每次运行的区域取决于已经走过的圈数。就只需要模拟当前前进情况即可。
代码:
class Solution {
public:
// 顺时针顺序, 右下左上
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> ans;
// di: 走哪个方向 circle: 第几圈
int di = 0, x = 0, y = 0, circle = 0, mxsize = matrix.size() * matrix[0].size(), row = matrix.size(), col = matrix[0].size();
for(int i = 0;i < mxsize;i++){
// 把当前答案存入
ans.push_back(matrix[x][y]);
// 判断各个方向碰到边界的条件
if(di == 0 && y + circle == col - 1) di++;
else if(di == 1 && x + circle == row - 1) di++;
else if(di == 2 && y == circle) di++;
// 如果是向上碰到边界还需要加当前圈数
else if(di == 3 && x == circle + 1) di = 0, circle++;
x += dir[di][0], y += dir[di][1];
}
return ans;
}
};
Leetcode55. 跳跃游戏
思路: 与45题一样,只不过多了nums[i]==0的情况。
代码:
class Solution {
public:
bool canJump(vector<int>& nums) {
// l区间左指针 r区间右指针
int l = 0,r = 0, tmp;
// 多判一个当前左区间指针在右区间指针右侧的情况
while(r < nums.size() && l <= r){
tmp = r;
for(int i = l;i <= r;i++) tmp = max(tmp, nums[i] + i);
l = r + 1, r = tmp;
}
// 如果右指针没碰到右边界, 说明到不了
if(r < nums.size() - 1) return false;
else return true;
}
};
Leetcode56. 合并区间
思路: 将各个区间按左区间从小到大排序。然后从左到右遍历所有区间。一共三种情况:
- 答案区间右指针处于遍历到的区间的左右指针区间,那么可以将两个区间合并
- 答案区间右指针大于遍历到的区间的右指针,那么忽略当前遍历到的区间
- 答案区间右指针小于遍历到的区间的左指针,那么将答案区间添加到答案,将遍历到的区间作为新的答案区间
代码:
class Solution {
public:
static bool cmpInMerge(vector<int> a, vector<int> b){
return a[0] < b[0];
}
vector<vector<int>> merge(vector<vector<int>>& intervals) {
// 排序
sort(intervals.begin(), intervals.end(), cmpInMerge);
vector<vector<int>> ans;
vector<int> tmp = intervals[0];
for(int i = 0;i < intervals.size();i++){
// 答案区间右指针处于遍历到的区间的左右指针区间
if(tmp[1] >= intervals[i][0] && tmp[1] <= intervals[i][1]) tmp[1] = intervals[i][1];
// 答案区间右指针小于遍历到的区间的左指针
else if(tmp[1] < intervals[i][0]) ans.push_back(tmp), tmp = intervals[i];
}
ans.push_back(tmp);
return ans;
}
};
Leetcode57. 插入区间
思路: 与上一题一样,将newInterval
插入intervals
就行。在for里面每次将newInterval
与答案区间比较可以将
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)复杂度降到
O
(
N
)
O(N)
O(N)(但我懒,没写)。
代码:
class Solution {
public:
static bool cmpInInsert(vector<int> a, vector<int> b){
return a[0] < b[0];
}
vector<vector<int>> insert(vector<vector<int>>& intervals, vector<int>& newInterval) {
intervals.push_back(newInterval);
sort(intervals.begin(), intervals.end(), cmpInInsert);
vector<vector<int>> ans;
vector<int> tmp = intervals[0];
for(int i = 0;i < intervals.size();i++){
if(tmp[1] >= intervals[i][0] && tmp[1] <= intervals[i][1]) tmp[1] = intervals[i][1];
else if(tmp[1] < intervals[i][0]) ans.push_back(tmp), tmp = intervals[i];
}
ans.push_back(tmp);
return ans;
}
};
Leetcode58. 最后一个单词的长度
思路: 先把结尾空格删完,然后统计出现空格前单词长度。
代码:
class Solution {
public:
int lengthOfLastWord(string s) {
int ans = 0, r = s.length() - 1;
while(r >= 0 && s[r] == ' ') --r;
while(r >= 0 && s[r] != ' ') --r, ++ans;
return ans;
}
};
Leetcode59. 螺旋矩阵 II
思路: 与54题一样,将54中输入数组用vector<vector<int>> ans
替代。每次将当前序号添加到ans中。
代码:
class Solution {
public:
int dir[4][2] = {0, 1, 1, 0, 0, -1, -1, 0};
vector<vector<int>> generateMatrix(int n) {
vector<vector<int>> ans;
vector<int> tmp;
for(int i = 0;i < n;i++) tmp.push_back(0);
for(int i = 0;i < n;i++) ans.push_back(tmp);
int di = 0, x = 0, y = 0, circle = 0, mxsize = n * n, row = n, col = n;
for(int i = 0;i < mxsize;i++){
ans[x][y] = i + 1;
if(di == 0 && y + circle == col - 1) di++;
else if(di == 1 && x + circle == row - 1) di++;
else if(di == 2 && y == circle) di++;
else if(di == 3 && x == circle + 1) di = 0, circle++;
x += dir[di][0], y += dir[di][1];
}
return ans;
}
};
Leetcode60. 排列序列
思路: 对于固定长度
p
p
p而言,排列个数为
p
!
p!
p!。那么对于长度为
n
+
1
n+1
n+1的数列而言,如果为第
k
k
k个排列,那么最前一个数可以用
k
/
(
n
!
)
k/(n!)
k/(n!)算出。以此类推。
代码:
class Solution {
public:
string getPermutation(int n, int k) {
// fact:阶乘 vis:标记是否访问
int fact[10] = {1}, vis[10] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, l, r;
string ans = "";
for(int i = 1;i < 10;i++) fact[i] = fact[i - 1] * i;
--k; // 相当于用了进制的思想
for(int i = 1;i <= n;i++){
l = k / fact[n - i]; // 算出最前一个数字是未访问数字中的第几个
k -= l * fact[n - i]; // 删除开头的影响
r = 0; // 寻找答案
while(true){
if(vis[r]) ++r;
else if(l > 0) --l, ++r;
else{
vis[r] = 1;
break;
}
}
ans = ans + (char)(r + '1');
}
return ans;
}
};