前言

力扣刷题套路(剑指offer)总结。

第 1 天、 栈与队列(简单)

  1. 判断两个出栈的元素是否相等,要用.equals()方法。因为Java 代码中,由于 Stack 中存储的是 int 的包装类 Integer ,因此需要使用 equals() 代替 == 来比较值是否相等。
  2. 栈的两种定义方法:
    1. 直接使用Java自带的栈类:Stack<Integer> stack = new Stack<>();
    2. 使用链表LinkList<Interger> stack = new LinkedList<>(); 模拟。为了不弄混还是用第一种方法好。
  3. 队列是先入先出,栈是先入后出
  4. 队列与栈的常用方法:
  • 队列:
方法名 说明
offer()/add() 入队。区别:一些队列有大小限制,因此如果想在一个满的队列中加入一个新项,多出的项就会被拒绝。这时新的 offer 方法就可以起作用了。它不是对调用 add() 方法抛出一个 unchecked 异常,而只是得到由 offer() 返回的 false。
poll()/remove() remove() 和 poll() 方法都是从队列中删除第一个元素。remove() 的行为与 Collection 接口的版本相似, 但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。
peek()/element() element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。
  • 栈:
方法名 说明
push() 压栈
pop() 弹栈
empty() 是否为空
peek() 返回栈顶元素

题目详解与代码实现:力扣刷题day1

1.1 用两个栈实现队列

题目链接用两个栈实现队列

要求:用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTaildeleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )

思路:一个栈负责入队,一个栈负责出队。出队的栈实现了对入队栈的反转。

  1. 当对前需要入队的元素执行appendTail(入队)时,把这些元素压入栈1。
  2. 当对当前队列第一个元素执行deleteHead(出队)时
    1. 若栈2为空,则先把栈1的所有元素压入栈2,然后对栈2执行pop,利用栈2进行反转。
    2. 若栈2不为空,则直接对栈2执行pop

1.2 包含min函数的栈

题目链接包含min函数的栈

要求:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 minpushtop pop 的时间复杂度都是 O(1)O(1)

思路:需求不难,难就难在如何让时间复杂度为O(1)O(1)。可以维护一个非严格最小的辅助栈2,保证栈2的顶端元素储存的是栈1每次push后的当前最小值。典型的空间换时间思想。

push函数的设计:

  1. 当栈1执行入栈的push操作时,判断栈2是否为空。
    1. 如果栈2为空,则将这个值也push入栈2.
    2. 如果栈2不为空,则将这个值和栈2顶端的值peek进行比较。
      1. 如果这个值大于peek,则不执行任何操作。
      2. 如果这个值小于等于peek,则将这个值入栈2。

pop函数的设计:

  1. 当栈1执行pop操作时,判断这个值和栈2顶端的值peek是否相同。
    1. 如果相同,则栈1和栈2的顶端元素分别出栈。
    2. 如果不同,则栈1的顶端元素出栈。

min函数的设计:

  1. 判断栈2是否为空。
    1. 若栈2为空,返回{}。
    2. 若栈2不为空,返回栈2的顶端元素peek

top函数的设计:

​ 返回栈1的顶端元素。

第 2 天、 链表(简单)

题目详解与代码实现:力扣刷题day2

链表这块一般都是定义指针指向头结点,然后慢慢指针右移去遍历。

遍历复杂链表,本质上就是一个图了。可以使用递归+哈希表的方法进行遍历。

2.1 从尾到头打印链表

题目链接从尾到头打印链表

要求:输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回),这里的头节点是有值的。

思路:使用栈来实现反序打印。

  1. 定义一个指针,指向头结点。
  2. 如果当前指针指向的节点不为null,则将这个节点压入栈中,指针右移。
  3. 重复步骤2,直到遍历所有的链表。
  4. 将栈中的元素依次pop出来,将值保存在数组中。
  5. 打印数组。

2.2 反转链表

题目链接反转链表

要求:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。

思路:经典头插法。数据结构学习笔记 02 链表–腾讯面试题-单链表反转

  • 现有一个链表:

    flowchart LR Head-->A-->B-->C

  • 定义一个辅助的头结点reverseHead

    flowchart LR Head-->A-->B-->C reverseHead

  • 从头到尾遍历原来的链表,每遍历一个节点,就将其取出,放在辅助头结点所在的新链表的最前端。

    flowchart LR Head-->B-->C reverseHead-->A

flowchart LR Head-->C reverseHead-->B-->A
flowchart LR Head reverseHead-->C-->B-->A
  • 让head节点指向reverseHead链表中的第一个节点,并将reverseHead指向null
flowchart LR Head-->C reverseHead--xC-->B-->A
  • 结果
flowchart LR Head-->C C-->B-->A

2.3 复杂链表的复制

题目链接复杂链表的复制

要求:请实现 copyRandomList 函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next 指针指向下一个节点,还有一个 random 指针指向链表中的任意节点或者 null。

复杂链表举例

思路:使用哈希表存储已经遍历到的节点,使用递归进行遍历。哈希表中,键为节点,代表了节点的身份证,值也为节点,但对值的nextrandom做了赋值,使值之间相互连线。

  1. 引入当前结点。
  2. 如果当前节点为空nullreturn null。这里是递归边界
  3. 如果当前节点在哈希表中的键集中找不到:
    1. 将当前节点和一个以当前节点的值创建的辅助节点填入哈希表中。
    2. 递归当前节点的next节点headNew.next = copyRandomList(head.next)
    3. next递归完后,递归当前节点的random节点headNew.random = copyRandomList(head.random)
  4. 如果走到这,说明当前节点在哈希表的键集中找得到,直接返回这个节点键对应的值return cachedNode.get(head)
  • 例如对于上述复杂的链表,在执行完next递归后(带箭头的实线表示next):
flowchart LR 2((7))--next-->4((13))--next-->6((11))--next-->8((10))--next-->10((1))--next-->null subgraph node1 1((7))-.->2((7)) end subgraph node2 3((13))-.->4((13)) end subgraph node3 5((11))-.->6((11)) end subgraph node4 7((10))-.->8((10)) end subgraph node5 9((1))-.->10((1)) end
  • 在执行完random递归后(圆头的实线表示random):
flowchart LR 2((7))--next-->4((13))--next-->6((11))--next-->8((10))--next-->10((1))--next-->null 2((7))--random--onull 4((13))--random--o2((7)) 6((11))--random--o10((1)) 8((10))--random--o6((11)) 10((1))--random--o2((7)) subgraph node1 1((7))-.->2((7)) end subgraph node2 3((13))-.->4((13)) end subgraph node3 5((11))-.->6((11)) end subgraph node4 7((10))-.->8((10)) end subgraph node5 9((1))-.->10((1)) end

第 3 天、 字符串(简单)

题目详解与代码实现:力扣刷题记录 day3 字符串

字符串没啥说的,主要是调用一些常见的String方法和StringBuilder方法,charAt也经常用到。

3.1 替换空格

题目链接剑指 Offer 05. 替换空格

要求:请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

最好思路:最简单的方法,直接调用String的replace("target","modified")方法即可。

其他思路

  1. charAt遍历,如果相等就替换。(字符char是可以直接使用"=="来进行比较的,底层就是数字。)
  2. **双指针法,极致解法。**如果不让调库函数最优解是双指针。
    1. 先扩充String空间
    2. 定义左指针指向原始字符串的最后一个位置和右指针指向扩充空间后的最后一个位置。
    3. 左右指针向左遍历。如果左指针指向了空格,那右指针分别填入"0",“2”,"%"并右移。
    4. 如果左指针没指向空格,就把左指针的值赋值给右指针。

3.2 左旋转字符串

题目链接剑指 Offer 58 - II. 左旋转字符串

要求: 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

最好思路:最简单的方法,拼接子字符串subString(目标索引,总字符串长度)+subString(0,目标索引)。一行搞定。

其他思路:利用charAt对字符串往一个StringBuilder里切片,最后输出这个StringBuilder.toString

第 4 天、 查找算法(简单)

题目详解与代码实现:力扣刷题记录 day 4 查找算法(简单)

Java中的字典 Dictionary类已经过时了,想要实现用哈希表就行了。

主要的就是考察的二分法。

如果是排好序的数组,二分法很好用!(二分法可以用递归或者循环来写,最好是循环。)

4.1 数组中的重复数字

题目链接剑指 Offer 03. 数组中重复的数字

要求:找出数组中重复的数字。

在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。

最好思路:利用哈希表(这里只需要判断数字是否相同,不需要判断别的,用hashset)储存数字,如果有重复的就return

其他思路

  1. 暴力遍历:不太行,时间复杂度高。
  2. 利用hashmap,更麻烦一点,时间复杂度也更高,可以但没必要。
  3. 原地交换位置

4.2 在排序数组中查找数字 I

题目链接剑指 Offer 53 - I. 在排序数组中查找数字 I

要求:统计一个数字在排序数组中出现的次数。

最好思路:二分法。

  1. start是0,endlength-1mid为二者中间。
  2. 比较mid和要找的数字的值。
    1. 如果mid比要找的数字大,说明要找的数字在mid的前面,即end = mid - 1
    2. 如果mid比要找的数字小,说明要找的数字在mid的后面,即start = mid + 1
    3. 如果mid和要找的数字相等,向前和向后统计和该数字相同的数字出现的频数。
  3. start<=end时结束循环,因为比较的是[start,end]这样的闭区间。
  4. 返回频数。

4.3 0~n-1中缺失的数字

题目链接剑指 Offer 53 - II. 0~n-1中缺失的数字

要求:一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。

最好思路:二分法。缺失的数字将数组分为左子数组和右子数组,只需要查找右子数组的第一个元素即可。

  1. start是0,endlength-1mid为二者中间。
  2. 比较midmid索引处应该出现的值nums[mid]
    1. 如果mid等于nums[mid],说明还没缺呢,缺失的数字在mid的后面,即start = mid + 1
    2. 不存在mid大于num[mid]的情况,不用考虑。
    3. 如果mid小于num[mid],说明已经缺了,缺失的数字在mid的前面,即end = mid - 1
  3. start<=end时结束循环,因为比较的是[start,end]这样的闭区间。注意结束循环的时候,start指针和end指针是在重合之后的下一次循环结束的。也就是要么end左移了,要么start右移了。无论是哪种情况,start指针始终是在end指针的后一位。
  4. start指针处本来应该出现的值,就是缺失的值。

个人理解:

  • 如果mid指针落入左边的子组,由于没有缺失,mid指针会因为向后二分查找而向右靠
  • 如果mid指针落入右边的子组,由于缺失了数字,mid指针会因为向前二分查找而向前靠
  • 最终,mid指针一定会停在左子组和右子组的交界处
  • 这里真正要找的值即不在左子组,也不再右子组,是一个缺失的值。因此可以用start = mid + 1end = mid - 1

其他思路:也是二分法,当mid指针落入右边子组时,看一下mid指针前面的元素正不正常,不正常就返回。这个思路不好。

第 5 天、查找算法(中等)

题目详解与代码实现:力扣刷题记录 day5 查找方法 - 2

二分法进阶需要注意的点:

  1. 循环条件是start<=end还是start<end?这个和end索引是否是闭区间有关。个人喜欢start<=end
  2. start指针和end指针重合时,如果使用start<=end,仍然会进入一次循环,此时应该注意返回值的变化。
  3. 到底是right = middle呢,还是要right = middle - 1呢?这个也和end索引是否是闭区间有关。同时,也要注意观察是否真的当前节点middle是不符合条件的节点,随机应变。

5.1 二维数组中的查找

题目链接:剑指 Offer 04. 二维数组中的查找

要求:在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

最好思路:从矩阵的右上角来看,把这个矩阵看成是一个排序二叉树。这个值的下面的都是比这个值大的,这个值左边都是比这个值小的。

  1. 考虑特殊情况矩阵为空矩阵{{}}或者{}时,直接返回false
  2. 将矩阵的右上角元素看成是根节点。
  3. 在矩阵的索引范围内,比较要找的值和这个根节点的值
    1. 如果要找的值比这个根节点的值大,检查这个根节点的下面的节点
    2. 如果要找的值比这个根节点的值小,检查这个根节点的左边的值。
    3. 如果正好相等,直接return true
  4. 如果走到这里,那就是找遍了也没找到,直接返回false

其他思路:暴力。

5.2 旋转数组的最小数字

题目链接剑指 Offer 11. 旋转数组的最小数字

要求:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。

给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为1。

最好思路:二分查找,和4.3题类似。不同的地方在于,这个数组是可以有相同的元素的,因此相比于4.3题,稍微复杂。可以把旋转后的数组近似的看成一个反比例函数。左子组都是逐渐上升的,右子组都是逐渐下降的。

旋转数组的近似
  1. start是0,endlength-1mid为二者中间。想要找的元素为右子组的第一个元素
  2. 比较nums[mid]nums[end]
    1. 如果nums[mid]大于nums[end],说明nums[mid]在y轴左边,也就是左子组。因此,要找的点肯定在nums[mid]的右边。因为nums[mid]肯定在左子组,一定不是右子组的第一个元素,因此包括nums[mid]在内的nums[mid]左边的所有的元素都可以忽略了。start = mid + 1;
    2. 如果nums[mid]小于nums[end],说明nums[mid]在y轴右边,也就是右子组。此时,要找的元素可能就是nums[mid],也可能在nums[mid]之前。因此nums[mid]左边的所有的元素都可以忽略,而nums[mid]本身还不能忽略。end = mid;
    3. 如果nums[mid]等于nums[end],此时循环还在继续,证明start指针和end指针还未重合,二者之间还有一定距离。此时由于数组含有重复元素,不好说这两个指针中间的mid是不是指向的最小的,没准还有还有更小的。为了让循环继续,就武断的将end指针向左移,继续循环过程。nums[end]nums[mid]值相同,而end一定在mid的右侧,和mid相比,end一定不是右子组的第一个元素。所以这样做无可厚非。
  3. start=end时,指针重合处一定右子组的第一个元素。当start``mid``end三个指针重合后,仍然会进行一次循环,此时nums[mid]等于nums[end],end指针会再左移一次。所以end指针最终循环结束后是不准的。
  4. 所以最终应该返回start指针指向的元素,而不是end

5.3 第一个只出现一次的字符

题目链接剑指 Offer 50. 第一个只出现一次的字符

要求:在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

最好思路:出现“重复”这种词,第一反应就是用哈希表来做。

  1. 考虑特殊情况,如果输入的字符串是"",返回一个空字符串。
  2. 如果输入的字符串不是"",使用charAt遍历字符串:
    1. 如果在哈希表键集中没有这个字符,就把这个字符存在哈希表的键中,值设置为0。
    2. 如果哈希下表键集中有这个字符,就把哈希表中这个字符的值设置为1。
  3. 使用charAt再次遍历这个字符串,如果哈希表中这个字符的键对应的值为0,则返回。否则继续执行。
  4. 遍历整个字符串后还没找到值是0的键,返回空的字符串。

第 6 天、搜索与回溯算法(简单)

队列使用LinkedList来定义:Queue<TreeNode> nodeQueue = new LinkedList<>();

注意: 层序遍历是按层来,一层一层的遍历节点。

而:

DLR–前序遍历(根在前,从左往右,一棵树的根永远在左子树前面,左子树又永远在右子树前面 )

LDR–中序遍历(根在中,从左往右,一棵树的左子树永远在根前面,根永远在右子树前面)

LRD–后序遍历(根在后,从左往右,一棵树的左子树永远在右子树前面,右子树永远在根前面)

实现前、中、后序遍历是用递归好一点,实现层序遍历理论基础还是BFS,还是队列好一点。

题目详情与代码实现:搜索与回溯算法(简单)

6.1 从上到下打印二叉树(I)

题目链接力扣刷题记录 day6 搜索与回溯算法(简单)

要求:从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。

最好思路:前序遍历的话就是BFS,BFS一般用队列来实现。

  1. 将根节点入队。
  2. 循环,循环结束的条件为队列为空。根节点出队,查找根节点的左子节点和右子节点
    1. 如果左子节点和右子节点存在,将左子节点和右子节点入队。
    2. 如果左子节点和右子节点不存在,抵达退出循环的边界条件,返回null。
  3. 使用一个列表来接收出队的值。

6.2 从上到下打印二叉树(II)

题目链接剑指 Offer 32 - II. 从上到下打印二叉树 II

要求:从上到下按层打印二叉树,同一层的节点按从左到右的顺序打印,每一层打印到一行。和上题类似,但是上题是打印到一个列表里(例如:{1, 2, 3, 4, 5})。但是这道题是要在列表里实现分层,例如:{{1}, {2, 3}, {4, 5}

最好思路:和上题类似,最精髓的地方是使用queue.size()来控制层数。

  1. 将根节点入队。
  2. 循环,循环结束的条件为队列为空。
    1. 新定义一个内层的tempList,用来接收每一次内层循环打印的节点。
    2. 定义一个内层循环,循环的次数为queue.size(),也就是当前层数下offer进队列的所有节点。循环的写法为:for (int i = nodeQueue.size(); i > 0; i--){}。巧妙的地方在于直接将nodeQueue.size()赋值给i将这个固定值保留了。不能写成for(int i = 0; i < nodeQueue.size();i++){},这样写nodeQueue.size()的值就是时刻变化的。
      1. 节点出队,查找节点的左子节点和右子节点
      2. 如果左子节点和右子节点存在,将左子节点和右子节点入队。
      3. 如果左子节点和右子节点不存在,抵达退出循环的边界条件,返回null。
    3. 完成一次内层循环后,用tempList接收这些节点。
    4. 使用FinalList来接受tempList这个列表。
  3. return FinalList。

6.3 从上到下打印二叉树(III)

题目链接剑指 Offer 32 - III. 从上到下打印二叉树 III

要求:请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。

最好思路:和上题类似,仍然定义一个内层循环,来控制层数。不同的地方就是定义一个count用来判定是奇数层数还是偶数层数。奇数层数就使用Collection.reverse翻转tempList,实现反向打印。

第 7 天、搜索与回溯算法(简单)

递归:主要是要定义好递归出口每次递归的逻辑递归函数返回值及其参数这三个要素。

题目详情与代码实现:力扣刷题记录 day7 搜索与回溯算法(简单)

7.1 树的子结构

题目链接剑指 Offer 26. 树的子结构

要求:输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)。B是A的子结构, 即 A中有出现和B相同的结构和节点值。

最好思路递归嵌套递归解法:由于没有要求逐层打印,就不需要BFS,也不需要队列了,直接使用递归嵌套递归前序遍历即可。

recur(A, B) 函数

  • 终止条件:
    • 当节点 B 为空:说明树 B 已匹配完成(越过叶子节点),因此返回 true
    • 当节点 A 为空:说明已经越过树 A 叶子节点,即匹配失败,返回 false
    • 当节点 A 和 B 的值不同:说明匹配失败,返回 false
  • 返回值:
    • 判断 A 和 B 的左子节点是否相等,即 recur(A.left, B.left)
    • 判断 A 和 B 的右子节点是否相等,即 recur(A.right, B.right)

isSubStructure(A, B) 函数

  • 特例处理: 当 树 A 为空 或 树 B 为空 时,直接返回 false
  • 返回值: 若树 B 是树 A 的子结构,则必满足以下三种情况之一,因此用或 || 连接;
    • 以 节点 A 为根节点的子树 包含树 B ,对应 recur(A, B)
    • 树 B 是 树 A 左子树 的子结构,对应 isSubStructure(A.left, B)
    • 树 B是 树 A 右子树 的子结构,对应 isSubStructure(A.right, B)

代码赏析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static boolean isSubStructure(TreeNode A, TreeNode B) {
// isSubStructure函数用来通过递归,前序遍历整个树A。
// 将树A的每一个节点都调用recur()函数,来判断是否是子树。
// 先判断当前节点行不行,然后递归判断左边的,再然后递归判断右边,是典型前序遍历。
return (A != null && B != null) && (recur(A,B)||recur(A.left,B)||recur(A.right,B));
}

//recur函数通过递归,比较当前节点下的子树是否是和B树相同。
public static boolean recur(TreeNode A, TreeNode B) {
if(B==null){
return true;
}
if(A==null||A.val!=B.val){
return false;
}
// 这里本质上也是前序遍历
return recur(A.left,B.left)&&recur(A.right,B.right);
}

其他思路:用队列层序遍历,时间复杂度很高。

7.2 二叉树的镜像

题目链接:[剑指 Offer 27. 二叉树的镜像](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)

要求:请完成一个函数,输入一个二叉树,该函数输出它的镜像。

最好思路 :还是递归。

  • 现有一二叉树如下所示
flowchart TB 1((4))-->2((2)) 1((4))-->3((7)) 2((2))-->4((1)) 2((2))-->5((3)) 3((7))-->6((6)) 3((7))-->7((9))
  • 保存根节点4的左子节点2,递归其右子节点7,并准备赋值给左子节点。
flowchart TB 1((4))-.->2((2)) 1((4))-->3((7)) 2((2))-->4((1)) 2((2))-->5((3)) 3((7))-->6((6)) 3((7))-->7((9)) style 3 fill:#f9f,stroke:#333,stroke-width:4px
  • 保存节点7的左子节点6,递归其右子节点9,并准备赋值给左子节点。
flowchart TB 1((4))-.->2((2)) 1((4))-->3((7)) 2((2))-->4((1)) 2((2))-->5((3)) 3((7))-.->6((6)) 3((7))-->7((9)) style 7 fill:#f9f,stroke:#333,stroke-width:4px
  • 保存节点9的左子节点null,递归其右子节点null,并准备赋值给左子节点。
flowchart TB 1((4))-.->2((2)) 1((4))-->3((7)) 2((2))-->4((1)) 2((2))-->5((3)) 3((7))-.->6((6)) 3((7))-->7((9)) 7((9))-.->8((null)) 7((9))-->9((null)) style 9 fill:#f9f,stroke:#333,stroke-width:4px
  • 已经遍历到叶子节点之下了,抵达递归出口,返回null,开始遍历之前储存的左子节点,也返回null。回到节点9
flowchart TB 1((4))-.->2((2)) 1((4))-->3((7)) 2((2))-->4((1)) 2((2))-->5((3)) 3((7))-.->6((6)) 3((7))-->7((9)) 7((9))-->8((null)) 7((9))-.->9((null)) style 7 fill:#f9f,stroke:#333,stroke-width:4px
  • 此时,将节点9赋值给其父节点的左子节点,并开始遍历其父节点的右子节点(之前储存的节点6
flowchart TB 1((4))-.->2((2)) 1((4))-->3((7)) 2((2))-->4((1)) 2((2))-->5((3)) 3((7))-->6((9)) 3((7))-.->7((6)) style 6 fill:#f9f,stroke:#333,stroke-width:4px
  • 节点6的遍历过程和上述相同。就这样一直递归,直到整个树完成镜像翻转。
flowchart TB 1((4))-->2((7)) 1((4))-->3((2)) 2((7))-->4((9)) 2((7))-->5((6)) 3((2))-->6((3)) 3((2))-->7((1)) style 1 fill:#f9f,stroke:#333,stroke-width:4px

7.3 对称的二叉树

题目链接:[剑指 Offer 28. 对称的二叉树](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)

要求:请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。

最好思路 :还是递归

isSymmetric(root)

  • 特例处理: 若根节点 root 为空,则直接返回true

  • 返回值: 即 recur(root.left, root.right) ;

recur(L, R)

终止条件:

  • 当 L 和 R 同时越过叶节点: 此树从顶至底的节点都对称,因此返回 true

  • 当 L 或 R 中只有一个越过叶节点: 此树不对称,因此返回false

  • 当节点 L 值不等于节点 R 值: 此树不对称,因此返回 false

递推工作:

  • 判断两节点 L.leftR.right 是否对称,即 recur(L.left, R.right)

  • 判断两节点 L.right R.left是否对称,即recur(L.right, R.left)

  • 返回值: 两对节点都对称时,才是对称树,因此用与逻辑符 && 连接。

  • 本质上,可以看成是对一棵树进行“前左右”和“前右左”两次严格与非严格的前序遍历。

其他思路:先镜像,再逐点比较。