[算法]算法专练—剑指offer链表

目录

前言

1.剑指 Offer 35. 复杂链表的复制

2.二叉搜索树和双向链表

3.剑指 Offer 06. 从尾到头打印链表

4.剑指 Offer 18. 删除链表的节点(双指针)

5链表中倒数第k个节点(快慢指针)

6.反转链表(双指针)

7.合并两个排序的链表

8.剑指 Offer 52. 两个链表的第一个公共节点(这个很经典,双指针)


前言

剑指offer中的所有链表题都做一遍。

还有快慢指针的题!

0812完成!

明天要完成所有的tree

1.剑指 Offer 35. 复杂链表的复制

使用hashmap解题即可,第一遍不存储关系,只按照顺序存一下所有node,并new出来。
第二遍根据cur.next 和cur.rand 来获取对应的node的指针~

2.二叉搜索树和双向链表

M难度
二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。 要求不能创建任何新的节点,只能调整树中节点指针的指向。
什么叫做二叉搜索树:
文件系统和数据库系统都有用到。
如果某个孩子结点或父结点不存在,则相应属性的值为空(NIL)。根结点是树中唯一父指针为NIL的结点,而叶子结点的孩子结点指针也为NIL。
结构
二叉搜索树是能够高效地进行如下操作的数据结构。
1.插入一个数值
2.查询是否包含某个数值
3.删除某个数值
// 打印中序遍历
void dfs(Node root) {
    if(root == null) return;
    dfs(root.left); // 左
    System.out.println(root.val); // 根
    dfs(root.right); // 右
}
算法流程:
dfs(cur): 递归法中序遍历;
终止条件: 当节点 cur 为空,代表越过叶节点,直接返回;
递归左子树,即 dfs(cur.left) ;
构建链表:
当 pre 为空时: 代表正在访问链表头节点,记为 head ;
当 pre 不为空时: 修改双向节点引用,即 pre.right = cur , cur.left = pre ;
保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
递归右子树,即 dfs(cur.right) ;
treeToDoublyList(root):
特例处理: 若节点 root 为空,则直接返回;
初始化: 空节点 pre ;
转化为双向链表: 调用 dfs(root) ;
构建循环链表: 中序遍历完成后,head 指向头节点, pre 指向尾节点,因此修改 head 和 pre 的双向节点引用即可;
返回值: 返回链表的头节点 head 即可;
class Solution {
// 标准的树型的结构,需要转换成排序的 且双向链表
// 而且修安排就地完成转换操作。 left为前驱节点, right 为后置的节点
// 返回第一个节点
// 解法:中序遍历:
// 设前驱节点 pre 和当前节点 cur ,不仅应构建 pre.right = cur ,也应构建 cur.left = pre 。
// 因为,pre.right 前置的右 指向当前的节点, 当前节点的左,指向前置
// 标准的双向链表
// 循环链表最后在考虑
// 循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail 和 tail.right = head 。
Node pre ,head = null;
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
// 头的左指针指向他的尾部
head.left = pre;
// 尾结点的右指针指向头
pre.right = head;
return head;
}
// 这个头和尾怎么看呢,因为是dfs, 所以最后会回到head 也就是头结点啊, 也就是head,但是head的pre 是什么呢
// 因为如果为空 那么head = cur,但是显然不是,所以 这个pre 就是执行到了末尾节点的,然后一层一层的都return了
void dfs(Node cur) {
if(cur == null) return;
dfs(cur.left);
if(pre != null) {
pre.right = cur;
}
else
{
head = cur;
}
cur.left = pre;
pre = cur;
dfs(cur.right);
}
}

3.剑指 Offer 06. 从尾到头打印链表

4.剑指 Offer 18. 删除链表的节点(双指针)

这个用双指针也是好办法: 跟我写得解法差不多
// 删除节点的话,
    public ListNode deleteNode(ListNode head, int val) {
        // 处理头结点
        if(head.val == val) return head.next;
       // pre 和cur指针
        ListNode pre = head, cur = head.next;
        while(cur != null && cur.val != val) {
            pre = cur;
            cur = cur.next;
        }
        if(cur != null) pre.next = cur.next;
        return head;
    }

5链表中倒数第k个节点(快慢指针)

最笨的方法,自己写的。
作为一个java程序员 干啥都想用map或者list 
stack倒是想的很少~
好方法应该用快慢指针
Krahets大佬的话,让我想起了链表经常用的快慢指针。
快指针先走k步,然后慢指针跟上,然后就是快指针到结束就完成了。


class Solution {
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode former = head, latter = head;
        for(int i = 0; i < k; i++)
            former = former.next;
        while(former != null) {
            former = former.next;
            latter = latter.next;
        }
        return latter;
    }
}

只能说,精彩!

6.反转链表(双指针)

定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
注意head==null的处理,第一遍写得时候没有在意这个东西。
别人的解法:
递归我可能想不太好。但是双指针可以
依旧是Krahets 大佬的解法;
就是一个翻转~
这个图讲的特别好,所以先有个pre ,把next 转向pre就好了
pre最早是null 就可以~ 就有了上面的代码

递归

考虑使用递归法遍历链表,当越过尾节点后终止递归,在回溯时修改各节点的 next 引用指向。
// 这个其实就是每次都递归cur 把cur 指向pre就好了

7.合并两个排序的链表

//2021.08新写的:
/**
* Definition for singly-linked list. public class ListNode { int val; ListNode next;
* ListNode(int x) { val = x; } }
*/
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 主要是排序的链表
        // 合并的话,找个head存一下头,然后谁大往后面挂一下呗
        // 有一个为空,那么就结束了
        ListNode head = new ListNode(0);
        ListNode cur = head;
        if (l1 == null) {
            return l2;
        } else if (l2 == null) {
            return l1;
        }
        // 进行条件
        while (l1 != null && l2 != null) {
            if (l1.val > l2.val) {
                cur.next = l2;
                l2 = l2.next;
            } else {
                cur.next = l1;
                l1 = l1.next;
            }
            cur = cur.next;
        }
        // 处理后续,需要需要把剩下的跑完
        if (l1 == null) {
            while (l2 != null) {
                cur.next = l2;
                l2 = l2.next;
                cur = cur.next;
            }
        } else {
            while (l1 != null) {
                cur.next = l1;
                l1 = l1.next;
                cur = cur.next;
            }
        }
        return head.next;
    }
}
官方题解:
public ListNode gf_mergeTwoLists(ListNode l1, ListNode l2) {
    ListNode dum = new ListNode(0), cur = dum;
    while (l1 != null && l2 != null) {
        if (l1.val < l2.val) {
            cur.next = l1;
            l1 = l1.next;
        } else {
            cur.next = l2;
            l2 = l2.next;
        }
        cur = cur.next;
    }
    cur.next = l1 != null ? l1 : l2;
    return dum.next;
}
这种做法,主要是需要关心的电视,双指针!!
 
我整体思路跟官方差不多,但是就是写的很丑陋呢???
官方的精髓在这句话:
cur.next = l1 != null ? l1 : l2;
我没必要后续的在合并一遍

8.剑指 Offer 52. 两个链表的第一个公共节点(这个很经典,双指针)

让人想到了树的公共节点;
对啊我写写着,hashset 他不香么???
但是还是用刚想到的思路完成了:
// 202108在刷:
public ListNode getIntersectionNode_08(ListNode headA, ListNode headB) {
    // 两个链表的第一个公共节点
    // 如果不相交,那么返回NUll
    // 可以两个都遍历一遍,拿到长度之后长的多走两步这种解法
    // 循环一遍放到hashSET也行啊
    ListNode tempA = headA;
    ListNode tempB = headB;
    int sizeA = 0;
    int sizeB = 0;
    while(tempA != null){
        tempA = tempA.next;
        sizeA++;
    }
    while(tempB != null){
        tempB = tempB.next;
        sizeB++;
    }
    // 绝对值
    int distance = sizeA - sizeB > 0 ? sizeA - sizeB  : sizeB - sizeA;
    //让长的多走两步
    tempA = headA;
    tempB = headB;
    if(sizeA - sizeB > 0){
        //A 长
        for(int i =0; i< distance;i++){
            tempA=tempA.next;
        }
    } else{
        for(int i =0; i< distance;i++){
            tempB=tempB.next;
        }
    }
    // 同时循环
    while(tempA !=null && tempB !=null){
        if(tempA == tempB){
            return tempA;
        }
        tempA = tempA.next;
        tempB = tempB.next;
    }
    return null;
}


官方题解:双指针法
这个太牛逼了!!看一遍觉得牛逼一遍
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值