*题目编号为Leetcode中对应的题号。
某位大佬的Leetcode题解参考链接
栈
-
(20有效的括号) 给定一个只包括
'('
,')'
,'{'
,'}'
,'['
,']'
的字符串,判断字符串是否有效。有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
注意空字符串可被认为是有效字符串。
示例 :
输入: "()[]{}" 输出: true
示例 :
输入: "([)]" 输出: false
bool isValid(string s) { stack<char> record; for (int i = 0; i < s.size(); i++) { if (s[i] == '(' || s[i] == '[' || s[i] == '{') { record.push(s[i]); continue; } else { // s[i]==)]} if (record.empty()) return false; char c=record.top(); record.pop(); switch (s[i]) { case ')': if (c != '(') return false; break; case ']': if (c != '[') return false; break; case '}': if (c != '{') return false; break; } } } if (!record.empty()) return false; return true; }
-
(150逆波兰表达式求值) 根据 逆波兰表示法,求表达式的值。
有效的运算符包括
+
,-
,*
,/
。每个运算对象可以是整数,也可以是另一个逆波兰表达式。说明:
- 整数除法只保留整数部分。
- 给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例:
输入: ["2", "1", "+", "3", "*"] 输出: 9
/// Time Complexity: O(n) /// Space Complexity: O(n) class Solution { public: int evalRPN(vector<string>& tokens) { stack<int> nums; for(const string& s: tokens){ if(s == "+" || s == "-" || s == "*" || s == "/"){ int a = nums.top(); nums.pop(); int b = nums.top(); nums.pop(); if(s == "+") nums.push(b + a); else if(s == "-") nums.push(b - a); else if(s == "*") nums.push(b * a); else if(s == "/") nums.push(b / a); } else nums.push(atoi(s.c_str())); } return nums.top(); } };
-
(71简化路径) 以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(
.
)表示当前目录本身;此外,两个点 (..
) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径请注意,返回的规范路径必须始终以斜杠
/
开头,并且两个目录名之间必须只有一个斜杠/
。最后一个目录名(如果存在)不能以/
结尾。此外,规范路径必须是表示绝对路径的最短字符串。示例:
输入:"/home/" 输出:"/home" 解释:注意,最后一个目录名后面没有斜杠。 输入:"/../" 输出:"/" 解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。 输入:"/home//foo/" 输出:"/home/foo" 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。 输入:"/a/./b/../../c/" 输出:"/c"
/// Time Complexity: O(n) /// Space Complexity: O(n) class Solution { public: string simplifyPath(string path) { vector<string> stack; for(int start = 1, i = 1; i <= path.size(); i ++) if(i == path.size() || path[i] == '/'){ string f = path.substr(start, i - start); if(f.size()){ if(f == ".."){ if(stack.size()) stack.pop_back(); } else if(f != ".") stack.push_back(f); } start = i + 1; } string res = ""; for(string e: stack) res += "/" + e; return res == "" ? "/" : res; } };
-
(144, 94, 145前中后序遍历二叉树) 二叉树的前序、中序、后续遍历。
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ // 前序,递归版 vector<int> preorderTraversal(TreeNode* root) { vector<int> ret; if (root) { ret.push_back(root->val); vector<int> ret1 = preorderTraversal(root->left); vector<int> ret2 = preorderTraversal(root->right); ret.insert(ret.end(), ret1.begin(), ret1.end()); ret.insert(ret.end(), ret2.begin(), ret2.end()); } return ret; } // 栈实现递归版 class Command{ string s;// go print TreeNode* node; Command(string s_, TreeNode* node_):s(s_), node(node_){ }; } vector<int> preorderTraversal(TreeNode* root) { vector<int> ret; if(root==NULL) return ret; stack<Command> stack; stack.push(Command("go", root)); while(!stack.empty()){ Command com=stack.top(); stack.pop(); if(com.s=="print") ret.push_back(com.node->val); else{ // go if(com.node->right) stack.push(Command("go", com.node->right)); if(com.node->left) stack.push(Command("go", com.node->left)); ret.push_back(Command("print", com.node)); } } return ret; }
-
(341扁平化嵌套列表迭代器) 给你一个嵌套的整型列表。请你设计一个迭代器,使其能够遍历这个整型列表中的所有整数。列表中的每一项或者为一个整数,或者是另一个列表。其中列表的元素也可能是整数或是其他列表。
示例:
输入: [[1,1],2,[1,1]] 输出: [1,1,2,1,1] 输入: [1,[4,[6]]] 输出: [1,4,6]
class NestedIterator { private: vector<int> data; int i; public: NestedIterator(vector<NestedInteger> &nestedList) { dfs(nestedList); i = 0; } int next() { return data[i ++]; } bool hasNext() { return i < data.size(); } private: void dfs(const vector<NestedInteger>& nestedList){ for(const NestedInteger& e: nestedList) if(e.isInteger()) data.push_back(e.getInteger()); else dfs(e.getList()); } };
队列
- 队列的基本应用:广度优先遍历
- 树:层序遍历
- 图:无权图的最短路径
-
(102二叉树的层序遍历) 给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
示例:
二叉树:[3,9,20,null,null,15,7]
,3 / \ 9 20 / \ 15 7
返回其层次遍历结果:
[ [3], [9,20], [15,7] ]
vector<vector<int>> levelOrder(TreeNode* root) { vector<vector<int>> res; if (root == NULL) return res; queue<pair<TreeNode*, int>> que;// 节点,所在层数 que.push(make_pair(root, 0)); while (!que.empty()) { TreeNode* top = que.front().first; int level = que.front().second; que.pop(); if (level >= res.size()) { // 如果是新的一层 res.push_back(vector<int>()); } res[level].push_back(top->val); if (top->left) que.push(make_pair(top->left, level + 1)); if (top->right) que.push(make_pair(top->right, level + 1)); } return res; }
-
(107二叉树的层序遍历II) 给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如:
给定二叉树[3,9,20,null,null,15,7]
,3 / \ 9 20 / \ 15 7 返回其自底向上的层次遍历为: [ [15,7], [9,20], [3] ]
/// BFS /// Time Complexity: O(n), where n is the number of nodes in the tree /// Space Complexity: O(n) class Solution { public: vector<vector<int>> levelOrderBottom(TreeNode* root) { vector<vector<int>> res; if(root == NULL) return res; queue<pair<TreeNode*,int>> q; q.push(make_pair(root, 0)); while(!q.empty()){ TreeNode* node = q.front().first; int level = q.front().second; q.pop(); if(level == res.size()) res.push_back(vector<int>()); assert( level < res.size() ); res[level].push_back(node->val); if(node->left) q.push(make_pair(node->left, level + 1 )); if(node->right) q.push(make_pair(node->right, level + 1 )); } reverse(res.begin(), res.end()); return res; } };
-
(103二叉树的锯齿形层次遍历) 给定一个二叉树,返回其节点值的锯齿形层次遍历。(即先从左往右,再从右往左进行下一层遍历,以此类推,层与层之间交替进行)。
例如:
给定二叉树[3,9,20,null,null,15,7]
,3 / \ 9 20 / \ 15 7
返回锯齿形层次遍历如下:
[ [3], [20,9], [15,7] ]
class Solution { public: vector<vector<int>> zigzagLevelOrder(TreeNode* root) { vector<vector<int>> res; if(!root) return res; vector<TreeNode*> cur; cur.push_back(root); int index = 0; while(cur.size()){ vector<TreeNode*> next; vector<int> tres; for(TreeNode* node: cur){ tres.push_back(node->val); if(node->left) next.push_back(node->left); if(node->right) next.push_back(node->right); } if(index % 2) reverse(tres.begin(), tres.end()); res.push_back(tres); cur = next; index ++; } return res; } };
-
(199二叉树的右视图) 给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例:
输入: [1,2,3,null,5,null,4] 输出: [1, 3, 4] 解释: 1 <--- / \ 2 3 <--- \ \ 5 4 <---
class Solution { public: vector<int> rightSideView(TreeNode* root) { vector<int> res; if(!root) return res; vector<TreeNode*> cur = { root}; while(cur.size()){ res.push_back(cur.back()->val); vector<TreeNode*> next; for(TreeNode* node: cur){ if(node->left) next.push_back(node->left); if(node->right) next.push_back(node->right); } cur = next; } return res; } };
-
(279完全平方数) 给定正整数 n,找到若干个完全平方数(比如
1, 4, 9, 16, ...
)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少示例:
输入: n = 12 输出: 3 解释: 12 = 4 + 4 + 4.
原问题建模:
整个问题转换为一个图论问题。
从n到0每个数字为一个节点。
如果两个数字相差一个完全平方数,则连接一条边。
我们得到一个无权图。
整个问题转换为:求这个无权图中从n到0的最短路径问题。
// 基础版 int numSquares(int n) { queue<pair<int, int>> que;// 第几个数字,经过几段路径到达该数字 que.push(make_pair(n, 0)); while (!que.empty()) { int num = que.front().first; int step = que.front().second; que.pop(); if (num == 0) return step; for (int i = 1; num - i * i >= 0; i++) { que.push(make_pair(num - i * i, step + 1)); } } } // 优化版 int numSquares(int n) { queue<pair<int, int>> que;// 第几个数字,经过几段路径到达该数字 que.push(make_pair(n, 0)); vector<bool> visited(n+1, false);// 判断是否遍历过 visited[n]=true; while (!que.empty()) { int num = que.front().first; int step = que.front().second; que.pop(); for (int i = 1; ; i++) { int a=num-i*i; if(a<0)// 提前返回0 break; if(a==0) return step+1; if(!visited[a]){ que.push(make_pair(a, step + 1)); visited[a]=true; } } } }
-
(127、126单词接龙) 给定两个单词(beginWord 和 endWord)和一个字典,找到从 beginWord 到 endWord 的最短转换序列的长度。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回 0。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入: beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"] 输出: 5 解释: 一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
/// BFS /// Time Complexity: O(n*n) /// Space Complexity: O(n) class Solution { public: int ladderLength(string beginWord, string endWord, vector<string>& wordList) { int end = find(wordList.begin(), wordList.end(), endWord) - wordList.begin(); if(end == wordList.size()) return 0; int begin = find(wordList.begin(), wordList.end(), beginWord) - wordList.begin(); if(begin == wordList.size()) wordList.push_back(beginWord); int n = wordList.size(); vector<vector<bool>> g(n, vector<bool>(n, false)); for(int i = 0 ; i < wordList.size() ; i ++) for(int j = 0 ; j < i ; j ++) g[i][j] = g[j][i] = similar(wordList[i], wordList[j]); // bfs queue<int> q; vector<int> step(n, 0); q.push(begin); step[begin] = 1; while(!q.empty()){ int cur = q.front(); q.pop(); for(int i = 0 ; i < n ; i ++) if(step[i] == 0 && g[cur][i]){ if(i == end) return step[cur] + 1; step[i] = step[cur] + 1; q.push(i); } } return 0; } private: bool similar(const string& word1, const string& word2){ assert(word1 != "" && word1.size() == word2.size() && word1 != word2); int diff = 0; for(int i = 0 ; i < word1.size() ; i ++) if(word1[i] != word2[i]){ diff ++; if(diff > 1) return false; } return true; } };
优先队列
-
C++: priority_queue默认是最大堆
-
实现最小堆:
priority_queue<int, vector<int>, greater<int>> qp;
-
实现自定义比较大小的堆
bool myCmp(int a, int b){ return a%10 < b%10;// 个位数越大越靠前 } priority_queue<int, vector<int>, func<bool(int, int)>> pq(myCmp);
-
(347前k个高频元素) 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]
// 基础版 vector<int> topKFrequent(vector<int>& nums, int k) { // 记录次数 unordered_map<int, int> myMap; for (int i = 0; i < nums.size(); i++) myMap[nums[i]]++; // 转为vector vector<pair<int, int>> myVec;// 数字,对应出现的次数 for (auto iter = myMap.begin(); iter != myMap.end(); iter++) { myVec.push_back(make_pair(iter->first, iter->second)); } // 插入排序,可用快排 for (int i = 0; i < myVec.size(); i++) for (int j = i; j > 0; j--) { if (myVec[j].second > myVec[j - 1].second) swap(myVec[j], myVec[j - 1]); } // 取出前k个 vector<int> ret; for (int i = 0; i < k; i++) ret.push_back(myVec[i].first); return ret; } // 排序版 vector<int> topKFrequent(vector<int>& nums, int k) { assert(k > 0); freq.clear(); for (int i = 0; i < nums.size(); i++) freq[nums[i]] ++; assert(k <= freq.size()); vector<int> res; for (pair<int, int> p : freq) res.push_back(p.first); sort(res.begin(), res.end(), [this](int a, int b) { if (this->freq[a] != this->freq[b]) return this->freq[a] > this->freq[b]; return a < b; }); return vector<int>(res.begin(), res.begin() + k); } // 优先队列版 vector<int> topKFrequent(vector<int>& nums, int k) { // 记录次数 unordered_map<int, int> myMap; for (int i = 0; i < nums.size(); i++) myMap[nums[i]]++; // 维护一个有k个元素的优先队列,当传进一个元素的频率大于该队列中最低频率的元素时,替换之 priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;// 频率,元素,从小到大 for (auto iter = myMap.begin(); iter != myMap.end(); iter++) { if (pq.size() == k) { if (iter->second > pq.top().first) { pq.pop(); pq.push(make_pair(iter->second, iter->first)); } } else pq.push(make_pair(iter->second, iter->first)); } // 放入vector vector<int> ret; for (int i = 0; i < k; i++) { ret.push_back(pq.top().second); pq.pop(); } return ret; }
-
(合并k个升序链表) 给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例 1:
输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [ 1->4->5, 1->3->4, 2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6
/// Using Priority Queue to compare each ListNode /// Time Complexity: O(nlogk) where k is len(lists) and n is the nodes number /// Space Complexity: O(1) class Solution { public: ListNode* mergeKLists(vector<ListNode*>& lists) { if(lists.size() == 0) return NULL; ListNode* dummyHead = new ListNode(-1); ListNode* curNode = dummyHead; priority_queue<ListNode*, vector<ListNode*>, CompareListNode> q; for(ListNode* node: lists) if(node != NULL) q.push(node); while(!q.empty()){ ListNode* nextNode = q.top(); q.pop(); curNode->next = nextNode; if(nextNode->next != NULL) q.push(nextNode->next); nextNode->next = NULL; curNode = curNode->next; } ListNode* ret = dummyHead->next; delete dummyHead; return ret; } };