面試手撕(一):圖搜尋,排布問題

某糕發表於2024-10-02

題目

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

相關文章