【LeetCode 热题 HOT 100】题解笔记 —— Day02

❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注点赞收藏评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉

一、有效的括号

1. 题目描述

在这里插入图片描述


2. 思路分析

从前往后枚举每个字符

  1. 当遇到左括号,则将元素压进栈中;
  2. 当遇到右括号
  • 如果栈为空,return false
  • 如果栈顶元素是对应的左括号,说明这是匹配的字符,将栈顶元素pop 出即可。
    否则,匹配不成功,return false.
  1. 最后,若栈是空栈,表示所有的字符都已经匹配好了,若不是空栈,表示还存在未能匹配好的字符。

注意: 由于'{'' }'以及'('')'他们的字符值只相差1, 而'[''']的字符值只相差2,所以可以通过这个来判断字符是否匹配。


3. 代码实现

class Solution {
public:
    bool isValid(string s) {
        stack<char> stk;

        for (auto c : s) {
            if (c == '(' || c == '{' || c == '[') stk.push(c);
            else {
                if (stk.size() && abs(stk.top() - c) <= 2) stk.pop();
                else return false;
            }
        }
        return stk.empty();
    }
};

二、合并两个有序列表

1. 题目描述

在这里插入图片描述


2. 思路分析

(线性合并) O(n)

  1. 新建头部的保护结点dummy,设置cur 指针指向dummy
  2. 若当前 l 1 l_1 l1指针指向的结点的值val l 2 l_2 l2指针指向的结点的值val小 ,则令curnext指针指向 l 1 l_1 l1,且 l 1 l_1 l1后移;否则指向 l 2 l_2 l2,且 l 2 l_2 l2后移。
  3. 然后cur指针按照上一部设置好的位置后移。
  4. 循环以上步骤直到 l 1 l_1 l1 l 2 l_2 l2为空。
  5. 将剩余的 l 1 l_1 l1 l 2 l_2 l2接到cur指针后边。

3. 代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        auto dummy = new ListNode(-1), cur = dummy;

        while (l1 && l2) {
            if (l1->val < l2->val) {
                cur = cur->next = l1;
                l1 = l1->next;
            } else {
                cur = cur->next = l2;
                l2 = l2->next;
            }
        }
        if (l1) cur->next = l1;
        if (l2) cur->next = l2;
        return dummy->next;
    }
};

三、括号生成

1. 题目描述

在这里插入图片描述


2. 思路分析

(直接生成合法发括号序列)

  1. 使用dfs
  2. 每次可以放置左括号的条件是当前左括号的数目不超过 n n n
  3. 每次可以放置右括号的条件是当前右括号的数目不超过 n n n 并且不超过左括号的数目。

3. 代码实现

class Solution {
public:
    vector<string> ans;

    vector<string> generateParenthesis(int n) {
        dfs(n, 0, 0, "");
        return ans;
    }

    void dfs(int n, int lc, int rc, string path) {
        if (lc == n && rc == n) ans.push_back(path);
        else {
            if (lc < n) dfs(n, lc + 1, rc, path + '(');
            if (rc < n && lc > rc) dfs(n, lc, rc + 1, path + ')');
        }
    }
};

四、合并K个升序链表

1. 题目描述

在这里插入图片描述


2. 思路分析

(优先队列)

  1. 一开始先用小根堆存储 k k k 个排序链表的头指针,每次操作后用小根堆维护 k k k 个链表当前最小的指针,并以指针对应的值进行排序。
  2. 操作过程中,当小根堆不为空时,堆顶元素即当前 k k k 个排序链表当前最小的元素的指针 t t t,将该值加入到 d u m m y dummy dummy 链表的后面,并把 t t t 指针往后移动一位,使得 t t t 指针指向的值变大,再加入到小根堆中。

3. 代码实现

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:

    struct Cmp {
        bool operator() (ListNode* a, ListNode* b) {
            return a->val > b->val;
        }
    };

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*, vector<ListNode*>, Cmp> heap;

        auto dummy = new ListNode(-1), tail = dummy;
        for (auto l : lists) if (l) heap.push(l);

        while (heap.size()) {
            auto t = heap.top();
            heap.pop();

            tail =  tail->next = t;
            if (t->next) heap.push(t->next);
        }
        return dummy->next;
    }
};

五、下一个排序

1. 题目描述

在这里插入图片描述


2. 思路分析

(找规律) O ( n ) O(n) O(n)

找下一个序列就是从后往前找第一个出现降序的地方,把这个地方的前一个数字与后边某个比它大的数字交换,再把该位置之后整理为升序。

  1. 从数组末尾往前找,找到第一个位置 k k k, 使得 nums[k] > nums[k - 1]
  2. 如果不存在这样的 j j j,说明数组是递减的,则直接将数组进行翻转即可;
  3. 如果存在这样的 j j j,则从末尾找到第一个位置 t t t,使得 nums[t] > nums[k];
  4. 交换 nums[t - 1]nums[k - 1],然后将数组从 k + 1 k + 1 k+1 到末尾部分继续逆转。

3. 代码实现

class Solution {
public:
    void nextPermutation(vector<int>& nums) {
        int k = nums.size() - 1;
        while (k > 0 && nums[k - 1] >= nums[k]) k --;
        if (k <= 0) {
            reverse(nums.begin(), nums.end());
        } else {
            int t = k;
            while (t < nums.size() && nums[t] > nums[k - 1]) t ++;
            swap(nums[t - 1], nums[k - 1]);
            reverse(nums.begin() + k, nums.end());
        }
    }
};

六、最长有效括号

1. 题目描述

在这里插入图片描述


2. 思路分析

已知每个有限的括号字符串必须是连续的,那么可以用栈来寻找以某个字符结尾最长的有限长度。

具体做法是我们始终保持栈底元素为当前已经遍历过的元素中 最后一个没有被匹配的右括号的下标,这样的做法主要是考虑了边界条件的处理,栈里其他元素维护左括号的下标

  • 对于遇到的每个 ‘(’ ,我们将它的下标放入栈中
  • 对于遇到的每个 ‘)’ ,我们先弹出栈顶元素表示匹配了当前右括号:
    • 如果栈为空,说明当前的右括号为没有被匹配的右括号,我们将其下标放入栈中来更新我们之前提到的 最后一个没有被匹配的右括号的下标;
    • 如果栈不为空,当前 右括号的下标减去栈顶元素 即为 以该右括号为结尾的最长有效括号的长度;

我们从前往后遍历字符串并更新答案即可。

注意: 如果一开始栈为空,第一个字符为左括号的时候我们会将其放入栈中,这样就不满足提及的 最后一个没有被匹配的右括号的下标,为了保持统一,我们在一开始的时候往栈中放入一个值为 − 1 −1 1 的元素。


3. 代码实现

class Solution {
public:
    int longestValidParentheses(string s) {
        stack<int> stk;
        int res = 0;
        stk.push(-1);

        for (int i = 0; i < s.size(); i ++) {
            if (s[i] == '(') stk.push(i);
            else {
                stk.pop();
                if (stk.empty()) stk.push(i);
                else res = max(res, i - stk.top());
            }
        }
        return res;
    }
};

七、搜索旋转排序数组

1. 题目描述

在这里插入图片描述


2. 思路分析

  1. 首先二分找出哪个点是将数组分割成两段的;
  2. 确定 t a r g e t target target 是在哪一段的,划分条件是 t a r g e t target target n u m s [ 0 ] nums[0] nums[0] 的大小关系;
  3. 最后在确定的分段中二分查找 t a r g e t target target 即可。

3. 代码实现

class Solution {
public:
    int search(vector<int>& nums, int target) {
        if (nums.empty()) return -1;

        // 二分找分割点, 这里用性质>=nums[0], 所以当找到边界时, 它将是最后一个比nums[0]大的数
        int l = 0, r = nums.size() - 1;
        while(l < r) {
            int mid = l + r + 1 >> 1;
            if (nums[mid] >= nums[0]) l = mid;
            else r = mid - 1; 
        }

        //确定target在哪一段
        if (target >= nums[0]) l = 0;
        else l = r + 1, r = nums.size() - 1;

        //二分查找目标值
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }

        // 这里写成nums[r], 当数组只有一个元素时, 两个二分查找代码都没有走, 而l在上面被+1, 这时会越界, 而r是length-1还是0, 不会产生越界
        if (nums[r] == target) return r;
        else return -1;
    }
};

八、在排序数组中查找元素的第一个和最后一个位置

1. 题目描述

在这里插入图片描述


2. 思路分析

直接进行二分查找即可找到目标值的第一个位置以及最后一个位置。


3. 代码实现

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        if (nums.empty()) return {-1, -1};

        int l = 0, r = nums.size() - 1;
        while (l < r) {
            int mid = l + r >> 1;
            if (nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        if (nums[l] != target) return {-1, -1};

        int L = r;

        l = 0, r = nums.size() - 1;
        while (l < r) {
            int mid = l + r + 1 >> 1;
            if (nums[mid] <= target) l = mid;
            else r = mid - 1;
        }
        return {L, r};
    }
};

九、组合总和

1. 题目描述

在这里插入图片描述


2. 思路分析

直接进行深度优先搜索即可。
在这里插入图片描述


3. 代码实现

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;

    vector<vector<int>> combinationSum(vector<int>& c, int target) {
        dfs(c, 0, target);
        return ans;
    }

    void dfs(vector<int>& c, int u, int target) {
        if (target == 0) {
            ans.push_back(path);
            return;
        }
        if (u == c.size()) return;

        //枚举一下当前的数可以选几个,可以选0个,或者选i个总和不超过target的个数
        for (int i = 0; c[u] * i <= target; i ++ ) {
            //第一次选了0个,也就是什么都没选,不需要记录此次的路径,所以直接dfs下一个
            dfs(c, u + 1, target - c[u] * i);
            path.push_back(c[u]);
        }

        // 回溯,恢复现场
        for (int i = 0; c[u] * i <= target; i ++ ) {
            path.pop_back();
        }
    }
};

十、接雨水

1. 题目描述

在这里插入图片描述


2. 思路分析

单调栈是本文想要重点说明的一个方法。

因为本题是一道典型的单调栈的应用题。

简单来说就是当前柱子如果小于等于栈顶元素,说明形不成凹槽,则将当前柱子入栈;反之若当前柱子大于栈顶元素,说明形成了凹槽,于是将栈中小于当前柱子的元素pop出来,将凹槽的大小累加到结果中。

3. 代码实现

class Solution {
    public int trap(int[] height) {
        Stack<Integer> stack = new Stack<>();
        int res = 0;
        // 遍历每个柱体
        for (int i = 0; i < height.length; i++) {
           while (!stack.isEmpty() && height[stack.peek()] < height[i]) {
                int bottomIdx = stack.pop();
                // 如果栈顶元素一直相等,那么全都pop出去,只留第一个。
                while (!stack.isEmpty() && height[stack.peek()] == height[bottomIdx]) {
                    stack.pop();
                }
                if (!stack.isEmpty()) {
                    // stack.peek()是此次接住的雨水的左边界的位置,右边界是当前的柱体,即i。
                    // Math.min(height[stack.peek()], height[i]) 是左右柱子高度的min,减去height[bottomIdx]就是雨水的高度。
                    // i - stack.peek() - 1 是雨水的宽度。
                    res += (Math.min(height[stack.peek()], height[i]) - height[bottomIdx]) * (i - stack.peek() - 1);
                }
            }
            stack.push(i);
        }
        return res;
    }
}

 
非常感谢您阅读到这里,如果这篇文章对您有帮助,希望能留下您的点赞👍 关注💖 分享👥 留言💬thanks!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Java技术一点通

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值