LeetCode刷题

目录

1.两数之和

2.字母异位词分组 

思路:

排序:

3.最长连续序列 

4.移动零 

5.盛最多水的容器 

​编辑思路(双指针+贪心):

6.三数之和 

思路:

7.接雨水

思路(动态规划):

8.无重复字符的最长子串

code1:

 code2:

9.找到字符串中所有字母异位词

思路(滑动窗口):

 10.和为k的子数组

 思路(前缀和 + 哈希表优化):

11.滑动窗口最大值

思路(单调队列):

12.最小覆盖子串

思路(双指针 + 滑动窗口):

13.最大子数组和

​编辑思路(动态规划):

14.合并区间

思路:

15.轮转数组

16.除自身以外数组的乘积

17.矩阵置零

18.螺旋矩阵

思路:

19.旋转图像 

20.搜索二维矩阵II

思路(二分查找):

21.岛屿数量 

思路(dfs): 

22.腐烂的橘子 

思路(bfs):

23.拓扑排序 

思路(topsort):

topsort板子:

图示模拟:


1.两数之和

暴力:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        
        for (int i = 0; i < nums.size(); i ++)
        {
          for (int j = i + 1; j < nums.size(); j ++)
          {
            if (nums[i] + nums[j] == target)
            {
              return {i, j};
            }
          }
        }
        return {};
    }
};

哈希表:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hash;
        set<int> judge;

        for (int i = 0; i < nums.size(); i ++) 
        {
          if (judge.find(target - nums[i]) != judge.end())
          {
              return {hash[target - nums[i]], i};
          }
          judge.insert(nums[i]);
          hash[nums[i]] = i;
        }
        return {};
    }
};


2.字母异位词分组 

思路:

        无论单词中字母如何组合,排序后的单词是一定的,用排序后的单词作为这一组单词的代表

        以代表分组

排序:

class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {

      unordered_map<string, vector<string>> mp;
      vector<vector<string>> res;

      for (int i = 0; i < strs.size(); i ++)
      {
        string x = strs[i];
        sort(x.begin(), x.end());
        mp[x].push_back(strs[i]);
      }

      for (auto it = mp.begin(); it != mp.end(); it ++) res.push_back(it->second);
      return res;
    }
};


3.最长连续序列 

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {

      
        sort(nums.begin(), nums.end());
        nums.erase(unique(nums.begin(), nums.end()), nums.end());
        int a[100010];
        int len = 0;
        for (int i = 0; i < nums.size(); i ++) a[len ++] = nums[i];
      
        if (nums.size() == 0) return 0;
        if (nums.size() == 1) return 1;
        map<int, int> cnt;
        int max = 0, d = 0;
        for (int i = 1, j = 0; i < len; i ++, j ++) 
        {
         
            if (a[i] - a[j] != d) 
            {
                d = a[i] - a[j];
                cnt.clear();
            }
            cnt[d] ++; 
           
            if (cnt[d] > max && d == 1) max = cnt[d];
            
        }
        return max + 1 ;
    }
};


4.移动零 

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int cnt = 0;
        
        for (int i = 0; i < nums.size(); i ++)
            if (nums[i]) nums[cnt ++] = nums[i];

        for (int i = cnt; i < nums.size(); i ++) nums[i] = 0;
        
    }
};


5.盛最多水的容器 

思路(双指针+贪心):

        1.p和q两个指针分别指向数组的两端

        2.指针指向的数值较小的移动

--->为什么数值较小的移动?

        答:无论移动p,q哪个指针,移动后宽度w是固定的,是个定值,那么最优解的更新关键看高度,如果移动较大的数值,那么高度h只有可能变小或者不变,如果移动较小的数值,高度h可能变小或者不变或者变大,综合来看,移动较小的更有可能得到更大的容量

class Solution {
public:
    int maxArea(vector<int>& height) {

       int ans = 0;
       int p = 0, q = height.size() - 1;
       
       while (p != q)
       {
           int w = q - p;
           int h = min(height[p], height[q]);
           ans = max(ans, h * w);
           if (height[p] < height[q]) p ++;
           else q --;
           
       }

       return ans;
    }
};


6.三数之和 

思路:

        将求解三数之和变为求解两数之和

        用哈希表对结果判重

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        
        sort(nums.begin(), nums.end());
        set<pair<int, pair<int, int>>> judge;
        vector<vector<int>> res;
        unordered_map<int, int> cnt;

        for (int i = 0; i < nums.size(); i ++) cnt[nums[i]] ++;
        
        int len = nums.size();
        for (int i = 0; i < len; i ++)
        {
            if (i > 0 && nums[i] == nums[i - 1]) continue;

            for (int j = i + 1; j < len; j ++)
            {
                int a = nums[i], b = nums[j];  
                int c = 0 - a - b;
                if (c < nums[0] || c > nums[len - 1]) break;

                cnt[a] --;
                cnt[b] --;

                if (cnt[c] >= 1 && judge.find({a, {b, c}}) == judge.end())
                {
                    vector<int> t;
                    t.push_back(a);
                    t.push_back(b);
                    t.push_back(c);
                    res.push_back(t);

                    //放入判重哈希表中
                    judge.insert({a, {b, c}});
                    judge.insert({a, {c, b}});
                    judge.insert({b, {a, c}});
                    judge.insert({b, {c, a}});
                    judge.insert({c, {b, a}});
                    judge.insert({c, {a, b}});

                }
                cnt[a] ++;
                cnt[b] ++;
            }
        }

        return res;
    }
};


7.接雨水

思路(动态规划):

        1.预处理出对于每一个高度从右向左看,以及从左向右看的最大高度

        2.对于每一个高度,根据其左右两端的最大高度的较小值,算出当前位置可以盛放的雨水 

class Solution {
public:
    int trap(vector<int>& height) {
        
        int l[101000], r[101000];

        int len = height.size();

        memset(l, 0, sizeof l);
        memset(r, 0, sizeof r);

        l[0] = height[0];
        for (int i = 1, j = len - 1; i < len; i ++, j --)
        {
            l[i] = max(l[i - 1], height[i]);
            r[j] = max(r[j + 1], height[j]); 
        }
        r[0] = max(r[1], height[0]);

        int sum = 0;
        for (int i = 1; i < len; i ++)
        {
            int h = min(l[i - 1], r[i + 1]);
            sum += max(0, h - height[i]);
        }

        return sum;
    }
};


8.无重复字符的最长子串

code1:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {

        //判断是否出现过
        set<char> hash;

        int res = 0; //返回最终答案

        int len = s.size();

        int p = 0, q = p;

        while (p < len)
        {
            while (q < len && !hash.count(s[q])) 
            { 
                hash.insert(s[q]);
                q ++;
            }
          
            res = max(res, q - p);
            hash.erase(s[p]);
            p ++;
        }    
        return res;
    }
};

 code2:

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        int len = s.size();

        map<char, int> cnt;
        int res = 0;
        for (int i = 0; i < len; i ++)
        {
            cnt.clear();
            cnt[s[i]] ++;
            int j = i + 1;
            for (;j < len; j ++)
            {
                cnt[s[j]] ++;
                if (cnt[s[j]] == 2) break;
            }
            res = max(res, j - i);
        }
        return res;
    }
};


9.找到字符串中所有字母异位词

思路(滑动窗口):

        在s串中维护长度为p串长度的窗口

        当窗口内的内容与p串相同时,找到其对应起点

class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
       vector<int> res;

       int ls = s.size(), lp = p.size();
       if (ls < lp) return {};

        vector<int> scount(26);
        vector<int> pcount(26);

        for (int i = 0; i < lp; i ++) 
        {
            scount[s[i] - 'a'] ++;
            pcount[p[i] - 'a'] ++;
        }

        if (scount == pcount) res.push_back(0);

        for (int i = 0; i < ls - lp; i ++)
        {
            scount[s[i] - 'a'] --;
            scount[s[i + lp] - 'a'] ++;

            if (scount == pcount)
            {
                res.push_back(i + 1);
            }
        }

        return res;
    }
};


 10.和为k的子数组

 思路(前缀和 + 哈希表优化):

在数组中求某一段区间的和,很容易能想到用前缀和算法

但是暴力枚举每一个端点,时间复杂度会比较高,很容易会超时

用哈希表对算法进行优化能大大降低时间复杂度

    

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        //前缀和数组
        int pre[200010];

        //哈希表存储 pre[j - 1]
        unordered_map<int, int> hash;
        hash[0] = 1;

        int len = nums.size(), cnt = 0;

        for (int i = 1; i <= len; i ++)
        {
            pre[i] = pre[i - 1] + nums[i - 1];
            if (hash[pre[i] - k] > 0) cnt += hash[pre[i] - k];
            hash[pre[i]] ++;
        }
        return cnt;
    }
};


11.滑动窗口最大值

思路(单调队列):

        1. 维护一个窗口

        2. 队列的队头和队尾元素与原数组中的下标存在映射关系

        3.当队列需要移动时(即当前遍历到的下标与队头对应的下标超出了滑动窗口的长度时),需要移动队列,即让队头元素弹出

        4.队头所存储的元素为当前最优解,当每次需要给队列中加入新元素时,则要判断是否需要从队列中弹出元素(从队尾弹出)

        5.什么时候弹出元素:当队列中含有元素,且当前元素比队尾元素更大且距离更近,则说明当前元素比队尾更优

        6.把新元素从队尾入队

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {

        vector<int> res;

        int q[100010], hh = 0, tt  = -1;

        int len = nums.size();

        for (int i = 0; i < len; i ++)
        {
            //当前滑动窗口大小已满 需要移动队头
            if (i - q[hh] + 1 > k && hh <= tt) hh ++;
            //插入新元素时需要判断 是否需要弹出队尾元素
            while (hh <= tt && nums[i] >= nums[q[tt]]) tt --;
            //向队列中插入新元素
            q[++ tt] = i;
            
            if (i + 1 >= k) res.push_back(nums[q[hh]]);
        }

        return res;
    }
};


12.最小覆盖子串

思路(双指针 + 滑动窗口):

        1.维护一个滑动窗口,滑动窗口里面存储的是当前所遍历到的且在模板串中字母,左右指针表示边界

        2.当滑动窗口内不包含模板串所有的字母时,移动右指针

        3.当滑动窗口中包含模板串所有的字母时,考虑更新最小窗口大小以及起点位置,移动左指针 

class Solution {
public:
    unordered_map<char, int> window, flag;

    //判断滑动窗口是否包含模板串所有字母
    bool check() {

       // if (window.size() < flag.size()) return false;

        for (auto it : flag) {
            if (window[it.first] < it.second) return false;
        }
        return true;
    }

    string minWindow(string s, string t) {
        for (auto x : t) flag[x] ++;

        int len = s.size();
        int l = 0, r = -1; //左右指针维护滑动窗口
        int min_len = 0x3f3f3f3f, idx = -1;

        while (r < len){
            
            //移动右指针:当在模板串中可以找到当前要放入滑动窗口的字母时
            if (flag.find(s[++ r]) != flag.end()) window[s[r]] ++;
           
          
            //如果当前滑动窗口内包含所有模板串所含字母
            while (check() && r >= l) { 
             
                //考虑更新窗口大小和起点位置
                if (min_len > r - l + 1) {
                    min_len = r - l + 1;   
                    idx = l;
                }

                //移动左指针:若要弹出的字母在滑动窗口中 则需弹出
                if (window.find(s[l]) != window.end()) window[s[l]] --;
                l ++; 
            }
        }

        if (idx == -1) return "";
        return s.substr(idx, min_len);
    }
};


13.最大子数组和

思路(动态规划):

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
     int len = nums.size();
     int f[100010];

     int res = nums[0];
     f[0] = nums[0];

     for (int i = 1; i < len; i ++)
     {
         f[i] = max(f[i - 1] + nums[i], nums[i]);
         res = max(res, f[i]); 
     }
     return res;
    }
};


14.合并区间

思路:

        1.将区间按左端点排序

        2.l,r记录当前合并区间的左右端点

        3.判断当前遍历到的区间的左端点是否可以合并到当前区间:

                                1>可以合并,考虑更新当前合并区间的右端点

                                2> 不可以合并,更新l,r

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        
        vector<vector<int>> res;
        int cnt = 0;
        int len = intervals.size();

        sort(intervals.begin(), intervals.end());
        int l = intervals[0][0], r = intervals[0][1];
        for (int i = 1; i < len; i ++)
        {
            int p = intervals[i][0], q = intervals[i][1];
            if (p <= r) r = max(r, q);
            else 
            {
                res.push_back({l, r});
                l = p;
                r = q;
            }
        }
        res.push_back({l, r});
        return res;
    }
};


15.轮转数组

class Solution {
public:
    void rotate(vector<int>& nums, int k) {
        vector<int> copy(nums);
        int len = nums.size();

        for (int i = 0; i < len; i ++)
        {
            int x = (i + k) % len;
            nums[x] = copy[i];
        }
    }
};


16.除自身以外数组的乘积

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        vector<int> res;
        int len = nums.size();
        int l[100010], r[100010];
        l[0] = 1;
        r[len - 1] = 1;
        for (int i = 1, j = len - 2; i < len; i ++, j --) 
        {
            l[i] = l[i - 1] * nums[i - 1];
            r[j] = r[j + 1] * nums[j + 1];
        }
        for (int i = 0; i < len; i ++) res.push_back(l[i] * r[i]);
        return res;
    }
};


17.缺失的第一个正数

class Solution {
public:
    int firstMissingPositive(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        nums.erase(unique(nums.begin(), nums.end()), nums.end());

        int len = nums.size();
        int cnt = 1;
        int p = 0;
        if (nums[len - 1] <= 0) return 1;
        while (p < len)
        {
            while (p < len && nums[p] <= 0) p ++;
            if (nums[p ++] == cnt) cnt ++;
            else break;
        }
        return cnt;
    }
};


17.矩阵置零

class Solution {
public:
    void setZeroes(vector<vector<int>>& matrix) {
        int len = matrix.size();
        int wid = matrix[0].size();

        vector<vector<int>> cp(matrix);

        set<int> len_zero, wid_zero;

        for (int i = 0; i < len; i ++)
        {
            for (int j = 0; j < wid; j ++)
            {
                if (cp[i][j] == 0)
                {
                    if (len_zero.find(i) == len_zero.end())
                    {
                        for (int k = 0; k < wid; k ++) matrix[i][k] = 0;
                        len_zero.insert(i);
                    }
                    if (wid_zero.find(j) == wid_zero.end())
                    {
                        for (int k = 0; k < len; k ++) matrix[k][j] = 0;
                        wid_zero.insert(j);
                    }
                }
            }
        }
    }
};


18.螺旋矩阵

思路:

        每遍历一行或一列就更新一下对应边界 

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        vector<int> res;

        if (matrix.empty()) return res;

        int u = 0;
        int d = matrix.size() - 1;
        int l = 0;
        int r = matrix[0].size() - 1;

        while (1)
        {
            //从左向右遍历 
            for (int i = l; i <= r; i ++) res.push_back(matrix[u][i]);
            if (++ u > d) break; //出边界

            //从上向下遍历
            for (int i = u; i <= d; i ++) res.push_back(matrix[i][r]);
            if (-- r < l) break; //出边界

            //从右向左遍历
            for (int i = r; i >= l; i --) res.push_back(matrix[d][i]);
            if (-- d < u) break; //出边界

            //从下向上遍历
            for (int i = d; i >= u; i --) res.push_back(matrix[i][l]);
            if (++ l > r) break; //出边界
        }

        return res;
    }
};


19.旋转图像 

class Solution {
public:
    void rotate(vector<vector<int>>& matrix) {

        vector<vector<int>> cp(matrix);

        int n = matrix.size();
        for (int i = 0; i < n; i ++)
            for (int j = n - 1; j >= 0; j --)
                matrix[i][n - j - 1] = cp[j][i];
     
    }
};


20.搜索二维矩阵II

思路(二分查找):

        因为每一行都是从小到大排序的,所以可以用二分查找 

        下面的博文里面有二分查找的相关总结:

        基础算法总结-CSDN博客

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
       
       int len = matrix.size();
       int wid = matrix[0].size();

       for (int i = 0; i < len; i ++)
       {
           //二分查找
           int l = 0, r = wid - 1;

           while  (l < r)
           {
               int mid = (l + r) / 2;
               if (matrix[i][mid] >= target) r = mid;
               else l = mid + 1;
           }
           if (matrix[i][l] == target) return true;
       }

       return false;
    }
};


21.岛屿数量 

思路(dfs): 

        分别向四个方向遍历,看哪个方向可达,标注已到达的坐标

class Solution {
public:
    int h, w;
    int dx[5] = {0, 0, 1, -1};
    int dy[5] = {1, -1, 0, 0};

    void dfs(int x, int y, vector<vector<char>>& grid, vector<vector<bool>>& used) {
        if (x < 0 || x >= h || y < 0 || y >= w) return;
        if (grid[x][y] == '0') return;

        for (int i = 0; i < 4; i ++)
        {
            int a = x + dx[i], b = y + dy[i];
            if (a < 0 || a >= h || b < 0 || b >= w ) continue;
            if (grid[a][b] == '1' && !used[a][b])
            {
                used[a][b] = true;
                dfs(a, b, grid, used);
            }
        }
    }
    int numIslands(vector<vector<char>>& grid) {
        h = grid.size();
        w = grid[0].size();
        vector<vector<bool>> used = vector<vector<bool>>(h, vector<bool>(w, false));
        int ans = 0;
        for (int i = 0; i < h; i ++)
        {
            for (int j = 0; j < w; j ++)
            {
                if (!used[i][j] && grid[i][j] == '1')
                {
                    ans ++;
                    dfs(i, j, grid, used);
                }
            }
        }

        return ans;
    }
};


22.腐烂的橘子 

注意:每分钟,所有腐烂的橘子都会向四周腐烂

思路(bfs):

        1. 队列中存储当前腐烂的橘子

        2. 取出当前队列中所有腐烂的橘子

        3. 向四周腐烂,若新鲜的橘子被腐烂,新鲜的橘子减一,并把其加入腐烂橘子的队列当中去

class Solution {
public:
    int h, w;
    int fresh = 0, ans = 0;
    int dx[5] = {1 , -1, 0, 0};
    int dy[5] = {0, 0, 1, -1};

    int orangesRotting(vector<vector<int>>& grid) {
        h = grid.size();
        w = grid[0].size();

        queue<pair<int, int>> q; //存储腐烂的橘子的坐标

        for (int i = 0; i < h; i ++)
        {
            for (int j = 0; j < w; j ++)
            {
                if (grid[i][j] == 1) fresh ++;
                else if (grid[i][j] == 2) q.push({i, j});
            }
        }

        while (!q.empty())
        {
           int len = q.size();

           bool flag = false;
           //取出当前队列中所有的腐烂橘子,向下一层腐烂
           for (int i = 0; i < len; i ++)
           {
               auto hh = q.front();
               q.pop();

               int x = hh.first, y = hh.second;

               for (int d = 0; d < 4; d ++)
               {
                   int a = x + dx[d], b = y + dy[d];

                   if (a < 0 || a >= h || b < 0 || b >= w) continue;

                   if (grid[a][b] == 1)
                   {
                       grid[a][b] = 2;
                       q.push({a, b});
                       fresh --;
                       flag = true;
                   }
               }
           }

           if (flag) ans ++;
        }
        if (fresh > 0) ans = -1;

        return ans;    
    }
};


23.拓扑排序 

思路(topsort):

        1. 明确这是一个有向图,并且y一定在x前面,这满足拓扑序列(有向无环图)的条件:

             (1)每个顶点出现且只出现一次。
        (2)若存在一条从顶点 A 到顶点 B 的路径,那么在序列中顶点 A 出现在顶点 B 的前面。

        2.  存储每一个节点的入度,入度为0的点为起点(入度:以当前节点为终点的边的个数)

        3.  每次都选取入度为 0 的点加入拓扑队列中,再删除与这一点连接的所有边。

        4.  邻接表存储边

topsort板子:

bool topsort(int numCourses)
{
    for (int i = 0; i < numCourses; i ++)
        if (d[i] == 0) q[++ tt] = i;

    while (hh <= tt)
    {
        int t = q[hh ++];

        for (int i = h[t]; i != -1; i = ne[i])
        {
            int j = e[i];
            if (d[j] != 0) d[j] --;
            if (d[j] == 0) q[++ tt] = j;
        }
    }

    if (tt == numCourses - 1) return true;
    return false;
}

图示模拟:

无环:

 

有环(不能构成拓扑序):

  

class Solution {
public:

    //邻接表
    int n;
    int h[100010], e[100010], ne[100010], idx = 0;
    int q[100010], d[100010], hh = 0, tt = -1;

    void add(int a, int b)
    {
        e[idx] = b;
        ne[idx] = h[a];
        h[a] = idx ++;
    }
    bool topsort(int numCourses)
    {
        for (int i = 0; i < numCourses; i ++)
            if (d[i] == 0) q[++ tt] = i;

        while (hh <= tt)
        {
            int t = q[hh ++];

            for (int i = h[t]; i != -1; i = ne[i])
            {
                int j = e[i];
                if (d[j] != 0) d[j] --;
                if (d[j] == 0) q[++ tt] = j;
            }
        }

        if (tt == numCourses - 1) return true;
        return false;
    }

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        if (prerequisites.empty()) return true;
        memset(h, -1, sizeof h);
        n = prerequisites.size();
        set<pair<int, int>> used;
        for (int i = 0; i < n; i ++)
        {
            int x = prerequisites[i][0], y = prerequisites[i][1];
            if (x == y) return false;
            if (used.find({x, y}) != used.end()) return false;
            used.insert({y, x});
            add(y, x);
            d[x] ++;
        }

        if (topsort(numCourses)) return true;
        return false;
    }
};


所有笔记总结目录-CSDN博客

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值