数据结构2:链表

================================================================================================

1.链表

  • 链表:与顺序表不同,链表不限制数据的物理存储状态,换句话说,使用链表存储的数据元素,其物理存储位置是随机的,所以链表不支持随机访问;

  • 链表由一系列结点(链表中每一个元素称为结点)组成,结点可以在运行时动态生成。我们用头节点 head 表示整个链表;

class Node {
    int val; // 保存我们的元素数据
    Node next; // 保存指向下一个结点的引用;其中尾节点的 next == null
}

在这里插入图片描述

  • 元素(element): 真实存于线性表中的内容,是我们关心的核心内容。
  • 结点(node): 为了组织链表而引入的一个结构,除了保存我们的元素之外,还会保存指向下一个结点的引用;
    • 当前结点(current / cur): 表示链表中某个结点。
    • 前驱结点(previous / prev): 表示链表中某个结点的前一个结点;头结点没有前驱结点。
    • 后继结点(next): 表示链表中某个结点的后一个结点;尾结点没有后继结点。
  • 链表的种类:单链表(带环/不带环;带傀儡节点/不带傀儡节点)、双链表、循环链表
  • 创建一个单链表:

// 创建节点类:

public class Node {
    public int val;
    public Node next;

    public Node(int val) {
        this.val = val;
    }

    @Override
    public String toString() {
        return "["+val+"]";
    }
}

// 创建链表的函数/方法:返回 头节点

    public static Node createList(){
        Node a=new Node(1);
        Node b=new Node(2);
        Node c=new Node(3);
        Node d=new Node(4);
        a.next=b;
        b.next=c;
        c.next=d;
        d.next=null;
        return a;
    }

2.单链表的遍历

  • 通过遍历,打印链表的每个元素。
    public static void main(String[] args) {
       //创建链表,用头节点代表整个链表
        Node head= createList();

        //通过遍历,打印链表的每个元素:
        for (Node cur=head;cur!=null;cur=cur.next){
            System.out.println(cur);
        }
    }
  • 通过遍历,找到链表的最后一个结点
        //通过遍历,找到链表的最后一个结点:
        Node node=head;
        while(cur!=null && node.next!=null){
            node=node.next;
        }
        System.out.println(node);
  • 通过遍历,找到链表的倒数第二个结点。
        //通过遍历,找到链表的倒数第二个结点:
        Node cur=head;
        while(cur!=null && cur.next!=null && cur.next.next!=null ){
            cur=cur.next;
        }
        System.out.println(cur);
  • 通过遍历,找到链表的第 n 个结点。(链表的长度 >= n)
    Node cur=head;
	for(int i=0;i<n;i++){
		cur=cur.next;
	}
    System.out.println(cur);
  • 通过遍历,计算链表中元素的个数。
	int length=0;
    for (Node cur=head;cur!=null;cur=cur.next){
        length++;
    }
  • 通过遍历链表,返回链表 倒数第 n 个节点:需已知链表长度 length;
  • 顺数第 m 个节点 = length +1 - 倒数 n;
    Node cur=head;
    for(int i=1;i<length+1-n;i++){
        cur=cur.next;
    }
    //或者 i 从 0 ~ length-n;
    return cur;
  • 通过遍历,找到链表中是否包含某个元素 e。
    Node cur=head;
    while(cur!=null){
		if(e==cur.val){
			return true;
		}
    }
    return false;

3.单链表面试题

(1)给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。Leetcode链接

    public ListNode removeElements(ListNode head, int val) {
        if(head==null){
            return null;
        }
        ListNode prev=head;
        while(prev.next!=null){
            if(prev.next.val==val){
                prev.next=prev.next.next;
            }else{
                prev=prev.next;
            }
        }
        if(head.val==val){
            head=head.next;
        }
        return head;
    }

(2)给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

    public ListNode reverseList(ListNode head) {
        if(head==null||head.next==null){
            return head;
        }
        ListNode newHead=null;
        ListNode prev=null;
        ListNode cur=head;
        while(cur!=null){
            ListNode nextNode=cur.next;
            if(nextNode==null){
                newHead=cur;
            }
            cur.next=prev;
            prev=cur;
            cur=nextNode;
        }
        return newHead;
    }

(3)给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

    public ListNode middleNode(ListNode head) {
        int count=0;
        for(ListNode cur=head;cur!=null;cur=cur.next){
            count++;
        }
        ListNode middleNode=head;
        for(int i=1;i<=count/2;i++){
            middleNode=middleNode.next;
        }
        return middleNode;
    }

(4)将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        if (l1==null){
            return l2;
        }
        if(l2==null){
            return l1;
        }

        ListNode cur1=l1;
        ListNode cur2=l2;
		//设置一个傀儡节点,方便尾插;
        ListNode newHead=new ListNode(0);
        ListNode tailNode=newHead;
        while(cur1!=null&&cur2!=null){
            if(cur1.val<cur2.val){
                tailNode.next=new ListNode(cur1.val);
                cur1=cur1.next;
            }else{
                tailNode.next=new ListNode(cur2.val);
                cur2=cur2.next;
            }
            tailNode=tailNode.next;
        }
        if (cur1==null){
            tailNode.next=cur2;
        }else{
            tailNode.next=cur1;
        }
        return newHead.next;
    }

(5)现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。

    public ListNode partition(ListNode pHead, int x) {
        // write code here
        if(pHead==null||pHead.next==null){
            return pHead;
        }
        ListNode smallList=new ListNode(0);
        ListNode tailSmall=smallList;
        ListNode largeList=new ListNode(0);
        ListNode tailLarge=largeList;
        ListNode cur=pHead;
        while(cur!=null){
            if(cur.val<x){
                tailSmall.next=new ListNode(cur.val);
                tailSmall=tailSmall.next;
            }else{
                tailLarge.next=new ListNode(cur.val);
                tailLarge=tailLarge.next;
            }
            cur=cur.next;
        }
        tailSmall.next=largeList.next;
        return smallList.next;
    }

(6)在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5;

    public ListNode deleteDuplication(ListNode pHead) {
        if (pHead==null||pHead.next==null){
            return pHead;
        }
        ListNode dummy=new ListNode(0);
        ListNode prev=dummy;
        ListNode cur=pHead;
        while(cur!=null){
            if(cur.next!=null && cur.val== cur.next.val){
                cur = cur.next;
                while(cur!=null && cur.next!=null&& cur.val== cur.next.val) {
                    cur = cur.next;
                }
            }else{
                prev.next=new ListNode(cur.val);
                prev=prev.next;
            }
            cur=cur.next;
        }
        return dummy.next;
    }

(7)对于一个链表,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
方法1:

//时间复杂度为O(n),额外空间复杂度为O(1)的算法:将后半段链表逆序再和前半段比较;
    public int size(ListNode A){
        int size=0;
        for(ListNode cur=A;cur!=null;cur=cur.next){
            size++;
        }
        return size;
    }
    public boolean chkPalindrome(ListNode A){
        if (A == null || A.next == null) {
            return true;
        }
        ListNode B=A;
        //为了得到原链表A的后半段链表;
        int size=size(A)/2;
        for(int i=0;i<size;i++){
            B=B.next;
        }
        //逆序B
        ListNode prev=null;
        ListNode cur=B;
        while (cur != null) {
            ListNode next=cur.next;
            if(next==null){
                B=cur;

            }
            cur.next=prev;
            prev=cur;
            cur=next;
        }
        ListNode a=A;
        ListNode b=B;
        while (a != null && b != null) {
            if (a.val != b.val) {
                return false;
            }
            a=a.next;
            b=b.next;
        }
        return true;
    }

方法2:
// 空间复杂度为O(n):拷贝一份链表,并逆序再比较两个链表,相同为回文;

    public boolean chkPalindrome(ListNode A) {
        if(A==null||A.next==null){
            return true;
        }
        ListNode head=new ListNode(0);
        ListNode c=head;
        for (ListNode cur=A;cur!=null;cur=cur.next){
            c.next=new ListNode(cur.val);
            c=c.next;
        }
        ListNode B=head.next;
        ListNode prev=null;
        ListNode cur=B;
        while (cur != null) {
            ListNode next=cur.next;
            if(next==null){
                B=cur;
            }
            cur.next=prev;
            prev=cur;
            cur=next;
        }
        ListNode a=A;
        ListNode b=B;
        while(a!=null&&b!=null){
            if(a.val!=b.val){
                return false;
            }
            a=a.next;
            b=b.next;
        }
        return true;
    }

(8)给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
// 长的链表引用多走多余长的节点;然后用 == 比较两个节点的引用(地址)是否相等;

    public int size(ListNode A){
        int size=0;
        for(ListNode cur=A;cur!=null;cur=cur.next){
            size++;
        }
        return size;
    }
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if (headA==null||headB==null){
            return null;
        }
        int la=size(headA);
        int lb=size(headB);
        if(la>lb){
            int step=la-lb;
            for (int i = 0; i < step; i++) {
                headA=headA.next;
            }
        }else{
            int step=lb-la;
            for (int i = 0; i < step; i++) {
                headB=headB.next;
            }
        }
        ListNode a=headA;
        ListNode b=headB;
        while (a != null && b != null) {
            if(a==b){
                return a;
            }
            a=a.next;
            b=b.next;
        }
        return null;
    }

(9)给定一个链表,判断链表中是否有环:使用快慢指针判断

    public boolean hasCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow){
                return true;
            }
        }
        return false;
    }

(10)给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
// 第一次碰撞点Pos到连接点Join的距离=头指针到连接点Join的距离,因此,分别从第一次碰撞点Pos、头指针head开始走,相遇的那个点就是连接点。
在这里插入图片描述

    public ListNode detectCycle(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==slow){
                break;
            }
        }
        if(fast==null||fast.next==null){
            return null;
        }
        ListNode cur1=head;
        ListNode cur2=fast;
        while (cur1!=cur2){
            cur1=cur1.next;
            cur2=cur2.next;
        }
        return cur1;
    }

4.双链表的实现

  • 带头节点、尾节点;
  • 双向链表插入节点:
    在这里插入图片描述
  • 双向链表删除节点:
    在这里插入图片描述
    每个节点定义有前驱和后继:
class Node {
    public int val;
    public Node prev=null;
    public Node next=null;

    public Node(int val) {
        this.val = val;
    }

    @Override
    public String toString() {
        return "[" + val +"]";
    }
}
  • MyLinkList实现:
//双向链表的实现
public class MyLinkList {
    private Node head;
    private Node tail;
    private int length;

    public MyLinkList() {
        head=null;
        tail=null;
    }

    //增加操作
    //双向链表头插法:
    public void addFirst(int val){
        Node newNode=new Node(val);
        //要插入的双向链表为空时
        if (head==null){
            head=newNode;
            tail=newNode;
        }else{
            newNode.next=head;
            head.prev=newNode;
            head=newNode;
        }
        length++;
    }
    //尾插法:
    public void addLast(int val){
        Node newNode = new Node(val);
        if(tail==null){
            head=newNode;
            tail=newNode;
        }else{
            tail.next=newNode;
            newNode.prev=tail;
            tail=newNode;
        }
        length++;
    }
    //在任一个指定位置,插入新节点
    public void add(int index,int val){
        if (index<1||index>length+1){
            return;
        }
        if (index==1){
            addFirst(val);
            return;
        }
        if (index==length+1){
            addLast(val);
            return;
        }
        Node cur=getNode(index);
        Node newNode = new Node(val);
        Node prevNode=cur.prev;
        //先处理前面的链接
        prevNode.next=newNode;
        newNode.prev=prevNode;
        //在处理和当前节点的链接
        newNode.next=cur;
        cur.prev=newNode;
        length++;
    }

    //删除操作
    //头删法:
    public void removeFirst() {
        if (head == null) {
            return;
        }
        head=head.next;
        length--;
    }
    //尾删法:
    public void removeLast() {
        if (head == null) {
            return;
        }
        tail.prev.next=tail.next;
        tail=tail.prev;
        length--;
    }
    //删除任意位置的一个节点
    public void removeByIndex(int index) {
        if (head == null) {
            return;
        }
        if (index<1||index>length){
            return;
        }
        if (index==1){
            removeFirst();
            return;
        }
        if (index==length){
            removeLast();
            return;
        }
        Node prevNode=getNode(index-1);
        Node nextNode=getNode(index+1);
        prevNode.next=nextNode;
        nextNode.prev=prevNode;
        length--;
    }
    //删除一个值为val的节点
    public void removeByVal(int val) {
        if (head == null) {
            return;
        }
        boolean b=contains(val);
        if(b){
            int index=indexOf(val);
            removeByIndex(index);
        }else{
            System.out.println("该链表没有该元素,删除失败!");
        }
    }

    //获取链表长度
    public int getLength() {
        return this.length;
    }

    //获取某一位置的节点
    public Node getNode(int index) {
        if (index<0||index>length){
            return null;
        }
        Node cur=head;
        for(int i=1;i<index;i++){
            cur=cur.next;
        }
        return cur;
    }

    //修改链表某一位置的节点的值
    public void setNode(int index, int val) {
        if (head == null) {
            return;
        }
        if (index < 1 || index > length) {
            return;
        }
        Node curNode = getNode(index);
        curNode.val=val;
    }

    //查找值为e的节点在链表中的第一个位置
    public int indexOf(int val){
        if (head == null) {
            return -1;
        }
        int index=0;
        for (Node cur = head; cur != null; cur = cur.next) {
            index++;
            if(cur.val==val){
                break;
            }
        }
        return index;
    }

    //查找值为e的节点在链表中的倒数第一的位置
    public int lastIndexOf(int val) {
        if (head == null) {
            return -1;
        }
        int index=0;
        for (Node cur = tail; cur != null; cur = cur.prev) {
            if(cur.val==val){
                break;
            }
            index++;
        }
        return getLength()-index;
    }

    //打印链表
    @Override
    public String toString() {
        StringBuilder list=new StringBuilder();
        list.append("[");
        Node cur=head;
        while(cur!=null){
            list.append(cur.val);
            if (cur.next!=null){
                list.append("->");
            }
            cur=cur.next;
        }
        list.append("]");
        return list.toString();
    }

    public boolean contains(int val){
        Node cur=head;
        while (cur != null) {
            if (cur.val==val){
                return true;
            }
            cur=cur.next;
        }
        return false;
    }

    public boolean isEmpty(){
        return head == null && tail == null;
    }

    public void clear(){
        head=null;
        tail=null;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值