LeetCode 热题 HOT 100 打卡计划 | 第七天 | 每日进步一点点

74 阅读2分钟

图片.png

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第8天,点击查看活动详情

39. 组合总和

思路

(dfs,递归)

递归枚举,枚举每个数字可以选多少次。

递归过程如下:

  • 1、遍历数组中的每一个数字。
  • 2、递归枚举每一个数字可以选多少次,递归过程中维护一个target变量。如果当前数字小于等于target,我们就将其加入我们的路径数组path中,相应的target减去当前数字的值。也就是说,每选一个分支,就减去所选分支的值。
  • 3、当target == 0时,表示该选择方案是合法的,记录该方案,将其加入res数组中。

递归树如下,以candidates = [2,3,6,7], target = 7为例。

图片.png

最终答案为:[[7],[2,2,3]] ,但是我们发现[[2, 2, 3], [2, 3, 2], [3, 2, 2]方案重复了。为了避免搜索过程中的重复方案,我们要去定义一个搜索起点,已经考虑过的数,以后的搜索中就不能出现,让我们的每次搜索都从当前起点往后搜索(包含当前起点),直到搜索到数组末尾。这样我们人为规定了一个搜索顺序,就可以避免重复方案。

如下图所示,处于黄色虚线矩形内的分支都不再去搜索了,这样我们就完成了去重操作。

图片.png 递归函数设计:

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]

图片.png

3、需要分别从左向右扫描求left_max,从右向左求 right_max,最后统计答案即可。

4、注意特判n0

时间复杂度分析: 三次线性扫描,故只需要 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为例:

图片.png

时间复杂度分析: 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();
             }
         }
     }
 };