18

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

 4 years ago
source link: https://leishen6.github.io/2020/06/07/rotate_Single_linked_list/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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

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

NV3Ezqj.gif

数据结构:

1. 单链表的数据结构:

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

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

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

yiQFzmf.png!web

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

RfIbyua.png!web

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

2QfeUfb.gif

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节点截取下来拼接到头结点上)

ummime3.png!web

实例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

如图:

ua6nqai.png!web

图解代码:

旋转链表使用的方法是 双指针法 ,会存在两个指针: current 指针、previous 指针 。通过两个指针移动,并且保证两个指针之间的间距为 需要移动的位置数

1. 先看图:

QFZbyqm.png!web

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);
    }

}

m67BvqJ.gif

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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK