数据结构与算法--单链表相关面试题

此文章仅作为自己学习过程中的记录和总结,同时会有意地去用英文来做笔记,一些术语的英译不太准确,内容如有错漏也请多指教,谢谢!


一、概述

  1. 获取单链表的有效元素个数【新浪面试题1】
  2. 获取单链表倒数第k个结点【新浪面试题2】
  3. 反转单链表【腾讯面试题】
  4. 从尾到头打印单链表【百度面试题】
  5. 合并两个有序的单链表,合并之后的链表依然有序

此文章会根据以上五个问题分别给出实现代码,以及一些注意事项。在文末会给出检测代码。

(关于属性、构造器、结点结构及基本方法,具体可见:数据结构与算法–单链表(Single Linked List)


二、获取单链表的有效元素个数

此题难度不高,没有多少笔记。

    /**
     * Get the length of the list.
     * 获取单链表的有效长度【新浪面试题1】
     *
     * @return The amount of the effective nodes
     */
    public int getLength() {
        HeroNode temp = head.next;
        int count = 0;

        while (temp != null) {
            count++;
            temp = temp.next;
        }
        return count;
    }

三、获取单链表倒数第k个结点

关于这个问题,有很多解决的方法,此处我只就其中两个方法做笔记。

  1. 蛮力算法(不推荐);
  2. 通过两个相隔k距离的“指针”一起移动来找到目标结点(更优解)
  • -方法①:蛮力算法
    首先,通过getLength()方法获取单链表的长度。
    之后,通过 (getLength()-k) 的循环,来找到目标结点。
    /**
     * Find the No.k penultimate node in a single linked list.
     * 查找单链表中的倒数第k个结点【新浪面试题2】
     * <p>
     * 方法:蛮力算法。
     * 首先,通过getLength()方法获取单链表的长度。
     * 之后,通过getLength()-k的循环,来找到目标结点。
     *
     * @return The No.k penultimate node in a single linked list
     */
    public HeroNode findLastIndexNode(int index) {
        if (head.next == null) {
            System.out.println("The list is empty, cannot find node...");
            return null;
        }
      /* Method : brutal force algorithm.
           First, get the length of the list. Judge if it's valid.
           Then, loop again to find the target node. */
		int length = getLength();
		if (index > length || index <= 0) { // Check if the index given is valid or not.
		    throw new RuntimeException("The given index is illegal (bigger than length / smaller than 1)...");
		}
		HeroNode temp = head.next;
		for (int i = 0; i < length - index; i++) {
		    temp = temp.next;
		}
		return temp;
    }

该方法的缺点:效率相对较低,由于getLength()方法需要遍历单链表,之后又要再一次循环去找到目标结点,因此此方法需要两次遍历(循环)。

  • -方法②:通过两个相隔k距离的“指针”一起移动来找到目标结点
    首先,让第一个“指针”移动k距离,再让二者一起移动。
    之后,当第一个“指针”到达单链表末尾时(通过辅助计数器判断),第二个“指针”所指即为目标结点。
   /**
     * Find the No.k penultimate node in a single linked list.
     * 查找单链表中的倒数第k个结点【新浪面试题2】
     * <p>
     * 方法:通过两个相隔k距离的“指针”一起移动来找到目标结点。(更优解)
     * 首先,让第一个“指针”移动k距离,再让二者一起移动。
     * 之后,当第一个“指针”到达单链表末尾时(通过辅助计数器判断),第二个“指针”所指即为目标结点。
     *
     * @return The No.k penultimate node in a single linked list
     */
    public HeroNode findLastIndexNode(int index) {
        if (head.next == null) {
            System.out.println("The list is empty, cannot find node...");
            return null;
        }
        /* Method : use two indicators that have the interval of "k".
           First, let one of the indicator moves "k".
           Then, let the two indicators move together.
           When the first one gets to the end of the list,
           The second indicator is now pointing to the target node.
           运用两个相隔k的“指针”来找到目标结点。
         */
        HeroNode first = head.next;
        HeroNode second = head.next;
        int count = 0;
        while (first != null) {
            count++;
            if (count > index) {
                // Already has an interval of "k" between the two indicators,
                // they can move together.
                // 此时两个“指针”已经相隔k,一起移动。
                second = second.next;
            }
            first = first.next;
        }
        if (index > count) { // If the given index is invalid.
            throw new RuntimeException("The given index is illegal (bigger than length / smaller than 1)...");
        }
        return second;
    }

相比之下,第二种方法只需要一次遍历,效率更高。


四、反转单链表

此题较难,需要多琢磨。

-方法:遍历原链表,每遍历一个结点,就将其取出,并放在新的链表reverseHead的最前端,此过程相当于使用头插法创建新的单链表。(注意:辅助变量的使用)

	/**
     * Reverse the single linked list.
     * 将单链表反转【腾讯面试题】
     * <p>
     * 方法:遍历原链表,每遍历一个结点,就将其取出,
     * 并放在新的链表reverseHead的最前端(头插法)。
     * (注意:辅助变量的使用)
     */
    public void reverseList() {
        if (head.next == null || head.next.next == null) {
            return;
        }
        HeroNode reverseHead = new HeroNode(0, "", "");
        HeroNode cur = head.next;
        HeroNode next = null;
        while (cur != null) {
            next = cur.next;
            cur.next = reverseHead.next;
            reverseHead.next = cur;
            cur = next;
        }
        head.next = reverseHead.next;
    }

五、从尾到头打印单链表

-方法

  1. 采用递归方法(不推荐,效率低)
  2. 先将单链表反转,再遍历打印(不推荐,因为会破坏原链表结构,需要复原,效率低)
  3. 利用栈(stack),遍历时将各个结点压入栈中。之后再次遍历,将各个结点逐个出栈(推荐)(虽然遍历两次,但时间复杂度为2n,可看作O(n))

此处只记录方法③的代码实现。

   /**
     * Print the nodes in the list reversely.
     * 从尾到头打印单链表【百度面试题】
     * <p>
     * 方法①:采用递归方法。(不推荐,效率低)
     * <p>
     * 方法②:先将单链表反转,再遍历打印。(不推荐,因为会破坏原链表结构,需要复原,效率低)
     * <p>
     * 方法③:利用栈(stack),遍历时将各个结点压入栈中。之后再次遍历,将各个结点逐个出栈。(推荐)
     * (虽然遍历两次,但时间复杂度为2n,可看作O(n))
     */
    public void reverseShow() {
        if (head.next == null) {
            System.out.println("The list is empty...");
            return;
        }
        HeroNode temp = head.next;
        Stack<HeroNode> heroNodeStack = new Stack<>();
        // Push the nodes into the stack.
        while (temp != null) {
            heroNodeStack.push(temp);
            temp = temp.next;
        }
        // Pop the nodes from the stack.
        while (!heroNodeStack.empty()) { // Or "heroNodeStack.size() > 0"
            System.out.println(heroNodeStack.pop());
        }
    }

此处的出栈条件可以是:

  1. !(heroNodeStack.empty())
  2. heroNodeStack.size() > 0

六、合并两个有序的单链表,使合并后的链表依然有序

-方法:
通过两个“指针”分别指示两个链表的当前位置。每次比较二者所指向元素的大小,根据所需顺序打印。

特别注意此题中辅助变量的使用。
(此例中,我通过结点的属性“Name”来排序)

  /**
     * Merge two single linked lists(having been ordered) by order.
     * 合并两个有序的单链表,合并之后的链表依然有序
     * <p>
     * 方法:通过两个“指针”分别指示两个链表的当前位置。每次比较二者所指向元素的大小,
     * 根据所需顺序打印。
     */
    public void mergeList(SingleLinkedList anotherList) {
        if (head.next == null || anotherList.head.next == null) {
            System.out.println("One of the lists is empty, cannot merge...");
            return;
        }
        
        // 指示单链表1上当前所判断到的结点
        HeroNode temp1 = head.next;
        // 指示单链表2上当前所判断到的结点
        HeroNode temp2 = anotherList.head.next;
        // 指示最近一个判断要加入合并链表的结点
        HeroNode cur = null;
        // 指示cur的下一个结点,起辅助作用,为了使得合并之后仍能继续遍历单链表
        HeroNode next = null;
        // 合并链表的头结点
        HeroNode mergedHead = new HeroNode(0, "", "");
        
        // 先让合并链表的头指针指向符合条件的结点
        if (temp1.getName().compareToIgnoreCase(temp2.getName()) <= 0) {
            mergedHead.next = temp1;
            cur = temp1;
            temp1 = temp1.next;
        } else {
            mergedHead.next = temp2;
            cur = temp2;
            temp2 = temp2.next;
        }
        // 逐个判断并插入合并链表
        while ((temp1 != null) && (temp2 != null)) {
            if (temp1.getName().compareToIgnoreCase(temp2.getName()) <= 0) {
                next = temp1.next; // 将即将被合并的结点的下一个结点存储起来
                cur.next = temp1; // 让合并链表的最后一个结点指向即将被合并的结点
                cur = temp1; // 此时已经合并,让被合并的结点成为合并链表的最后一个结点
                temp1 = next; // 存储起来的结点成为单链表1上当前所被判断到的结点
            } else {
                next = temp2.next;
                cur.next = temp2;
                cur = temp2;
                temp2 = next;
            }
        }
        // 当单链表2已经遍历结束,只需要将单链表1剩余的结点插入到合并链表
        while (temp1 != null) {
            next = temp1.next;
            cur.next = temp1;
            cur = temp1;
            temp1 = next;
        }
        // 当单链表1已经遍历结束,只需要将单链表2剩余的结点插入到合并链表
        while (temp2 != null) {
            next = temp2.next;
            cur.next = temp2;
            cur = temp2;
            temp2 = next;
        }
        head = mergedHead;
    }

七、测试代码

/*
 SingleLinkedListDemo

 Zzay

 2021/01/18
 */
package com.zzay.linkedlist.demo;


import com.zzay.linkedlist.impl.HeroNode;
import com.zzay.linkedlist.impl.SingleLinkedList;

/**
 * CONTENTS:
 * (1) Get the length of the single linked list. 获取单链表有效结点个数【新浪面试题1】
 * (2) Find the No.k penultimate node in a single linked list. 获取单链表倒数第k个结点【新浪面试题2】
 * (3) Reverse the single linked list. 反转单链表【腾讯面试题】
 * (4) Print the nodes in the list reversely. 从尾到头打印单链表【百度面试题】
 * (5) Merge two single linked lists(having been ordered) by order. 合并两个有序的单链表,合并之后的链表依然有序
 * 
 * @author Zzay
 * @version 2021/01/18
 */
public class SingleLinkedListDemo {

    public static void main(String[] args) {

        SingleLinkedList singleLinkedList = new SingleLinkedList();

        /* Add nodes into the single linked list by order (ascending). */
        singleLinkedList.addByOrder(new HeroNode(1, "Jay", "Yellow Chocolate"));
        singleLinkedList.addByOrder(new HeroNode(4, "Paul Pierce", "Truth"));
        singleLinkedList.addByOrder(new HeroNode(2, "Kobe Bryant", "Black Mamba"));
        singleLinkedList.addByOrder(new HeroNode(3, "Dirk Nowitzki", "German Race Car"));

        /* Show the contents of the single linked list. */
        singleLinkedList.show();

        /* Get the length of the single linked list.
           获取单链表的有效元素个数【新浪面试题1】 */
        System.out.println("The length of the linked list is: " + singleLinkedList.getLength());
        
        /* Find the No.k penultimate node in a single linked list.
           获取单链表倒数第k个结点【新浪面试题2】 */
        try {
            System.out.println(singleLinkedList.findLastIndexNode(3)); // Valid
//            System.out.println(singleLinkedList.findLastIndexNode(5)); // Out of range(bigger).
//            System.out.println(singleLinkedList.findLastIndexNode(0)); // Out of range(smaller)
            System.out.println();
        } catch (RuntimeException e) {
            System.out.println(e.getMessage());
        }
        
        /* Reverse the single linked list.
           反转单链表【腾讯面试题】 */
        singleLinkedList.reverseList();
        System.out.println("Here's the reversed version of the list:");
        singleLinkedList.show();
        singleLinkedList.reverseList();
        System.out.println();
        
        /* Print the nodes in the list reversely.
           从尾到头打印单链表【百度面试题】 */
        System.out.println("Here we print the list reversely:");
        singleLinkedList.reverseShow();
        System.out.println();
        
        /* Merge two single linked lists(having been ordered) by order.
           合并两个有序的单链表,合并之后的链表依然有序 */
        SingleLinkedList anotherList = new SingleLinkedList();
        anotherList.addByOrder(new HeroNode(5, "Allen Iverson", "The Answer"));
        anotherList.addByOrder(new HeroNode(6, "Michael Jordan", "Air Jordan"));
        singleLinkedList.mergeList(anotherList);
        singleLinkedList.show();
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值