75. 颜色分类
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你能想出一个仅使用常数空间的一趟扫描算法吗?
主要思路就是,把0从左往右排,把2从右往左排,这样中间剩下的将都是1了,进而实现了分类。
官方的题解也是利用了这个思路,其实也是快排中的 partition
思想。一次排序,把数组分为 2
个部分。
#include<iostream>
#include<algorithm>
#include<vector>
using namespace std;
class Solution {
public:
void sortColors(vector<int>& nums) {
int left = 0;
int right = nums.size() - 1;
int i = 0;
while (i <= right) {
if (nums[i] == 0 && i != left) {
swap(nums[i], nums[left]);
left++;
}
else if (nums[i] == 2 && i != right) {
swap(nums[i], nums[right]);
right--;
}
else {
i++;
}
}
}
};
int main() {
Solution sol;
vector<int> vec = { 1,2,0 };
sol.sortColors(vec);
for (int i = 0; i < vec.size(); i++) {
cout << vec[i] << " ";
}
}
76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
示例 1:
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
解释:最小覆盖子串 “BANC” 包含来自字符串 t 的 ‘A’、‘B’ 和 ‘C’。
示例 2:
输入:s = “a”, t = “a”
输出:“a”
解释:整个字符串 s 是最小覆盖子串。
示例 3:
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
提示:
m
=
=
s
.
l
e
n
g
t
h
m == s.length
m==s.length
n
=
=
t
.
l
e
n
g
t
h
n == t.length
n==t.length
1
<
=
m
,
n
<
=
1
0
5
1 <= m, n <= 10^5
1<=m,n<=105
s 和 t 由英文字母组成
进阶:你能设计一个在 o(m+n) 时间内解决此问题的算法吗?
这道题目有几个点需要注意:
首先,根据提示如下:
可以知道是利用哈希表+滑动窗口来做。
这样可以解,但无法在O(m+n)的时间复杂度内完成,因为我们要判断哈希表中的所有元素是否都为0,如果都为0了,那么证明找到了一个可行解,而且需要判断当前字符是否在给出的子串t中,一旦判断这个,复杂度就上去了,查找至少也是 l o g n logn logn 的复杂度,总的复杂度就会变成 m ∗ l o g n m*logn m∗logn 。
优化方法是,采用了一个 needCnt
,这个是本题的精华!
还需要注意的是, n e e d [ c ] < 0 need[c]<0 need[c]<0 时都是我们不需要的字母(或者需要,但是当前串中有多余的),只有 n e e d [ c ] > 0 need[c]>0 need[c]>0 才是我们需要的,当 n e e d [ c ] = = 0 need[c]==0 need[c]==0 说明我们找到了一个可行串。
#include<vector>
#include<iostream>
#include<map>
using namespace std;
class Solution {
public:
string minWindow(string s, string t) {
map<char, int> need; // 还需要的各个字母的数量
int needCnt = t.size(); // 还需要的字母总数量
int minLen = 0x3f3f3f3f;
int start = 0;
for (int i = 0; i < t.size(); i++) {
need[t[i]]++;
}
int left = 0;
for (int i = 0; i < s.size(); i++) {
if (need[s[i]] > 0) { // 如果是需要的字母
needCnt--;
}
need[s[i]]--; // need中的负数表示这个字母是我们不需要的
if (needCnt == 0) { // 滑动窗口包含了所有元素
while (true)
{
if (need[s[left]] == 0) {// 说明遇到了一个有用到的字符
break;
}
need[s[left]]++; // 没有用的字符,need++,0以下证明这个字符是无用的,这样保证当为need[s[i]]==0的时候是正好需要的
left++; // 移动左边窗口
}
if (minLen > i - left + 1) {
minLen = i - left + 1;
start = left;
}
need[s[left]]++; // 更新哈希表为缩小窗口后的字母需要数量
needCnt++; // 更新需要的字母总数量
left++; // 缩小窗口
}
}
if (minLen == 0x3f3f3f3f) {
return "";
}
return s.substr(start, minLen);
}
};
int main() {
Solution sol;
cout << sol.minWindow("a", "aa");
}
78. 子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
提示:
1 <= nums.length <= 10
-10 <= nums[i] <= 10
nums 中的所有元素 互不相同
这道题属于集合问题,没有想到。
思路是,对于数组中的每一个元素,它都有可能加入,有可能不加入。所以采用递归法很好解决,有点像回溯。所以最终的子集数量一定为 2 n 2^n 2n 个(包括空集),其中, n n n 为集合中元素个数。
#include<vector>
#include<iostream>
using namespace std;
class Solution {
public:
void dfs(int cur, vector<int>& nums, vector<int>& t, vector<vector<int>>& ans) {
if (cur == nums.size()) {
ans.push_back(t);
return;
}
t.push_back(nums[cur]); // 加入当前这一个
dfs(cur + 1, nums, t, ans);
t.pop_back(); // 不加入当前这个
dfs(cur + 1, nums, t, ans);
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> t;
vector<vector<int>> ans;
dfs(0, nums, t, ans);
return ans;
}
};
int main() {
Solution sol;
vector<int> vec = { 1,2,3 };
vector<vector<int>> res = sol.subsets(vec);
for (int i = 0; i < res.size(); i++) {
for (int j = 0; j < res[i].size(); j++) {
cout << res[i][j] << " ";
}
cout << endl;
}
}
另一种方法也很巧妙,如下:
首先,我们确定答案有 2 n 2^n 2n 个,那么我们就生成这些个mask即可,他们一定有n个二进制位。
0表示不包含当前位置的元素,1表示包含当前位置的元素,组合就罗列出来了。
class Solution {
public:
vector<int> t;
vector<vector<int>> ans;
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size();
for (int mask = 0; mask < (1 << n); ++mask) {
t.clear();
for (int i = 0; i < n; ++i) {
if (mask & (1 << i)) {
t.push_back(nums[i]);
}
}
ans.push_back(t);
}
return ans;
}
};
6. N 字形变换
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 “PAYPALISHIRING” 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:“PAHNAPLSIIGYIR”。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
示例 1:
输入:s = “PAYPALISHIRING”, numRows = 3
输出:“PAHNAPLSIIGYIR”
示例 2:
输入:s = “PAYPALISHIRING”, numRows = 4
输出:“PINALSIGYAHRPI”
解释:
P I N
A L S I G
Y A H R
P I
示例 3:
输入:s = “A”, numRows = 1
输出:“A”
提示:
1
<
=
s
.
l
e
n
g
t
h
<
=
1000
1 <= s.length <= 1000
1<=s.length<=1000
s
由英文字母(小写和大写)
、
′
,
′
和
′
.
′
组成
s 由英文字母(小写和大写)、',' 和 '.' 组成
s由英文字母(小写和大写)、′,′和′.′组成
1
<
=
n
u
m
R
o
w
s
<
=
1000
1 <= numRows <= 1000
1<=numRows<=1000
这道题目也很绕,主要是要找到周期 2 r − 2 2r-2 2r−2 个字母一个循环,不是很好想出来,但是空间复杂度是O(1),代码如下:
class Solution {
public:
string convert(string s, int numRows) {
int n = s.size(), r = numRows;
if (r == 1 || r >= n) {
return s;
}
string res = "";
int t = 2 * r - 2; // 一个周期的大小
for (int i = 0; i < r; i++) { // 行数
for (int j = 0; j + i < n; j += t) {
res += s[j + i]; // 一个周期中左边的字母
if (0 < i && i < r - 1 && j + t - i < n) {
res += s[j + t - i]; // 这个周期中的第二个字母
}
}
}
return res;
}
};
一种比较好想的做法是用二维数组去模拟一下,但是空间复杂度是 O(n) 级别,如下:
class Solution {
public:
string convert(string s, int numRows) {
int r = numRows;
if (r == 1 || r >= s.size())return s;
int flag = -1;
vector<string> vec(r, "");
int curRow = 0;
for (int i = 0; i < s.size(); i++) {
if (curRow % r == 0||curRow % r==r-1) {
flag = -flag;
}
vec[curRow] += s[i];
curRow += flag;
}
string res = "";
for (int i = 0; i < vec.size(); i++) {
res += vec[i];
}
return res;
}
};