单链表反转以及相关面试题

(一)单链表的结点结构: 

    data域:存储数据元素信息的域称为数据域; 
    next域:存储直接后继位置的域称为指针域,它是存放结点的直接后继的地址(位置)的指针域(链域)。
    data域+ next域:组成数据ai的存储映射,称为结点;
    注意:①链表通过每个结点的链域将线性表的n个结点按其逻辑顺序链接在一起的。   
          ②每个结点只有一个链域的链表称为单链表(Single Linked List)。
     所谓的链表就好像火车车厢一样,从火车头开始,每一节车厢之后都连着后一节车厢。
     要实现单链表存储,首先是创建一结点类,其Java代码如下:
 

package Single_Linked_List;
class Node {
    private int Data;// 数据域
    private Node Next;// 指针域
    public Node(int Data) {
        // super();
        this.Data = Data;
    }
    public int getData() {
        return Data;
    }
    public void setData(int Data) {
        this.Data = Data;
    }
 
    public Node getNext() {
        return Next;
    }
    public void setNext(Node Next) {
        this.Next = Next;
    }
}


(二)实现反转的方法:


  (1)递归反转法:在反转当前节点之前先反转后续节点。这样从头结点开始,层层深入直到尾结点才开始反转指针域的指向。简单的说就是从尾结点开始,逆向反转各个结点的指针域指向,其过程图如下所示:
   head:是前一结点的指针域(PS:前一结点的指针域指向当前结点)
   head.getNext():是当前结点的指针域(PS:当前结点的指针域指向下一结点)
   reHead:是反转后新链表的头结点(即原来单链表的尾结点)

Java代码实现:

递归实质上就是系统帮你压栈的过程,系统在压栈的时候会保留现场。

//递归反转单链表  特征:只要我有一个指针始终指向头结点的地址,它就相当于一个集合的功能了

package Single_Linked_List;
public class Di_Gui{
    public static void main(String[] args) {
        Node head = new Node(0);
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
        head.setNext(node1);
        node1.setNext(node2);
        node2.setNext(node3);
 
        // 打印反转前的链表
        Node h = head;
        while (null != h) {
            System.out.print(h.getData() + " ");
            h = h.getNext();
        }
        // 调用反转方法
        head = Reverse1(head);
 
        System.out.println("\n**************************");
        // 打印反转后的结果
        while (null != head) {
            System.out.print(head.getData() + " ");
            head = head.getNext();
        }
    }
 
    /**
     * 递归,在反转当前节点之前先反转后续节点
     */
    public static Node Reverse1(Node head) {
        // head看作是前一结点,head.getNext()是当前结点,reHead是反转后新链表的头结点
        if (head == null || head.getNext() == null) {
            return head;// 若为空链或者当前结点在尾结点,则直接还回
        }
         先反转后续节点head.getNext(),reHead永远指向的是Node3节点
        Node reHead = Reverse1(head.getNext());
        // 将当前结点的指针域指向前一结点,下面 是弹栈的过程
        head.getNext().setNext(head);
        // 前一结点的指针域令为null;
        head.setNext(null);
        // 反转后新链表的头结点
        return reHead;
    }

}
 
    

我们来看是怎样的一个递归过程:1->2->3->4

程序到达Node newHead = reverse(head.next);时进入递归
我们假设此时递归到了3结点,此时head=3结点,temp=3结点.next(实际上是4结点)
执行Node newHead = reverse(head.next);传入的head.next是4结点,返回的newHead是4结点。
接下来就是弹栈过程了
程序继续执行 temp.next = head就相当于4->3
head.next = null 即把3结点指向4结点的指针断掉。
返回新链表的头结点newHead
注意:当retuen后,系统会恢复2结点压栈时的现场,此时的head=2结点;temp=2结点.next(3结点),再进行上述的操作。最后完成整个链表的翻转。

 

(2)遍历反转法:递归反转法是从后往前逆序反转指针域的指向,而遍历反转法是从前往后反转各个结点的指针域的指向。
   基本思路是:将当前节点cur的下一个节点 cur.getNext()缓存到temp后,然后更改当前节点指针指向上一结点pre。也就是说在反转当前结点指针指向前,先把当前结点的指针域用tmp临时保存,以便下一次使用,其过程可表示如下:
   pre:上一结点
   cur: 当前结点
   tmp: 临时结点,用于保存当前结点的指针域(即下一结点)


Java代码实现:

package Single_Linked_List;
public class Di_Gui{
    public static void main(String[] args) {
        Node head = new Node(0);
        Node node1 = new Node(1);
        Node node2 = new Node(2);
        Node node3 = new Node(3);
 
        head.setNext(node1);
        node1.setNext(node2);
        node2.setNext(node3);
 
        // 打印反转前的链表
        Node h = head;
        while (null != h) {
            System.out.print(h.getData() + " ");
            h = h.getNext();
        }

        // 调用反转方法
        head = reverse2(head);
 
        System.out.println("\n**************************");

        // 打印反转后的结果
        while (null != head) {
            System.out.print(head.getData() + " ");
            head = head.getNext();
        }
    }

 
    /**
     * 遍历,将当前节点的下一个节点缓存后更改当前节点指针
     */
    public static Node reverse2(Node head) {

        if (head == null) return head;

        // 上一结点
        Node pre = head;

        // 当前结点
        Node cur = head.getNext();

        // 临时结点,用于保存当前结点的指针域(即下一结点)
        Node tmp;

        // 当前结点为null,说明位于尾结点
        while (cur != null) {
            
            tmp = cur.getNext();
            // 反转指针域的指向
            cur.setNext(pre);
 
            // 指针往下移动
            pre = cur;
            cur = tmp;
        }

        // 最后将原链表的头节点的指针域置为null,还回新链表的头结点,即原链表的尾结点
        head.setNext(null);
        
        return pre;
    }

     //循环实现链表反转--效率最佳
    public static ListNode ReverseList(ListNode head) {
        if (head == null || head.next == null) return head;

        ListNode pre = null;//存放当前节点前一个元素
        ListNode post = null;//当前节点的下一个节点

        while (head != null) {
            post = head.next;
            head.next = pre;
            pre = head;
            head = post;
        }
        return pre;
    }
}
 

 面试题二、返回一个链表的倒数第K个元素

//查找倒数第k个节点
    public static ListNode FindKthToTail(ListNode head, int k) {
        ListNode p, q;
        p = head;
        q = head;
        int i = 0;
        //两个指针p、q,查询倒数第k个元素,正序先让k先走K-1步,
        //然后两个指针同时走,当p走到链表的尾部时,q刚好指的是倒数第K个
        for (; p != null; i++) {
            if (i >= k) {
                q = q.next;
            }
            p = p.next;
        }

        return i < k ? null : q;
    }

 个人建议:

        递归方法实现链表反转虽然比较优雅,但是对于不了解递归的同学来说还是有理解难度的。请读者好好自习琢磨一下,递归的妙法所在,当时自己理解了半天,搞错了。递归最后一边时,把head理解成了Node3节点,实际是Node2节点。如有相同的理解错误的读者,请仔细思考一番。

 

2、 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

public class RandomListNode {
    int label;
    RandomListNode next = null;
    RandomListNode random = null;

    RandomListNode(int label) {
        this.label = label;
    }
}

public class Solution {

    public RandomListNode Clone(RandomListNode pHead) {

        if(pHead == null) {

            return null;

        }

        RandomListNode currentNode = pHead;

        //1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;

        while(currentNode != null){

            RandomListNode cloneNode = new RandomListNode(currentNode.label);

            RandomListNode nextNode = currentNode.next;

            currentNode.next = cloneNode;

            cloneNode.next = nextNode;

            currentNode = nextNode;

        }

        currentNode = pHead;

        //2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;

        while(currentNode != null) {

            currentNode.next.random = currentNode.random==null?null:currentNode.random.next;

            currentNode = currentNode.next.next;

        }

        //3、拆分链表,将链表拆分为原链表和复制后的链表

        currentNode = pHead;

        RandomListNode pCloneHead = pHead.next;

        while(currentNode != null) {

            RandomListNode cloneNode = currentNode.next;

            currentNode.next = cloneNode.next;

            cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;

            currentNode = currentNode.next;

        }

        return pCloneHead;

    }

}

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值