一,栈
150. 逆波兰表达式求值
根据逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入: ["2", "1", "+", "3", "*"]
输出: 9
解释: ((2 + 1) * 3) = 9
示例 2:
输入: ["4", "13", "5", "/", "+"]
输出: 6
解释: (4 + (13 / 5)) = 6
示例 3:
输入: ["10", "6", "9", "3", "+", "-11", "*", "/", "*", "17", "+", "5", "+"]
输出: 22
解释:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
class Solution {
public:
int evalRPN(vector<string>& tokens) {
int n = tokens.size();
int ret = 0;
stack<int> num;
for(int i = 0; i < n; i++)
{
if(tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/" )
{
int temp1 = num.top();
num.pop();
int temp2 = num.top();
num.pop();
if(tokens[i] == "+")
ret = temp1+temp2;
else if(tokens[i] == "-")
ret = temp2-temp1;
else if(tokens[i] == "*")
ret = temp1*temp2;
else
{
if(temp1 == 0)
ret = 0;
else
ret = temp2/temp1;
}
num.push(ret);
}
else
{
int temp = atoi(tokens[i].c_str());
num.push(temp);
}
}
return num.top();
}
};
71. 简化路径
以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。
在 Unix 风格的文件系统中,一个点(.)表示当前目录本身;此外,两个点 (..) 表示将目录切换到上一级(指向父目录);两者都可以是复杂相对路径的组成部分。更多信息请参阅:Linux / Unix中的绝对路径 vs 相对路径
请注意,返回的规范路径必须始终以斜杠 / 开头,并且两个目录名之间必须只有一个斜杠 /。最后一个目录名(如果存在)不能以 / 结尾。此外,规范路径必须是表示绝对路径的最短字符串。
示例 1:
输入:"/home/"
输出:"/home"
解释:注意,最后一个目录名后面没有斜杠。
示例 2:
输入:"/../"
输出:"/"
解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。
示例 3:
输入:"/home//foo/"
输出:"/home/foo"
解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。
示例 4:
输入:"/a/./b/../../c/"
输出:"/c"
示例 5:
输入:"/a/../../b/../c//.//"
输出:"/c"
示例 6:
输入:"/a//bc/d//././/.."
输出:"/a/b/c"
class Solution {
public:
string simplifyPath(string path) {
if(path.size() == 0)
return path;
vector<string> res;
int i = 0;
while(i < path.size())
{
while(i < path.size() && path[i] == '/')
i++;
if(i == path.size())
break;
int start = i;
while(i < path.size() && path[i] != '/')
i++;
string temp = path.substr(start, i - start);
if(temp == "..")
{
if(!res.empty())
res.pop_back();
}
else if(temp != ".")
res.push_back(temp);
}
if(res.size() == 0)
return "/";
string result = "";
for(int i = 0; i < res.size(); i++)
result = result + "/" + res[i];
return result;
}
};
二,队列(BFS广度优先遍历)
107. 二叉树的层次遍历 II
给定一个二叉树,返回其节点值自底向上的层次遍历。 (即按从叶子节点所在层到根节点所在的层,逐层从左向右遍历)
例如:
给定二叉树 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其自底向上的层次遍历为:
[
[15,7],
[9,20],
[3]
]
class Solution {
public:
vector<vector<int>> levelOrderBottom(TreeNode* root) {
queue<TreeNode*> q;
vector<vector<int>> ret;
if(root == NULL)
return ret;
q.push(root);
while(!q.empty())
{
vector<int>temp;
int n = q.size();
for(int i = 0; i < n;i++)
{
TreeNode *node = q.front();
q.pop();
temp.push_back(node->val);
if(node->left)
q.push(node->left);
if(node->right)
q.push(node->right);
}
ret.push_back(temp);
}
reverse(ret.begin(), ret.end());
return ret;
}
};
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) {
deque<TreeNode*> q;
vector<vector<int>> ret;
if(root == NULL)
return ret;
q.push_back(root);
int flag = 0;
while(!q.empty())
{
vector<int>temp;
int n = q.size();
for(int i = 0; i < n;i++)
{
if(flag % 2 == 0)
{
TreeNode *node = q.front();
q.pop_front();
temp.push_back(node->val);
if(node->left)
q.push_back(node->left);
if(node->right)
q.push_back(node->right);
}
else
{
TreeNode *node = q.back();
q.pop_back();
temp.push_back(node->val);
if(node->right)
q.push_front(node->right);
if(node->left)
q.push_front(node->left);
}
}
flag++;
ret.push_back(temp);
}
return ret;
}
};
199. 二叉树的右视图
给定一棵二叉树,想象自己站在它的右侧,按照从顶部到底部的顺序,返回从右侧所能看到的节点值。
示例:
输入: [1,2,3,null,5,null,4]
输出: [1, 3, 4]
解释:
1 <---
/ \
2 3 <---
\ \
5 4 <---
思路:巧妙使用二叉树的层序遍历,每次只取每一层的最后一个元素。
class Solution {
public:
vector<int> rightSideView(TreeNode* root) {
queue<TreeNode*> q;
vector<int> ret;
if(root == NULL)
return ret;
q.push(root);
while(!q.empty())
{
int n = q.size();
for(int i = 0; i < n;i++)
{
TreeNode *node = q.front();
q.pop();
if(i == n-1)
ret.push_back(node->val);
if(node->left)
q.push(node->left);
if(node->right)
q.push(node->right);
}
}
return ret;
}
};
无权图的最短路径
我们采用了广度优先搜索BFS求出无权图的最短路径。我们这里就会使用队列这个数据结构,首先将初始节点0入队,然后出队;接着将0的所有子节点入队,然后队首出队,同时队首子节点入队,不断循环。
因为我们是不断加入子节点的,同一个节点的子节点到原始节点的距离就一定相同,当然距离不可能超过节点的总个数,距离我们使用ord表示。当然也和之前一样,需要对上一个节点from进行记录。
private:
Graph &G;
int s;
bool *visited;
int *from;//上一个节点
int *ord;//s到每一个节点的最短距离
public:
ShortestPath(Graph &graph, int s):G(graph)
{
//算法初始化
assert( s >= 0 && s < graph.V() );
visited = new bool[graph.V()];
from = new int[graph.V()];
ord = new int[graph.V()];
for(int i = 0; i < graph.V(); i++){
visited[i] = false;
from[i] = -1;
ord[i] = -1;
}
this->s = s;
queue<int> q;
//无向图最短路径算法
q.push( s );
visited[s] = true;
ord[s] = 0;
while( !q.empty() ){
int v = q.front();
q.pop();
typename Graph::adjIterator adj(G, v);
for(int i = adj.begin(); !adj.end(); i = adj.next()){
if( !visited[i] ){
q.push(i);
visited[i] = true;
from[i] = v;
ord[i] = ord[v] + 1;//距离+1
}
}
}
}
完成初始化之后,所有节点到v的距离和上一个节点我们就形成了。然后通过from,查找v到w的最短路径。
void path(int w, vector<int> &vec)
{
assert( w >= 0 && w < G.V() );
stack<int> s;
//放入栈中
int p = w;
while(p != -1){
s.push(p);
p = from[p];
}
//顺序放入vector
vec.clear();
while( !s.empty() ){
vec.push_back( s.top() );
s.pop();
}
}
//w为目标节点
void showPath(int w){
assert( w >= 0 && w < G.V() );
vector<int> vec;
path(w, vec);
for(int i = 0; i < vec.size(); i++){
cout<<vec[i];
if( i == vec.size() - 1 )
cout<<endl;
else
cout<<" -> ";
}
}
279. 完全平方数
给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
示例 1:
输入: n = 12
输出: 3
解释: 12 = 4 + 4 + 4.
示例 2:
输入: n = 13
输出: 2
解释: 13 = 4 + 9.
第一种方法:动态规划法
class Solution {
public:
int numSquares(int n) {
if(n <= 0)
return 0;
vector<int>num(n+1, INT_MAX);
num[0] = 0;
num[1] = 1;
for(int i = 2; i <= n; i++)
for(int j = 1; j*j <= i; j++)
num[i] = min(num[i], num[i-j*j]+1);
return num[n];
}
};
第二种方法:转换成最短路径
class Solution {
public:
int numSquares(int n) {
if(n <= 0)
return 0;
queue< pair<int, int> > res;
res.push(make_pair(n, 0));
vector<bool>visited(n+1, false);
visited[n] = true;
while(!res.empty())
{
int num = res.front().first;
int step = res.front().second;
res.pop();
for(int i = 1; ; i++)
{
int a = num - i*i ;
if(a < 0)
break;
if(a == 0)
return step+1;
if(!visited[a])
{
res.push(make_pair(a, step+1));
visited[a] = true;
}
}
}
return n;
}
};
127. 单词接龙
给定两个单词(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。
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: 0
解释: endWord "cog" 不在字典中,所以无法进行转换。
class Solution {
public:
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> dict(wordList.begin(), wordList.end());
unordered_map<string, int> step;
queue<string> q;
step[beginWord] = 1;
q.push(beginWord);
while(!q.empty())
{
string word = q.front();
q.pop();
for(int i = 0; i < word.size(); i++)
{
string newword = word;
for(char c = 'a'; c <= 'z'; c++)
{
newword[i] = c;
if(dict.count(newword) && newword == endWord)
return step[word] + 1;
if(dict.count(newword) && step[newword] == 0)
{
q.push(newword);
step[newword] = step[word] + 1;
}
}
}
}
return 0;
}
};
优先队列
使用方法
#include<iostream>
#include<ctime>
#include<queue>
using namespace std;
int main()
{
srand(time(NULL));
priority_queue<int> pq;
//优先队列默认最大堆
for(int i = 0; i < 10; i++)
{
int a = rand()%100;
pq.push(a);
cout<<"insert "<<a<<" insert priority_queue"<<endl;
}
while(!pq.empty())
{
cout<<pq.top()<<" ";
pq.pop();
}
cout<<endl;
//底层是最小堆
priority_queue<int, vector<int>, greater<int> > pq2;
return 0;
}
347. 前 K 个高频元素
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
说明:
你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> m;
priority_queue<pair<int, int>> q;
vector<int> res;
for (auto a : nums)
++m[a];
//根据频次创建一个最大堆,取出频次最高的前K项
for (auto it : m)
q.push({it.second, it.first});
for (int i = 0; i < k; ++i)
{
res.push_back(q.top().second);
q.pop();
}
return res;
}
};
23. 合并K个排序链表
合并 k 个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。
示例:
输入:
[
1->4->5,
1->3->4,
2->6
]
输出: 1->1->2->3->4->4->5->6
第一种方法:思路是每次取出两条,用merge2Lists的方法合并为一条,再将这条和下一条用merge2Lists来合并为一条,以此类推。假设每条链表平均有n个元素,此种时间复杂度是O(2n+3n+…+kn), 为O(nk²),时间复杂度较高。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* L1, ListNode* L2)
{
ListNode* L = new ListNode(0);
ListNode* p = L;
while(L1 && L2)
{
if(L1->val < L2->val)
{
p->next = L1;
L1 = L1->next;
p = p->next;
}
else
{
p->next = L2;
L2 = L2->next;
p = p->next;
}
}
if(L1)
p->next = L1;
if(L2)
p->next = L2;
return L->next;
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0)
return NULL;
if(lists.size() == 1)
return lists[0];
ListNode* res = NULL;
res = mergeTwoLists(lists[0], lists[1]);
for(int i = 2; i < lists.size(); i++)
res = mergeTwoLists(res, lists[i]);
return res;
}
};
第二种方法:分治法
时间复杂度为O(klogkn)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* L1, ListNode* L2)
{
ListNode* L = new ListNode(0);
ListNode* p = L;
while(L1 && L2)
{
if(L1->val < L2->val)
{
p->next = L1;
L1 = L1->next;
p = p->next;
}
else
{
p->next = L2;
L2 = L2->next;
p = p->next;
}
}
if(L1)
p->next = L1;
if(L2)
p->next = L2;
return L->next;
}
ListNode* mergeHelper(vector<ListNode*>& lists, int start, int end)
{
if(start == end)
return lists[start];
int mid = start + (end - start)/2;
ListNode* Left = mergeHelper(lists, start, mid);
ListNode* Right = mergeHelper(lists, mid+1, end);
return mergeTwoLists(Left, Right);
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0)
return NULL;
return mergeHelper(lists, 0, lists.size()-1);
}
};
第三种方法:使用优先队列
通过优先级队列查找K个有序链表中的最小元素表头,链接到有序链表中去,然后把表头的下一个元素插入到优先级队列。
struct cmp
{
bool operator () (ListNode * a, ListNode * b) {
return a->val > b->val;
}
};
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0)
return NULL;
if(lists.size() == 1)
return lists[0];
priority_queue<ListNode*, vector<ListNode*>, cmp> pq;
for(int i = 0; i < lists.size(); i++)
{
if(lists[i] != NULL)
pq.push(lists[i]);
}
ListNode* dummy = new ListNode(0);
ListNode* head = dummy;
while(!pq.empty())
{
ListNode* temp = pq.top();
pq.pop();
head->next = temp;
head = head->next;
if(temp->next != NULL)
pq.push(temp->next);
}
return dummy->next;
}
};