面试手撕(一):图搜索,排布问题
题目
在规定 M * N 大小的地图,放置 n 个矩形,是否能够放下,若能,请给出排布结果。
输入:M, N, n, n 个矩形的长宽。
分析
- n个矩形必须要一个一个放入地图,当放不下时回退 ---- DFS
- 为什么某些放置策略会失败?
矩形的横纵不对,矩形的位置不对,矩形的放置顺序不对 - 放置策略
- 放置顺序:应当先放大块矩形,再放小块矩形。大小并不是按面积,而是按 max(长, 宽) 排,使不出现前序块别住当前矩形的情况;
- 横纵:先考虑横放,不行就尝试纵放;
- 放置位置:像俄罗斯方块一样,尝试放在每一行的最左边,直到放在行首为止。注意,当连续两行的最左可放置位置一样时,可跳过后面行,因为前一行都不可放置了,第二行这样放置将产生更多碎片,更放不下。
实现
处理输入
#include <iostream>
#include <vector>
#include <bitset>
#include <algorithm>
using namespace std;
int M = 0, N = 0, AREA = 0;
int main() {
cin >> M >> N;
AREA = M * N;
int n = 0;
cin >> n;
// 分别存一份横纵状态下的长宽
vector<pair<int, int>> blocks0(n);
vector<pair<int, int>> blocks1(n);
for (int i = 0, tmp1 = 0, tmp2 = 0; i < n; ++i) {
cin >> tmp1 >> tmp2;
if (tmp1 > M || tmp1 > N) {
cout << "放不下!" << endl;
return -1;
}
if (tmp2 > M || tmp2 > N) {
cout << "放不下!" << endl;
return -1;
}
sumArea += tmp1 * tmp2;
if (sumArea > AREA) {
cout << "放不下!" << endl;
return -1;
}
if (tmp1 > tmp2) {
blocks0[i].first = tmp1;
blocks0[i].second = tmp2;
}
else {
blocks0[i].first = tmp2;
blocks0[i].second = tmp1;
}
}
sort(blocks0.begin(), blocks0.end(), [](const pair<int, int>& p1, const pair<int, int>& p2) {
if (p1.first == p2.first) {
return p1.second > p2.second;
}
return p1.first > p2.first;
});
blocks1.assign(blocks0.begin(), blocks0.end());
for (auto& p : blocks1) {
swap(p.first, p.second);
}
// ··· 运算和输出···
}
计算----DFS
参数设置:
- ans矩阵:记录矩形放置的位置和方向
- isFilled矩阵:记录当前地图被填放的情况
- curBlock:记录当前要放置的矩阵编号
vector<vector<pair<int ,bool>>> ans(M, vector<pair<int, bool>>(N, pair<int, bool>(-1, false)));
vector<vector<bool>> isFilled(M, vector<bool>(N, false));
myFill(blocks0, blocks1, ans, isFilled, 0);
细说myFill()实现
DFS探索过程
bool myFill(const vector<pair<int, int>>& blocks0, const vector<pair<int, int>>& blocks1,
vector<vector<pair<int, bool>>>& ans, vector<vector<bool>>& isFilled,int curBlock) {
// 所有矩形都放完了,返回成功
if (curBlock == blocks0.size()) {
return true;
}
// 横向放置
int nextLine = 0;
// 逐行检查能否横向放下,直到行首非isFilled的行也放不下就停止检查
for (int nextPlace = 0, prePlace = -1; nextLine + blocks0[curBlock].second <= M; ++nextLine) {
// 若该行能放下就继续DFS
if ((nextPlace = canSet(isFilled, nextLine, blocks0[curBlock], prePlace)) != -1) {
// 记录路径
ans[nextLine][nextPlace].first = curBlock;
ans[nextLine][nextPlace].second = false;
// 放下一矩形
if (myFill(blocks0, blocks1, ans, isFilled, curBlock + 1)) {
return true;
}
// 失败撤回
unSet(isFilled, nextLine, nextPlace, blocks0[curBlock]);
ans[nextLine][nextPlace].first = -1;
// 辅助判断对齐的行
prePlace = nextPlace;
}
// 直到行首非isFilled的行也放不下就停止检查
if (isFilled[nextLine][0] == false) {
break;
}
}
// 纵向放置,同上,只是此时用的数据集为纵向排布版本blocks1
nextLine = 0;
for (int nextPlace = 0, prePlace = -1; nextLine + blocks1[curBlock].second <= M; ++nextLine) {
if ((nextPlace = canSet(isFilled, nextLine, blocks1[curBlock], prePlace)) != -1) {
if (curBlock == 1) {
curBlock = 1;
}
ans[nextLine][nextPlace].first = curBlock;
ans[nextLine][nextPlace].second = true;
if (myFill(blocks0, blocks1, ans, isFilled, curBlock + 1)) {
return true;
}
unSet(isFilled, nextLine, nextPlace, blocks1[curBlock]);
ans[nextLine][nextPlace].first = -1;
prePlace = nextPlace;
}
if (isFilled[nextLine][0] == false) {
break;
}
}
return false;
}
矩形对地图的填入和移除操作
// 判断某矩形是否可以放在第line行。若可以,填充isFilled矩阵,返回位置纵坐标;若不可以,返回-1。
// prePlace避免重复判断对齐的行
int canSet(vector<vector<bool>>& isFilled, int line, const pair<int, int >& block, int prePlace) {
int beg = 0, end = 0;
// 找到该行第一个非isFilled的位置
for (; beg < N && isFilled[line][beg]; ++beg);
// 若该行余下位置不够 或 连续对齐行,返回-1
if (beg + block.first > N || beg == prePlace) {
return -1;
}
// 检查余下连续矩形宽度个位置都非isFilled
for (end = beg + 1; end < beg + block.first && !isFilled[line][end]; ++end);
// 填充
if (end == beg + block.first) {
for (int i = line; i < line + block.second; ++i) {
for (int j = beg; j < end; ++j) {
isFilled[i][j] = true;
}
}
return beg;
}
// 无法放下返回-1
return -1;
}
// 撤回该位置矩形的isFilled标记
void unSet(vector<vector<bool>>& isFilled, int line, int place, const pair<int, int >& block) {
for (int i = line; i < line + block.second; ++i) {
for (int j = place; j < place + block.first; ++j) {
isFilled[i][j] = false;
}
}
return;
}
全部代码
#include <iostream>
#include <vector>
#include <bitset>
#include <algorithm>
using namespace std;
int M = 0, N = 0, AREA = 0;
int canSet(vector<vector<bool>>& isFilled, int line, const pair<int, int >& block, int prePlace) {
int beg = 0, end = 0;
for (; beg < N && isFilled[line][beg]; ++beg);
if (beg + block.first > N || beg == prePlace) {
return -1;
}
for (end = beg + 1; end < beg + block.first && !isFilled[line][end]; ++end);
if (end == beg + block.first) {
for (int i = line; i < line + block.second; ++i) {
for (int j = beg; j < end; ++j) {
isFilled[i][j] = true;
}
}
return beg;
}
return -1;
}
void unSet(vector<vector<bool>>& isFilled, int line, int place, const pair<int, int >& block) {
for (int i = line; i < line + block.second; ++i) {
for (int j = place; j < place + block.first; ++j) {
isFilled[i][j] = false;
}
}
return;
}
bool myFill(const vector<pair<int, int>>& blocks0, const vector<pair<int, int>>& blocks1, vector<vector<pair<int, bool>>>& ans,
vector<vector<bool>>& isFilled,int curBlock) {
if (curBlock == blocks0.size()) {
return true;
}
// 横向放置
int nextLine = 0;
for (int nextPlace = 0, prePlace = -1; nextLine + blocks0[curBlock].second <= M; ++nextLine) {
if ((nextPlace = canSet(isFilled, nextLine, blocks0[curBlock], prePlace)) != -1) {
ans[nextLine][nextPlace].first = curBlock;
ans[nextLine][nextPlace].second = false;
if (myFill(blocks0, blocks1, ans, isFilled, curBlock + 1)) {
return true;
}
unSet(isFilled, nextLine, nextPlace, blocks0[curBlock]);
ans[nextLine][nextPlace].first = -1;
prePlace = nextPlace;
}
if (isFilled[nextLine][0] == false) {
break;
}
}
// 纵向放置
nextLine = 0;
for (int nextPlace = 0, prePlace = -1; nextLine + blocks1[curBlock].second <= M; ++nextLine) {
if ((nextPlace = canSet(isFilled, nextLine, blocks1[curBlock], prePlace)) != -1) {
if (curBlock == 1) {
curBlock = 1;
}
ans[nextLine][nextPlace].first = curBlock;
ans[nextLine][nextPlace].second = true;
if (myFill(blocks0, blocks1, ans, isFilled, curBlock + 1)) {
return true;
}
unSet(isFilled, nextLine, nextPlace, blocks1[curBlock]);
ans[nextLine][nextPlace].first = -1;
prePlace = nextPlace;
}
if (isFilled[nextLine][0] == false) {
break;
}
}
return false;
}
int main() {
cin >> M >> N;
AREA = M * N;
int n = 0;
cin >> n;
vector<pair<int, int>> blocks0(n);
vector<pair<int, int>> blocks1(n);
for (int i = 0, tmp1 = 0, tmp2 = 0, sumArea = 0; i < n; ++i) {
cin >> tmp1 >> tmp2;
if (tmp1 > M || tmp1 > N) {
cout << "放不下!" << endl;
return -1;
}
if (tmp2 > M || tmp2 > N) {
cout << "放不下!" << endl;
return -1;
}
sumArea += tmp1 * tmp2;
if (sumArea > AREA) {
cout << "放不下!" << endl;
return -1;
}
if (tmp1 > tmp2) {
blocks0[i].first = tmp1;
blocks0[i].second = tmp2;
}
else {
blocks0[i].first = tmp2;
blocks0[i].second = tmp1;
}
}
sort(blocks0.begin(), blocks0.end(), [](const pair<int, int>& p1, const pair<int, int>& p2) {
if (p1.first == p2.first) {
return p1.second > p2.second;
}
return p1.first > p2.first;
});
blocks1.assign(blocks0.begin(), blocks0.end());
for (auto& p : blocks1) {
swap(p.first, p.second);
}
vector<vector<pair<int ,bool>>> ans(M, vector<pair<int, bool>>(N, pair<int, bool>(-1, false)));
vector<vector<bool>> isFilled(M, vector<bool>(N, false));
vector<vector<int>> map(M, vector<int>(N, -1));
if (!myFill(blocks0, blocks1, ans, isFilled, 0)) {
cout << "放不下!" << endl;
}
else {
for (int i = 0; i < M; ++i) {
for (int j = 0; j < N; ++j) {
if (ans[i][j].first != -1) {
const int id = ans[i][j].first;
if (ans[i][j].second) {
for (int k = 0; k < blocks1[id].second; ++k) {
for (int l = 0; l < blocks1[id].first; ++l) {
map[i + k][j + l] = id;
}
}
}
else {
for (int k = 0; k < blocks0[id].second; ++k) {
for (int l = 0; l < blocks0[id].first; ++l) {
map[i + k][j + l] = id;
}
}
}
}
}
}
for (auto& a : map) {
for (auto b : a) {
cout << b << " ";
}
cout << endl;
}
cout << endl;
}
return 0;
}