leetcode热题HoT 100

本文详细解析了LeetCode中的热门算法问题,包括寻找两个正序数组的中位数、正则表达式匹配、矩阵中的路径等,并提供了每道题目的思路和代码实现,涵盖了动态规划、回溯、二叉树等多种算法思想。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

4. 寻找两个正序数组的中位数

 

思路

/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码

class Solution {
public:
    int getKthNum(vector<int>& nums1, vector<int>& nums2, int k){
        int len1 = nums1.size(), len2 = nums2.size();
        int index1 = 0, index2 = 0;
        while(true){
            if(index1 == len1){
                return nums2[index2 + k - 1];
            }
            if(index2 == len2){
                return nums1[index1 + k -1];
            }
            if(k == 1){
                return min(nums1[index1], nums2[index2]);
            }
            
            int pos1 = min(index1 + k/2 - 1, len1 - 1);
            int pos2 = min(index2 + k/2 - 1, len2 - 1);
            if(nums1[pos1] <= nums2[pos2]){
                k -= pos1 - index1 + 1;
                index1 = pos1 + 1;
            }
            else{
                k -= pos2 - index2 + 1;
                index2 = pos2 + 1;
            }    
        }
    }
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int total_len = nums1.size() + nums2.size();
        if(total_len % 2 == 1){
            return getKthNum(nums1, nums2, (total_len + 1) / 2);
        }
        else{
            return (getKthNum(nums1, nums2, total_len / 2) + getKthNum(nums1, nums2, total_len / 2 + 1)) / 2.0;
        }
    }
};

10. 正则表达式匹配

10.1 思路

1、动态规划:

dp[i][j] 表示s[0 - i]在p[0 - j]中是否匹配。有两种情况:

(1)如果p[j] != '.',可以把' . '当作一个特殊的字母,则有:

如果s[i - 1] == p[j - 1],则dp[i][j] = dp[ i - 1][j - 1];否则为false。

(2)如果p[j] == '*',则有两种情况:

*前的元素重复使用,则dp[i][j] = dp[i - 1][j],通俗理解为,是否匹配取决于s[0, i -1]和p[0, j]的匹配关系

*前的元素被舍弃,则dp[i][j] = dp[i][j - 2],通俗理解为,是否匹配取决于s[0, i]和p[0, j - 2]的匹配关系

当s[i] == p[j - 1]时,则*前元素可舍弃,可不舍弃,两者取或

当s[i] != p[j - 1]时,则*前元素必须舍弃

class Solution {
public:
    bool isMatch(string s, string p) {
        s = " " + s;
        p = " " + p;//一定要保证第一个数匹配成功
        vector<vector<bool>> dp(s.size() + 1, vector<bool>(p.size() + 1, false));
        dp[0][0] = true;
        for(int i = 1; i <= s.size(); i++){
            for(int j = 1; j <= p.size(); j++){
                if(p[j - 1] != '*'){
                    if(s[i - 1] == p[j - 1] || p[j - 1] == '.'){
                        dp[i][j] = dp[i - 1][j - 1];
                    }
                    else{
                        dp[i][j] = false;
                    }
                }                
                else{
                        if(s[i - 1] == p[j - 2] || p[j - 2] == '.'){
                            dp[i][j] = dp[i - 1][j] || dp[i][j - 2] || dp[i][j - 1];
                        }
                        else{
                            dp[i][j] = dp[i][j - 2];
                        }
                }
                cout<<dp[i][j]<<" ";
            }
            cout<<endl;
        }
        return dp[s.size()][p.size()];
    }
};

10.2 代码

剑指offer 矩阵中的路径

思路

回溯+剪枝(遇到边界直接返回)

代码

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        rows = board.size();
        cols = board[0].size();
        for(int i = 0; i < rows; i++) {
            for(int j = 0; j < cols; j++) {
                if(dfs(board, word, i, j, 0)) return true;
            }
        }
        return false;
    }
private:
    int rows, cols;
    bool dfs(vector<vector<char>>& board, string word, int i, int j, int k) {
        if(i >= rows || i < 0 || j >= cols || j < 0 || board[i][j] != word[k]) return false;
        if(k == word.size() - 1) return true;
        board[i][j] = '\0'; //做标记,表示已经使用,回溯
        bool res = dfs(board, word, i + 1, j, k + 1) || dfs(board, word, i - 1, j, k + 1) || 
                      dfs(board, word, i, j + 1, k + 1) || dfs(board, word, i , j - 1, k + 1);
        board[i][j] = word[k]; //还原
        return res;
    }
};

括号生成

思路

回溯:因为一定是现有左括号,再有右括号,所以如代码所示

代码

class Solution {
    void backtrack(vector<string>& ans, string& cur, int open, int close, int n) {
        if (cur.size() == n * 2) { //数量够就成功
            ans.push_back(cur);
            return;
        } 
        if (open < n) { //左括号是小于括号对数
            cur.push_back('(');
            backtrack(ans, cur, open + 1, close, n);
            cur.pop_back();
        }
        if (close < open) { //右括号必须小于左括号,省去了判断是否合理
            cur.push_back(')');
            backtrack(ans, cur, open, close + 1, n);
            cur.pop_back();
        }
    }
public:
    vector<string> generateParenthesis(int n) {
        vector<string> result;
        string current;
        backtrack(result, current, 0, 0, n);
        return result;
    }
};

最大矩形

思路

和单调栈中的求最大矩形的思路基本类似:只不过每一层,类似一个最大矩形,逐层求出最大矩形即可。

代码

class Solution {
public:
    int maximalRectangle(vector<vector<char>>& matrix) {
        vector<int> height(matrix[0].size() + 2, 0); //左右两边设置最低边界
        stack<int> stack;
        int maxArea = 0;
        for(int i = 0; i < matrix.size(); i++){ //逐层找出最大矩形
            for(int j = 0; j < matrix[0].size(); j++){
                if(matrix[i][j] == '0'){
                    height[j + 1] = 0;
                }
                else{
                    height[j + 1] += 1;
                }
            }
            stack.push(0);
            for(int j = 1; j < height.size(); j++){
                if(height[j] > height[stack.top()]){
                    stack.push(j);
                }
                else if(height[j] == height[stack.top()]){
                    stack.pop(); //其实替不替换都能成功
                    stack.push(j);
                }
                else{
                    while(!stack.empty() && height[j] < height[stack.top()]){
                        int mid = stack.top();
                        stack.pop();
                        int left = stack.top();
                        maxArea = max(height[mid] * (j - left - 1), maxArea);
                        cout << maxArea <<endl;
                    }
                    stack.push(j);
                }
            }
        }
        return maxArea;
    }
};

二叉树中的最大路径和

思路

树状dp,类似于求数组中的最大数组子集和,dp[i]表示以i结尾的数组的子集的最大和,dp[i] = max(dp[i - 1], 0) + val;

所以本题后序遍历,拿到节点的左右子树的dp,分别表示为dp[L]和dp[R],且其小于0时直接取0。并记录最大值。

代码

class Solution {
public:
    int result = INT_MIN;
    int postTravel(TreeNode* root){
        if(root == nullptr){
            return 0;
        }
        int left = postTravel(root->left);
        int right = postTravel(root->right);
        int dpL = max(left, 0);
        int dpR = max(right, 0);
        result = max(result, dpL + dpR + root->val);
        return max(dpL, dpR) + root->val;
    }
    int maxPathSum(TreeNode* root) {
        postTravel(root);
        return result;
    }
};

岛屿数量

思路

岛屿类问题,有限使用dfs,深度优先遍历,不断扩充找到连续的岛屿,每一次dfs,岛屿数量+1

代码

class Solution {
public:
    void dfs(vector<vector<char>>& grid, int i, int j){
        if(i >= grid.size() || j >= grid[0].size() || i < 0 || j < 0){
            return;
        }
        if(grid[i][j] == '0'){
            return;
        }
        grid[i][j] = '0';
        dfs(grid, i + 1, j);
        dfs(grid, i - 1, j);
        dfs(grid, i, j + 1);
        dfs(grid, i, j - 1);
    }
    int numIslands(vector<vector<char>>& grid) {
        int num_island = 0;
        for(int i = 0; i < grid.size(); i++){
            for(int j = 0; j < grid[0].size(); j++){
                if(grid[i][j] == '1'){
                    dfs(grid, i, j);
                    num_island++;
                }
            }
        }
        return num_island;
    }
};

课程表

思路

这是一道经典的拓扑排序的问题,我们使用深度有限遍历来实现拓扑排序。

先统计所有节点的入度,对于入度为0的节点就可以分离出来,然后把这个节点指向的节点的入度减一。

一直做改操作,直到所有的节点都被分离出来。

如果最后不存在入度为0的节点,那就说明有环,不存在拓扑排序,也就是很多题目的无解的情况。

代码

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        if(prerequisites.size() == 0){
            return true;
        }
        unordered_map<int, int> indegree;
        unordered_map<int, int> x; //判断初始化入度为0的点不重复
        queue<int> indegree0;
        //初始化入度表
        for(int i = 0; i < prerequisites.size(); i++){
            indegree[prerequisites[i][0]]++;
        }
        //初始化入度为0的节点
        for(int i = 0; i < prerequisites.size(); i++){
            if(indegree.count(prerequisites[i][1]) == 0){
                if(x.count(prerequisites[i][1]) == 0){
                    indegree0.push(prerequisites[i][1]);
                    x[prerequisites[i][1]]++;
                }                
            }
        }
        while(!indegree0.empty()){
            int course = indegree0.front();
            indegree0.pop();
            for(int i = 0; i < prerequisites.size(); i++){
                if(course == prerequisites[i][1] && indegree.count(prerequisites[i][0]) != 0){
                    indegree[prerequisites[i][0]]--;
                    if(indegree[prerequisites[i][0]] == 0){
                        indegree0.push(prerequisites[i][0]);
                        indegree.erase(prerequisites[i][0]);
                    }
                }
            }
            numCourses--;
            if(numCourses == 0){
                return true;
            }
        }
        if(numCourses > 0 && indegree.size() > 0){
            return false;
        }
        return true;
    }
};

最大正方形

思路

1、可以使用单调栈,和最大矩形的做法一样,也可以使用动态规划

2、动态规划:dp[i][j]表示以ij为右下角的正方形的最大边长。

应该为其左边,上边,和左上三个dp的最小值+1。

代码

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        int m = matrix.size();
        int n = matrix[0].size();
        int maxside = 0;
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(matrix[i][j] == '1'){
                    if(i == 0 || j == 0){
                        dp[i][j] = 1;
                    }
                    else{
                        dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]}) + 1;
                    }
                    maxside = max(maxside, dp[i][j]);
                }
            }
        }
        return maxside * maxside;
    }
};

除自身以外数组的乘积

思路

不能使用除法:则我们需要将每一个元素的左边累乘和右边累乘相乘即可。

即遍历数组两遍,求出元素的左右累乘的乘积。

代码

class Solution {
public:
    vector<int> productExceptSelf(vector<int>& nums) {
        int n = nums.size();
        vector<int> result(n, 1);
        int left = 1, right = 1;
        for(int i = 0; i < n; i++){
            result[i] *= left ; //左边的累积
            left *= nums[i];
            result[n - i - 1] *= right; //右边的累积
            right *= nums[n - i - 1];
        }
        return result;
    }
};

寻找重复数

思路

本题和找到环的入口一题一样,但是难以想到。将数组的值作为下一个的索引

代码

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        //和环形链表题类似,以下标和值作为索引
        int low = 0;
        int fast = 0;
        low = nums[low];
        fast = nums[nums[fast]];
        while(low != fast){
            low = nums[low];
            fast = nums[nums[fast]];
        }
        fast = 0;
        while(low != fast){
            low = nums[low];
            fast = nums[fast];
        }
        return low;
    }
};

二叉树序列化

思路

很多方法,但是优雅的字符串流

先用先序遍历将节点存起来,如果是null就存”#“

使用字节流分割

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:

    // Encodes a tree to a single string.
    string serialize(TreeNode* root) { 
        if( root==NULL ) return "#";
        //先序遍历,这种遍历很难写的啦
        return to_string(root->val) + ' ' + serialize(root->left) + ' ' + serialize(root->right); 
    }

    TreeNode *dfs( istringstream &s ){
        string tmp;
        //按空格分割
        s>>tmp;
        if( tmp=="#" ) return nullptr;
        TreeNode* node = new TreeNode(stoi(tmp));
        
        node->left = dfs(s);//因为流会读完前一部分,所以后一部分和前一部分的s不同
        node->right= dfs(s);
        return node;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        istringstream ss(data); //创建字节流
        return dfs(ss); 
    }
};

// Your Codec object will be instantiated and called as such:
// Codec ser, deser;
// TreeNode* ans = deser.deserialize(ser.serialize(root));

戳气球

思路

此题乍一看是01背包问题,其实没那么简单

具体思路见力扣题解

要特别注意动态规划的遍历顺序,因为dp[i][j] = dp[i][k] + dp[k][j] + val,所以需要从每一个小区间慢慢构建为大区间,也即自底向上。

代码

class Solution {
public:
    int maxCoins(vector<int>& nums) {
        //dp[i][j]表示在开区间(i, j)中的戳破气球的最大值
        int n = nums.size();
        vector<vector<int>> dp(n + 2, vector<int>(n + 2, 0));
        vector<int> val(n + 2, 1);
        for(int i = 1; i < n + 1; i++){
            val[i] = nums[i - 1];
        }
        //从最小的区间开始,即区间内只存在最后一个气球,戳破获得的最大价值
        for(int i = n - 1; i >= 0; i--){
            for(int j = i + 2; j < n + 2; j++){
                for(int k = i + 1; k < j; k++){ 
                    int sum = val[i] * val[k] *val[j];
                    sum += dp[i][k] + dp[k][j]; //之后进行分治,大区间由小区间构成
                    dp[i][j] = max(dp[i][j], sum);
                }
            }
        }
        return dp[0][n + 1];
    }
};

和为K的子数组

思路

因为存在负数,所以使用双指针是行不通的,应使用前缀和的思路

代码

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        //不能使用双指针,因为存在负数
        //需要使用前缀和,遍历得到每一个索引的前缀和,后一个前缀和 - 前一个前缀和 = k则找到
        unordered_map<int, int> map;
        int sum = 0;
        int result = 0;
        map[0] = 1; //前缀和为0的节点数为1 
        for(int num : nums){
            sum += num; //该节点的前缀和
            result += map[sum - k]; //如果当前前缀和 - k 为之前的前缀和 且存在,则次数可相加
            map[sum]++; //更新现在的前缀和
        }      
        return result;
    }
};

任务调度器

思路

代码

class Solution {
public:
    int leastInterval(vector<char>& tasks, int n) {
        int len = tasks.size();
        vector<int> task_num(26);
        for(char ch : tasks){ 
            task_num[ch - 'A']++; //每个任务的数量
        }
        sort(task_num.rbegin(), task_num.rend()); //任务数量多的排在前面
        int cnt = 1;
        while(cnt < task_num.size() && task_num[cnt] == task_num[0]){ 
            cnt++; //求出最后一个桶有多少个任务
        }
        //要么任务之间没有间隙,则执行时间为任务长度,要么有间隙,则为前桶的体积加最后一个桶的宽度
        return max(len, (task_num[0] - 1) * (n + 1) + cnt); 
    }
};

除法求值

思路

图论的题目,a/b = 2可以理解为a = 2 * b,其中a, b为图中的两个节点,2为节点之间的权值。所以这题的一种思路就是进行搜索,dfs或bfs,搜索指定路径上的节点,比如找a/c,可以找到a/b,再找b/c,遍历的节点顺序为a, b, c,将该路径上的权值之积就出来即可。

所以本题的难点:1、建图,2、深搜的实现。

代码 

class Solution {
public:
    //定义一个邻接表
    unordered_map<string, vector<string>> nodeNeighber;
    //定义一个边表
    map<pair<string, string>, double> edge;
    //定义一个以访问节点的表
    set<string> visited;
    double dfs(string start, string end){
        //左右节点均没有邻居 或 该节点已经访问过,则直接返回
        if(visited.count(start) || !nodeNeighber.count(start) || !nodeNeighber.count(end)){
            return 0.0;
        }
        //如果有直接相连的边,直接返回
        if(edge.count(make_pair(start, end))) return edge[make_pair(start, end)];
        if(edge.count(make_pair(end, start))) return edge[make_pair(end, start)];
        //没有直接相连的边
        visited.insert(start); 
        vector<string> neighbers = nodeNeighber[start];
        double res = 0.0;
        for(string neighber : neighbers){        
            double temp = dfs(neighber, end);
            if(temp > 0){ //如果neighber能到达end,则可以计算两数之积
                res = temp * edge[make_pair(start, neighber)];
                //生成两个边,方便优化
                edge[make_pair(start, end)] = res;
                edge[make_pair(end, start)] = 1.0 / res;
                break;
            }    
        }
        visited.erase(start);
        return res;
    }

    vector<double> calcEquation(vector<vector<string>>& equations, vector<double>& values, vector<vector<string>>& queries) {
        //初始化邻接表和边表
        vector<double> result;
        int index = 0;
        for(vector<string> equation: equations){
            nodeNeighber[equation[0]].push_back(equation[1]);
            nodeNeighber[equation[1]].push_back(equation[0]);
            edge[make_pair(equation[0], equation[1])] = values[index];
            edge[make_pair(equation[1], equation[0])] = 1.0 / values[index];
            // cout<<edge[make_pair(equation[0], equation[1])]<<" "<<edge[make_pair(equation[1], equation[0])]<<endl;
            index++;
        }
        //开始遍历queries数组
        for(vector<string> querie : queries){
            double res = dfs(querie[0], querie[1]);
            if(res == 0.0){
                result.push_back(-1);
            }
            else{
                result.push_back(res);
            }           
        }
        return result;
    }
    
};

路径总和III

思路

使用前缀和的思想:每个节点维系从根节点到该节点的前缀和。如果该节点的前缀和为curnum,则如果在前面的前缀和中存在curnum - target,那说明这两个节点之间的差为target,即找到。

需要用hash来存储指定前缀和的数量,回溯之和,要把前缀和减去当前节点值,防止curnum一直累加(因为是一个全局的值)

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    unordered_map<int, int> map;
    int result = 0;
    void preflexSum(TreeNode* root, int targetSum, int& cursum){
        if(root == nullptr){
            return;
        }
        cursum += root->val;
        if(map.find(cursum - targetSum) != map.end()){
            result += map[cursum - targetSum];
        }
        map[cursum]++;
        preflexSum(root->left, targetSum, cursum);
        preflexSum(root->right, targetSum, cursum);
        map[cursum]--;
        cursum -= root->val;
    }
    int pathSum(TreeNode* root, int targetSum) {
        map[0] = 1;
        int cursum = 0;
        preflexSum(root, targetSum, cursum);
        return result;
    }

};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值