面试手撕(一):图搜索,排布问题

题目

  在规定 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;
}

posted @ 2024-10-02 15:31  某糕  阅读(4)  评论(0编辑  收藏  举报