Letbook Cookbook题单——数组2
39. 组合总和
难度中等
给你一个 无重复元素 的整数数组 candidates
和一个目标整数 target
,找出 candidates
中可以使数字和为目标数 target
的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。
candidates
中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
对于给定的输入,保证和为 target
的不同组合数少于 150
个。
示例 1:
输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。
示例 2:
输入: candidates = [2,3,5], target = 8
输出: [[2,2,2,2],[2,3,3],[3,5]]
示例 3:
输入: candidates = [2], target = 1
输出: []
提示:
1 <= candidates.length <= 30
2 <= candidates[i] <= 40
candidates
的所有元素 互不相同1 <= target <= 40
注意每个元素可以重复选取无数次,因此我们多一种递归就行了,第一个直接不选取当前元素从而进入下一个元素的递归,一个选取当前元素后依旧进入当前元素的递归
class Solution {
public:
void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx) {
if (idx == candidates.size()) {
return;
}
if (target == 0) {
ans.emplace_back(combine);
return;
}
// 直接跳过
dfs(candidates, target, ans, combine, idx + 1);
// 选择当前数
if (target - candidates[idx] >= 0) {
combine.emplace_back(candidates[idx]);
dfs(candidates, target - candidates[idx], ans, combine, idx);
combine.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ans;
vector<int> combine;
dfs(candidates, target, ans, combine, 0);
return ans;
}
};
40. 组合总和 II
难度中等
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
**注意:**解集不能包含重复的组合。
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
输出:
[
[1,2,2],
[5]
]
提示:
1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
排序后去重就很容易了,前面和当前相同就不用选了
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 要对同一树层使用过的元素进行跳过
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
path.clear();
result.clear();
// 首先把给candidates排序,让其相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
};
41. 缺失的第一个正数
难度困难
给你一个未排序的整数数组 nums
,请你找出其中没有出现的最小的正整数。
请你实现时间复杂度为
O(n)
并且只使用常数级别额外空间的解决方案。
示例 1:
输入:nums = [1,2,0]
输出:3
示例 2:
输入:nums = [3,4,-1,1]
输出:2
示例 3:
输入:nums = [7,8,9,11,12]
输出:1
提示:
1 <= nums.length <= 5 * 105
-231 <= nums[i] <= 231 - 1
有意思的题,原地哈希
官方题解讲得挺好的,就不自己多加阐述
42. 接雨水
难度困难
给定 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
提示:
n == height.length
1 <= n <= 2 * 104
0 <= height[i] <= 105
比较恶心的一道题,还只过了一种写法,双指针和栈写法暂时还没写
参考这个大佬的写法
按行求解(超时)
整个思路就是,求第 i 层的水,遍历每个位置,如果当前的高度小于 i,并且两边有高度大于等于 i 的,说明这个地方一定有水,水就可以加 1。
如果求高度为 i 的水,首先用一个变量 temp 保存当前累积的水,初始化为 0。从左到右遍历墙的高度,遇到高度大于等于 i 的时候,开始更新 temp。更新原则是遇到高度小于 i 的就把 temp 加 1,遇到高度大于等于 i 的,就把 temp 加到最终的答案 ans 里,并且 temp 置零,然后继续循环。
我们就以题目的例子讲一下。
先求第 11 行的水。
也就是红色区域中的水,数组是 height = [ 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 ] 。
原则是高度小于 1,temp ++,高度大于等于 1,ans = ans + temp,temp = 0。
temp 初始化为 0,ans = 0
height[0] 等于 0 < 1,不更新。
height[1] 等于 1 >= 1,开始更新 temp。
height[2] 等于 0 < 1,temp = temp + 1 = 1。
height[3] 等于 2 >= 1,ans = ans + temp = 1,temp = 0。
height[4] 等于 1 >= 1,ans = ans + temp = 1,temp = 0。
height[5] 等于 0 < 1,temp = temp + 1 = 1。
height[6] 等于 1 >= 1,ans = ans + temp = 2,temp = 0。
剩下的 height[7] 到最后,高度都大于等于 1,更新 ans = ans + temp = 2,temp = 0。而其实 temp 一直都是 0,所以 ans 没有变化。
再求第 2 行的水。
也就是红色区域中的水,数组是 height = [ 0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1 ]。
原则是高度小于 2,temp ++,高度大于等于 2,ans = ans + temp,temp = 0。
temp 初始化为 0,ans 此时等于 2。
height[0] 等于 0 < 2,不更新。
height[1] 等于 1 < 2,不更新。
height[2] 等于 0 < 2,不更新。
height[3] 等于 2 >= 2,开始更新
height[4] 等于 1 < 2,temp = temp + 1 = 1。
height[5] 等于 0 < 2,temp = temp + 1 = 2。
height[6] 等于 1 < 2,temp = temp + 1 = 3。
height[7] 等于 3 >= 2,ans = ans + temp = 5,temp = 0。
height[8] 等于 2 >= 2,ans = ans + temp = 3,temp = 0。
height[9] 等于 1 < 2,temp = temp + 1 = 1。
height[10] 等于 2 >= 2,ans = ans + temp = 6,temp = 0。
height[11] 等于 1 < 2,temp = temp + 1 = 1。
然后结束循环,此时的 ans 就是6。
再看第 3 层。
按照之前的算法,之前的都是小于 3 的,不更新 temp,然后到 height[7] 等于 3,开始更新 temp,但是后边没有 height 大于等于 3 了,所以 ans 没有更新。
所以最终的 ans 就是 6。
思路很清楚,就是h[i]的最大有点大,所以会超时
class Solution {
public:
int trap(vector<int>&height) {
int sum = 0;
int max = getMax(height);//找到最大的高度,以便遍历。
for (int i = 1; i <= max; i++) {
bool isStart = false; //标记是否开始更新 temp
int temp_sum = 0;
for (int j = 0; j < height.size(); j++) {
if (isStart && height[j] < i) {
temp_sum++;
}
if (height[j] >= i) {
sum = sum + temp_sum;
temp_sum = 0;
isStart = true;
}
}
}
return sum;
}
int getMax(vector<int>& height) {
int max = 0;
for (int i = 0; i < height.size(); i++) {
if (height[i] > max) {
max = height[i];
}
}
return max;
}
};
按列求解(优化后不会超时)
求每一列的水,我们只需要关注当前列,以及左边最高的墙,右边最高的墙就够了。
装水的多少,当然根据木桶效应,我们只需要看左边最高的墙和右边最高的墙中较矮的一个就够了。
所以,根据较矮的那个墙和当前列的墙的高度可以分为三种情况。
较矮的墙的高度大于当前列的墙的高度
把正在求的列左边最高的墙和右边最高的墙确定后,然后为了方便理解,我们把无关的墙去掉。
这样就很清楚了,现在想象一下,往两边最高的墙之间注水。正在求的列会有多少水?
很明显,较矮的一边,也就是左边的墙的高度,减去当前列的高度就可以了,也就是 2 - 1 = 1,可以存一个单位的水。
较矮的墙的高度小于当前列的墙的高度
同样的,我们把其他无关的列去掉。
想象下,往两边最高的墙之间注水。正在求的列会有多少水?
正在求的列不会有水,因为它大于了两边较矮的墙。
较矮的墙的高度等于当前列的墙的高度。
和上一种情况是一样的,不会有水。
明白了这三种情况,程序就很好写了,遍历每一列,然后分别求出这一列两边最高的墙。找出较矮的一端,和当前列的高度比较,结果就是上边的三种情况。
这份代码比上面那个好点,但是会在最后一个恶心数据上过不去
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IHVWkUW7-1670348541360)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\1670348284997.png)]
class Solution {
public:
int trap(vector<int>& height) {
int sum = 0;
//最两端的列不用考虑,因为一定不会有水。所以下标从 1 到 length - 2
for (int i = 1; i < height.size() - 1; i++) {
int max_left = 0;
//找出左边最高
for (int j = i - 1; j >= 0; j--) {
if (height[j] > max_left) {
max_left = height[j];
}
}
int max_right = 0;
//找出右边最高
for (int j = i + 1; j < height.size(); j++) {
if (height[j] > max_right) {
max_right = height[j];
}
}
//找出两端较小的
int minx = min(max_left, max_right);
//只有较小的一段大于当前列的高度才会有水,其他情况不会有水
if (minx > height[i]) {
sum = sum + (minx - height[i]);
}
}
return sum;
}
};
因此我们需要优化下,注意我们当前位置左边最大后右边最大可以由当前位置的前面一个位置的左右最大值得出
class Solution {
public:
int trap(vector<int>& h) {
int l, r, maxl = 0, maxr = 1e6, ans = 0, num = 0;
for (int i = 1; i < h.size() - 1; i++)
{
if (maxl <= h[i - 1])
maxl = h[i - 1];
if (h[i] == maxr)
num--;
if (num == 0||i==1)
{
maxr = 0;
r = i + 1;
while (r < h.size())
{
if (h[r] > maxr)
maxr = h[r], num = 1;
else if (h[r] == maxr)
num++;
r++;
}
}
if (min(maxl, maxr) > h[i])
ans += min(maxl, maxr) - h[i];
}
return ans;
}
};