力扣leetcode-算法基础21天刷题记录②
力扣【leetcode】 算法基础21天刷题 记录篇二
打题啊打题,打题啊打题,坚持!!!
⭐广度优先搜索 / 深度优先搜索
200. 岛屿数量
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
深搜DFS
一个点,再向四周进行遍历搜索
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int sum = 0;
// 对岛屿每个点进行遍历
for(int i = 0; i < grid.size(); ++i){
for (int j = 0; j < grid[0].size(); ++j){
if (grid[i][j] == '1') {
++sum;
dfs(grid, i, j);
}
}
}
return sum;
}
void dfs(vector<vector<char>>& grid, int x, int y){
// 岛屿触碰边界以及本身是水即返回
if(x < 0 || x >= grid.size() || y < 0 || y >=grid[0].size() || grid[x][y] == '0') {
return;
}
// 将自身变成水
grid[x][y] = '0';
// 下上右左
int ix[4] = {0, 0, 1, -1};
int iy[4] = {1, -1, 0, 0};
for(int i = 0; i < 4; ++i){
int xx = x + ix[i], yy = y + iy[i];
// 继续往四周探寻
dfs(grid, xx, yy);
}
return;
}
};
广搜BFS
一个点的四周加入队列,进行循环pop首部节点
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int sum = 0;
// 对岛屿每个点进行遍历
for(int i = 0; i < grid.size(); ++i){
for (int j = 0; j < grid[0].size(); ++j){
if (grid[i][j] == '1') {
++sum;
bfs(grid, i, j);
}
}
}
return sum;
}
void bfs(vector<vector<char>>& grid, int x, int y){
if (x < 0 || y < 0 || x >= grid.size() || y >= grid[0].size() || grid[x][y] == '0')
return;
// 创建队列
grid[x][y] = '0';
queue<pair<int, int>> q;
int ix[4] = {0, 0, 1, -1};
int iy[4] = {1, -1, 0, 0};
// 将该点作为首部节点传入队列
q.emplace(x, y);
// 进行循环pop队列首节点
while(!q.empty()){
// 取出首部节点,对其进行条件判断
int x = q.front().first;
int y = q.front().second;
// 满足是岛屿,则将该节点pop且将四周节点加入到队列
q.pop();
for(int i = 0; i < 4; ++i) {
int xx = x + ix[i], yy = y + iy[i];
if (xx >= 0 && xx < grid.size() && yy >= 0 && yy < grid[0].size() && grid[xx][yy] == '1') {
// 将搜索过的岛屿置0
grid[xx][yy] = '0';
q.emplace(xx,yy);
}
}
}
return;
}
};
547. 省份数量
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
DFS
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int sum = 0;
// 初始化0 记录该点是否遍历过
vector<int> visited(isConnected.size(),0);
for(int i = 0; i < isConnected.size(); ++i) {
if(!visited[i]) {
dfs(isConnected, visited, i);
++sum;
}
}
return sum;
}
// 以下两者皆可
// void dfs(vector<vector<int>>& isConnected, vector<int>& visited, int x){
// // 标记已经遍历过
// visited[x] = 1;
// for(int i = 0; i < isConnected[0].size(); ++i) {
// // 如果未探寻过并且存在连接则继续探寻
// if(isConnected[x][i] == 1 && !visited[i]) {
// dfs(isConnected, visited, i);
// }
// }
// }
void dfs(vector<vector<int>>& isConnected, vector<int>& visited, int x){
for(int i = 0; i < isConnected.size(); ++i) {
// 如果未探寻过并且存在连接则继续探寻
if(isConnected[x][i] == 1 && !visited[i]) {
// 标记已经遍历过
visited[i] = 1;
dfs(isConnected, visited, i);
}
}
}
};
BFS
class Solution {
public:
int findCircleNum(vector<vector<int>>& isConnected) {
int sum = 0;
// 初始化0 记录该点是否遍历过
vector<int> visited(isConnected.size(),0);
for(int i = 0; i < isConnected.size(); ++i) {
if(!visited[i]) {
bfs(isConnected, visited, i);
++sum;
}
}
return sum;
}
void bfs(vector<vector<int>>& isConnected, vector<int>& visited, int k){
queue<int> q;
q.push(k);
while(!q.empty()) {
int x = q.front();
q.pop();
visited[x] = 1;
for(int i = 0; i < isConnected.size(); ++i){
if(!visited[i] && isConnected[x][i] == 1) {
q.push(i);
}
}
}
}
};
117. 填充每个节点的下一个右侧节点指针 II
给定一个二叉树
struct Node { int val; Node *left; Node *right; Node *next; }
它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
进阶:
你只能使用常量级额外空间。
使用递归解题也符合要求,本题中递归程序占用的栈空间不算做额外的空间复杂度。
BFS
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
if(!root){
return nullptr;
}
queue<Node*> q;
q.push(root);
while(!q.empty()){
int n = q.size();
Node* p = nullptr;
for(int i = 0; i < n; ++i){
Node* m = q.front();
q.pop();
if(m->left){
q.push(m->left);
}
if(m->right){
q.push(m->right);
}
if(i!=0){
p->next = m;
}
p = m;
}
}
return root;
}
};
层序遍历模板
class Solution {
public:
vector<vector<int>> levelOrder(Node* root) {
if (!root){
return nullptr;
}
queue<Node*> q;
q.push(root);
vector<vector<int>> result;
while (!q.empty()) {
int n = q.size();
vector<int> p;
for (int i = 0; i < n; i++) {
Node* node = q.front();
q.pop();
p.push_back(node->val);
if (node->left) {
q.push(node->left);
}
if (node->right) {
q.push(node->right);
}
}
result.push_back(p);
}
return result;
}
};
572. 另一棵树的子树
给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。
二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所有后代节点。tree 也可以看做它自身的一棵子树。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isSame(TreeNode*root,TreeNode*subRoot)
{
if(!root && !subRoot) {
return true;
}
if(!root||!subRoot) {
return false;
}
if(root->val != subRoot->val){
return false;
}
// 若为同个树,则移动左右指针
return isSame(root->left,subRoot->left) && isSame(root->right,subRoot->right);
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if(!subRoot){
return true;
}
if(!root) {
return false;
}
// 树相同 为左子树 为右子树
return isSame(root,subRoot) || isSubtree(root->left,subRoot) || isSubtree(root->right,subRoot);
}
};
1091. 二进制矩阵中的最短路径
给你一个 n x n 的二进制矩阵 grid 中,返回矩阵中最短 畅通路径 的长度。如果不存在这样的路径,返回 -1 。
二进制矩阵中的 畅通路径 是一条从 左上角 单元格(即,(0, 0))到 右下角 单元格(即,(n - 1, n - 1))的路径,该路径同时满足下述要求:
路径途经的所有单元格都的值都是 0 。
路径中所有相邻的单元格应当在 8 个方向之一 上连通(即,相邻两单元之间彼此不同且共享一条边或者一个角)。
畅通路径的长度 是该路径途经的单元格总数。
// 该题求解最短路径,自然而可以考虑 BFS
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
// 起点就不符合条件
if(grid[0][0] != 0) {
return -1;
}
// 生成队列,首部节点
queue<pair<int, int>> q;
q.emplace(0, 0);
// 标记
grid[0][0] = 1;
// 记录路径数
int sum = 0;
// 方向数组
int dirX[8] = {0, 0, 1, -1, 1, 1, -1, -1};
int dirY[8] = {1, -1, 0, 0, 1, -1, -1, 1};
// 进行循环队列首部节点判断
while(!q.empty()) {
++sum;
for (int i = q.size(); i > 0; --i) {
// 取出首部节点
int x = q.front().first;
int y = q.front().second;
q.pop();
// 到达目的地
if (x == grid.size() - 1 && y == grid[0].size() - 1) {
return sum;
}
// 对八个方向进行遍历
for(int i = 0; i < 8; ++i) {
int xx = x + dirX[i], yy = y + dirY[i];
// 若满足在矩阵内且通路
if (xx >= 0 && xx < grid.size() && yy >= 0 && yy < grid[0].size() && grid[xx][yy] == 0) {
// 标记
grid[xx][yy] = 1;
// 加入队列
q.emplace(xx, yy);
}
}
}
}
return -1;
}
};
130. 被围绕的区域
给你一个
m x n
的矩阵board
,由若干字符'X'
和'O'
,找到所有被'X'
围绕的区域,并将这些区域里所有的'O'
用'X'
填充。
DFS
// 找出边界上的和O的连通点
class Solution {
public:
void solve(vector<vector<char>>& board) {
if (board.size() == 0) {
return;
}
// 深搜第一层和最后一层
for (int i = 0; i < board.size(); ++i) {
dfs(board, i, 0);
dfs(board, i, board[0].size() - 1);
}
// 深搜第一列和最后一列
for (int i = 1; i < board[0].size() - 1; ++i) {
dfs(board, 0, i);
dfs(board, board.size() - 1, i);
}
for(int i = 0; i < board.size(); ++i) {
for(int j = 0; j < board[0].size(); ++j) {
// 若被标记,则该点无法变成X
if(board[i][j] == 'P') {
board[i][j] = 'O';
}
// 满足条件,将O变为X
else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
void dfs(vector<vector<char>>& board, int x, int y) {
// 边界,无需变X 的条件
if (x < 0 || x >= board.size() || y < 0 || y >= board[0].size() || board[x][y] != 'O') {
return ;
}
// 标记边界上的可通点,即边界上无法变X
board[x][y] = 'P';
// 向四周进行搜索
int ix[4] = {0, 0, 1, -1};
int iy[4] = {1, -1, 0, 0};
for(int i = 0; i < 4; ++i) {
int xx = x + ix[i], yy = y + iy[i];
dfs(board, xx, yy);
}
return;
}
};
BFS
// 找出边界上的和O的连通点
class Solution {
public:
void solve(vector<vector<char>>& board) {
if (board.size() == 0) {
return;
}
// 深搜第一层和最后一层
for (int i = 0; i < board.size(); ++i) {
bfs(board, i, 0);
bfs(board, i, board[0].size() - 1);
}
// 深搜第一列和最后一列
for (int i = 1; i < board[0].size() - 1; ++i) {
bfs(board, 0, i);
bfs(board, board.size() - 1, i);
}
for(int i = 0; i < board.size(); ++i) {
for(int j = 0; j < board[0].size(); ++j) {
// 若被标记,则该点无法变成X
if(board[i][j] == 'P') {
board[i][j] = 'O';
}
// 满足条件,将O变为X
else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
void bfs(vector<vector<char>>& board, int x, int y) {
// 边界,无需变X 的条件
if (x < 0 || x >= board.size() || y < 0 || y >= board[0].size() || board[x][y] != 'O') {
return ;
}
// 标记边界上的可通点,即边界上无法变X
board[x][y] = 'P';
queue<pair<int, int>> q;
q.emplace(x, y);
// 向四周进行搜索
int ix[4] = {0, 0, 1, -1};
int iy[4] = {1, -1, 0, 0};
while(! q.empty()) {
int x = q.front().first;
int y = q.front().second;
q.pop();
for(int i = 0; i < 4; ++i) {
int xx = x + ix[i], yy = y + iy[i];
bfs(board, xx, yy);
}
}
return;
}
};
797. 所有可能的路径
给一个有 n 个结点的有向无环图,找到所有从 0 到 n-1 的路径并输出(不要求按顺序)
二维数组的第 i 个数组中的单元都表示有向图中 i 号结点所能到达的下一些结点(译者注:有向图是有方向的,即规定了 a→b 你就不能从 b→a )空就是没有下一个结点了。
class Solution {
public:
vector<vector<int>> allPathsSourceTarget(vector<vector<int>>& graph) {
vector<vector<int>> sum;
vector<int> n = {0};
dfs(sum, graph, n);
return sum;
}
void dfs(vector<vector<int>> &sum, vector<vector<int>> &graph, vector<int> &n) {
for(auto &p: graph[n.back()]) {
n.push_back(p);
if (p == graph.size() - 1) {
sum.push_back(n);
}
else {
dfs(sum, graph, n);
}
n.pop_back();
}
}
};
⭐递归 / 回溯
迭代回溯
从根节点进行搜索 =》 如果当前节点存在子节点,否则不存在子节点,返回上一层 =》遍历当前节点的所有子节点 =》 若满足约束条件和限界条件则得到一个可行解,否则继续向下搜索。
78. 子集
给你一个整数数组
nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
class Solution {
public:
// 记录当前路径,便于回溯
vector<int> path;
// 返回结果集
vector<vector<int>> result;
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
// 进行回溯
void backtracking(vector<int>& nums, int start) {
// 结束条件,当start指向最后
if (start == nums.size()) {
// 搜寻结束,加入结果集
result.push_back(path);
return;
}
// 将该选择加入路径队列中
path.push_back(nums[start]);
// 进行回溯
backtracking(nums, start + 1);
// 回退一格
path.pop_back();
backtracking(nums,start + 1);
}
};
90. 子集 II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
// 与上一题不同点是,可能存在重复元素
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
// 先对其进行排序
sort(nums.begin(), nums.end());
// 回溯
backtracking(nums, 0);
return result;
}
void backtracking(vector<int>& nums, int start) {
// 将path加入结果集
result.push_back(path);
// 结束条件
if (nums.size() == start) {
return;
}
// 遍历该结点以下的结点
for (int i = start; i < nums.size(); ++i) {
// 防止因重复元素出现重复子集
if (i > start && nums[i] == nums [i-1]) continue;
path.push_back(nums[i]);
backtracking(nums, i + 1);
path.pop_back();
}
}
};
47. 全排列 II
给定一个可包含重复数字的序列
nums
,按任意顺序 返回所有不重复的全排列。
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
vector<vector<int>> permuteUnique(vector<int>& nums) {
// 排序,为剪枝做准备
sort(nums.begin(), nums.end());
// 申请一个数组,来标记是否已经经过,初始化为0
vector<int> visited(nums.size(), 0);
backtracking(nums, visited);
return result;
}
void backtracking(vector<int>& nums, vector<int> visited) {
// 此题为全排列,即路径长度等于nums元素总个数
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
// 跟之前的去重不同,此时是同层去重
if (i != 0 && nums[i] == nums[i-1] && visited[i-1] == 0) continue;
// 元素未被使用
if (visited[i] == 0) {
// 加入路径
path.push_back(nums[i]);
// 标记元素被访问
visited[i] = 1;
backtracking(nums, visited);
// 回溯上一个元素
path.pop_back();
// 此时标记该元素为未访问
visited[i] = 0;
}
}
}
};
39. 组合总和
给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
// 调用回溯
backtracking(candidates, target, 0);
return result;
}
void backtracking(vector<int>& candidates, int target, int start) {
// 结束条件,元素利用完了
if (start == candidates.size()) return;
// 此时和等于给定数,则加入结果集
if (target == 0) {
result.push_back(path);
return;
}
// 开始寻找
if (target - candidates[start] >= 0) {
path.push_back(candidates[start]);
// 注意这里的起始点还是i,因为元素可以重复使用
backtracking(candidates, target - candidates[start], start);
path.pop_back();
}
// 跳过继续搜寻
backtracking(candidates, target, start + 1);
}
};
40. 组合总和 II
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
注意:解集不能包含重复的组合。
// 与上题不同,数字、结果均不能重复
class Solution {
public:
vector<int> path;
vector<vector<int>> result;
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
// 剪枝必备
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0);
return result;
}
void backtracking(vector<int>& candidates, int target, int start) {
// 结束条件
if (target == 0) {
// 符合条件加入结果集
result.push_back(path);
return;
}
// 往下遍历结点
for (int i = start; i < candidates.size(); ++i) {
// 去重
if (i > start && candidates[i] == candidates[i - 1]) continue;
// 给定求和条件
if (target - candidates[i] >= 0) {
path.push_back(candidates[i]);
// 这题是i+1,因为每个数字不可重复使用
backtracking(candidates, target-candidates[i], i + 1);
path.pop_back();
}
}
}
};
17. 电话号码的字母组合
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
// 题目本质就是组合,可先用哈希表映射字符和数字的对应关系,然后继续回溯
class Solution {
public:
vector<string> result;
string path;
vector<string> letterCombinations(string digits) {
// 当输入的字符串为空,直接返回
if (digits == "") return result;
// 数字和字母的映射,存至哈希表
unordered_map<char, string> phoneMap{
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
};
backtracking(digits, phoneMap, 0);
return result;
}
// 回溯函数
void backtracking(string digits, const unordered_map<char, string>& phoneMap, int start) {
// 结束条件
if (start == digits.size()) {
result.push_back(path);
}
else {
// 根据哈希表获取输入数字对应的字母,指向second,是因为字母字符串在第二个位置
auto str = phoneMap.find(digits[start])->second;
for (auto s: str) {
// 将单个字符作为一个结点,进行回溯组合
path.push_back(s);
backtracking(digits, phoneMap, start + 1);
path.pop_back();
}
}
}
};
22. 括号生成
数字
n
代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。有效括号组合需满足:左括号必须以正确的顺序闭合。
class Solution {
public:
vector<string> result;
string path;
vector<string> generateParenthesis(int n) {
// n左右,一半就行
backtracking(n * 2);
return result;
}
void backtracking(int n) {
// 结束条件
if (n == path.size()) {
// 检验path左右括号是否合法
if (isCheck(path)) {
result.push_back(path);
}
return;
}
// 进行回溯
// 左括号
path.push_back('(');
backtracking(n);
path.pop_back();
// 右括号
path.push_back(')');
backtracking(n);
path.pop_back();
}
bool isCheck(string path) {
// 记录左括号和右括号的平衡度
int ans = 0;
for (auto s : path) {
if (s == '(') {
++ans;
}
else {
--ans;
}
// 此时不平衡
if (ans < 0) {
return false;
}
}
// 平衡则返回true
return ans == 0;
}
};
79. 单词搜索
给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
class Solution {
public:
bool exist(vector<vector<char>>& board, string word) {
vector<vector<int>> visited(board.size(), vector<int>(board[0].size()));
for(int i = 0; i < board.size(); ++i) {
for (int j = 0; j < board[0].size(); ++j) {
if (dfs(board, word, visited, i , j, 0)) {
return true;
}
}
}
return false;
}
// visited 数组
bool dfs(vector<vector<char>>& board, string word, vector<vector<int>>& visited, int x, int y, int start) {
// 当前位置不存在或访问使用过
if (x < 0 || x > board.size() || y < 0 || y > board[0].size() || visited[x][y] == 1) {
return false;
}
// 不符合条件
if (board[x][y] != word[start]) {
return false;
}
// 搜寻完毕
if (start == word.size() - 1) {
return true;
}
// 定义四周方向
int dirX[4] = {0, 0, 1, -1};
int dirY[4] = {1, -1, 0, 0};
// 标记
visited[x][y] = 1;
// 四周探寻
for (int i = 0; i < 4; ++i) {
int xx = x + dirX[i], yy = y + dirY[i];
if (xx >= 0 && xx < board.size() && yy >= 0 && yy < board[0].size() && visited[xx][yy] == 0) {
if (dfs(board, word, visited, xx, yy, start + 1)) {
return true;
}
}
}
// 回退
visited[x][y] = 0;
return false;
}
};