至于什么是链表就不详细展开了,这里放一个用Java实现链表的一些基本操作的代码,可以去看看。链表的遍历,插入。等等。


链表的应用场景

1,如下,有操作系统内存分配区域隔断以后,剩下的空间就是用链表串起来,作为一片连续的空间使用

JAVA的链表操作 java链表使用场景_链表

 2,缓存淘汰算法。

缓存的空间是有限的,所以,缓存不能一直放一些固定的东西,可以理解为喜新厌旧,有新的来就有旧的被替换掉,

最近最少使用, 应该需要一个有序的结构, 每加入一个元素, 都放在元素的末尾, 每访问一个元素, 就删除原位置的元素, 并在末尾添加, 这样后面的都是最近被使用过的, 头部是最近没有使用的, 当内存空间不足时, 淘汰头部的元素。

JAVA的链表操作 java链表使用场景_链表_02

 

JAVA的链表操作 java链表使用场景_JAVA的链表操作_03

力扣第141题,如何判断一个链表中是否有环形链表

1  哈希表法

public class Solution {
    public boolean hasCycle(ListNode head) {
      Set <ListNode> hSet=new HashSet<ListNode>();
      while(head!=null){
          if(!hSet.add(head)){
              return true;
          }
          head=head.next;
      }
      return false;
    }
}

2  快慢指针法

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head==null||head.next==null){
           return false;
        }
        ListNode slow=head;
        ListNode Fast=head.next;
        while(slow!=Fast){
            if(Fast==null||Fast.next==null){
                return false;
            }
            slow=slow.next;
            Fast=Fast.next.next;
        }
       return true;
    }
}

3 找环的起始位置

p,q在环中相遇点的位置在哪。当p走到环的起点的时候,q已经走了两倍 head--》起点的位置。

所以可以得到结论  p,q相遇的时候,将p放到起始位置,两个指针在同时同速度走,相遇的位置就是环起始点的位置。

JAVA的链表操作 java链表使用场景_JAVA的链表操作_04

 

JAVA的链表操作 java链表使用场景_java_05

 力扣142 找环的起始位置

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null)
        return null;

        ListNode slow=head,fast=head;
        while(fast!=null){
          slow=slow.next;
          if(fast.next!=null){
              fast=fast.next.next;
          }else{
              return null;
          }
          if(fast==slow){
              ListNode tmp=head;
              while(tmp!=slow){
                 tmp=tmp.next;
                 slow=slow.next;
              }
              return tmp;
          }
        }
        return null;
    }
}

 链表的反转

  

JAVA的链表操作 java链表使用场景_数据结构_06

 

JAVA的链表操作 java链表使用场景_数据结构_07

 用普通遍历实现

class Solution {
    public ListNode reverseList(ListNode head) {
       ListNode p=null;
       ListNode cur=head;
       while(cur!=null){
         ListNode next =cur.next;
         cur.next=p;
         p=cur;
         cur=next;
       }
       return p;
    }
}

基于递归的实现方法:

递归的回溯就是把链表翻转了,如果链表有两个节点,

JAVA的链表操作 java链表使用场景_java_08

 接下看有三个节点

JAVA的链表操作 java链表使用场景_头结点_09

看着官方的解释还行,自己画着画着就没了头绪,因为画了三个节点,所以就理解不了

 nk.next.next=nk的意思是什么。

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode p = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return p;
    }
}

这里其实按照上面那个公式就可以清晰的明白了,此刻也感受到了递归的魔力,真的栓Q啊!

反转链表二

JAVA的链表操作 java链表使用场景_头结点_10

 

JAVA的链表操作 java链表使用场景_java_11

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;

        ListNode pre = dummyNode;
        // 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
        // 建议写在 for 循环里,语义清晰
        for (int i = 0; i < left - 1; i++) {
            pre = pre.next;
        }

        // 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
        ListNode rightNode = pre;
        for (int i = 0; i < right - left + 1; i++) {
            rightNode = rightNode.next;
        }

        // 第 3 步:切断出一个子链表(截取链表)
        ListNode leftNode = pre.next;
        ListNode curr = rightNode.next;

        // 注意:切断链接
        pre.next = null;
        rightNode.next = null;

        // 第 4 步:同第 206 题,反转链表的子区间
        reverseLinkedList(leftNode);

        // 第 5 步:接回到原来的链表中
        pre.next = rightNode;
        leftNode.next = curr;
        return dummyNode.next;
    }

    private void reverseLinkedList(ListNode head) {
        // 也可以使用递归反转一个链表
        ListNode pre = null;
        ListNode cur = head;

        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
    }
}

 K个一组反转链表

JAVA的链表操作 java链表使用场景_头结点_12

 

JAVA的链表操作 java链表使用场景_头结点_13

 

 dummy 是我们构造出来 的虚拟头结点。

JAVA的链表操作 java链表使用场景_JAVA的链表操作_14

 

步骤分解:

链表分区为已翻转部分+待翻转部分+未翻转部分
每次翻转前,要确定翻转链表的范围,这个必须通过 k 此循环来确定
需记录翻转链表前驱和后继,方便翻转完成后把已翻转部分和未翻转部分连接起来
初始需要两个变量 pre 和 end,pre 代表待翻转链表的前驱,end 代表待翻转链表的末尾
经过k此循环,end 到达末尾,记录待翻转链表的后继 next = end.next
翻转链表,然后将三部分链表连接起来,然后重置 pre 和 end 指针,然后进入下一次循环
特殊情况,当翻转部分长度不足 k 时,在定位 end 完成后,end==null,已经到达末尾,说明题目已完成,直接返回即可
时间复杂度为 O(n*K)O(n∗K) 最好的情况为 O(n)O(n) 最差的情况未 O(n^2)O(n2 )
空间复杂度为 O(1)O(1) 除了几个必须的节点指针外,我们并没有占用其他空间

作者:reals
链接:https://leetcode.cn/problems/reverse-nodes-in-k-group/solution/tu-jie-kge-yi-zu-fan-zhuan-lian-biao-by-user7208t/
来源:力扣(LeetCode)

 

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        if (head == null || head.next == null){
            return head;
        }
        //定义一个假的节点。
        ListNode dummy=new ListNode(0);
        //假节点的next指向head。
        // dummy->1->2->3->4->5
        dummy.next=head;
        //初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
        ListNode pre=dummy;
        ListNode end=dummy;

        while(end.next!=null){
            //循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
            //dummy->1->2->3->4->5 若k为2,循环2次,end指向2
            for(int i=0;i<k&&end != null;i++){
                end=end.next;
            }
            //如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
            if(end==null){
                break;
            }
            //先记录下end.next,方便后面链接链表
            ListNode next=end.next;
            //然后断开链表
            end.next=null;
            //记录下要翻转链表的头节点
            ListNode start=pre.next;
            //翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
            pre.next=reverse(start);
            //翻转后头节点变到最后。通过.next把断开的链表重新链接。
            start.next=next;
            //将pre换成下次要翻转的链表的头结点的上一个节点。即start
            pre=start;
            //翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
            end=start;
        }
        return dummy.next;


    }
    //链表翻转
    // 例子:   head: 1->2->3->4
    public ListNode reverse(ListNode head) {
         //单链表为空或只有一个节点,直接返回原单链表
        if (head == null || head.next == null){
            return head;
        }
        //前一个节点指针
        ListNode preNode = null;
        //当前节点指针
        ListNode curNode = head;
        //下一个节点指针
        ListNode nextNode = null;
        while (curNode != null){
            nextNode = curNode.next;//nextNode 指向下一个节点,保存当前节点后面的链表。
            curNode.next=preNode;//将当前节点next域指向前一个节点   null<-1<-2<-3<-4
            preNode = curNode;//preNode 指针向后移动。preNode指向当前节点。
            curNode = nextNode;//curNode指针向后移动。下一个节点变成当前节点
        }
        return preNode;

    }


}

旋转链表:

JAVA的链表操作 java链表使用场景_头结点_15

JAVA的链表操作 java链表使用场景_链表_16

 注意上面公式这里 n-1 的话是从物理地址来说的,那个起始点 n应该设计为0,而下面代码的n是从1开始的,是逻辑上的第一个节点,所以在add--的时候要想清楚,另外()优先级高,所以先走add--再去判断>0

class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if(k==0||head==null||head.next==null){
            return head;
        }
        int n=1;
      ListNode p=head;
      while(p.next!=null){
        p=p.next;
        n++;
      }
      int add=n-k%n;
      if(add==n){
          return head;
      }
      p.next=head;
      while((add--)>0){
          p=p.next;
      }
      ListNode ret=p.next;
      p.next=null;
      return ret;
    }
}

 

JAVA的链表操作 java链表使用场景_数据结构_17

删除链表的第n个节点

JAVA的链表操作 java链表使用场景_头结点_18

 

 这个题的解法比较简单,就不赘述了

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0, head);
        int length = getLength(head);
        ListNode cur = dummy;
        for (int i = 1; i < length - n + 1; ++i) {
            cur = cur.next;
        }
        cur.next = cur.next.next;
        ListNode ans = dummy.next;
        return ans;
    }

    public int getLength(ListNode head) {
        int length = 0;
        while (head != null) {
            ++length;
            head = head.next;
        }
        return length;
    }
}

删除链表的重复节点:

JAVA的链表操作 java链表使用场景_链表_19

 

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null) {
            return head;
        }
        
        ListNode dummy = new ListNode(0, head);

        ListNode cur = dummy;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                int x = cur.next.val;
                while (cur.next != null && cur.next.val == x) {
                    cur.next = cur.next.next;
                }
            } else {
                cur = cur.next;
            }
        }

        return dummy.next;
    }
}

总结:什么情况下会用到虚拟头结点,只有用在链表的首地址有可能发生改变的情况。