Leetcode练习题:搜索


这章节内容包含的是深度优先搜索和宽度优先搜索的算法题目,虽然这两种思想很好理解,应用也十分广泛,在之前递归回溯的题目也学习了,但是我个人就不是很喜欢写dfs和bfs,每次函数设计时,代码总是写的不够简洁或者是情况考虑不完善,但即使不擅长不喜欢,也要(也更要)多学习多做多积累。

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’ 都不会被填充为 ‘X’。 任何不在边界上,或不与边界上的 ‘O’ 相连的 ‘O’ 最终都会被填充为 ‘X’。

因此找到边界的O,或者是任何与边界O相连的O就都是无法包围的,将它们标记。
首先找到所有边界上的O,进入队列,进行BFS往四个方向开始搜索,如果没有超过边界并且是O则进入队列,将其都标识为"A"
最后对矩阵进行填充,是A的变为O,是O的变为X

代码实现

    int dir[5]={0,-1,0,1,0};

    void solve(vector<vector<char>>& board) {
        int row=board.size();
        if(row==0)
        {
            return;
        }
        int column=board[0].size();

        queue<pair<int,int>> q;
        //从外往里
        for(int i=0;i<row;i++)
        {
            if(board[i][0]=='O')
            {
                q.emplace(i,0);
            }
            if(board[i][column-1]=='O')
            {
                q.emplace(i,column-1);
            }
        }
        for(int i=1;i<column-1;i++)
        {
            if(board[0][i]=='O')
            {
                q.emplace(0,i);
            }
            if(board[row-1][i]=='O')
            {
                q.emplace(row-1,i);
            }
        }

        while(!q.empty())
        {
            int x=q.front().first;
            int y=q.front().second;
            q.pop();

            board[x][y]='A';
            for(int i=0;i<4;i++)
            {
                int dx=x+dir[i];
                int dy=y+dir[i+1];
                if(dx<0||dx>=row||dy<0||dy>=column||board[dx][dy]!='O')
                {
                    continue;
                }
                q.emplace(dx,dy);
            }
        }

        for(int i=0;i<row;i++)
        {
            for(int j=0;j<column;j++)
            {
                if(board[i][j]=='A')
                {
                    board[i][j]='O';
                }else if(board[i][j]=='O')
                {
                    board[i][j]='X';
                }
            }
        }
    }

反思与收获

一维的方向数组,以及读懂题目的解释,凡是不能被包围的,那就是跟边界O相连的或者就是边界O,从外往里思考,再对每一个边界O进行BFS搜索解决环绕区域问题。

207:课程表

问题描述

你必须选修 numCourse 门课程,记为 0 到 numCourse-1 。

在选修某些课程之前需要一些先修课程。 例如,想要学习课程 0 ,你需要先完成课程 1 ,我们用一个匹配来表示他们:[0,1]

给定课程总量以及它们的先决条件,请你判断是否可能完成所有课程的学习?

示例 1:

输入: 2, [[1,0]]

输出: true

解释: 总共有 2 门课程。学习课程 1 之前,你需要完成课程 0。所以这是可能的。

示例 2:

输入: 2, [[1,0],[0,1]]

输出: false

解释: 总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0;并且学习课程 0 之前,你还应先完成课程 1。这是不可能的。

提示:

输入的先决条件是由边组成,每条边包含两个元素,比如[1,0],表示学习课程 1 之前,需要先完成课程 0

你可以假定输入的先决条件中没有重复的边。

解题思路

建立图模型,先决课程之间存在一条边。
一开始容易想错,以为是从源头遍历能遍历到全部,但其实有些课程可能是单独。
该问题的实质其实是来判断这个图是有没有环,如果有环 则说明不能实现,只要没有环那就可以修完全部课程。

判断环的办法是,借用visited数组,0表示为访问过,1表示开始访问正在遍历中,-1表示访问结束遍历结束。

代码实现

  vector<int> visited;

    bool dfs(int a,vector<vector<int>> &edges)
    {
        //如果该节点没有边
      if(edges[a].size()==0)
      {
          return true;
      }
      //如果该节点访问结束了
      if(visited[a]==-1)
      {
          return true;
      }
      //如果该节点正在访问中,但是有访问到它了,则说明有环
      if(visited[a]==1)
      {
          return false;
      }
        //将状态改为正在访问
      visited[a]=1;

      bool valid=true;
      for(int i=0;i<edges[a].size();i++)
      {
          valid=dfs(edges[a][i],edges);
          if(!valid)
          {
              break;
          }
      }
      //访问结束
      visited[a]=-1;
      return valid;
    }

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {

        vector<vector<int>> edges(numCourses);
        visited=vector<int> (numCourses+1,0);

        //edges.resize(numCourses);
        for(int i=0;i<prerequisites.size();i++)
        {

           edges[prerequisites[i][0]].push_back(prerequisites[i][1]);

        }
        bool valid=true;

        for(int i=0;i<numCourses;i++)
        {
            for(int j=0;j<edges[i].size();j++)
            {
                valid=dfs(edges[i][j],edges);
                if(!valid)
                {
                    return valid;
                }
            }
        }
        return valid;
    }

反思与收获

判断图中是否存在环,利用visited数组作为状态数组,多增加一个开始访问的状态,用0,-1,1来表示,如果再次访问到状态为正在访问的节点则说明存在环。

332:重新安排行程

问题描述

给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。

说明:

如果存在多种有效的行程,你可以按字符自然排序返回最小的行程组合。例如,行程 [“JFK”, “LGA”] 与 [“JFK”, “LGB”] 相比就更小,排序更靠前

所有的机场都用三个大写字母表示(机场代码)。

假定所有机票至少存在一种合理的行程。

示例 1:

输入: [[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]

输出: [“JFK”, “MUC”, “LHR”, “SFO”, “SJC”]

示例 2:

输入:
[[“JFK”,“SFO”],[“JFK”,“ATL”],[“SFO”,“ATL”],[“ATL”,“JFK”],[“ATL”,“SFO”]]

输出: [“JFK”,“ATL”,“JFK”,“SFO”,“ATL”,“SFO”]

解释: 另一种有效的行程是 [“JFK”,“SFO”,“ATL”,“JFK”,“ATL”,“SFO”]。但是它自然排序更大更靠后。

解题思路

建立图模型,进行DFS搜索,最初元素为JFK,关键就是需要排序更小的为答案,因此我们对邻接表做一点操作,与该节点相邻的节点用小顶堆来存储,每次优先遍历堆顶节点即为字符排序最小的,用哈希表来实现邻接表。

代码实现

    vector<string> ans;
    void dfs(unordered_map<string,priority_queue<string,vector<string>,greater<string>>> &m,string now)
    {
        while(m.find(now)!=m.end()&&m[now].size()>0)
        {
            string next=m[now].top();
            m[now].pop();
            dfs(m,next);
        }
        ans.insert(ans.begin(),now);
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        unordered_map<string,priority_queue<string,vector<string>,greater<string>>> m;
        for(int i=0;i<tickets.size();i++)
        {
            m[tickets[i][0]].push(tickets[i][1]);
        }

        dfs(m,"JFK");
        return ans;

    }

反思与收获

 unordered_map<string,priority_queue<string,vector<string>,greater<string>>>

用哈希表+堆来实现图模型学习到了,可以对每个节点链接的所有节点进行一些排序操作,学习到了。

491:递增子序列

问题描述

给定一个整型数组, 你的任务是找到所有该数组的递增子序列并输出其数量,递增子序列的长度至少是2。

示例:

输入: [4, 6, 7, 7]

输出: 8

解释:递增子序列包括:[[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7,7], [7,7], [4,7,7]],共8个

说明:

给定数组的长度不会超过15。

数组中的整数范围是 [-100,100]。

给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

解题思路

使用递归来实现枚举,使用最基本的模板即可,这一段话来自官方题解
这是一个递归枚举子序列的通用模板,即用一个临时数组temp来保存当前选出的子序列,使用 cur 来表示当前位置的下标,在 dfs(cur, nums) 开始之前,[0,cur−1] 这个区间内的所有元素都已经被考虑过,而 [cur,n] 这个区间内的元素还未被考虑。在执行 dfs(cur, nums) 时,我们考虑 cur 这个位置选或者不选,如果选择当前元素,那么把当前元素加入到 temp 中,然后递归下一个位置,在递归结束后,应当把 temp 的最后一个元素删除进行回溯;如果不选当前的元素,直接递归下一个位置。

该题目就在于怎么来舍弃重复的情况,比如4677,467和467,题解这一部分说的很绕

前面选,后面选
前面选,后面不选
前面不选,后面选
前面不选,后面不选

问题是2,3这两种是重复的情况,关于前面选还是不选递归已经在进行了,选择排除第二情况就是方便,就是当前Index元素=temp数组的最后一个元素,此时不选的操作也无需进行了,直接return

代码实现

    vector<vector<int>> ans;

    vector<vector<int>> findSubsequences(vector<int>& nums) {
        vector<int> now;
        dfs(nums,now,0);
        return ans;
    }

    void dfs(vector<int> nums,vector<int> &now,int index)
    {
        if(index>=nums.size())
        {
            if(now.size()>=2)
            {
                ans.push_back(now);

            }
            return;
        }
        //选择
        if(now.empty()||nums[index]>=now[now.size()-1])
        {
            now.push_back(nums[index]);
            dfs(nums,now,index+1);
            now.pop_back();
        }
        //相同的话 提前Return 把不选的情况的避免,从而避免了重复情况
        if(index>=0&&!now.empty()&&nums[index]==now[now.size()-1])
        {
            return;
        }
        //不选择
        dfs(nums,now,index+1);
    }

反思与收获

要记住递归枚举的这个实现模板,不要忘记在选择时候也要将最后一个元素弹出再进行回溯。
去重的这个操作,要好好理解,将四种情况都列出来,怎么样方便的去掉重复的情况。就是当元素相同的时候,该元素不允许不被选择进行下面的递归。

529:扫雷游戏

问题描述

让我们一起来玩扫雷游戏!

给定一个代表游戏板的二维字符矩阵。 ‘M’ 代表一个未挖出的地雷,‘E’ 代表一个未挖出的空方块,‘B’ 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的已挖出的空白方块,数字(‘1’ 到 ‘8’)表示有多少地雷与这块已挖出的方块相邻,‘X’ 则表示一个已挖出的地雷。

现在给出在所有未挖出的方块中(‘M’或者’E’)的下一个点击位置(行和列索引),根据以下规则,返回相应位置被点击后对应的面板:

如果一个地雷(‘M’)被挖出,游戏就结束了- 把它改为 ‘X’。

如果一个没有相邻地雷的空方块(‘E’)被挖出,修改它为(‘B’),并且所有和其相邻的未挖出方块都应该被递归地揭露。

如果一个至少与一个地雷相邻的空方块(‘E’)被挖出,修改它为数字(‘1’到’8’),表示相邻地雷的数量。

如果在此次点击中,若无更多方块可被揭露,则返回面板。

示例 1:

输入:

[[‘E’, ‘E’, ‘E’, ‘E’, ‘E’],

[‘E’, ‘E’, ‘M’, ‘E’, ‘E’],

[‘E’, ‘E’, ‘E’, ‘E’, ‘E’],

[‘E’, ‘E’, ‘E’, ‘E’, ‘E’]]

Click : [3,0]

输出:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],

[‘B’, ‘1’, ‘M’, ‘1’, ‘B’],

[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],

[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

解释:
在这里插入图片描述

示例 2:

输入:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],

[‘B’, ‘1’, ‘M’, ‘1’, ‘B’],

[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],

[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

Click : [1,2]

输出:

[[‘B’, ‘1’, ‘E’, ‘1’, ‘B’],

[‘B’, ‘1’, ‘X’, ‘1’, ‘B’],

[‘B’, ‘1’, ‘1’, ‘1’, ‘B’],

[‘B’, ‘B’, ‘B’, ‘B’, ‘B’]]

解释:

在这里插入图片描述

注意:

输入矩阵的宽和高的范围为 [1,50]。

点击的位置只能是未被挖出的方块 (‘M’ 或者 ‘E’),这也意味着面板至少包含一个可点击的方块。

输入面板不会是游戏结束的状态(即有地雷已被挖出)。

简单起见,未提及的规则在这个问题中可被忽略。例如,当游戏结束时你不需要挖出所有地雷,考虑所有你可能赢得游戏或标记方块的情况。

解题思路

很有意思的dfs问题,玩过很久的扫雷游戏,可以自己写程序来实现。
这一次的点击,如果该位置是炸弹,那直接将M变成X,返回游戏结束了,无需再搜索
如果不是炸弹,那么可能要变成B可能要变成数字,取决于周围有没有炸弹。
首先对八个方向进行判断,count记录周围炸弹数量,如果count>0,则更新数字,无需再搜索。
如果周围没有炸弹,那将其改为B,继续向八个方向探索只要方块为E。

代码实现

  int dir_x[8]={-1,-1,-1,0,0,1,1,1};
    int dir_y[8]={1,0,-1,1,-1,1,0,-1};

    void dfs(vector<vector<char>> &board,int x, int y)
    {
        int count=0;
        //记录周围炸弹的个数
        for(int i=0;i<8;i++)
        {
            int dx=dir_x[i]+x;
            int dy=dir_y[i]+y;
            if(dx<0||dx>=board.size()||dy<0||dy>=board[0].size())
            {
                continue;
            }
            count+=board[dx][dy]=='M';
        }
        //如果有炸弹,则更新为数字
        if(count>0)
        {
            board[x][y]=count+'0';
        }else
        {
            //如果没有炸弹 则向周围继续进行dfs搜索
            board[x][y]='B';
            for(int i=0;i<8;i++)
            {
                int dx=dir_x[i]+x;
                int dy=dir_y[i]+y;
                //如果越界或者已经被访问过
                if(dx<0||dx>=board.size()||dy<0||dy>=board[0].size()||board[dx][dy]!='E')
                {
                    continue;
                }
                dfs(board,dx,dy);
            }
        }
    }

    vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
        int x=click[0];
        int y=click[1];

        //点到炸弹 无需搜索
        if(board[x][y]=='M')
        {
            board[x][y]='X';
        }else
        {
            dfs(board,x,y);
        }
        return board;
    }

反思与收获

在搜索的时候,对周围进行统计判断,在考虑是否进行深度搜索。了解了小时候玩的扫雷游戏的算法原理。

542:01矩阵

问题描述

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

示例 1:

输入:

0 0 0

0 1 0

0 0 0

输出:

0 0 0

0 1 0

0 0 0

示例 2:

输入:

0 0 0

0 1 0

1 1 1

输出:

0 0 0

0 1 0

1 2 1

解题思路

这题跟第一道包围的题目有点类似的感觉,建立一个答案矩阵,显然我们可以将所有0先访问一遍并且放入队列当中,开始进行BFS搜索,注意最短路径都是BFS来实现的,然后访问队首元素四个方向上相连的元素,如果没有访问过,则该位置的答案就是队首答案+1.

代码实现

    int dir[5]={-1,0,1,0,-1};

    vector<vector<int>> updateMatrix(vector<vector<int>>& matrix) {
        int row=matrix.size();
        int column=matrix[0].size();

        vector<vector<int>> ans(row,vector<int>(column,0));
        vector<vector<int>> visited(row,vector<int>(column,0));

        queue<pair<int,int>> q;

        for(int i=0;i<row;i++)
        {
            for(int j=0;j<column;j++)
            {
                if(matrix[i][j]==0)
                {
                    q.emplace(i,j);
                    visited[i][j]=1;
                }
            }
        }

        while(!q.empty())
        {
            int x=q.front().first;
            int y=q.front().second;

            q.pop();

            for(int i=0;i<4;i++)
            {
                int dx=x+dir[i];
                int dy=y+dir[i+1];
                if(dx>=0&&dx<row&&dy>=0&&dy<column&&!visited[dx][dy])
                {
                    ans[dx][dy]=ans[x][y]+1;
                    q.emplace(dx,dy);
                    visited[dx][dy]=1;
                }
            }
        }
        return ans;
    }

反思与收获

BFS来实现最短路径,这题不要从
不为0的元素往0元素靠来思考,这样子的话相当于每一个不为0的元素都要从头进行一个BFS搜索。
而是从0元素往不为0元素思考,最外一圈与0相连的元素距离为1,再BFS搜索就继续+1。

638:大礼包

问题描述

在商店中, 有许多在售的物品。

然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。

现给定每个物品的价格,每个大礼包包含物品的清单,以及待购物品清单。请输出确切完成待购清单的最低花费。

每个大礼包的由一个数组中的一组数据描述,最后一个数字代表大礼包的价格,其他数字分别表示内含的其他种类物品的数量。

任意大礼包可无限次购买。

示例 1:

输入: [2,5], [3,2], [[3,0,5],[1,2,10]]

输出: 14

解释:

有A和B两种物品,价格分别为¥2和¥5。

你需要购买3个A和2个B。

大礼包1,你可以以¥5的价格购买3A和0B。

大礼包2, 你可以以¥10的价格购买1A和2B。

所以你付了¥10购买了1A和2B(大礼包2),以及¥4购买2A。

示例 2:

输入: [2,3,4], [1,2,1], [[1,1,0,4],[2,2,1,9]]

输出: 11

解释:

A,B,C的价格分别为¥2,¥3,¥4.

你需要买1A,2B和1C

你可以用¥4购买1A和1B,也可以用¥9购买2A,2B和1C。

所以你付了¥4买了1A和1B(大礼包1),以及¥3购买1B, ¥4购买1C。

你不可以购买超出待购清单的物品,尽管购买大礼包2更加便宜。

说明:

最多6种物品, 100种大礼包。

每种物品,你最多只需要购买6个。

你不可以购买超出待购清单的物品,即使更便宜。

解题思路

这题可能一开始会想着使用动态规划来解决,但其实使用深度搜索递归实现也很方便。首先计算全部单买的需要的花费,然后考虑使用大礼包,如果数量符合要求,就使用该礼包,并将need更新,继续递归进行搜索。每个礼包能用多次,因此无需记录下标,最后会返回最小的ans值。

代码实现

   int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {
        int ans=0;
        for(int i=0;i<needs.size();i++)
        {
            ans+=price[i]*needs[i];
        }

        for(int i=0;i<special.size();i++)
        {
            vector<int> temp=needs;
            int j;
            for(j=0;j<needs.size();j++)
            {
                temp[j]=temp[j]-special[i][j];
                if(temp[j]<0) break;
            }
            if(j==needs.size())
            {
                ans=min(ans,special[i][j]+shoppingOffers(price,special,temp));
            }
        }
        return ans;

    }

反思与收获

深度搜索解决大礼包这类问题,使用ans来不断更新最小值,最后返回ans来获取答案。

756:

问题描述

现在,我们用一些方块来堆砌一个金字塔。 每个方块用仅包含一个字母的字符串表示。

使用三元组表示金字塔的堆砌规则如下:

对于三元组(A, B, C) ,“C”为顶层方块,方块“A”、“B”分别作为方块“C”下一层的的左、右子块。当且仅当(A, B, C)是被允许的三元组,我们才可以将其堆砌上。

初始时,给定金字塔的基层 bottom,用一个字符串表示。一个允许的三元组列表 allowed,每个三元组用一个长度为 3 的字符串表示。

如果可以由基层一直堆到塔尖就返回 true ,否则返回 false 。

示例 1:

输入:bottom = “BCD”, allowed = [“BCG”, “CDE”, “GEA”, “FFF”]

输出:true

解析:

可以堆砌成这样的金字塔:

 A

/ \

G   E

/ \ / \

B   C   D

因为符合(‘B’, ‘C’, ‘G’), (‘C’, ‘D’, ‘E’) 和 (‘G’, ‘E’, ‘A’) 三种规则。

示例 2:

输入:bottom = “AABA”, allowed = [“AAA”, “AAB”, “ABA”, “ABB”, “BAC”]

输出:false

解析:

无法一直堆到塔尖。

注意, 允许存在像 (A, B, C) 和 (A, B, D) 这样的三元组,其中 C != D。

提示:

bottom 的长度范围在 [2, 8]。

allowed 的长度范围在[0, 200]。

方块的标记字母范围为{‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’}。

解题思路

该题我没有用递归来实现,直接写了循环来操作的,但其实是一样的。

每一个位置可能存在多个答案,因此有set来表示,三角形用二维vector来表示。

不断从底向上,如果该层不符合要求,或者到顶了就退出。
底 A B 就对allowed里面所有字符串进行判断,如果A B出现了,则将第三个字符放到上一层set中,注意这里的A B不是单个元素,而是该位置的set,因此我们使用的是set.count来判断元素是否存在

代码实现

   bool pyramidTransition(string bottom, vector<string>& allowed) {
        int level=bottom.length();

        vector<vector<set<char>>> m(level);

        vector<bool> valid(level,false);
        valid[level-1]=true;

        for(int i=0;i<level;i++)
        {
            m[i].resize(i+1);
        }

        for(int i=0;i<level;i++)
        {
            m[level-1][i].insert(bottom[i]);
        }

        for(int i=level-1;i>=1;i--)
        {
            if(!valid[i]||valid[0])
            {
                break;
            }
            for(int j=0;j<i;j++)
            {
                for(int k=0;k<allowed.size();k++)
                {
                    //底部这两个集合中 存在符合allowed的情况
                    if(m[i][j].count(allowed[k][0])&&m[i][j+1].count(allowed[k][1]))
                     {
                        //则将上一行状态改为true 
                      valid[i-1]=true;
                        m[i-1][j].insert(allowed[k][2]);
                    }
                }
            }
        }
        return valid[0];
    }

反思与收获

其实这样的做法是有问题的,因为仅仅一组情况实现了的话,上一层的状态就被改为true了,但事实上需要的是全层,不过依旧没有错,原因是最后返回的是valid[0]顶层的状态,如果不是这层全部满足的话,是堆不起上一层的。但或许还是递归会好一点参考题解

1306:跳跃游戏 III

问题描述

这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。

请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。

注意,不管是什么情况下,你都无法跳到数组之外。

示例 1:

输入:arr = [4,2,3,0,3,1,2], start = 5

输出:true

解释:

到达值为 0 的下标 3 有以下可能方案:

下标 5 -> 下标 4 -> 下标 1 -> 下标 3

下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3

示例 2:

输入:arr = [4,2,3,0,3,1,2], start = 0

输出:true

解释:

到达值为 0 的下标 3 有以下可能方案:

下标 0 -> 下标 4 -> 下标 1 -> 下标 3

示例 3:

输入:arr = [3,0,2,1,2], start = 2

输出:false

解释:无法到达值为 0 的下标 1 处。

解题思路

宽度搜索实现,visited数组辅助。
如果当前start下标元素为0,则直接返回true
将该点进入队列,
i+arr[i]
i-arr[i]
判断这两个位置是否符合范围,并且未访问过,然后查看是否为0,是的话返回true,不是的话修改访问状态,进入队列。

代码实现

 bool canReach(vector<int>& arr, int start) {
        if(arr[start]==0)
        {
            return true;
        }

        int len=arr.size();
        vector<bool> visited(len,false);

        queue<int> q;
        q.push(start);
        visited[start]=true;

        while(!q.empty())
        {
            int cur=q.front();
            //cout<<cur<<endl;
            q.pop();

            int jia=cur+arr[cur];
            int jian=cur-arr[cur];

            if(jia<len&&!visited[jia])
            {
                if(arr[jia]==0)
                {
                    return true;
                }
                q.push(jia);
                visited[jia]=true;
            }

            if(jian>=0&&!visited[jian])
            {
                if(arr[jian]==0)
                {
                    return true;
                }
                q.push(jian);
                visited[jian]=true;
            }
        }
        return false;
    }

反思与收获

跳跃游戏算是经典的宽度搜索方法能解决的问题,前进或后退,完成访问后进行队列,继续搜索。

1345: 跳跃游戏 IV

问题描述

给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。

每一步,你可以从下标 i 跳到下标:

i + 1 满足:i + 1 < arr.length

i - 1 满足:i - 1 >= 0

j 满足:arr[i] == arr[j] 且 i != j

请你返回到达数组最后一个元素的下标处所需的最少操作次数 。

注意:任何时候你都不能跳到数组外面。

示例 1:

输入:arr = [100,-23,-23,404,100,23,23,23,3,404]

输出:3

解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。

示例 2:

输入:arr = [7]

输出:0

解释:一开始就在最后一个元素处,所以你不需要跳跃。

示例 3:

输入:arr = [7,6,9,6,9,6,9,7]

输出:1

解释:你可以直接从下标 0 处跳到下标 7 处,也就是数组的最后一个元素处。

示例 4:

输入:arr = [6,1,9]

输出:2

示例 5:

输入:arr = [11,22,7,7,7,7,7,7,7,22,13]

输出:3

解题思路

这题与上一题有所不同和升级
目的是:从第一个位置到最后一个位置,就是尽量往后走
方法是:i+1或者i-1或者是跳到跟自己一样的元素位置。
如果还是跟上面做法一样的话,就太慢了,因此在找跟自己元素一样这一步骤浪费很多时间,因此我们需要提前用哈希表来存跟该位置元素一样的元素下标。
但注意第三种跳法,跳到跟自己一样的元素,如果这些元素是分开的,那没有问题,但是如果这些元素是连在一起的,比如 1 7 7 7 7 7 3 根本没必要放中间7的位置,只需要放第一个和最后一个就可以了,因为目的是往后面跳。
这一步代码实现的很巧妙
队中的元素为了方便起见设为pai<index,count>

代码实现

    int minJumps(vector<int>& arr) {
        int len=arr.size();
        if(len==1)
        {
            return 0;
        }

        unordered_map<int,vector<int>> m;
        for(int i=0;i<len;i++)
        {
            //相同的话只放第一个和最后一个的index,不同的话放自己就好
            if((i>0&&arr[i]!=arr[i-1])||(i<len-1&&(arr[i]!=arr[i+1])))
               {
                   m[arr[i]].push_back(i);
               }
        }

        queue<pair<int,int>> q;
        //index, count
        q.emplace(0,0);
        vector<bool> visited(len,false);
        visited[0]==true;

        int ans=0;
        bool ok=false;
        while(!q.empty())
        {
            int pos=q.front().first;
            int now=q.front().second;
            int num=arr[pos];
            q.pop();
            //第三种情况 arr[i]==arr[j]
            for(int i=0;i<m[num].size();i++)
            {
                int next=m[num][i];
                if(next==len-1||pos+1==len-1)
                {
                    ans=now+1;
                    ok=true;
                    break;
                }
                if(next>1&&next<len&&!visited[next])
                {
                    q.emplace(next,now+1);
                    visited[next]=true;
                }
            }
            if(ok)
            {
                break;
            }
            int plus_index=pos+1;
            int minus_index=pos-1;

            if(plus_index>0&&plus_index<len&&!visited[plus_index])
            {
                q.emplace(plus_index,now+1);
                visited[plus_index]=true;
            }
            if(minus_index>0&&minus_index<len&&!visited[minus_index])
            {
                q.emplace(minus_index,now+1);
                visited[minus_index]=true;
            }

        }
        return ans;
    }

反思与收获

跳到相同元素的位置,这一步很关键,实现的方法值得学习和思考,舍去中间重复的位置,只记录第一个和最后一个。

 if((i>0&&arr[i]!=arr[i-1])||(i<len-1&&(arr[i]!=arr[i+1])))
               {
                   m[arr[i]].push_back(i);
               }

这个代码也要好好看一下。
队列元素设为pair,自动记录步长,舍去了需要判断层次的操作。否则是len=q.size()…等常规步骤。
——————————————————————————————————
搜索的题目真的很多,而且BFS,DFS太经典了。基本最短路径是利用BFS,其他是DFS,主要注意函数的设计和参数的设计。以及递归枚举的基本模板要牢记。在图结构中应用的比较多,就是灵活使用哈希表、最小堆、集合等实现特定的功能。(๑´ㅂ`๑)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值