目录
二维平面上的递归回溯,经常使用 FloodFill 算法,泛洪填充算法,也是深度优先dfs算法
例如从图的开始,向外扩散蔓延,像洪水泛滥一样扩散到满足条件的所有区域,即FloodFill 算法
对于下列的题目,79和200是整个图进行FloodFill ,而130和417则是仅从边界进行泛洪
79.单词搜索
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
board = [ ['A','B','C','E'], ['S','F','C','S'], ['A','D','E','E'] ] 给定 word = "ABCCED", 返回 true. 给定 word = "SEE", 返回 true. 给定 word = "ABCB", 返回 false.
从第一个元素开始去寻找,四个方向进行dfs,递归回溯
解法:如下图
(1)从第一个元素开始取A
(2)A的上下左右四个方向依次进行dfs,递归回溯,因为A没有上和左,所有先从右开始
(3)右取到B,再从B的四个方向继续dfs,依次类推,直到整张图遍历完毕
class Solution {
private $m,$n,$board,$word,$len;
private $direction = [[-1,0],[0,1],[1,0],[0,-1]]; //初始化方向数组,[左,右,下,上]
private $visited = []; //初始化 已经访问过的元素下标 的记录数组
/**
* @param String[][] $board
* @param String $word
* @return Boolean
*/
function exist($board, $word) {
$this->m = count($board); //初始化行,使其成为成员变量
$this->n = count($board[0]); //初始化列
$this->board = $board; //使其成为成员变量
$this->word = $word;
$len = strlen($word); //初始化条件判断
if($len == 0) return true;
$this->len = $len - 1;
if($this->m == 0) return false;
//依次遍历每一个数组元素,进行搜索,成功搜索到则停止递归,返回结果
for($i = 0;$i<$this->m;++$i){
for($j=0;$j<$this->n;++$j){
if($this->searchWord($board,$word,$len-1,0,$i,$j)){
return true;
}
}
}
return false;
}
/**
* [递归回溯取结果]
* @param [type] $index [长度]
* @param [type] $startx [开始的x坐标值]
* @param [type] $starty [开始的y坐标值]
*/
private function searchWord($index,$startx,$starty){
//当长度 = word 的长度-1时,最后一次判断数组表的值是否等于单词的最后一个字符,返回bool结果
if($index == $this->len){
return $this->board[$startx][$starty] == $this->word[$index];
}
//当前表中的值满足单词的对应下标的值,则可以继续下方四个方向的取值递归回溯
if($this->board[$startx][$starty] == $this->word[$index]){
$this->visited[$startx][$starty] = 1; //记录该下标已经被访问过
for($i=0;$i<4;++$i){
$newx = $startx + $this->direction[$i][0]; //对四个方向取新的xy值,进行递归
$newy = $starty + $this->direction[$i][1];
if($this->inArea($newx,$newy) //判断新的xy值是否在合法区域内
&& //判断新的xy值是否被访问过
(empty($this->visited[$newx][$newy]) || $this->visited[$newx][$newy] == 0)
&& //可以进行递归回溯,取bool值
$this->searchWord($index+1,$newx,$newy)){
return true;
}
}
$this->visited[$startx][$starty] = 0; //回溯,将该下标置为未被访问
}
return false;
}
private function inArea($x,$y){
return $x>=0 && $x < $this->m && $y >= 0 && $y<$this->n;
}
}
200.岛屿的个数
给定一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入: 11110 11010 11000 00000 输出: 1示例 2:
输入: 11000 11000 00100 00011 输出: 3
解法:如下图,进行泛洪填充,FloodFill
(1)遍历整个图,从第一个节点开始,已经找到了一个岛屿,进行四个方向的深度遍历dfs,直到把满足条件的岛屿全部标记为2
(2)遍历到水域时,直接跳过
(3)直到遍历到第3行,第3列,又找到一个标记为1的新岛屿,则再进行四个方向的深度遍历dfs,标记整个岛屿
(4)以此类推,遍历完整个图,解题结束
class Solution {
private $m, $n, $grid; //使行列图成为成员变量
private $direction = [[-1, 0], [0, 1], [1, 0], [0, -1]]; //初始化方向数组,[左,右,下,上]
/**
* @param String[][] $grid
* @return Integer
*/
function numIslands($grid) {
$this->grid = $grid; //初始化图,行,列,使其成为类的成员变量
$this->m = count($grid);
$this->n = count($grid[0]);
$count = 0; //初始化最终找到的岛屿数量
if ($this->m == 0) return $count; //初始化判断,当图为空时,找到0个岛屿
for ($i = 0; $i < $this->m; ++$i) { //遍历图,进行递归
for ($j = 0; $j < $this->n; ++$j) {
if ($this->grid[$i][$j] == 1) { //当找到一个标记为1的点,则该点为新找到的岛屿点
$this->markIsland($i, $j); //标记从这个点开始,泛洪后的整个岛屿
$count++; //整个岛屿标记完毕,岛屿数 + 1
}
}
}
return $count;
}
/**
* [标记整个岛屿]
* @param [type] $startx [开始行下标]
* @param [type] $starty [开始列下标]
*/
private function markIsland($startx, $starty) {
if ($this->grid[$startx][$starty] == 1) { //该点是岛屿点,则继续下面的递归洪泛
$this->grid[$startx][$starty] = 2; //将该岛屿点标记为2
for ($i = 0; $i < 4; ++$i) { //从四个方向探索岛屿点
$newx = $startx + $this->direction[$i][0];
$newy = $starty + $this->direction[$i][1];
if ($this->inArea($newx, $newy)) { //判断新的坐标点是否越界
$this->markIsland($newx, $newy); //探索标记
}
}
}
}
private function inArea($x, $y) {
return $x >= 0 && $x < $this->m && $y >= 0 && $y < $this->n;
}
}
130.被围绕的区域
给定一个二维的矩阵,包含 'X'
和 'O'
(字母 O)。
找到所有被 'X'
围绕的区域,并将这些区域里所有的 'O'
用 'X'
填充。
示例:
X X X X X O O X X X O X X O X X 运行你的函数后,矩阵变为: X X X X X X X X X X X X X O X X解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的
'O'
都不会被填充为'X'
。 任何不在边界上,或不与边界上的'O'
相连的'O'
最终都会被填充为'X'
。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
解法:可以标记所有边界的O元素,这些元素不被围绕
(1)遍历行列的边界,如果找到O,则进行FloodFill标记
(2)把所有边界上的O标记为 -
(3)再遍历整张图,将所有 - 转换为 O,所有 O 转换为 -
class Solution {
private $direction = [[-1,0],[0,1],[1,0],[0,-1]]; //初始化方向数组,[左,右,下,上]
private $m,$n; //使行列成为成员变量
/**
* @param String[][] $board
* @return NULL
*/
function solve(&$board) {
$this->m = count($board); //初始化条件判断,如果数组为空,则无需操作
if ($this->m == 0) {
return;
}
$this->n = count($board[0]);
//在边界中寻找是否存在'O',以这个'O'进行FloodFill,标记这些'O',表示之后不对这些元素进行操作,因为这些'O'不被围绕
for ($i = 0;$i<$this->m;++$i) { //从每一行开始,对边界值,即左元素和右元素进行标记
$this->changeO($board, $i, 0);
$this->changeO($board, $i, $this->n - 1);
}
for ($i = 0;$i<$this->n;++$i) { //从每一列开始,对边界值,即上元素和下元素进行标记
$this->changeO($board, 0, $i);
$this->changeO($board, $this->m -1, $i);
}
//遍历整张图
//把所有标记为'-'转变成'O',因为这些'-'原本为不被围绕的'O'
//把所有标记为'O'转变成'-',因为'O'为被围绕的'O',所以不被标记
for ($i = 0;$i<$this->m;++$i) {
for ($j = 0;$j<$this->n;++$j) {
if ($board[$i][$j]=='O') {
$board[$i][$j]='X';
}
if ($board[$i][$j]=='-') {
$board[$i][$j]='O';
}
}
}
return;
}
/**
* [标记'O']
* @param [type] &$board [引用传值原始图]
* @param [type] $startx [开始行下标]
* @param [type] $starty [开始列下标]
*/
private function changeO(&$board, $startx, $starty) {
if ($this->outArea($startx, $starty) || $board[$startx][$starty] != 'O') {
return; //如果该下标越界 或者 不为需要标记的'O',则直接返回
}
$board[$startx][$starty] = '-'; //将该点标记为'-'
for ($i = 0;$i<4;++$i) { //进行四个方向的FloodFill,深度递归遍历
$newx = $startx + $this->direction[$i][0];
$newy = $starty + $this->direction[$i][1];
$this->changeO($board, $newx, $newy);
}
return;
}
/**
* [判断下标点是否越界]
*/
private function outArea($x, $y) {
return $x < 0 || $x >= $this->m || $y < 0 || $y >= $this->n;
}
}
417.太平洋大西洋水流问题
给定一个 m x n
的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
提示:
- 输出坐标的顺序不重要
- m 和 n 都小于150
示例:
给定下面的 5x5 矩阵: 太平洋 ~ ~ ~ ~ ~ ~ 1 2 2 3 (5) * ~ 3 2 3 (4) (4) * ~ 2 4 (5) 3 1 * ~ (6) (7) 1 4 5 * ~ (5) 1 1 2 4 * * * * * * 大西洋 返回:[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).
解法:边界都是可以到达对应边界的海洋的,因此可以从对应边界进行FloodFill,标记所有可以到达该海洋的点,
(1)初始化,$pacific = $atlantic = [];两个数组,这两个数组记录能到达对应海洋的下标志
(2)遍历行边界,左边界能到达太平洋,右边界能到达大西洋
(3)注意判断条件,进入递归函数的坐标点是否被访问过;是否越界;是否比前一坐标值小;满足其一则不可到达对应海洋。
(4)将满足条件的坐标点标记为true,进行四个方向的FloodFill,递归标记
(5)遍历列边界,上边界能到达太平洋,下边界能到达大西洋,重复上面的过程
(6)遍历整张图,坐标点在$pacific,$atlantic 两个数组中同时标记为true时为该题目满足条件的点
class Solution {
private $m, $n, $matrix; //使行,列,海域成为成员变量
private $direction = [[-1, 0], [0, 1], [1, 0], [0, -1]]; //初始化方向数组,[左,右,下,上]
/**
* @param Integer[][] $matrix
* @return Integer[][]
*/
function pacificAtlantic($matrix) {
$this->matrix = $matrix; //初始化行,列,海域
$this->m = count($matrix);
$this->n = count($matrix[0]);
$res = [];
if ($this->m == 0) return $res; //初始化判断
//Pacific:太平洋 Atlantic:大西洋
$pacific = []; //初始化太平洋,大西洋数组
$atlantic = [];
//每一行遍历,对左边界能到太平洋的元素进行FloodFill
//对右边界能到大西洋的元素进行FloodFill
for ($i = 0; $i < $this->m; ++$i) {
$this->getOcean($i, 0, $pacific, $matrix[$i][0]);
$this->getOcean($i, $this->n - 1, $atlantic, $matrix[$i][$this->n - 1]);
}
//每一列遍历,对上边界能到太平洋的元素进行FloodFill
//对下边界能到大西洋的元素进行FloodFill
for ($i = 0; $i < $this->n; ++$i) {
$this->getOcean(0, $i, $pacific, $matrix[0][$i]);
$this->getOcean($this->m - 1, $i, $atlantic, $matrix[$this->m - 1][$i]);
}
//遍历整张图,若图的下标在两个数组中都被标记为true,则证明该点可以到达太平洋和大西洋
for ($i = 0; $i < $this->m; ++$i) {
for ($j = 0; $j < $this->n; ++$j) {
if (isset($pacific[$i][$j]) && isset($atlantic[$i][$j]))
$res[] = [$i, $j];
}
}
return $res;
}
/**
* [泛洪标记所有能到达海洋的点]
* @param [type] $startx [开始行下标]
* @param [type] $starty [开始列下标]
* @param [type] &$visited [引用取值,该数组标记所有能到达海洋的点]
* @param [type] $pre [该坐标的前一坐标的值]
*/
private function getOcean($startx, $starty, &$visited, $pre) {
//判断:坐标是否越界;是否被访问过;是否小于前一个坐标的值;满足其中一个都无法继续标记,直接返回
if ($this->outArea($startx, $starty) || isset($visited[$startx][$starty]) || $this->matrix[$startx][$starty] < $pre)
return;
$visited[$startx][$starty] = true; //该点可以到达海洋,标记为true
for ($i = 0; $i < 4; ++$i) { //进行四个方向的递归标记
$newx = $startx + $this->direction[$i][0];
$newy = $starty + $this->direction[$i][1];
//(新的行坐标,新的列坐标,引用传值把该数组继续传递下去,把当前的坐标作为下一坐标的前一个坐标)
$this->getOcean($newx, $newy, $visited, $this->matrix[$startx][$starty]);
}
}
/**
* [判断坐标是否越界]
*/
private function outArea($x, $y) {
return $x < 0 || $x >= $this->m || $y < 0 || $y >= $this->n;
}
}