单链表和双链表

单链表和双链表

单链表:只有一个指向下一节点的指针 --> 单向读取

双链表:既有指向下一节点的指针,也有指向上一节点的指针,可以通过此向前查找

单链表和双链表的反转:逆序

整个链表逆序、部分链表逆序(修改循环条件即可)

是否需要返回值

链表的反转需要换头(整个链表逆序)的操作,则需要返回值(需要一个Node类型的返回值,存储换头之后的head值)

单链表的逆序及部分链表的逆序

注:当head为整个链表的头结点时,pre初始值为null,next指针反转之后head头节点指向pre,也就是null

package linkedlist;

public class SingleLinkedList {

    class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }

    }

    //单链表的反转
    //单链表的反转只需要将指针的方向反转,在逆序整个链表的时候,用head遍历链表的所有结点,使得每个指针由后一结点指向前一结点
    public static Node reverseList(Node head) {//head传入时是链表的头结点,在循环中为当前结点
        Node pre = null;//处理的结点的前一个结点
        Node next = null;//处理的结点的后一个结点
        while (head != null) {
            next = head.next;//head的后一个结点
            head.next = pre;//反转指针
            pre = head;
            head = next;
        }
        return pre;//出循环时head==null,pre为整个链表的最后一个结点,也就是链表反转之后的头
    }

    //部分单链表的反转
    //这里说部分反转应当是不需要返回值的,但是取的不同的start和end数值,可能会导致链表的头改变,需要返回值,因此返回
    public static Node reverselistPart(Node head, int start, int end) {//head为当前结点,starthe end为起始位置
        Node startNode = null;
        Node endNode = null;
        int length = 0;//因为链表与数组不同,不能够直到当前结点在整个链表之间的位置,需要计数
        Node pre = head;

        //遍历整个链表,此时pre指的是当前结点
        while (pre != null) {//获得整个链表的长度
            length++;
            //找到startNode节点的前一个结点
            if (length == start - 1) {
                startNode = pre;
            }
            //找到endNode节点的后一个节点
            if (length == end + 1) {
                endNode = pre;
            }
            pre = pre.next;//遍历,避免死循环
        }


        //判断所给的start和end是否合理
        if (start < 1 || end > length || start >= end) {
            return head;//不合理的数视为不对链表进行逆序
        } else if (startNode == null) {//start==1,反转的部分包含头结点
            pre = head;
        } else {
            pre = startNode.next;
        }


        //反转startNode和后一个结点
        //pre为开始结点的下一个结点,headNode为pre结点的下一个结点
        Node headNode = pre.next;
        pre.next = startNode;//next指针由pre指向startNode,实现指针的反转

        Node next = null;


        //反转从startNode后一个结点到endNode的前一个结点中所有的结点
        while (headNode != endNode) {//链表的反转
            next = headNode.next;
            headNode.next = pre;
            pre = headNode;
            headNode = next;
        }

//        if (startNode != null) {
//            startNode.next = pre;
//            return headNode;
//        }

        return pre;
    }
}

双链表的逆序

双链表的逆序和单链表相同,但是需要反转pre和next两个指针

package linkedlist;

class DoubleNode {
    public DoubleNode(int data) {
        this.value = data;
    }

    public int value;
    public DoubleNode next;
    public DoubleNode pre;

    public static DoubleNode reverseList(DoubleNode head) {
        DoubleNode pre = null;
        DoubleNode next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            head.pre = next;
            pre = head;
            head = next;
        }
        return pre;
    }
}

打印两个有序链表的公共部分

判断一个链表是否是一个回文结构

1、将链表结构放到栈中,比较栈弹出的顺序是否与原序相同(栈先进后出),全部相同则是回文

O(n)的额外空间

package linkedlist;

import java.util.Stack;

public class IsPalindromic {
    class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static boolean isplindromicbyStack(Node head) {
        Stack<Node> stack = new Stack<Node>();//创建栈结构

        Node temp = head;
        while (temp != null) {
            stack.push(temp);//入栈
            temp = temp.next;
        }
        
        while (head != null){
            if(head.value != stack.pop().value){//出栈,判断前后顺序是否相同
                return false;
            }
            head = head.next;
        }
        return true;
    }
}

2、优化:降低空间复杂度

O(n/2)的额外空间

将右半部分的链表放到栈中,从链表左侧开始遍历,每遍历一个栈弹出一个,一一进行比较,全部相同则回文

单链表无法获知整个链表有多少数,单指针无法判断此时经历的结点是否是链表结构的一半

**快慢指针:**快指针一次走2步,慢指针一次走1步;当快指针遍历完整个链表,慢指针走到中点

在实际情况中快慢指针需要根据题目调整代码

public static boolean isplindromicbyfastandslowPointer(Node head){
        if(head == null || head.next == null){
            return true;//整个链表为空或是只有一个Node也被视为回文结构
        }

        //快慢指针
        Node slow = head.next;
        Node fast = head;

        //整个链表为偶数时,慢指针位于n/2位置上;奇数时,整个链表位于n/2位置向上取整+1(5 - 4)
        while(fast.next != null && fast.next.next != null){
            slow = slow.next;
            fast = fast.next.next;
        }

       //取出后半段入栈
        Stack<Node> stack = new Stack<Node>();
        while(slow != null){
            stack.push(slow);
            slow = slow.next;
        }
        
        //将后半段与前半段比较,是否相同
        while(!stack.isEmpty()){
            if(head.value != stack.pop().value){
                return false;
            }
            head = head.next;
        }
        return true;
    }

3、优化:不使用额外的数据结构

O(1)的额外空间

快指针一次走2步,慢指针一次走1步;慢指针走到中间的位置,遍历后面部分的链表逆序;

两个指针,一个从head向后遍历,一个从end向前遍历,直到一个指针指向空

public static boolean isplindromicbytwoPointer(Node head) {
        if (head == null || head.next == null) {
            return true;//整个链表为空或是只有一个Node也被视为回文结构
        }

        Node slow = head;
        Node fast = head;
        while (fast.next != null && fast.next.next != null) {
            slow = slow.next;//最后到达中点位置,奇数时中点位置向上取整
            fast = fast.next.next;//最后到达链表最后一位或两位
        }

        fast = slow.next;//从右半部分的起点开始
        slow.next = null;//空出空间便于后半部分逆序

        //后半部分逆序
        Node temp = null;
        while (fast != null) {
            temp = fast.next;
            fast.next = slow;//反转指针
            slow = fast;
            fast = temp;
        }
        temp = slow;//slow为反转部分的最后一个结点,也就是后半部分反转部分的头
        fast = head;//左半部分的头

        boolean res = true;
        //slow表示右半部分,fast表示左半部分
        while (slow != null && fast != null) {
            if (slow.value != fast.value) {
                res = false;
                break;
            }
            slow = slow.next;
            fast = fast.next;
        }

        //将链表反转回去  slow -- fast ,  temp -- slow , fast -- temp
        slow = temp.next;
        temp.next = null;
//      fast = slow.next;
//      slow.next = null;
        
        while (slow != null) {
//      while (fast != null)

            fast = slow.next;
            slow.next = temp;
            temp = slow;
            slow = fast;
//          temp = fast.next;
//          fast.next = slow;//反转指针
//          slow = fast;
//          fast = temp;
        }
        
        return res;
    }

将单链表按某值划分为左边小、中间相等、右边大的形式

**使用额外空间:**将单链表的每个结点放到数组中,对数组进行partition操作(左边小、中间相等、右边大)

**不使用额外空间:**六个变量,分别为小于、等于、大于部分的头和尾

注:三区域串起来的时候需要注意区域是否存在,否则会指向null导致出错

java 复制代码
package linkedlist;

public class ListPartition {
    class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }


    public static Node listPattition(Node head, int pivot) {
        Node SH = null;
        Node ST = null;
        Node EH = null;
        Node ET = null;
        Node BH = null;
        Node BT = null;
        Node temp = null;

        while (head != null) {
            temp = head.next;//存储下一个head
            head.next = null;//消去指针


            if (head.value < pivot) {
                if (SH == null) {
                    SH = head;
                    ST = head;
                } else {
                    ST.next = head;
                    ST = head;
                }
            }
            if (head.value == pivot) {
                if (EH == null) {
                    EH = head;
                    ET = head;
                } else {
                    ET.next = head;
                    ET = head;
                }
            }
            if (head.value > pivot) {
                if (BH == null) {
                    BH = head;
                    BT = null;
                } else {
                    BT.next = head;
                    BT = head;
                }
            }

            head = temp;//再次进行赋值,使得head遍历整个链表
        }

        if (ST != null) {//有小于区域
            if (ET != null) {//有等于区域
                ST.next = EH;
            } else if (BT != null) {//没有等于区域,有大于区域
                ST.next = BH;
            } else {//啥也没有
                ST.next = null;
            }
        }

        if (ET != null) {//有等于区域
            if (BT != null) {//有大于区域
                ET.next = BH;
            } else {//无大于区域
                ET.next = null;
            }
        }
        
        //最终返回结果
        if(ST != null){
            return SH;
        } else if (ET != null) {
            return EH;
        }else{
            return BH;
        }
    }
}

复制含有随机指针节点的链表

**使用额外空间:**哈希表拷贝节点,通过原来的节点1的next指针找到节点2,拷贝后的节点1'对应节点2',由此可知节点1'的next指针指向节点2'。random指针同理。

**不使用额外空间:**在链表的每个节点后插入其对应拷贝节点,通过节点1的next指针找到拷贝的节点1',节点1的random指针指向节点3,节点3的next指针指向节点3',由此可以拷贝节点1'指向节点3'的random指针

由next指针向下遍历,将每一对的指针都拷贝出,拷贝所有的random指针后抽离出拷贝的链表(画的好丑)

java 复制代码
package linkedlist;

public class CopyLinkedListWithRand {
    class Node {
        public int value;
        public Node next;
        public Node rand;

        public Node(int data) {
            this.value = data;
        }

    }

    public Node copylistwithRand(Node head) {
        if (head == null) {
            return null;
        }

        Node node1 = head;
        Node node2 = null;

        //1 -> 2  
        //1 -> 1' -> 2
        while (node1 != null) {
            node2 = node1.next;
            node1.next = new Node(node1.value);//克隆的新节点
            node1.next.next = node2;
            node1 = node2;
        }
        
        node1 = head;//重新来过
        
        //拷贝random指针
        while (node1 != null) {
            
            if (node1.rand != null) {//节点1的random指针
                node1.next.rand = node1.rand.next;//节点1'的random指针=节点3的next=节点3'
            } else {
                node1.next.rand = null;
            }

            node1 = node1.next.next;
        }
        
        Node res = head.next;//拷贝出来的链表的头节点
        node1 = head;//再次重新来过
        Node temp = res;
        
        while(node1 != null){
            node1 = node1.next.next;//跳到第三个,第五个...
            if(node1.next != null) {
                temp.next = node1.next;//第二个指向第四个,第四个指向第六个...
            }else{
                temp.next = null;
            }
            temp = temp.next;//第二个跳第四个,第四个跳第六个...
        }

        return res;//返回拷贝链表的头
    }
}
相关推荐
lapiii3581 小时前
图论-代码随想录刷题记录[JAVA]
java·数据结构·算法·图论
win x3 小时前
链表(Linkedlist)
数据结构·链表
杜若南星3 小时前
保研考研机试攻略(满分篇):第二章——满分之路上(1)
数据结构·c++·经验分享·笔记·考研·算法·贪心算法
曙曙学编程3 小时前
初级数据结构——栈
数据结构
严文文-Chris3 小时前
【B+树特点】
数据结构·b树
严文文-Chris3 小时前
B-树特点以及插入、删除数据过程
数据结构·b树
欧阳枫落4 小时前
python 2小时学会八股文-数据结构
开发语言·数据结构·python
手握风云-4 小时前
零基础Java第十六期:抽象类接口(二)
数据结构·算法
<但凡.5 小时前
编程之路,从0开始:知识补充篇
c语言·数据结构·算法
f狐0狸x5 小时前
【数据结构副本篇】顺序表 链表OJ
c语言·数据结构·算法·链表