王者編程大賽之二 — 蓄水池

首發於 樊浩柏科學院

自如寓打算門口用磚頭圍立一個蓄水池子,從上面看凹凸不平,凹的地方會有積水。那若是用數字表明每一個磚頭的高度,就造成一個二維數據(如示例),請問這個池子能存儲多少單位的水?
php

例如二維數組爲:html

9 9 9 9
3 0 0 9
7 8 2 6
時,答案是中間的 0,0 位置能夠存儲 2(由於其外面最低是 2)個單位的水,所以答案爲 2 + 2 = 4。git

示例:
輸入:[1 1 1 1,1 0 0 1,1 1 1 1]
輸出:2
輸入:[12 11 12 0 13,12 9 8 12 12,13 10 0 3 15,19 4 4 7 15,19 4 3 0 15,12 13 10 15 13]
輸出:58github

解題思路

這道題是全部題中困惑我時間最長的題,一開始思惟禁錮在想直接經過找到每塊磚的四周有效最低磚高度 $H_{min}$,而後這塊磚所剩的水爲 $w[i][j] = H_{min}-h[i][j]$($h[i][j]$ 爲磚的高度,i 和 j 爲磚的位置座標),所以蓄水池能蓄下的水爲 $\sum_{i=1}^n\sum_{j=1}^n w[i][j]$。通過一番嘗試,發現尋找某塊磚四周最低有效磚邏輯比較複雜,且不易理解,又嘗試過使用回溯算法尋找出池子中的全部連通圖,可是也未有果。面試

最後,發現基礎平臺一位同窗的實現思路很清晰,我認爲他的實現是最合適的,因此研究了一下。該實現中機智地採用逆向思惟,首先往池子注滿水(最高磚的高度),而後再經過條件斷定每塊磚是否須要進行漏水,一直到沒有磚須要進行漏水操做算法

實現思路以下:編程

  1. 找出高度最高的磚,高度記爲 $H_{max}$;
  2. 對除去邊界的磚進行注水操做,每塊磚加水量爲 $w[i][j] = H_{max} - h[i][j]$($h[i][j]$ 爲磚的高度);
  3. 對某塊磚進行漏水操做,只要這塊磚有盛水且上下左右相鄰的 4 塊磚高度和盛水量之和小於這塊磚高度和盛水量之和,則須要進行一次漏水,漏水條件能夠描述爲 $w[i][j] > 0$ && $h[i][j-1] + w[i][j-1] < h[i][j] + w[i][j]$(該條件爲磚左側相鄰的漏水條件,右、上、下同理可得);
  4. 持續漏水操做,一直重複步驟 3 直至沒有磚須要進行漏水操做;
  5. 求和磚的盛水量,$\sum_{i=1}^n\sum_{j=1}^n w[i][j]$ 即爲水池的蓄水量;

算法流程圖示以下:數組

編碼實現

實現的類結構以下,特殊的方法已提取出,並將一一詳細說明。this

<?php

class Pool
{
    public $gridArray = array();
    public $maxHeight = 0;
    public $row = 0;
    public $col = 0;

    public function __construct(array $data)
    {
        $this->row = count($data);
        $this->col = count($data[0]);

        foreach ($data as $row => $rowArray) {
            foreach ($rowArray as $col => $height) {
                $height = (int)$height;
                $this->gridArray[$row][$col]['height'] = $height;
                $this->gridArray[$row][$col]['water'] = 0;
                //獲取最高磚的高度
                if ($this->maxHeight < $height) {
                    $this->maxHeight = $height;
                }
            }
        }
    }
    
    //判斷是不是水池邊界
    public function isBorder($row, $col)
    {
        if ($row == 0
            || $row == $this->row - 1
            || $col == 0
            || $col == $this->col - 1
        ) {
            return true;
        }

        return false;
    }

    public function run()
    {
        $this->addWater();

        while ($this->removeWater()) ;

        return $this->collect();
    }
}

注水操做:編碼

public function addWater()
{
    foreach ($this->gridArray as $row => $rowArray) {
        foreach ($rowArray as $col => $grid) {
            if (!$this->isBorder($row, $col)) {
                $this->gridArray[$row][$col]['water'] = $this->maxHeight - $this->gridArray[$row][$col]['height'];
            }
        }
    }
}

漏水操做:

public function removeWater()
{
    foreach ($this->gridArray as $row => $rowArray) {
        foreach ($rowArray as $col => $grid) {
            if ($this->canRemove($row, $col)) {
                return true;
            }
        }
    }

    return false;
}

漏水條件實現以下:

public function canRemove($row, $col)
{
    $can = false;

    if ($this->gridArray[$row][$col]['water'] > 0) {
        //上
        if ($this->gridArray[$row][$col]['water'] + $this->gridArray[$row][$col]['height'] >
            $this->gridArray[$row - 1][$col]['water'] + $this->gridArray[$row - 1][$col]['height']) {
            $this->gridArray[$row][$col]['water'] =
                $this->gridArray[$row - 1][$col]['water'] + $this->gridArray[$row - 1][$col]['height']
                - $this->gridArray[$row][$col]['height'];
            if ($this->gridArray[$row][$col]['water'] < 0) {
                $this->gridArray[$row][$col]['water'] = 0;
            }
            $can = true;
        }
        //右
        if ($this->gridArray[$row][$col]['water'] + $this->gridArray[$row][$col]['height'] >
            $this->gridArray[$row][$col + 1]['water'] + $this->gridArray[$row][$col + 1]['height']) {
            $this->gridArray[$row][$col]['water'] =
                $this->gridArray[$row][$col + 1]['water'] + $this->gridArray[$row][$col + 1]['height']
                - $this->gridArray[$row][$col]['height'];
            if ($this->gridArray[$row][$col]['water'] < 0) {
                $this->gridArray[$row][$col]['water'] = 0;
            }
            $can = true;
        }
        //下
        if ($this->gridArray[$row][$col]['water'] + $this->gridArray[$row][$col]['height'] >
            $this->gridArray[$row + 1][$col]['water'] + $this->gridArray[$row + 1][$col]['height']) {
            $this->gridArray[$row][$col]['water'] =
                $this->gridArray[$row + 1][$col]['water'] + $this->gridArray[$row + 1][$col]['height']
                - $this->gridArray[$row][$col]['height'];
            if ($this->gridArray[$row][$col]['water'] < 0) {
                $this->gridArray[$row][$col]['water'] = 0;
            }
            $can = true;
        }
        //左
        if ($this->gridArray[$row][$col]['water'] + $this->gridArray[$row][$col]['height'] >
            $this->gridArray[$row][$col - 1]['water'] + $this->gridArray[$row][$col - 1]['height']) {
            $this->gridArray[$row][$col]['water'] =
                $this->gridArray[$row][$col - 1]['water'] + $this->gridArray[$row][$col - 1]['height']
                - $this->gridArray[$row][$col]['height'];
            if ($this->gridArray[$row][$col]['water'] < 0) {
                $this->gridArray[$row][$col]['water'] = 0;
            }
            $can = true;
        }
    }

    return $can;
}

持續漏水操做:

public function run()
{
    while ($this->removeWater()) ;
}

求和磚的盛水量:

public function collect()
{
    $sum = 0;
    foreach ($this->gridArray as $row => $rowArray) {
        foreach ($rowArray as $col => $grid) {
            $sum += $grid['water'];
        }
    }

    return $sum;
}

接收標準輸入處理並輸出結果:

$filter = function ($value) {
    return explode(' ', $value);
};

$pool = new Pool(array_map($filter, explode(',', $input)));
echo $pool->run(), PHP_EOL;

類似題目

Twitter 以前曾經出過相似蓄水池的筆試題,只不過本題是立體水池(二維數組),Twitter 蓄水池筆試題是平面水池(一維數組),解題複雜度也就下降了,固然 Twitter 蓄水池筆試題也能夠採用本題的思想來實現,可是時間複雜度爲 $O(n^2)$,採用 個人Twitter技術面試失敗了 的實現時間複雜度爲 $O(n)$。

實現思路以下:

  1. 首先,用兩個指針(left 和 right)分別指向數組的第一個元素和最後一個元素,左指針從左向右遍歷,右指針從右向左遍歷;
  2. 初始化數組中一個元素(a[0])爲左邊遍歷獲得的最大值(max_left),最後一個元素(a[a.length-1])爲從右邊遍歷獲得的最大值(max_right);
  3. 開始遍歷,遍歷結束條件爲 左指針不小於右指針
  4. 若是左邊遍歷的最大值小於右邊遍歷的最大值,說明只要有水溝(即小於左邊最大值 max_left 的元素)就會有積水,由於右邊的最大值能夠保證左邊水溝的積水不會流失掉;一樣,若是左邊遍歷的最大值不小於右邊遍歷的最大值,只要右邊有水溝(即小於右邊最大值 max_right 的元素)就會有積水;

具體實現,請直接參考 CuGBabyBeaR 文章。

總結

本題的蓄水池問題,若是理解了問題本質並逆向思惟,將尋找某塊磚四周最低有效磚高度(尋找有效磚涉及到邊界擴散)轉化爲判斷某塊磚是否須要漏水條件,那麼問題就簡化不少了,那後續編碼也就很容易實現了,本文算法的時間複雜度爲 $O(n^3)$。

相關文章 »

(2017-12-05)

相關文章
相關標籤/搜索