持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第6天,点击查看活动详情
22. 括号生成
思路
(dfs)
首先我们需要知道一个结论,一个合法的括号序列需要满足两个条件:
- 1、左右括号数量相等
- 2、任意前缀中左括号数量
>=右括号数量 (也就是说每一个右括号总能找到相匹配的左括号)
题目要求我们生成n对的合法括号序列组合,可以考虑使用深度优先搜索,将搜索顺序定义为枚举序列的每一位填什么,那么最终的答案一定是有n个左括号和n个右括号组成。
如何设计dfs搜索函数?
最关键的问题在于搜索序列的当前位时,是选择填写左括号,还是选择填写右括号 ?因为我们已经知道合法的括号序列任意前缀中左括号数量一定 >= 右括号数量,因此,如果左括号数量不大于 n,我们可以放一个左括号,等待一个右括号来匹配 。如果右括号数量小于左括号的数量,我们可以放一个右括号,来使一个右括号和一个左括号相匹配。
递归树如下:
递归函数设计
void dfs(int n ,int lc, int rc ,string str)
n是括号对数,lc是左括号数量,rc是右括号数量,str是当前维护的合法括号序列。
搜索过程如下:
- 1、初始时定义序列的左括号数量
lc和右括号数量rc都为0。 - 2、如果
lc < n,左括号的个数小于n,则在当前序列str后拼接左括号。 - 3、如果
rc < n && lc > rc, 右括号的个数小于左括号的个数,则在当前序列str后拼接右括号。 - 4、当
lc == n && rc == n时,将当前合法序列str加入答案数组res中。
时间复杂度分析: 经典的卡特兰数问题,因此时间复杂度为 。
c++代码
class Solution {
public:
vector<string> res;
vector<string> generateParenthesis(int n) {
dfs(n, 0, 0, "");
return res;
}
void dfs(int n, int lc, int rc, string str){
if(lc == n && rc == n){
res.push_back(str);
return ;
}
if(lc < n) dfs(n, lc + 1, rc, str + '(');
if(rc < n && lc > rc) dfs(n, lc, rc + 1, str + ')');
}
};
23. 合并K个升序链表
思路
(优先队列)
我们可以通过双路归并合并两个有序链表,但是这题要求对多个链表进行并操作。 其实和双路归并思路类似,我们分别用指针指向该链表的头节点,每次找到这些指针中值最小的节点,然后依次连接起来,并不断向后移动指针。
如何找到一堆数中的最小值?
用小根堆维护指向k个链表当前元素最小的指针,因此这里我们需要用到优先队列,并且自定义排序规则,如下:
struct cmp{ //自定义排序规则
bool operator() (ListNode* a, ListNode* b){
return a->val > b->val; // val值小的在队列前
}
};
具体过程如下:
-
1、定义一个优先队列,并让
val值小的元素排在队列前。 -
2、新建虚拟头节点
dummy,定义cur指针并使其指向dummy。 -
3、首先将
k个链表的头节点都加入优先队列中。 -
4、当队列不为空时:
- 取出队头元素
t(队头即为k个指针中元素值最小的指针); - 令
cur的next指针指向t,并让cur后移一位; - 如果
t的next指针不为空,我们将t->next加入优先队列中;
- 取出队头元素
-
5、最后返回
dummy->next。
时间复杂度分析: ,n表示的是所有链表的总长度,k表示k个排序链表。
c++代码
/**
* 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; // val值小的在队列前
}
};
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0) return nullptr;
priority_queue<ListNode*, vector<ListNode*>, cmp> heap;
auto dummy = new ListNode(-1), cur = dummy;
for(ListNode* l : lists) if(l) heap.push(l);
while(heap.size()){
ListNode* t = heap.top(); // k个指针中元素值最小的指针t取出来
heap.pop();
cur = cur->next = t;
if(t->next) heap.push(t->next); //将t->next加入优先队列中
}
return dummy->next;
}
};
31. 下一个排列
思路
(找规律)
对于数组排列问题,我们可以知道,如果一个数组是升序数组,那么它一定是最小的排列。如果是降序数组,那么它一定是最大的排列。
而找下一个排列就是从后往前寻找第一个出现降序的地方,把这个地方的数字与后边第一个比它大的的数字交换,再把该位置之后整理为升序。
换句话说,就是为了从后往前找,找到第一个“ 可以变大的数 “,而从前往后的降序序列已经最大了,因此第一个可以变大的数一定出现在从前往后的升序序列中,即从后往前的第一个降序地方。
具体过程如下:
- 1、从数组末尾往前找,找到 第一个 位置
k,使得nums[k-1] < nums[k],则从后往前看nums[k-1],nums[k]满足降序,nums[k-1]就是第一个可以变大的数。 - 2、如果
k <=0,说明不存在这样的k,则数组是不递增的,直接将数组逆转即可。 - 3、如果存在这样的
k,则让t = k,从前往后找到第一个位置t,使得nums[t] <= nums[k-1],则nums[t-1]就是第一个大于nums[k-1]的数。 - 4、交换
nums[k-1]与nums[t-1],然后将数组从k到末尾部分逆序。
图示过程
时间复杂度分析: 遍历整个数组需要的时间为
c++代码
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());
}
}
};