持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情
39. 组合总和
思路
(dfs,递归)
递归枚举,枚举每个数字可以选多少次。
递归过程如下:
- 1、遍历数组中的每一个数字。
- 2、递归枚举每一个数字可以选多少次,递归过程中维护一个
target变量。如果当前数字小于等于target,我们就将其加入我们的路径数组path中,相应的target减去当前数字的值。也就是说,每选一个分支,就减去所选分支的值。 - 3、当
target == 0时,表示该选择方案是合法的,记录该方案,将其加入res数组中。
递归树如下,以candidates = [2,3,6,7], target = 7为例。
最终答案为:[[7],[2,2,3]] ,但是我们发现[[2, 2, 3], [2, 3, 2], [3, 2, 2]方案重复了。为了避免搜索过程中的重复方案,我们要去定义一个搜索起点,已经考虑过的数,以后的搜索中就不能出现,让我们的每次搜索都从当前起点往后搜索(包含当前起点),直到搜索到数组末尾。这样我们人为规定了一个搜索顺序,就可以避免重复方案。
如下图所示,处于黄色虚线矩形内的分支都不再去搜索了,这样我们就完成了去重操作。
递归函数设计:
void dfs(vector<int>&c,int u ,int target)
变量u表示当前枚举的数字下标,target是递归过程中维护的目标数。
递归边界:
- 1、
if(target < 0),表示当前方案不合法,返回上一层。 - 2、
if(target == 0),方案合法,记录该方案。
时间复杂度分析: 无
c++代码
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
dfs(candidates, 0, target);
return res;
}
void dfs(vector<int>& c, int u, int target){
if(target < 0) return;
if(target == 0){
res.push_back(path);
}
for(int i = u; i < c.size(); i++){
if(c[i] <= target){
path.push_back(c[i]);
dfs(c, i, target - c[i]);
path.pop_back();
}
}
}
};
42. 接雨水
思路
(三次线性扫描) O(n)
1、观察整个图形,考虑对水的面积按 列 进行拆解、
2、注意到,每个矩形条上方所能接受的水的高度,是由它左边最高的矩形,和右边最高的矩形决定的。具体地,假设第i个矩形条的高度为 height[i],且矩形条左边最高的 矩形条的高度为 left_max[i],右边最高的矩形条高度为 right_max[i],则该矩形条上方能接受水的高度为min(left_max[i], right_max[i]) - height[i]。
3、需要分别从左向右扫描求left_max,从右向左求 right_max,最后统计答案即可。
4、注意特判n为 0。
时间复杂度分析: 三次线性扫描,故只需要 O(n) 的时间。
空间复杂度分析: 需要额外 O(n)的空间记录每个位置左边最高的高度和右边最高的高度。
c++代码
class Solution {
public:
int trap(vector<int>& h) {
int n = h.size();
vector<int> left_max(n); //每个柱子左边最大值
vector<int> right_max(n); //每个柱子右边最大值
left_max[0] = h[0];
for(int i = 1; i < n; i++){
left_max[i] = max(left_max[i - 1], h[i]);
}
right_max[n - 1] = h[n - 1];
for(int i = n - 2; i >= 0; i--){
right_max[i] = max(right_max[i + 1], h[i]);
}
int res = 0;
for(int i = 0; i < n; i++){
res += min(left_max[i], right_max[i]) - h[i];
}
return res;
}
};
46. 全排列
思路
(dfs) O(n×n!)
具体解题过程:
- 1、我们从前往后,一位一位枚举,每次选择一个没有被使用过的数。
- 2、选好之后,将该数的状态改成“已被使用”,同时将该数记录在相应位置上,然后递归下一层。
- 3、递归返回时,不要忘记将该数的状态改成“未被使用”,并将该数从相应位置上删除。
辅助数组:
vector<bool> st; //标记数组
vector<int> path; //记录路径
递归函数设计:
void dfs(vector<int>& nums, int u)
nums是选择数组,u是当前正在搜索的答案数组下标位置。
递归搜索树
我们以1 ,2 ,3为例:
时间复杂度分析: O(n×n!) ,总共n!种情况,每种情况的长度为n。
c++代码
class Solution {
public:
vector<vector<int>> res;
vector<int> st;
vector<int> path;
vector<vector<int>> permute(vector<int>& nums) {
st = vector<int>(nums.size() + 1,false);
dfs(nums, 0);
return res;
}
void dfs(vector<int>& nums, int u){
if(u == nums.size()){
res.push_back(path);
return ;
}
for(int i = 0; i < nums.size(); i++){
if(!st[i]){
st[i] = true;
path.push_back(nums[i]);
dfs(nums, u + 1);
st[i] = false;
path.pop_back();
}
}
}
};