【PHP解法==LeetCode(回溯递归4-二维平面上的回溯)】79.单词搜索 && 200.岛屿的个数 && 130.被围绕的区域 && 417.太平洋大西洋水流问题

目录

79.单词搜索

200.岛屿的个数

130.被围绕的区域

417.太平洋大西洋水流问题


二维平面上的递归回溯,经常使用 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 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。

规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。

请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。

提示:

  1. 输出坐标的顺序不重要
  2. 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;
    }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
题目描述: 给定一个只包含正整数的非空数组,是否可以将这个数组分成两个子集,使得两个子集的元素和相等。 示例: 输入:[1, 5, 11, 5] 输出:true 解释:数组可以分割成 [1, 5, 5] 和 [11]。 解题思路: 这是一道经典的 0-1 背包问题,可以使用动态规划或者回溯算法解决。 使用回溯算法,需要定义一个 backtrack 函数,该函数有三个参数: - 数组 nums; - 当前处理到的数组下标 index; - 当前已经选择的元素和 leftSum。 回溯过程中,如果 leftSum 等于数组元素和的一半,那么就可以直接返回 true。如果 leftSum 大于数组元素和的一半,那么就可以直接返回 false。如果 index 到达数组末尾,那么就可以直接返回 false。 否则,就对于当前元素,有选择和不选择两种情况。如果选择当前元素,那么 leftSum 就加上当前元素的值,index 就加 1。如果不选择当前元素,那么 leftSum 不变,index 也加 1。最终返回所有可能性的结果中是否有 true。 Java 代码实现: class Solution { public boolean canPartition(int[] nums) { int sum = 0; for (int num : nums) { sum += num; } if (sum % 2 != 0) { return false; } Arrays.sort(nums); return backtrack(nums, nums.length - 1, sum / 2); } private boolean backtrack(int[] nums, int index, int leftSum) { if (leftSum == 0) { return true; } if (leftSum < 0 || index < 0 || leftSum < nums[index]) { return false; } return backtrack(nums, index - 1, leftSum - nums[index]) || backtrack(nums, index - 1, leftSum); } }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值