目录
8--在排序数组中查找元素的第一个和最后一个位置(34)
1--有效的括号(20)
主要思路:
利用栈,遍历字符串,遇到左括号则入栈,遇到右括号则出栈,并判断出栈元素是否与右括号匹配;
当字符串有效时,栈为空(所有左括号都匹配出栈);当字符串无效时,则栈不为空(仍有未匹配的左括号);
#include <iostream>
#include <string>
#include <stack>
class Solution {
public:
bool isValid(std::string s) {
int len = s.length();
if (len == 0) return true;
for(int i = 0; i < len; i++){
if (s[i] == '(' || s[i] == '{' || s[i] == '['){
st.push(s[i]);
}
else if(s[i] == ')' && !st.empty()){
char c = st.top();
st.pop();
if (c != '(') return false;
}
else if(s[i] == '}' && !st.empty()){
char c = st.top();
st.pop();
if (c != '{') return false;
}
else if(s[i] == ']' && !st.empty()){
char c = st.top();
st.pop();
if (c != '[') return false;
}
else
return false;
}
return st.empty();
}
private:
std::stack<char> st;
};
int main(int argc, char *argv[]){
std::string test = "()[]{}";
Solution S1;
bool res = S1.isValid(test);
if(res) std::cout << "true" << std::endl;
else std::cout << "false" << std::endl;
return 0;
}
2--合并两个有序链表(21)
主要思路:
归并排序,分别遍历比较两个链表的结点,数值小的结点归并到新链表中;
#include <iostream>
#include <string>
#include <stack>
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* list1, ListNode* list2) {
if(list1 == NULL && list2 == NULL) return NULL;
ListNode* head = new ListNode();
ListNode* l_back = head; // 指向新链表最后一个结点
ListNode* l1 = list1;
ListNode* l2 = list2;
while(l1 != NULL && l2 != NULL){
if(l1->val < l2->val){
l_back->next = l1;
l1 = l1->next;
l_back = l_back->next;
}
else{
l_back->next = l2;
l2 = l2->next;
l_back = l_back->next;
}
}
if(l1 == NULL) l_back->next = l2;
if(l2 == NULL) l_back->next = l1;
return head->next;
}
};
int main(int argc, char *argv[]){
ListNode* Node1 = new ListNode(1);
ListNode* Node2 = new ListNode(2);
ListNode* Node3 = new ListNode(4);
Node1->next = Node2;
Node2->next = Node3;
ListNode* Node4 = new ListNode(1);
ListNode* Node5 = new ListNode(3);
ListNode* Node6 = new ListNode(4);
Node4->next = Node5;
Node5->next = Node6;
Solution S1;
ListNode* l = S1.mergeTwoLists(Node1, Node4);
while(l != NULL){
std::cout << l->val << " ";
l = l->next;
}
return 0;
}
3--括号生成(22)
主要思路:视频讲解
递归遍历第 num 个字符是属于左括号还是右括号,递归终止条件是剩下的左括号和右括号数为 0;
有效的括号组合必须是当前剩下的右括号数必须大于剩下的左括号数;
#include <iostream>
#include <string>
#include <vector>
class Solution {
public:
std::vector<std::string> generateParenthesis(int n) {
recur(n, n, 0);
return Res;
}
// 还剩下 left 个和 right 个左括号和右括号,当前要填第 num 个字符
void recur(int left, int right, int num){
if(left == 0 && right == 0){
c[num] = 0;
Res.push_back(c);
return;
}
if(left){ // 剩下的左括号不为 0
c[num] = '(';
recur(left - 1, right, num + 1);
}
if(left < right){ // 剩下的右括号必须大于剩下的左括号
c[num] = ')';
recur(left, right - 1, num + 1);
}
}
private:
std::vector<std::string> Res;
char c[20];
};
int main(int argc, char *argv[]){
int n = 3;
Solution S1;
std::vector<std::string> Res = S1.generateParenthesis(n);
for(std::string value : Res){
std::cout << value << std::endl;
}
return 0;
}
4--合并K个升序链表(23)
主要思路:视频讲解参考
与归并排序两个有序链表的思想类似,可以用 k 个结点指针指向 k 个链表的结点,每次归并值最小的结点到链表中,同时结点下移;
为了方便比较 k 个链表结点的值大小,可以用一个集合来存储和维护 k 个链表结点,其中这个集合默认以升序排列;则每次只需要取集合的头元素出来即可,并将该元素的下一个结点重新存储在 k 个链表结点中;
#include <iostream>
#include <queue>
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* mergeKLists(std::vector<ListNode*>& lists) {
using Elem = std::pair<int, ListNode*>;
// 定义一个堆,用于存储 k 个头结点(升序排列)
std::priority_queue<Elem, std::vector<Elem>, std::greater<Elem>> Stack;
// 用 k 个链表的头结点初始化堆
for(ListNode* item : lists) if(item){
Stack.push({item->val, item});
}
// 定义归并后的链表
ListNode *head = nullptr;
ListNode *tail = nullptr;
while(!Stack.empty()){
Elem tmp = Stack.top();
Stack.pop();
if(head == nullptr){
head = tail = tmp.second; // 空链表时第一个结点作为头节点
}
else{
tail->next = tmp.second; // 链表非空,将新结点加入到链表尾部
tail = tail->next; // 更新尾指针
}
if(tmp.second->next != nullptr){ // 将下一个结点放到堆中,用于下一轮的比较
Stack.push({tmp.second->next->val, tmp.second->next});
}
}
return head;
}
};
int main(int argc, char *argv[]){
ListNode *Node1 = new ListNode(1);
ListNode *Node2 = new ListNode(4);
ListNode *Node3 = new ListNode(5);
Node1->next = Node2;
Node2->next = Node3;
ListNode *Node4 = new ListNode(1);
ListNode *Node5 = new ListNode(3);
ListNode *Node6 = new ListNode(4);
Node4->next = Node5;
Node5->next = Node6;
ListNode *Node7 = new ListNode(2);
ListNode *Node8 = new ListNode(6);
Node7->next = Node8;
std::vector<ListNode*> Lists = {Node1, Node4, Node7};
Solution S1;
ListNode *NewList = S1.mergeKLists(Lists);
while(NewList != nullptr){
std::cout << NewList->val << " ";
NewList = NewList->next;
}
return 0;
}
5--下一个排列(31)
主要思路:
先判断当前排列是否是最大的排列;
不是最大的排列,记录第一个不满足 a[i] > a[i+1] 的位置;
从尾开始遍历,找到第一个 a[j] > a[i] 的位置,交换 a[j] 和 a[i],并将 a[i] 之后的数据反转,使其变成升序;(结合实例比较容易理解:实例讲解)
#include <iostream>
#include <vector>
#include <algorithm>
class Solution {
public:
void nextPermutation(std::vector<int>& nums) {
int len = nums.size();
int i;
for(i = len - 2; i >= 0; i--){
if(nums[i+1] > nums[i]) break;
}
if(i == -1){ // 最大的排列
std::reverse(nums.begin(), nums.end());
return;
}
int j;
for(j = len - 1; j > i; j--){
if(nums[j] > nums[i]) break;
}
std::swap(nums[i], nums[j]);
std::reverse(nums.begin()+i+1, nums.end());
}
};
int main(int argc, char *argv[]){
Solution S1;
std::vector<int> input = {0, 3, 5, 4, 2, 1};
S1.nextPermutation(input);
for(int item : input){
std::cout << item << " ";
}
return 0;
}
6--最长有效括号(32)
主要思路:
左括号入栈,每遇到一个右括号弹出一个栈顶元素;当栈空时,表明某一段合法的字符串已经完全匹配成功,这时可以重新计算下一段合法的字符串;
为了正确计算合法字符串的长度,需要记录一个左边界元素,初始化为-1;有效字符串长度等于最后一个合法字符串的索引值减去左边界元素;
视频讲解参考:最长有效括号
#include <iostream>
#include <string>
#include <stack>
class Solution {
public:
int longestValidParentheses(std::string s) {
int n = s.length();
std::stack<int> stk;
stk.push(-1); // 左边界
int ret = 0;
for(int i = 0; i < n; i++){
if(s[i] == '(') stk.push(i); // 左括号压栈
else{ // 右括号
if(stk.top() == -1 || s[stk.top()] == ')'){ // 无法匹配的情况
stk.push(i); // 更新左边界
}else{
stk.pop();
int b = stk.top();
ret = std::max(ret, i - b); // 计算有效长度
}
}
}
return ret;
}
};
int main(int argc, char *argv[]){
Solution S1;
std::string test = "()";
int res = S1.longestValidParentheses(test);
std::cout << res << std::endl;
return 0;
}
7--搜索旋转排序数组(33)
主要思路:
基于二分法,二分后肯定有一边是有序的,因此首先判断哪一边是有序,接着继续判断target是否在有序部分,如果不在则下一次二分的范围是无序部分;
本题一个细节是边界=号的取法需要细心;
#include <iostream>
#include <vector>
class Solution {
public:
int search(std::vector<int>& nums, int target) {
if(nums.size() == 0) return -1;
if (nums.size() == 1) {
return nums[0] == target ? 0 : -1;
}
int l = 0;
int r = nums.size() - 1;
while(l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] == target) return mid;
if(nums[l] <= nums[mid]){ // [l, mid-1]有序
if(nums[l] <= target && target < nums[mid]){
r = mid - 1;
}
else{
l = mid + 1;
}
}
else{ // [mid, r]有序
if(nums[mid] < target && target <= nums[r]){
l = mid + 1;
}
else{
r = mid - 1;
}
}
}
return -1;
}
};
int main(int argc, char *argv[]){
Solution S1;
std::vector<int> test = {4,5,6,7,0,1,2};
int target = 0;
int res= S1.search(test, target);
std::cout << res << std::endl;
}
8--在排序数组中查找元素的第一个和最后一个位置(34)
主要思路:
基于二分法,实现两个二分函数,分别查找第一个位置和最后一个位置;
#include <iostream>
#include <vector>
class Solution {
public:
std::vector<int> searchRange(std::vector<int>& nums, int target) {
int left = find_left(nums, target);
int right = find_right(nums, target);
std::vector<int> res = {left, right};
return res;
}
// 查找第一个等于target的位置
int find_left(std::vector<int>& nums, int target){
int l = 0, r = nums.size() - 1, res = -1;
while(l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] < target){
l = mid + 1;
}
// nums[mid] >= target: 当 nums[mid] == mid 时,左移(因为要找到第一个),当 nums[mid] > target 时,也应该左移
else{
res = mid; // 记录可能的位置
r = mid - 1;
}
}
if(res == -1 || nums[res] != target) return -1; //未找到target
return res;
}
// 查找最后一个等于target的位置
int find_right(std::vector<int>& nums, int target){
int l = 0, r = nums.size() - 1, res = -1;
while(l <= r){
int mid = l + (r - l) / 2;
if(nums[mid] <= target){ // 分析同理
res = mid; // 记录可能的值
l = mid + 1;
}
else{ // nums[mid] > target
r = mid - 1;
}
}
if(res == -1 || nums[res] != target) return -1;
return res;
}
};
int main(int argc, char *argv[]){
// nums = [5,7,7,8,8,10], target = 8
std::vector<int> test = {1};
int target = 0;
Solution S1;
std::vector<int> res = S1.searchRange(test, target);
std::cout << "[" << res[0] << "," << res[1] << "]" << std::endl;
return 0;
}
9--组合总和(39)
主要思路:
递归暴力枚举是否选一个数,并更新对应的加和;
回溯时应把当前数从容器中剔除;
#include <iostream>
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> combinationSum(std::vector<int>& candidates, int target) {
dfs(candidates, target, 0, 0, tmp);
return res;
}
void dfs(std::vector<int>& candidates, int target, int sum, int cur, std::vector<int> &tmp){
if(cur == candidates.size()){
if(sum == target) res.push_back(tmp);
return;
}
// 不选择candidates[cur]
dfs(candidates, target, sum, cur+1, tmp);
// 选择candidates[cur]
for(int i = 1; sum+i*candidates[cur] <= target; i++){ // 选取i个
tmp.push_back(candidates[cur]);
dfs(candidates, target, sum + candidates[cur] * i, cur+1, tmp);
}
// 回溯弹出
while(!tmp.empty() && tmp.back() == candidates[cur]){
tmp.pop_back();
}
}
private:
std::vector<std::vector<int>> res;
std::vector<int> tmp;
};
int main(int argc, char *argv[]){
// candidates = [2,3,6,7], target = 7
std::vector<int> test = {2, 3, 6, 7};
int target = 7;
Solution S1;
std::vector<std::vector<int>> res = S1.combinationSum(test, target);
for(auto v : res){
for(auto item : v){
std::cout << item << " ";
}
std::cout << std::endl;
}
return 0;
}
10--接雨水(42)
主要思路:
维护一个单调栈,存储索引,栈顶到栈底元素对应的高度逐渐递增;
#include <iostream>
#include <vector>
#include <stack>
class Solution {
public:
int trap(std::vector<int>& height) {
std::stack<int> stk;
int res = 0;
for(int i = 0; i < height.size(); i++){
while(!stk.empty() && height[i] > height[stk.top()]){
int mid = stk.top();
stk.pop();
if(stk.empty()){
break;
}
int h = std::min(height[i], height[stk.top()]) - height[mid];
int w = i - stk.top() - 1;
res += h * w;
}
stk.push(i);
}
return res;
}
};
int main(int argc, char *argv[]){
// height = [0,1,0,2,1,0,1,3,2,1,2,1]
std::vector<int> test = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};
Solution S1;
int res = S1.trap(test);
std::cout << res << std::endl;
return 0;
}