文章目录
以下为Datawhale Leetcode开源学习思路总结,以下代码均为Leetcode代码,但不一定是最优解,仅供参考学习。
堆栈基础知识
155. 最小栈
本题是让你设计一个带有push
、pop
、top
操作并可以获取最小元素值的栈,我才用的内部数据结构是vector
,当眼你也可以使用数组对其进行封装以及动态扩容。
那么push
操作实际上对应vector
中的push_back
,pop
操作对应vector
的pop_back
,那么top
操作获取栈顶元素,对应vector
的back
操作,
具体代码实现如下所示:
class MinStack {
public:
MinStack() {}
void push(int val) {
vec_.push_back(val);
}
void pop() {
if(vec_.size() > 0) vec_.pop_back();
}
int top() {
return vec_.back();
}
int getMin() {
int min_num = vec_[0];
for(auto x : vec_)
min_num = min_num > x ? x : min_num;
return min_num;
}
vector<int> vec_;
};
20. 有效括号
本题输入仅包含左右小、中、大括号的字符串,让你判断字符串对应括号是否匹配。该题的做法是,通过栈实现对括号匹配的验证。
思路是:遍历字符串,只要当我们遇到左括号时就将其对应的有括号压入栈中,如果此时遍历遇到了右括号,就应当与栈顶元素相匹配并把栈顶弹出栈。如果发现在比较途中(字符串还没有遍历完毕),栈已经为空,或者说遍历过程中遇到的右括号与栈顶元素不一致,那么就说明括号匹配失败,返回false
,具体代码如下所示:
class Solution {
public:
bool isValid(string s) {
stack<int> st;
for(auto x : s) {
if(x == '(') st.push(')');
else if(x == '[') st.push(']');
else if(x == '{') st.push('}');
else if(st.empty() || st.top() != x) return false;
else st.pop();
}
return st.empty();
}
};
227. 基本计算器II
本题大意是说,给定一个字符串表达式,让你输出表达式的结果,这个字符串表达式只包括四则运算符,而不需要考虑括号的情况。我们的思路是,将字符串中的数值进行入栈操作,考虑到优先级的问题,我们首先对加减法不做运算,而是将减法转换为加法乘以 − 1 -1 −1,对于乘除法,我们对其进行运算并入栈,因此经过一轮的运算后,栈中保存的均为可进行加法运算的数值,我们将栈中元素全部相加,即得到了最终表达式运算的结果。具体代码实现如下:
class Solution {
public:
int calculate(string s) {
stack<int> stk;
s += '+';
long long int cur = 0;
char sign = '+';
for(auto x : s) {
if(isdigit(x)) cur = cur*10+x-'0';
if(x=='+' || x=='-' || x=='*' || x=='/') {
if(sign == '+') stk.push(cur);
else if(sign == '-') stk.push(cur*(-1));
else if(sign == '*') {
int num = stk.top(); stk.pop();
stk.push(num * cur);
} else if(sign == '/') {
int num = stk.top(); stk.pop();
stk.push(num / cur);
}
cur = 0;
sign = x;
}
}
long long int res = 0;
while(stk.size()) {
res += stk.top(); stk.pop();
}
return res;
}
};
150. 逆波兰表达式求值
逆波兰表达式又被称为后缀表达式
,本题传入一个逆波兰式,使用vector<string> &tokens
保存该逆波兰式的每一个数字串或符号,逆波兰式的计算则是直接遍历表达式并将数值进行入栈操作,当遇到字符为符号时,则将栈顶两个元素进行二元操作运算,并将运算结果再次入栈,直到最终计算完毕,栈中仅剩一个结果即为逆波兰表达式求得的结果。本人代码逻辑简单,就是冗余较明显,代码如下:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> s;
int op1, op2, op;
for(auto x : tokens) {
if(x!="+"&&x!="-"&&x!="*"&&x!="/") {
stringstream ss;
ss << x;
ss >> op;
s.push(op);
} else if(x=="+") {
stringstream ss;
ss << s.top();
ss >> op1;
s.pop();
ss.clear();
ss << s.top();
ss >> op2;
s.pop();
s.push(op1+op2);
} else if(x=="-") {
stringstream ss;
ss << s.top();
ss >> op1;
s.pop();
ss.clear();
ss << s.top();
ss >> op2;
s.pop();
s.push(op2-op1);
} else if(x=="*") {
stringstream ss;
ss << s.top();
ss >> op1;
s.pop();
ss.clear();
ss << s.top();
ss >> op2;
s.pop();
s.push(op1*op2);
} else {
stringstream ss;
ss << s.top();
ss >> op1;
s.pop();
ss.clear();
ss << s.top();
ss >> op2;
s.pop();
s.push(op2/op1);
}
}
return s.top();
}
};
这里贴出比较优秀的题解代码,用到了c++11
的新特性,function
函数对象以及lambda
表达式,非常值得学习,代码如下:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
unordered_map<string, function<int (int, int) > > map = {
{ "+" , [] (int a, int b) { return a + b; } },
{ "-" , [] (int a, int b) { return a - b; } },
{ "*" , [] (int a, int b) { return a * b; } },
{ "/" , [] (int a, int b) { return a / b; } }
};
std::stack<int> stack;
for (string& s : tokens) {
if (!map.count(s)) {
stack.push(stoi(s));
} else {
int op1 = stack.top();
stack.pop();
int op2 = stack.top();
stack.pop();
stack.push(map[s](op2, op1));
}
}
return stack.top();
}
};
394. 字符串解码
要解决这道题要知道从哪里入手,这道题也采用栈的思想进行表达式展开,但是我们需要知道第一次出现]
右括号的位置,然后在这个位置之前必然出现过一个[
左括号,找到这两个括号区间中的值,同时确定[
左侧的数值,用于对括号间的串进行成倍的连接,在确定这个数值时,用到了字符串与整型之间的转换:num = num*10+x-'0'
这样类似的操作,具体可以看代码:
class Solution {
public:
string decodeString(string s) {
stack<char> stk;
for(auto x : s) {
if(x != ']') {
stk.push(x);
} else {
string str = "";
while(stk.top() != '[') {
str = stk.top() + str; // str最终保存的是括号之间的元素
stk.pop(); // 弹出与"]"匹配的左括号"[""
}
stk.pop();
string num = "";
while(!stk.empty() && isdigit(stk.top())) {
// num = stk.top()-'0' + num * 10;
num = stk.top() + num;
stk.pop();
}
int num1 = 0;
for(auto x : num) {
num1 = num1*10 + x-'0';
}
while(num1--) {
for(auto k : str) {
stk.push(k);
}
}
}
}
string res = "";
while(!stk.empty()) {
res = stk.top() + res;
stk.pop();
}
return res;
}
};
946. 验证栈序列
本题给了两个序列,一个是入栈的顺序,另一个是出栈的顺序,让你判断这两个顺序描述的是不是同一个栈上的行为。这道题比较简单,我们的思路是,一边入栈一边检查出栈序列的元素,从0
开始到popped.size()-1
,若入栈元素匹配到了出栈元素的值,则将入栈元素弹出即可,最终如果栈空间为空,则说明栈序列为真,反之为假。
本题比较简单,代码如下:
class Solution {
public:
bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
stack<int> judge;
int i = 0;
for(auto x : pushed) {
judge.push(x);
while(!judge.empty() && judge.top() == popped[i]) {
judge.pop();
i++;
}
}
return judge.empty();
}
};
栈与深度优先搜索
200. 岛屿数量
本题是dfs
的典型应用,当我们dfs上下左右搜索到的是陆地时,我们就继续搜索,并且将搜索到的位置置为0,然后遍历整个地图,最终确定岛屿的数量,本题代码实现不难理解,我们直接看代码:
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int landnum = 0;
for(int i = 0; i < grid.size(); ++i) {
for(int j = 0; j < grid[0].size(); ++j) {
if(grid[i][j] == '1') {
landnum++; // 这里就是用来记录岛屿数量的 最终返回该结果
dfs(grid, i, j);
}
}
}
return landnum;
}
void dfs(vector<vector<char>> &grid, int i, int j) {
if(i < 0 || j < 0 || i >= grid.size() || j >= grid[0].size() || grid[i][j] == '0')
return;
grid[i][j] = '0';
dfs(grid, i+1, j);
dfs(grid, i, j+1);
dfs(grid, i-1, j);
dfs(grid, i, j-1);
}
};
133. 克隆图
本题实际上是图的深拷贝问题,通过map的方式建立原图与新图节点映射关系,同时递归遍历当前结点的邻接结点,最终建立结点之间的邻接关系。代码如下:
class Solution {
public:
Node* cloneGraph(Node* node) {
if(!node) return NULL;
if(mp.find(node) == mp.end()) {
mp[node] = new Node(node->val);
for(auto neighbor : node->neighbors) {
mp[node]->neighbors.push_back(cloneGraph(neighbor));
}
}
return mp[node];
}
private:
unordered_map<Node *, Node *> mp;
};
494. 目标和
这个题递归的过程中,我们以target
为起始点,分别试探每一位正负值运算后能否使得target
的值变为0
,如果可以,并且是数值序列最后一个元素已经进行操作,这时说明存在一种情况使得我们的数值序列计算的结果为target
。直接看代码吧,代码比较好理解:
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
return dfs(nums, target, 0);
}
int dfs(vector<int> &nums, int target, int cur) {
if(target == 0 && cur == nums.size()) return 1;
if(cur >= nums.size()) return 0;
int ans = 0;
ans += dfs(nums, target-nums[cur], cur+1);
ans += dfs(nums, target+nums[cur], cur+1);
return ans;
}
};
841. 钥匙和房间
这道题我们默认从第一个房间开始搜索,遍历其邻接位置,同时对其邻接位置进行递归操作,这样一直下去,将遍历过的屋子标记为true
,当遍历结束时,如果每个屋子对应的布尔值都是true
,则说明所有屋子都能抵达,否则不能完全抵达,代码如下:
class Solution {
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
vector<bool> judge(rooms.size(), false);
judge[0] = true;
dfs(rooms, judge, 0);
for(auto x : judge) {
if(x == false)
return x;
}
return true;
}
void dfs(vector<vector<int>> &rooms, vector<bool> &judge, int cur) {
for(auto key : rooms[cur]) {
if(!judge[key]) { // 这里是关键 如果不跳过以访问的结点 可能会无限递归下去
judge[key] = true;
dfs(rooms, judge, key);
}
}
}
};
需要补充说明的是,因为屋子的个数在传入rooms
参数时已经确定,即为rooms.size()
个房屋,那么我们可以使用bitset
,同时题目也明确规定了房屋最大数量为1001
个,使用bitset
替换上述代码中的vector<bool>
可以更节省空间,代码如下:
class Solution {
public:
bool canVisitAllRooms(vector<vector<int>>& rooms) {
bitset<1000> visited;
visited.set(0);
dfs(rooms, visited, 0);
return visited.count() == rooms.size();
}
void dfs(vector<vector<int>>& rooms, bitset<1001>& visited, int currentKey) {
for (int key : rooms[currentKey]) {
if (!visited.test(key)) {
visited.set(key);
dfs(rooms, visited, key);
}
}
}
};
695. 岛屿的最大面积
这道题是让你统计岛屿的对大面积,和前面的200
题岛屿数量很相似,只不过这里在遍历岛屿的时候,记录每个岛屿的面积数tmpS
,最终与最大面积变量maxS
进行比较和替换,得到最大面积即可,代码如下:
class Solution {
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int maxS = 0, tmpS = 0;
for(int i = 0; i < grid.size(); ++i) {
for(int j = 0; j < grid[0].size(); ++j) {
if(grid[i][j] == 1) {
dfs(grid, i, j, tmpS);
maxS = (tmpS > maxS ? tmpS : maxS);
tmpS = 0;
}
}
}
return maxS;
}
void dfs(vector<vector<int>> &grid, int i, int j, int &tmpS) {
if(i < 0 || j < 0 || i >= grid.size() || j>= grid[0].size() || grid[i][j] == 0) return;
grid[i][j] = 0;
tmpS = tmpS + 1;
dfs(grid, i, j+1, tmpS);
dfs(grid, i, j-1, tmpS);
dfs(grid, i-1, j, tmpS);
dfs(grid, i+1, j, tmpS);
}
};
130. 被围绕的区域
这道题是典型的dfs
,入手点较难,其巧妙的思路是,遍历边界位置,将所有与边界相连的区域标记为#
,然后将内陆地区替换成X
,再将#
还原成O
即可,思路非常巧妙,下题中1020
题飞地的数量与此题相似,入手点均为边界位置,那么我们贴出此题实现代码:
class Solution {
public:
void solve(vector<vector<char>>& board) {
for(int i = 0; i < board.size(); ++i) {
for(int j = 0; j < board[0].size(); ++j) {
// 从边缘开始找'O'
if((i==0||i==board.size()-1||j==0||j==board[0].size()-1)&&board[i][j]=='O') {
dfs(board, i, j);
}
}
}
for(int i = 0; i < board.size(); ++i) {
for(int j = 0; j < board[0].size(); ++j) {
if(board[i][j] == 'O')
board[i][j] = 'X';
if(board[i][j] == '#')
board[i][j] = 'O';
}
}
}
void dfs(vector<vector<char>> &board, int i, int j) {
if(i<0||i>=board.size()||j<0||j>=board[0].size()||board[i][j]=='X'||board[i][j]=='#')
return;
board[i][j] = '#';
dfs(board, i, j+1);
dfs(board, i, j-1);
dfs(board, i+1, j);
dfs(board, i-1, j);
}
};
417. 太平洋大西洋水流问题
对于此题我们可以直观的取暴力搜索每一个点是否可以达到两边的大洋,当然这样子要处理的东西会很多,并且思路不够明显。有小伙伴会说,水怎么能往高处流动,现实生活中水当然是往低处流的。但是现在键盘在你手上,你让他往东流他能往西流?咱这是能上天的黄河之水!对于一个点它能流动两边的大洋,那么反过来,两边大洋的水反着流就能达到这个点。既然水开始倒流了,那么逻辑也需要反过来,因此只有将下一个点比当前的点大时或者等于当前点的高度时,水才能流过去。 那么我们需要怎么做才能找出所有点?
-
找出所有从太平洋出发的水所能达到的点
-
找出所有从大西洋出发的水所能达到的点
- 这些重合的点便是我们要找的点
以上就是本题的思路,抓住思路以后就开始写代码,这道题代码还是比较复杂的,涉及到边界问题,代码如下:
class Solution {
public:
vector<vector<int>> left, right, ans;
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
left = vector<vector<int>>(heights.size(), vector<int>(heights[0].size(), 0));
right = vector<vector<int>>(heights.size(), vector<int>(heights[0].size(), 0));
for(int i = 0; i < heights.size(); ++i) {
dfs(heights, left, i, 0);
dfs(heights, right, i, heights[0].size()-1);
}
for(int j = 0; j < heights[0].size(); ++j) {
dfs(heights, left, 0, j);
dfs(heights, right, heights.size()-1, j);
}
return ans;
}
void dfs(vector<vector<int>> &heights, vector<vector<int>> &visited, int i, int j) {
if(visited[i][j]) return;
visited[i][j] = 1;
if(left[i][j] && right[i][j]) ans.push_back({i, j});
if(i-1>= 0 && heights[i-1][j] >= heights[i][j])
dfs(heights, visited, i-1, j);
if(i+1<heights.size() && heights[i+1][j] >= heights[i][j])
dfs(heights, visited, i+1, j);
if(j-1>= 0 && heights[i][j-1] >= heights[i][j])
dfs(heights, visited, i, j-1);
if(j+1<heights[0].size() && heights[i][j+1] >= heights[i][j])
dfs(heights, visited, i, j+1);
}
};
有小伙伴可能会问,if(left[i][j] && right[i][j]) ans.push_back({i, j});
这里难道不会把相同的值重复添加吗?答案是不会的,遍历过的点会直接返回,不会重复添加。
1020. 飞地的数量
上面说到130
题被围绕的区域,那么这道题很明显需要排除掉与边界相连接的大陆,因此也需要从边界进行dfs
,把与边界连接的陆地均置为0
,最终统计二纬地图上1
的个数,即为内陆个数。具体代码如下:
class Solution {
public:
int numEnclaves(vector<vector<int>>& grid) {
for(int i = 0; i < grid.size(); ++i) {
for(int j = 0; j < grid[0].size(); ++j) {
if((i==0||i==grid.size()-1||j==0||j==grid[0].size()-1)&&grid[i][j]==1) {
dfs(grid, i, j);
}
}
}
int cnt = 0;
for(auto &x : grid) {
for(auto &y : x) {
if(y == 1)
cnt++;
}
}
return cnt;
}
void dfs(vector<vector<int>> &grid, int i, int j) {
if(i<0||i>=grid.size()||j<0||j>=grid[0].size()||grid[i][j]==0)
return;
grid[i][j] = 0;
dfs(grid, i, j+1);
dfs(grid, i, j-1);
dfs(grid, i+1, j);
dfs(grid, i-1, j);
}
};
1254. 统计封闭岛屿的数目
这道题非常的经典,要寻找内部被围住的岛屿数量,首先要排除与边界相连的岛屿,我们可以对四条边进行dfs
,把与边相连的岛屿均变为海水,处理完毕后再对整个二位数组进行dfs
,这时候就变为了与200
题相同的岛屿数量统计问题,代码如下:
class Solution {
public:
int closedIsland(vector<vector<int>>& grid) {
// 将左右侧为0的岛屿进行dfs全化为1
for(int i = 0; i < grid.size(); ++i) {
if(grid[i][0] == 0)
dfs(grid, i, 0);
if(grid[i][grid[0].size()-1] == 0)
dfs(grid, i, grid[0].size()-1);
}
// 将上下侧为0的岛屿进行dfs全化为1
for(int i = 0; i < grid[0].size(); ++i) {
if(grid[0][i] == 0)
dfs(grid, 0, i);
if(grid[grid.size()-1][i] == 0)
dfs(grid, grid.size()-1, i);
}
int cnt = 0;
for(int i = 0; i < grid.size(); ++i) {
for(int j = 0; j < grid[0].size(); ++j) {
if(grid[i][j] == 0) {
dfs(grid, i, j);
cnt++;
}
}
}
return cnt;
}
void dfs(vector<vector<int>> &grid, int i, int j) {
if(i<0 || j<0 || i>=grid.size() || j>=grid[0].size() || grid[i][j]==1) return;
grid[i][j] = 1;
dfs(grid, i-1, j);
dfs(grid, i+1, j);
dfs(grid, i, j-1);
dfs(grid, i, j+1);
}
};