week 3 7月13

二叉树的层次遍历

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

示例 1:

week 3 7月13_第1张图片

输入:root = [3,9,20,null,null,15,7]
输出:[[3],[9,20],[15,7]]

示例 2:

输入:root = [1]
输出:[[1]]

示例 3:

输入:root = []
输出:[]

提示:

  • 树中节点数目在范围 [0, 2000] 内
  • -1000 <= Node.val <= 1000

关键词:层次遍历

week 3 7月13_第2张图片

任务:以层为单位,输出二叉树的节点值

 任务1: 对二叉树进行层序遍历

任务2:以层为单位,返回二维数组

任务1:

要实现层次遍历,我们需要借助“队列”这一数据结构。因为队列有着“先进先出”的原则。利用该原则,我们将已经遍历的节点存入队列中,以实现顺序不变。

如上图所示,层序遍历有以下步骤。

 1、根节点root入列

 2、根节点出列,如果根节点有左子树的话,左子树的根节点入列,如果没有就跳过;如果根节点有右子树的话,右子树的根节点入列,如果没有就跳过。

3、当队列不为空时,将队首节点出队,并将队首节点的左右孩子入列,如果没有则跳过

4、重复步骤3,直到队列为空。

代码如下:

queue dui;//定义一个队列
dui.push(root);//把根节点存入
while(!dui.empty())
{
  TreeNode p;
  p = dui.front();//p为队列首个元素
  dui.pop();//弹出p
  if(p->left!=nullptr) //如果p的左子树不为空
  {dui.push(p->left);} //左孩子入列
  if(p->right!=nullptr) //如果p的右子树不为空
  {dui.push(p->right);} //右孩子入列
  //任务2处理
}

任务2:

要把每层节点作为一组,输出所有层的节点。

要实现每层为单位输出元素,可以维护一个二元组dui。在任务1处理时,遍历到i节点,将该节点和层数同时存入result中。

因为每个结点的层数必定是父节点的层数加1。所以,在父节点i出队列时,将其左右孩子同时入队列,同时,记录下他们的层数。

week 3 7月13_第3张图片

 在二元组队列dui中,遍历到某个元素f时,有以下步骤。

 1、找到该元素f的第一个元素(p节点)和p节点的层次deep

 2、如果在最终数组result中,没有p节点所在层数的节点的话,在result中新加一个数组{},并把该节点的值放入数组{}中。如果已经有了该层的某个节点,只需在数组{}中,继续添加即可

 3、如果该节点p有左孩子的话,把p的左孩子和左孩子的层次deep+1放入dui中;如果没有,则跳过。同理,处理右孩子。

 4、重复步骤2和3,直到队列为空。最终输出result。

代码如下:

class Solution {
public:
    vector> levelOrder(TreeNode* root) {
        vector> result;
        if(!root) return result;//如果根节点为空 就返回根节点
        queue> dui;//定义一个队列
        dui.push({root,0});//把根节点和其层数存入
        while(!dui.empty())
        {
            pair f;
            f = dui.front();//f为队列首个元素
            dui.pop();//弹出f
            auto p=f.first;//p为弹出的节点
            int deep = f.second;//deep为弹出节点的层数
            if(p->left!=nullptr) //如果左孩子不为空
            {dui.push({p->left,deep+1});}//将左孩子的层数加入队列中
            if(p->right!=nullptr) //如果右孩子不为空
            {dui.push({p->right,deep+1});}//将右孩子的层数加入队列中
            if(result.size()val);//将p的值加入到该层数组中
        }
        return result;
    }
};

不同路径II

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish”)。

现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

网格中的障碍物和空位置分别用 1 和 0 来表示。

示例 1:

week 3 7月13_第4张图片

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右

示例 2:

week 3 7月13_第5张图片

输入:obstacleGrid = [[0,1],[0,0]]
输出:1

提示:

  • m == obstacleGrid.length
  • n == obstacleGrid[i].length
  • 1 <= m, n <= 100
  • obstacleGrid[i][j] 为 0 或 1

关键词1:路径总数

关键词2:只能向左或者向下

关键词3:障碍物

任务:找到从起点出发,只能向右或者向下,并且避开障碍物的所有路径总数

任务1:从起点到终点的所有路径

任务2:排除所有非向右或者向下的路径

任务3:排除路径上有障碍物的路径

任务1:

维护一个二维数组dp,dp[i][j]表示从起点到(i,j)的所有路径总数。

采用动态规划的方法。对于如下图所示的点(i,j)。去往(i,j)的所有路径只有从上下左右四个方向出来的,即(i-1,j)、(i+1,j)、(i,j+1)和(i,j-1)四点。那么从起点到该点的路径数肯定是四个方向的路径总数。即,dp[i][j]=dp[i-1][j]+dp[i+1][j]+dp[i][j-1]+dp[i][j+1]。所以,下图所示的dp[i][j]的值为1+3+2+1=7。表示,从起点到(i,j)总共可以有7条路径。

week 3 7月13_第6张图片

当然,动态规划必不可少的是需要考虑特殊情况和初始情况,即,边界问题。

在横坐标上,由于纵坐标j为0,所以dp[i][j-1]的情况必定会造成数组越界。同样,纵坐标上也会面临相同问题。所以我们需要特殊考虑横纵坐标上的值。

以横坐标为例,由于任务2的要求,横坐标上的路径只能有1条,那就是从起点直接横着出发所以,dp[i][0]必定为1。纵坐标同样。

对于起点来说,从起点到起点肯定只有1条路,那就是原地不动。所以dp[0][0]可以初始化为1。

代码如下所示:

for(int i=0;i

任务2:

排除向左和向上的路径

其实就是把任务1里的4个方向去掉两个,分别是右边的和上边的方向,即dp[i+1][j]和dp[i][j+1]。

代码修改如下:

dp[i][j]=dp[i-1][j]+dp[i][j-1];
//去掉了两个方向的路径数

 任务3:

排除有障碍物的路径。

如果遇到障碍物,说明该条路不可走。不管该障碍物之前有多少多的路径可以走,一旦遇到障碍物,统统消失。即,从起点到该点的路径数为0。所以只需加一个判断条件即可。

 if(obstacleGrid[i][j]==1) //该点上有障碍物
   {dp[i][j]=0;}//该路径不可达,路径数为0
 else
   {dp[i][j]=dp[i-1][j]+dp[i][j-1];}
   //否则就按照正常路径计算

同样,我们需要考虑边界问题。当横纵坐标上有障碍物时,因为必须满足任务2,只能向右走和向下的条件,该障碍物之前的点的路径数均为1,该点之后的路径数均为0。纵坐标同理。

for(int i = 0;i

当然,如果起点上有障碍物,则起点到任何位置均不可达。总路径数均为0。

if(obstacleGrid[0][0]==1) //起点上有障碍物
{return 0;}//直接返回0路径数

完整的代码如下所示:

class Solution {
public:
    int uniquePathsWithObstacles(vector>& obstacleGrid) {
        int m=obstacleGrid.size();//有m行
        int n=obstacleGrid[0].size(); //有n列
        vector> dp(m,vector(n));
        //dp[i][j]表示从起点到(i,j)有多少路径
        if(obstacleGrid[0][0]==1) {return 0;}
        //如果障碍物在起点,那么从起点到任何点的路径数均为0
        else{dp[0][0]=1;}//否则起点到起点的路径数为1
        for(int i = 0;i

旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

示例 1:

week 3 7月13_第7张图片

输入:head = [1,2,3,4,5], k = 2

输出:[4,5,1,2,3]

示例 2:

week 3 7月13_第8张图片

输入:head = [0,1,2], k = 4

输出:[2,0,1]

提示:

  • 链表中节点的数目在范围 [0, 500] 内

  • -100 <= Node.val <= 100

  • 0 <= k <= 2 * 109

思路:官方的做法是将链表连成一个环,遍历k个节点,最终停靠的位置上断开,就形成了新的链表。

week 3 7月13_第9张图片

遍历两次链表,找到重要的三个结点,分别是尾指针jishu、断开的节点first和断开节点的前一个节点pre。其中,first用于作为新链表的头结点,pre作为新链表的尾结点,jish用于首尾连接。

同时,找到特例if (head == nullptr || head->next == nullptr || k == 0)以及if(n-k==n)  该情况无法找到以上三种节点,所以都需要特殊处理,即,返回原链表。

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
        if (head == nullptr || head->next == nullptr || k == 0) return head;
        int n=1;//记录链表节点个数
        ListNode *first = head;
        ListNode *jishu = head;//用于记录节点数,顺便找到尾结点
        ListNode *pre = head;
        while(jishu->next!=nullptr) { jishu=jishu->next;n++;}
        k=k%n;//k为实际右移次数
        first=head;
        pre=head;
        if(n-k==n) return head;
        for(int i=0;inext;first=first->next;}//找到需要改变的点和他的前一个节点
        first=first->next;
        ListNode *temphead=new ListNode(0);//创建一个临时头结点,并且他的下一节点为转折点
        temphead->next=first;
        pre->next=nullptr;
        jishu->next=head;
        return temphead->next;
    }
};


你可能感兴趣的:(算法,数据结构,c++)