❤ 作者主页:欢迎来到我的技术博客😎
❀ 个人介绍:大家好,本人热衷于Java后端开发,欢迎来交流学习哦!( ̄▽ ̄)~*
🍊 如果文章对您有帮助,记得关注、点赞、收藏、评论⭐️⭐️⭐️
📣 您的支持将是我创作的动力,让我们一起加油进步吧!!!🎉🎉
文章目录
- 一、有效的括号
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 二、合并两个有序列表
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 三、括号生成
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 四、合并K个升序链表
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 五、下一个排序
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 六、最长有效括号
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 七、搜索旋转排序数组
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 八、在排序数组中查找元素的第一个和最后一个位置
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 九、组合总和
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
- 十、接雨水
- 1. 题目描述
- 2. 思路分析
- 3. 代码实现
一、有效的括号
1. 题目描述
2. 思路分析
从前往后枚举每个字符
- 当遇到左括号,则将元素压进栈中;
- 当遇到右括号
- 如果栈为空,
return false
。 - 如果栈顶元素是对应的左括号,说明这是匹配的字符,将栈顶元素
pop
出即可。
否则,匹配不成功,return false
.
- 最后,若栈是空栈,表示所有的字符都已经匹配好了,若不是空栈,表示还存在未能匹配好的字符。
注意: 由于'{'
和' }'
以及'('
和')'
他们的字符值只相差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)
- 新建头部的保护结点
dummy
,设置cur
指针指向dummy
。 - 若当前
l
1
l_1
l1指针指向的结点的值
val
比 l 2 l_2 l2指针指向的结点的值val
小 ,则令cur
的next
指针指向 l 1 l_1 l1,且 l 1 l_1 l1后移;否则指向 l 2 l_2 l2,且 l 2 l_2 l2后移。 - 然后
cur
指针按照上一部设置好的位置后移。 - 循环以上步骤直到 l 1 l_1 l1或 l 2 l_2 l2为空。
- 将剩余的
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. 思路分析
(直接生成合法发括号序列)
- 使用
dfs
。 - 每次可以放置左括号的条件是当前左括号的数目不超过 n n n。
- 每次可以放置右括号的条件是当前右括号的数目不超过 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. 思路分析
(优先队列)
- 一开始先用小根堆存储 k k k 个排序链表的头指针,每次操作后用小根堆维护 k k k 个链表当前最小的指针,并以指针对应的值进行排序。
- 操作过程中,当小根堆不为空时,堆顶元素即当前 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)
找下一个序列就是从后往前找第一个出现降序的地方,把这个地方的前一个数字与后边某个比它大的数字交换,再把该位置之后整理为升序。
- 从数组末尾往前找,找到第一个位置
k
k
k, 使得
nums[k] > nums[k - 1]
; - 如果不存在这样的 j j j,说明数组是递减的,则直接将数组进行翻转即可;
- 如果存在这样的
j
j
j,则从末尾找到第一个位置
t
t
t,使得
nums[t] > nums[k]
; - 交换
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. 思路分析
- 首先二分找出哪个点是将数组分割成两段的;
- 确定 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] 的大小关系;
- 最后在确定的分段中二分查找 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!!!