数据结构

链表

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    if (l1 == null || l2 == null) return l1 == null ? l2 : l1;
    if (l1.val > l2.val) {
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    } else {
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    }
}
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    if (l1 == null || l2 == null) {
        return l1 == null ? l2 : l1;
    }
    ListNode head = new ListNode(0);
    ListNode node = head;
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            node.next = l1;
            l1 = l1.next;
        } else {
            node.next = l2;
            l2 = l2.next;
        }
        node = node.next;
    }
    if (l1 != null) {
        node.next = l1;
    } else {
        node.next = l2;
    }
    return head.next;
}

Follow Up:合并两个有序链表并去重

方法1:优先队列

  • 将链表统一地送到优先队列,然后开始一个一个弹出队列的元素开始组装

方法2:堆排序

方法1:迭代

  • 需要注意是end节点的跳步的判断,可能不足k步,处理掉这种特殊情况

方法2:递归

方法1:迭代

方法2:递归

出口条件

  • head节点和head.next节点为null的时候,开始返回head节点

逻辑

  • 1.headnext指针指向递归的结果

  • 2.head的值和head.next的值是否相同,相同则返回head.next不同则返回head本身

方法3:双指针

方法1:迭代

方法2:递归

方法1:双指针

只要是对的人,就算开始错过了,最终还是会再次相遇在一起的

方法1.一遍遍历+哈希表

方法2:两次遍历+哈希表

方法3:记忆化递归

  • 1.从head节点开始dfs遍历整个链表

  • 2.创建一个和当前节点cur相同的节点mirror,并建立映射

  • 3.递归调用当前节点curnext节点和random节点,进行复制,让mirror节点的nextrandom指针分别指向这个复制的节点

  • 4返回复制的mirror节点

方法4:连接-恢复

  • 2.遍历整个链表,处理random指针

  • 3.定义一哑结点,然后让原来的cur节点指向的mirror节点断开,恢复到原来的状态

方法1:快慢指针

方法2:Hash

  • d

方法1:双指针

方法1:快慢指针+翻转

i

方法2:头插法

方法1:快排

方法2:归并排序

方法1:递归

方法2:迭代

方法1:头插法

方法2:迭代

  • 前序遍历:根结点 ---> 左子树 ---> 右子树

  • 中序遍历:左子树---> 根结点 ---> 右子树

  • 后序遍历:左子树 ---> 右子树 ---> 根结点

方法1:DFS

方法2:DP

方法1:BFS

方法1:DFS

方法2:BFS

follow up

  • 如何求倒数第2个或者倒数第k个最小深度?

方法1:迭代

  • 1.定义一个cur节点,将其入栈

  • 2.对cur节点的左子树重复步骤1,直到左子树为空

  • 3.弹出栈内保存到左子树的节点,开始遍历右子树,重复步骤1

  • 4.遍历完整个二叉树,结束

方法1:迭代

方法2:递归

方法3:Morris遍历

方法1:DFS

方法2:BFS

方法1:DFS

方法2:BFS

方法1:递归

方法2:迭代

Follow up :如何打印出路径

  • 相同的值的路径,全部打印出来

方法1:迭代法

  • 前序遍历和后序遍历之间的关系:

    • 前序遍历顺序为:根 -> 左 -> 右

    • 后序遍历顺序为:左 -> 右 -> 根

  • 如果1: 我们将前序遍历中节点插入结果链表尾部的逻辑,修改为将节点插入结果链表的头部

    • 那么结果链表就变为了:右 -> 左 -> 根

  • 如果2: 我们将遍历的顺序由从左到右修改为从右到左,配合如果1

    • 那么结果链表就变为了:左 -> 右 -> 根

  • 这刚好是后序遍历的顺序

  • 基于这两个思路,我们想一下如何处理:

    • 修改前序遍历代码中,节点写入结果链表的代码,将插入队尾修改为插入队首

    • 修改前序遍历代码中,每次先查看左节点再查看右节点的逻辑,变为先查看右节点再查看左节点

链接来自:这里arrow-up-right

方法1:DFS

最近公共祖先(LCA|Lowest Common Ancestor)

一棵有根的树T。两个节点n1和n2之间的最低共同祖先被定义为T中具有n1和n2作为后代的最低节点(允许一个节点是其自身的后代)。 T中n1和n2的LCA是距离根最远的n1和n2的共同祖先。例如,作为确定树中节点对之间距离的过程的一部分,计算最低共同祖先可能是有用的:从n1到n2的距离可以计算为从根到n1的距离,加上从根到n2的距离,减去从根到其最低共同祖先的距离的两倍。

方法1:DFS

思路:

1.leftright都为空,说明root的左右子树中都不包含pq节点,返回null即可

2.left不为空,right为空,说明pq不在右子树中(因为右子树为空了),这时,返回left,这里面有下面的两种情况:

  • pq都在left即左子树上,而root节点恰好指向了p或者q

  • pq都在left即左子树上,而root节点未指向了p或者q,指向的是最近公共祖先节点

3.right不为空,left为空,说明pq不在左子树中(因为左子树为空了),这时,返回right,这里面有下面的两种情况:

  • pq都在right即右子树上,而root节点恰好指向了p或者q

  • pq都在right即右子树上,而root节点未指向了p或者q,指向的是最近公共祖先节点

4.left不为空,并且right不为空,说明pq分布在root节点的左右子树的两侧,这时rootpq的最近公共祖先节点,返回

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试

方法2:迭代

在这里插入图片描述

如果这棵二叉树是一棵二叉搜索树(BST)呢?

对于BST,当从上到下遍历树时,位于两个数字n1和n2之间的第一个节点是LCA,即位于n1和n2(n1<=n<=n2)之间的具有最低深度的第一个节点n。所以只要递归地遍历BST,如果节点的值大于n1和n2,那么LCA位于节点的左侧,如果它小于n1和n2,那么LCA位于右侧。否则,根为LCA(假设BST中同时存在n1和n2)

方法1:DFS

思路:

  • 1.创建一个递归函数,该函数接受一个节点和两个值n1和n2。

  • 2.如果当前节点的值小于n1和n2,则LCA位于右侧子树中。调用右子树的递归函数。

  • 3.如果当前节点的值大于n1和n2,则LCA位于左子树中。调用左子树的递归函数。

  • 4.如果上述两种情况均为false,则将当前节点作为LCA返回

复杂度分析:

  • 时间复杂度:O(h),h为树的高度

  • 空间复杂度:O(h),如果忽略递归堆栈空间,则上述解决方案的空间复杂度是常数级别。

迭代实现:上述解决方案使用递归。递归解决方案需要函数调用堆栈形式的额外空间。因此,可以实现一个迭代解决方案,它不会以函数调用堆栈的形式占用空间。

复杂度分析:

  • 时间复杂度:O(h),h为树的高度

  • 空间复杂度:O(1),间复杂度是常数级别。

Reference

方法2:找分割点

方法1:BFS

方法2:DFS

方法1:BFS

方法2:DFS

Follow Up:1.自底向上的层序遍历如何做?

Follow Up:2.按奇偶层数锯齿层序遍历如何做?

方法1:递归+分情况讨论

  • 当前节点的值比目标值小,说明目标节点在右子树上,删除后的结果挂在当前节点的右孩子上

  • 当前节点的值比目标值大,说明目标节点在左子树上,删除后的结果挂在当前节点的左孩子上

  • 如果当前节点与目标值相同,说明当前节点为目标节点,要删除当前节点,分为以下三种情况:

    • 其无左子树:其右子树顶替其位置,删除了该节点

    • 其无右子树:其左子树顶替其位置,删除了该节点

    • 其左右子节点都有:其左子树转移到其右子树的最左节点的左子树上,然后右子树顶替其位置,由此删除了该节点。

方法1:DFS

方法1:BFS

  • 层序遍历,迭代每一层,取最左边的

方法2:DFS

  • 先左子树节点后右子树节点,左子树切换到右子树的时机进行最大深度maxDepth的更新与记录,并保存结果值

Follow Up:找树右下角的值

方法1:BFS

  • 记录每一层的最后一个值

方法2:DFS

  • 先右子树后左子树

方法1:层序遍历

方法2:DFS

方法1:DFS

方法1:dfs

方法1:迭代-标识节点

方法2:迭代-Set

方法3:递归

方法1:递归+Set

方法2:双指针

方法1:递归

  • 不带helper函数:

方法2:迭代

方法1:递归

方法2:递归

方法3:迭代

迭代的思路不如递归好写

方法1:中序+归并

方法2:BFS

  • 另一种写法

  • 另另外一种写法

方法1:普通树+中序遍历

  • 没有使用到二叉搜索树的性质

  • 另:

方法2:迭代+二分

  • 利用BST的性质

方法3:递归

  • 利用BST的性质

方法1:遍历

  • 找递增的两个点之间

  • 找旋转点

Last updated