数据结构基础3:链表常见面试题

前言:单链表的反转、合并两个有序链表和倒序打印链表等算法,是各大公司Java面试开发中常考的题目。

一、单链表反转

算法思想:所谓的单链表反转,就是把每个节点的指针域由原来的指向下一个节点变为指向其前一个节点。

由于单链表没有指向前一个节点的指针域,因此我们需要增加一个指向前一个节点的指针pre,用于存储每一个节点的前一个节点。此外,还需要定义一个保存当前节点的指针cur,以及下一个节点的temp。定义好这三个指针后,遍历单链表,将当前节点的指针域指向前一个节点,之后将定义三个指针往后移动,直至遍历到最后一个节点停止。
 

核心:将当前节点cur的下一个节点 cur.next缓存到temp后,然后才更改当前节点指针指向上一结点pre。也就是说在反转当前结点指针指向前驱结点时,先把当前结点的指针域用tmp临时保存,以便下一次使用。

  •    pre:上一结点,置为null避免打印出原头结点数据

  •    cur: 当前结点

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

遍历反转法:从前往后反转各个结点的指针域的指向。

public static Node reverseChain(Node head) 
{
	if(head.next==null)
	{
		System.out.println("链表为空");
		return head;
	}
		
	Node pre = null;//前驱节点
	Node cur = head.next;//当前结点
	Node temp = null;//临时结点,保存当前结点的下一结点,方便移动
		
	while(cur!=null)  //结点为null时,说明位于尾结点
	{
	 //反转当前结点指针指向前驱结点时,先把当前结点的指针域用tmp临时保存,以便下一次使用。
		temp = cur.next;
		cur.next = pre;//反转指针域的指向
			 
		// 指针往下移动
		pre = cur;
		cur = temp;
	}
		
	// 最后将原链表的头节点置为null(否则会打印出原表数据),返回新链表的首元结点,即原链表的尾结点
        head.next = null;
	
        return pre;
		
}

二、合并两个有序链表

例如:

链表1:

  1->2->3->4

链表2:

  2->3->4->5

合并后:

  1->2->2->3->3->4->4->5

算法思想:与合并两个有序数组类似,挨着循环比较两链表,较小元素放入新链表。

//两个参数代表的是两个链表的头结点
public Node mergeLinkList(Node head1, Node head2) 
{

        if (head1 == null && head2 == null) 
        {  //如果两个链表都为空
            return null;
        }

        if (head1 == null) {
            return head2;
        }
        if (head2 == null) {
            return head1;
        }

        Node head; //新链表的头结点
        Node current;  //current结点指向新链表

        // 一开始,我们让current结点指向head1和head2中较小的数据,得到head结点
        if (head1.data < head2.data) {
            head = head1;
            current = head1;
            head1 = head1.next;
        } else {
            head = head2;
            current = head2;
            head2 = head2.next;
        }

        while (head1 != null && head2 != null) {
            if (head1.data < head2.data) {
                current.next = head1;  //新链表中,current指针的下一个结点对应较小的那个数据
                current = current.next; //current指针下移
                head1 = head1.next;
            } else {
                current.next = head2;
                current = current.next;
                head2 = head2.next;
            }
        }

        //合并剩余的元素
        if (head1 != null) { //说明链表2遍历完了,是空的
            current.next = head1;
        }

        if (head2 != null) { //说明链表1遍历完了,是空的
            current.next = head2;
        }

        return head;
}

三、倒序打印链表

前面我们已经学了反转单链表的算法,貌似直接打印出来即可,但是这样会破坏原有链表结构,故不推荐。

对于这种颠倒顺序的问题,我们应该就会想到栈,后进先出。所以,这一题要么自己使用栈,要么让系统使用栈,也就是递归

①自己新建一个栈

//方法:从尾到头打印单链表
 public void reversePrint(Node head) 
 {

        if (head == null) {
            return;
        }

        Stack stack = new Stack();  //新建一个栈
        Node current = head.next;

        //将链表的所有结点压栈
        while (current != null) 
        {
            stack.push(current);  //将当前结点压栈
            current = current.next;
        }

        //将栈中的结点打印输出即可
        while (stack.size() > 0) {
            System.out.println(stack.pop().data);  //出栈操作
        }
   }

②递归法:

public void reversePrint(Node head)
{
        if (head == null) 
            return;
       
        reversePrint(head.next);
        System.out.println(head.data);
}

总结:方法2是基于递归实现的,代码看起来简洁优雅,但有个问题:当链表很长的时候,就会导致方法调用的层级很深,有可能造成栈溢出。而方法1的显式用栈,是基于循环实现的,代码的鲁棒性要更好一些。

 

参考链接:链表面试题Java实现

 

你可能感兴趣的:(数据结构与算法)