旋转链表?面试官你确定要让手写这个吗?

前言:

今天练习了一道关於单链表的算法题 《旋转链表》,由于之前写过一篇 《单链表反转?面试官你确定要问这个吗?》 的文章,然后今天又碰到了这道有关单链表的算法,就想着再 “水篇文章” 吧(带引号的哈),可以证明我没偷懒,按时写作业了。嘿嘿 . . . . . . . . .

接下来,①、首先回忆下单链表的数据结构 ;②、详解描述下什么是旋转链表(题目描述); ③、图解旋转链表代码

数据结构:

1. 单链表的数据结构:

单链表是一种线性结构,它是由一个个 节点(Node) 组成的。并且每个节点(Node)是由一块数据域(data)和一块指针域(next)组成的。     

①、节点(Node)结构图如下:

  1. 节点的数据域:data数据域一般就是用来存放数据的 。(注:data域需要指定类型,只能存放指定类型的数据,不能什么东西都放,是不是呀; 那代码中是怎么实现的呢? 使用 泛型 。)
  2. 节点的指针域:next指针域一般就是存放的指向下一个节点的指针;这个指针其实是一个内存地址,因为Node节点对象是存放在JVM中的堆内存中,所以节点的next指针域中存放就是下一个节点在堆内存中的地址;而在代码中对象的内存地址是赋值给其引用变量了,所以指针域中存放的是下一个节点对象的引用变量

②、单链表结构图如下:(下图是由三个节点构成的单链表

若有所思,en en en . . . . . . 好像单链表的知识在脑海中清晰了些呀;那要不我们快马加鞭,赶紧把单链表的数据结构代码弄出来,然后再思索下怎么实现旋转操作, en en en. . . … . . 嘿嘿!

2. 节点类Node代码:

创建Node节点类,节点类中并且额外提供了两个方法(单链表的创建方法、单链表的遍历方法);

注意:单链表的创建方法 createLinkedList( ):Node节点的插入方式为 尾插法 , 其实还有 头插法 方式;

扩展:链表中节点的插入方式还在 HashMap 中使用到了,在 JDK 1.7 时是头插法,JDK 1.8时是尾插法

/**
 * @PACKAGE_NAME: com.lyl.linklist
 * @ClassName: Node
 * @Description:  单链表的 节点类
 * @Date: 2020-06-07 15:51
 **/
public class Node<T> {

    // 节点的数据域
    public T data;
    // 节点的指针域
    public Node next;

    /**
     * 构造方法
     * @param data 数据域值
     */
    public Node(T data) {
        this.data = data;
    }


    /**
     * 创建 单链表 (尾插法)
     * @return  返回头结点
     */
    public static Node createLinkedList(){
        // 头节点
        Node<String> head;

        Node<String> n = new Node<String>("111");
        Node<String> n1 = new Node<String>("222");
        Node<String> n2 = new Node<String>("333");
        Node<String> n3 = new Node<String>("444");
        Node<String> n4 = new Node<String>("555");
        Node<String> n5 = new Node<String>("666");
        // 指定头节点
        head = n;
        n.next = n1;
        n1.next = n2;
        n2.next = n3;
        n3.next = n4;
        n4.next = n5;
        // 返回头结点
        return head;
    }


    /**
     * 遍历单链表并在控制台打印输出
     * @param head  单链表的 头结点
     */
    public static void traverse(Node head) {
        while (head != null) {
            System.out.print(head.data + " --> ");
            head = head.next;
        }
        System.out.print("null");
        System.out.println();
    }

}

题目描述:

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

自我理解:其实是将从尾部数的 k 个节点截取出来再拼接到 head 头节点上。

注意:旋转链表操作会存在两种情况的,正如下面的 实例1 和 实例2

还需知道的一点就是当移动的位置数是链表长度的 整数倍 时,最终相当于没有移动一样,还是原来的样子。

实例1:(移动位置 k 小于 单链表的长度)

输入: 1->2->3->4->5->NULL , k = 2 (每个节点向右移动2个位置)
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

如图:(直接将 4 、5节点截取下来拼接到头结点上)

实例2:(移动位置 k 大于 单链表的长度)

输入: 0->1->2->NULL , k = 4 (每个节点向右移动4个位置)
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL

如图:

图解代码:

旋转链表使用的方法是双指针法,会存在两个指针:current 指针、previous 指针 。通过两个指针移动,并且保证两个指针之间的间距为需要移动的位置数(当移动的位置数大于链表长度时,此时为两者取余的余数)

1. 先看图:

2. 代码:

注意:代码中使用的节点类Node在本文的上面已经提供了。

/**
 * @PACKAGE_NAME: com.lyl.linklist
 * @ClassName: RotateLinkedListByDoublePointer
 * @Description:  通过双指针法 旋转链表
 * @Date: 2020-06-07 16:00
 **/
public class RotateLinkedListByDoublePointer {

    /**
     *  旋转单链表
     * @param head   单链表 头结点
     * @param placeNum  向右移动的位置数
     */
    public static Node rotate(Node head, int placeNum){
        if (head == null)
            return null;
        // 临时节点
        Node temp;
        // 将临时节点指向头结点
        temp = head;
        // 记录单链表的长度
        int length = 1;
        // 遍历单链表得到其长度
        while (temp.next != null){
            length++;
            temp = temp.next;
        }
        /**
         *  如果单链表的长度小于移动的位置数
         * (注意:移动位置数是单链表长度的整数倍时,其实相当於单链表没有移动,还是原来的样式)
         */
        if (length <= placeNum){
            // 获取余数,也就是最终要向右移动的位置数
            placeNum = placeNum % length;
        }
        // 如果余数为0,和上面所说的单链表其实没有变化的
        if (placeNum == 0){
            return head;
        }

        // 当前节点指针
        Node current;
        // 前节点指针
        Node previous;
        current = head;
        previous = head;

        // 记录当前指针是否移动了(要求移动的位置数)
        int i = 0;
        while (current.next != null){
            /**
             * 在当前指针移动了(要求的位置数)后,并且当前指针还未移动到单链表的尾节点的话,
             * 此时需要current、previous指针一起移动了
             */
            if (i == placeNum){
                current = current.next;
                previous = previous.next;
            }else {
                // 在当前指针还未移动(要求的位置数)前,只有当前指针移动,previous指针不动
                i++;
                current = current.next;
            }
        }

        /**
         * 当 current指针移动到了链表的尾部后,此时指针移动结束,将当前previous、current指针截取的
         * 这段节点拼接到头节点前,并将previous指针指向的节点与后继结点断开连接
         */
        Node newTemp = previous.next;
        previous.next = null;
        current.next = head;

        return newTemp;
    }

    // Test
    public static void main(String[] args) {
        // 创建单链表
        Node head = Node.createLinkedList();
        System.out.print("新创建的单链表: ");
        // 遍历新创建的单链表
        Node.traverse(head);
        // 旋转链表,向右移动 10 个位置数
        Node newHead = rotate(head, 10);
        System.out.print("反转后的单链表: ");
        // 遍历反转后的单链表
        Node.traverse(newHead);
    }

}

不要忘记留下你学习的足迹 [点赞 + 收藏 + 评论]嘿嘿ヾ

一切看文章不点赞都是“耍流氓”,嘿嘿ヾ(◍°∇°◍)ノ゙!开个玩笑,动一动你的小手,点赞就完事了,你每个人出一份力量(点赞 + 评论)就会让更多的学习者加入进来!非常感谢! ̄ω ̄=

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章