文章目录
- 0. Leetcode [LCP 44. 开幕式焰火](https://leetcode.cn/problems/sZ59z6/)
- 1. Leetcode [102. 二叉树的层序遍历](https://leetcode.cn/problems/binary-tree-level-order-traversal/)
- 2. Leetcode [1609. 奇偶树](https://leetcode.cn/problems/even-odd-tree/)
- 3. Leetcode [1263. 推箱子](https://leetcode.cn/problems/minimum-moves-to-move-a-box-to-their-target-location/)
- 总结
0. Leetcode LCP 44. 开幕式焰火
「力扣挑战赛」开幕式开始了,空中绽放了一颗二叉树形的巨型焰火。
给定一棵二叉树 root 代表焰火,节点值表示巨型焰火这一位置的颜色种类。请帮小扣计算巨型焰火有多少种不同的颜色。
分析与解答
直接进行搜索即可,这里因为训练广度优先搜索,因此使用广搜方法:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
set<int> result;
deque<TreeNode*> deq;
public:
void bfs(TreeNode* root) {
while (!deq.empty()) {
// 获取当前节点
TreeNode* cur = deq.front();
// 有效进行下一步搜索
if (cur) {
// 根据节点更新结果
result.insert(cur->val);
// 放入待搜索节点
deq.push_back(cur->left);
deq.push_back(cur->right);
}
deq.pop_front();
}
}
int numColor(TreeNode* root) {
deq.push_back(root);
// 节点数大于等于 1
bfs(root);
return result.size();
}
};
1. Leetcode 102. 二叉树的层序遍历
给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
分析与解答
广度优先搜索,没层搜索完后将当前层结果放入结果集中:
/**
* 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 {
vector<vector<int>> result;
queue<TreeNode*> q;
public:
void bfs(TreeNode* root) {
// 初始化
q.push(root);
int nodeNum(0);
int validNode(0);
int cellNum(1);
vector<int> curResult;
// 广度搜索
while (!q.empty()) {
TreeNode* curNode = q.front();
q.pop();
if (curNode) {
curResult.push_back(curNode->val);
q.push(curNode->left);
q.push(curNode->right);
validNode++;
}
nodeNum++;
if (cellNum == nodeNum) {
if (curResult.size() > 0) {
result.push_back(curResult);
}
cellNum = validNode * 2; // 每个有效节点有两个子节点
nodeNum = 0;
validNode = 0;
curResult.clear();
}
}
}
vector<vector<int>> levelOrder(TreeNode* root) {
bfs(root);
return result;
}
};
2. Leetcode 1609. 奇偶树
如果一棵二叉树满足下述几个条件,则可以称为 奇偶树 :
二叉树根节点所在层下标为 0 ,根的子节点所在层下标为 1 ,根的孙节点所在层下标为 2 ,依此类推。
偶数下标 层上的所有节点的值都是 奇 整数,从左到右按顺序 严格递增
奇数下标 层上的所有节点的值都是 偶 整数,从左到右按顺序 严格递减
给你二叉树的根节点,如果二叉树为 奇偶树 ,则返回 true ,否则返回 false 。
分析与解答
与上一题类似,使用哈希表记录每个节点对应的深度,若当前节点对应的深度为奇数,则该层应严格递减,反之应严格递增:
/**
* 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 {
queue<TreeNode*> q; // 广搜使用的队列
unordered_map<TreeNode*, int> depMap; // 存储节点对应的深度
public:
bool bfs(TreeNode* root) {
q.push(root);
depMap[root] = 0;
int preDep = 0;
int curNum(0);
while (!q.empty()) {
TreeNode* curNode = q.front();
q.pop();
if (depMap[curNode] != preDep) {
// 上层结束
if (depMap[curNode] % 2 == 1) {
curNum = INT_MAX;
} else {
curNum = 0;
}
preDep = depMap[curNode];
}
if (curNode->left) { // 放入左子节点
q.push(curNode->left);
depMap[curNode->left] = depMap[curNode] + 1;
}
if (curNode->right) { // 放入右子节点
q.push(curNode->right);
depMap[curNode->right] = depMap[curNode] + 1;
}
if (depMap[curNode] % 2 == 1) {
// 奇数层,应为偶数
if (curNode->val % 2 == 1) {
return false;
}
if (curNode->val >= curNum) { // 严格递减
return false;
} else {
curNum = curNode->val;
}
} else {
// 偶数层,应为奇数
if (curNode->val % 2 == 0) {
return false;
}
if (curNode->val <= curNum) { // 严格递增
return false;
} else {
curNum = curNode->val;
}
}
}
return true;
}
bool isEvenOddTree(TreeNode* root) {
return bfs(root);
}
};
3. Leetcode 1263. 推箱子
「推箱子」是一款风靡全球的益智小游戏,玩家需要将箱子推到仓库中的目标位置。
游戏地图用大小为 m x n 的网格 grid 表示,其中每个元素可以是墙、地板或者是箱子。
现在你将作为玩家参与游戏,按规则将箱子 ‘B’ 移动到目标位置 ‘T’ :
玩家用字符 ‘S’ 表示,只要他在地板上,就可以在网格中向上、下、左、右四个方向移动。
地板用字符 ‘.’ 表示,意味着可以自由行走。
墙用字符 ‘#’ 表示,意味着障碍物,不能通行。
箱子仅有一个,用字符 ‘B’ 表示。相应地,网格上有一个目标位置 ‘T’。
玩家需要站在箱子旁边,然后沿着箱子的方向进行移动,此时箱子会被移动到相邻的地板单元格。记作一次「推动」。
玩家无法越过箱子。
返回将箱子推到目标位置的最小 推动 次数,如果无法做到,请返回 -1。
分析与解答
将箱子与人的位置压缩为一维状态,使用压缩后的一维状态进行判断。**注意:**相同状态下,步长不同时应将该状态放入待搜索集中:
vector<vector<int>> pos2State;
vector<vector<int>> gridMap;
int rows;
int cols;
class Solution {
struct Pos {
int x;
int y;
Pos() {
}
Pos(int inx, int iny) {
x = inx;
y = iny;
}
};
struct BFSState {
Pos person;
Pos box;
Pos target;
int step;
bool ifFinish() {
return box.x == target.x && box.y == target.y;
}
int getState() { // 状态压缩
return pos2State[box.x][box.y] * 11250 + pos2State[person.x][person.y];
}
};
BFSState iniPos;
queue<BFSState> q; // 广搜队列
unordered_map<int, int> stateMap; // 状态 -> 出现次数
unordered_map<int, int> stepMap; // 状态 -> 步长
int dir[4][2] = {{1, 0}, {0, 1}, {-1, 0}, {0, -1}}; // 移动状态
public:
void initialStateMat(vector<vector<char>>& grid) { // 位置初始化
rows = grid.size();
cols = grid[0].size();
int stateId(0);
Pos boxPos;
Pos targetPos;
Pos personSPos;
for (int i = 0; i < grid.size(); ++i) {
vector<int> curResult;
vector<int> curMap;
for (int j = 0; j < grid[i].size(); ++j) {
if (grid[i][j] == 'B') {
boxPos.x = i;
boxPos.y = j;
}
if (grid[i][j] == 'S') {
personSPos.x = i;
personSPos.y = j;
}
if (grid[i][j] == 'T') {
targetPos.x = i;
targetPos.y = j;
}
if (grid[i][j] == '#') {
curMap.push_back(INT_MAX);
} else {
curMap.push_back(1);
}
curResult.push_back(stateId++);
}
pos2State.push_back(curResult);
gridMap.push_back(curMap);
}
iniPos.person = personSPos;
iniPos.target = targetPos;
iniPos.box = boxPos;
}
int bfs(vector<vector<char>>& grid) {
// 状态初始化
q.push(iniPos);
stepMap.insert(std::make_pair(iniPos.getState(), 0));
int minResult(-1);
// 广度优先搜索
while (!q.empty()) {
BFSState curState = q.front();
q.pop();
// 判断是否终止
if (curState.ifFinish()) {
if (minResult < 0) {
minResult = curState.step;
} else {
minResult = minResult > curState.step? curState.step: minResult;
}
continue;
}
// 尝试向 4 个方向移动
for (int i = 0; i < 4; ++i) {
BFSState newState = curState;
newState.person.x += dir[i][0];
newState.person.y += dir[i][1];
// 判断当前位置人是否可达
if (newState.person.x < 0 || newState.person.x >= rows ||
newState.person.y < 0 || newState.person.y >= cols ||
gridMap[newState.person.x][newState.person.y] == INT_MAX) {
continue;
}
// 判断人与箱子位置是否重合
if (newState.person.x == newState.box.x && newState.person.y == newState.box.y) {
newState.box.x += dir[i][0];
newState.box.y += dir[i][1];
// 判断箱子是否能推动
if (newState.box.x < 0 || newState.box.x >= rows ||
newState.box.y < 0 || newState.box.y >= cols ||
gridMap[newState.box.x][newState.box.y] == INT_MAX) {
continue;
}
newState.step++;
// !注意这里要将步长更小的状态作为备选搜索状态!
if (newState.step < stepMap[newState.getState()] || stateMap[newState.getState()] == 0) {
q.push(newState);
stepMap[newState.getState()] = newState.step;
stateMap[newState.getState()]++;
} else {
continue;
}
} else {
if (newState.step < stepMap[newState.getState()] || stateMap[newState.getState()] == 0) {
q.push(newState);
stepMap[newState.getState()] = newState.step;
stateMap[newState.getState()]++;
} else {
continue;
}
}
}
}
return minResult;
}
int minPushBox(vector<vector<char>>& grid) {
pos2State.clear();
gridMap.clear();
initialStateMat(grid);
return bfs(grid);
}
};
总结
广度优先搜索并不难理解,但对于如何判断为重复状态(例如推箱子中步长不同的相同状态应该判断为不同状态),需要加强理解。