【剑指Offer】高质量的代码(下)

链表中的倒数第k个结点

题目描述

输入一个链表,输出该链表中倒数第k个结点。由头结点开始从1开始计数,尾结点就是倒数第1个结点。
结点定义

public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}

分析

一般的思路是:
由于是单向链表,所以先遍历链表得到其长度L,然后再遍历一次,只走L-k+1步即可得到倒数第k个结点。
但是这种方法需要遍历链表两次,如果只遍历一次,应采用以下方法:
设置两个指针,令p1指向头结点head。p1成功走k-1步之后,才让p2指向head,然后p1和p2两个指针一起向前遍历,直到p1指向尾结点。此时,p2指向的指针就是倒数第k个元素。
为提高代码的鲁棒性,还要注意以下三点:

  1. 如果head指针为空,代码会试图访问空指针指向的内存,程序崩溃;
  2. 如果链表的长度小于k,代码会崩溃;
  3. 如果k=0,因为k是无符号的int型,则k-1为4204067295(无符号的0x)

Java代码

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        if(head == null || k == 0)
            return null;
        ListNode p1 = head;
        ListNode p2 = null;
        for(int i=0; i<k-1; i++){
            if(p1.next != null)
                p1 = p1.next;
            else
                return null;
        }
        p2 = head;
        while(p1.next != null){
            p1 = p1.next;
            p2 = p2.next;
        }
        return p2;
    }
}

反转链表

题目描述

输入一个链表,反转链表后,输出新链表的表头。
结点定义:

public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}

分析

假设有一条链表,现在结点h及之前的结点已经完成了反转,pNode指向结点i,首先将pNode.next用pNext保存下来,再将pNode的下一跳指向pPrev,也就是前一结点h。这样反转后的链表才不会出现断裂。
在这里插入图片描述
此外,为了代码的鲁棒性,还必须考虑到以下问题:

  1. 输入的标头指针为NULL,或者整个链表只有一个结点的情况;
  2. 返回的反转链表的表头应该是原链表的尾结点。

Java代码

    public ListNode ReverseList(ListNode head) {
        ListNode pPrev = null;
        ListNode pNode = head;
        ListNode pReverseHead = null;
        // 链表的头指针不为空
        while(pNode != null){
            ListNode pNext = pNode.next;
            // 循环到链表的尾结点
            if(pNext == null)
                pReverseHead = pNode;
            // 链表长度大于等于2
            pNode.next = pPrev;
            pPrev = pNode;
            pNode = pNext;
        }
        return pReverseHead;
    }
}

合并两个排序的链表

题目描述

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
结点的定义:

public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}

分析

现在有两个链表,链表1的头结点是p1,链表2的头结点是p2。一开始,因为1<2,所以p1作为头结点。后面也是一样,只要比较p1和p2的大小,然后把小的作为合并链表的下一个结点即可。
在这里插入图片描述
为提高代码的鲁棒性,应注意:

  1. 如果两个链表其中一个是空的,则返回另一个链表;如果两个链表都是空的,则返回空链表。
  2. 可使用递归的方法来处理。

Java代码

public class Solution {
    public ListNode Merge(ListNode list1,ListNode list2) {
        if(list1 == null)
            return list2;
        else if(list2 == null)
            return list1;
        
        ListNode p = null;
        if(list1.val <= list2.val){
            p = list1;
            p.next = Merge(list1.next, list2);
        }
        else{
            p = list2;
            p.next = Merge(list1, list2.next);
        }
        return p;
    }
}

树的子结构

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)。
树的结点定义:

public class TreeNode {
    int val = 0;
    TreeNode left = null;
    TreeNode right = null;

    public TreeNode(int val) {
        this.val = val;
    }
}

分析

要查找树B是不是树A的子结构,可以分成两步:

  1. 在树A中找到与树B的根节点一样的结点R;
  2. 判断树A中以R为根结点的子树是不是包含和树B一样的结构。

如下图所示的树A和树B的结构图
在这里插入图片描述
首先在树A中找到根节点(黄色)和树B的根节点一样,然后比较子树。
由于左右两个孩子都和树B不同,所有不是。
在这里插入图片描述
接着又找到了一个和树B根节点相同的结点(黄色),经过比较发现其子树也是一样的,所以,树B是树A的子结构。
在这里插入图片描述

其实第一步在树A中查找和树B根结点一样的结点,就是树的遍历。树的遍历可以利用递归或者循环来实现。相对而言,递归的代码更为简洁。
第二步也可以使用递归的思路来实现:如果结点R和树B的根节点不同,则以R为根节点的子树和树B肯定不同;如果结点R和树B的根节点相同,则递归判断其左右结点的值是否相同。

Java代码

public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        boolean res = false;
        
        if(root1 != null && root2 != null){
            if(root1.val == root2.val)
                res = DoesTreeAHasTreeB(root1, root2);
            // 如果不是子结构,就遍历左子树,继续查找
            if(!res)
                res = HasSubtree(root1.left, root2);
            // 如果在左子树中也没有找到子结构,在遍历右子树
            if(!res)
                res = HasSubtree(root1.right, root2);
        }
        return res;
    }
    public boolean DoesTreeAHasTreeB(TreeNode root1, TreeNode root2){
        if(root2 == null) //树B遍历结束(无论A是否遍历结束,B都是A的子结构)
            return true;
        if(root1 == null) //树B没有遍历结束,但树A遍历完
            return false;
        if(root1.val != root2.val)
            return false;
        return DoesTreeAHasTreeB(root1.left, root2.left) &&
            DoesTreeAHasTreeB(root1.right, root2.right);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值