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

114 阅读2分钟

图片.png

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

75. 颜色分类 *

思路

(双指针) O(n)

类似于刷油漆。

图片.png

时间复杂度分析: 一次遍历,因此为O(n)。

c++代码

 class Solution {
 public:
     void sortColors(vector<int>& nums) {
         int j = 0, k = 0;
         for(int i = 0; i < nums.size(); i++){
             int num = nums[i];
             nums[i] = 2;
             
             if(num < 2) nums[j++] = 1;
             if(num < 1) nums[k++] = 0; 
         }
     }
 };

76. 最小覆盖子串

思路

(滑动窗口) O(n)

这道题要求我们返回字符串s中包含字符串 t 的全部字符的最小窗口,我们利用滑动窗口的思想解决这个问题。因此我们需要两个哈希表,hs哈希表维护的是s字符串中滑动窗口中各个字符出现多少次,ht哈希表维护的是t字符串各个字符出现多少次。如果hs哈希表中包含ht哈希表中的所有字符,并且对应的个数都不小于ht哈希表中各个字符的个数,那么说明当前的窗口是可行的,可行中的长度最短的滑动窗口就是答案。

图片.png

过程如下:

1、遍历t字符串,用ht哈希表记录t字符串各个字符出现的次数。

图片.png

2、定义两个指针jij指针用于收缩窗口,i指针用于延伸窗口,则区间[j,i]表示当前滑动窗口。首先让ij指针都指向字符串s开头,然后枚举整个字符串s ,枚举过程中,不断增加i使滑动窗口增大,相当于向右扩展滑动窗口。

图片.png

3、每次向右扩展滑动窗口一步,将s[i]加入滑动窗口中,而新加入了s[i],相当于滑动窗口维护的字符数加一,即hs[s[i]]++

图片.png

4、对于新加入的字符s[i],如果hs[s[i]] <= ht[s[i]],说明当前新加入的字符s[i]是必需的,且还未到达字符串t所要求的数量。因此我们还需要事先定义一个cnt变量, cnt维护的是s字符串[j,i]区间中满足t字符串的元素的个数,记录相对应字符的总数。新加入的字符s[i]必需,则cnt++

5、我们向右扩展滑动窗口的同时也不能忘记收缩滑动窗口。因此当hs[s[j]] > ht[s[j]时,说明hs哈希表中s[j]的数量多于ht哈希表中s[j]的数量,此时我们就需要向右收缩滑动窗口,j++并使hs[s[j]]--,即hs[s[j ++ ]] --

6、当cnt == t.size时,说明此时滑动窗口包含符串 t 的全部字符。我们重复上述过程找到最小窗口即为答案。

图片.png

时间复杂度分析: 两个指针都严格递增,最多移动 n 次,所以总时间复杂度是 O(n)。

c++代码

 class Solution {
 public:
     string minWindow(string s, string t) {
         unordered_map<int, int> hs, ht;
         for(char c : t) ht[c]++;
         string res;
         int cnt = 0;
         for(int i = 0, j = 0; i < s.size(); i++){
             hs[s[i]]++;
             if(hs[s[i]] <= ht[s[i]]) cnt++;
             while(hs[s[j]] > ht[s[j]]) hs[s[j++]]--;
             if(cnt == t.size()){
                 if(res.empty() || i - j + 1 < res.size())
                     r0.es = s.substr(j, i - j + 1); 
             }
         }
         return res;
     }
 };

78. 子集

思路

思路1

(二进制) O(2^nn)

对于一个大小为n的数组nums来说,由于每个数有不选两种情况,因此总共有 2^n 种情况。我们用n位二进制数 0 到 2^n-1 表示每个数的选择状态情况,在某种情况i中,若该二进制数i的第j位是1,则表示nums数组第j位这个数选,我们将nums[j]加入到path中,枚举完i这种情况,将path加入到res中 。

例如对于集合[1, 2, 3]

0/1序列表示集合对应的二进制数
000[]0
001[3]1
010[2]2
011[2, 3]3
100[1]4
101[1, 3]5
110[1, 2]6
111[1, 2, 3]7

时间复杂度分析: 一共枚举 2^n 个数,每个数枚举 n 位,所以总时间复杂度是 O(2^nn)。

c++代码1

 class Solution {
 public:
     vector<vector<int>> subsets(vector<int>& nums) {
         vector<vector<int>>res;
         int n = nums.size();
         for(int i = 0; i < 1<<n; i++)
         {
             vector<int>path;
             for(int j = 0; j < n; j++)
             {
                 if(i>>j&1)
                     path.push_back(nums[j]);
             }
             res.push_back(path);
         }
         return res;
     }
 };

时间复杂度分析: 一共枚举 2^n 个数,每个数枚举 n 位,所以总时间复杂度是 O(2^nn)。

思路2

(递归) O(2^nn)

一共n个位置,递归枚举每个位置的数 还是 不选,然后递归到下一层。

递归函数设计

  • 递归参数:void dfs(vector<int>& nums, int u) ,第一个参数是nums数组,第二个参数是u,表示当前枚举到nums数组中的第u位。
  • 递归边界:u == nums.size(),当枚举到第nums.size()位时,递归结束,我们将结果放到答案数组res中。

时间复杂度分析: 一共 2^n 个状态,每种状态需要 O(n) 的时间来构造子集。

c++代码2

 class Solution {
 public:
     vector<vector<int>>res;
     vector<int>path;
     vector<vector<int>> subsets(vector<int>& nums) {
         dfs(nums,0);
         return res;
     }
     void dfs(vector<int>&nums,int u)
     {
         if( u == nums.size()) //递归边界
         {
             res.push_back(path);
             return;
         }
         dfs(nums,u+1);  //不选第u位,递归下一层
         path.push_back(nums[u]);
         dfs(nums,u+1);  //选第u位,递归下一层
         path.pop_back(); //回溯
     }
 };