链表使用突破篇


学习链表之前,我们应该对指针有一定了解。

#include<stdio.h>
#include<stdlib.h>

int main()
{
  int *p=(int*)malloc(sizeof(int));
  *p=10;
  printf("%d",*p);
}

链表的基本实现

链表有俩类节点,头节点和其他节点。
头结点只有数据域,其他节点包含数据域和指针域,最后一个节点的地址域为null.
在这里插入图片描述

struct node{
  int data;
  struct node*next;
  }

创建链表:

int main(){
//head指向链表的最开始,链表还没有的时候head==null
struct node*head=NULL,*p,*q;

for(i=1;i<n;i++)
 {//创建节点
struct node* p=(struct node*)malloc(sizeof(struct node));
p->data=a;
p->next=NULL;

//设置头指针的指向
 if(head==NULL)
  head=p;//如果这是创建的第一个结点,将头指针指向这个结点
 else q->next=p;
 //如果不是第一个创建的结点,则把上一个结点的后续指针指向当前结点
 q=p;//指针q指向当前结点
   } 
}

在有序链表中插入一个比某个值大的数字。

Node* insert(int a,Node* head)
{
   Node* t=head;
   while(t!=NULL)
   {
      if(t->next->data>a)
      //当前结点的下一个结点的值大于待插入的数字,将这个数字插入到中间
       {
          p=(struct node*)malloc(sizeof(node));
          p->data=a;
          //插入操作
          p->next=t->next;
          t->next=p;
          break;
        }
      t=t->next;
}
return head;
}

数组模拟实现链表
请添加图片描述

t=1;
while(t!=0)
{
  if(data[right[t]]>data[t])
  //如果当前节点的下一个结点的值大于待插入的数,将数字插到中间
   {
     right[len]=right[t];
     //新插入的下一个节点标号等于当前结点的下一个结点编号
     right[t]=len;
     //当前节点的下一个节点编号就是新插入数字的编号
     break;
   }
   t=right[t];
  }
    

链表的应用

(一)对系统中不连续的内存进行动态分配
(二)LRU缓存淘汰算法
CPU不能直接操纵地址,需要借助寄存器AX来存储,但是造价比较高。于是引入了L1,L2的cache缓存器,然后再到内存。
在这里插入图片描述在这里插入图片描述

力扣刷题

c++中

#define NULL 0;
#define nullpter ((void*)0);

141 142 201环形链表

有一个链表的头节点 head ,判断链表中是否有环并且返回入环的点。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环, pos 来表示链表尾连接到链表中的位置。

思考一:(1)打张哈希表记录遍历得到的每个值。

如果一直遍历到某节点的next结点为null,则链表没有环,遍历结束。如果遍历到出现重复值的时候,说明链表有环。而当要寻找节点的时候,返回该节点就可以。
在这里插入图片描述

不足:(1)链表中不同节点有重复值的时候,该判断失误。
(2)哈希表的长度不确定(通过数组可以实现一个哈希表)

int k[100],count=0;
for(int i=0;i<10;i++)
{
   i=k[count++];
 }

(3)需要申请hashmap动态增长,消耗空间。

思考二 快慢指针,步长为一
在这里插入图片描述(1)首次相遇时,慢和快指向同一个节点,S慢=S1+D S快=D+S1+n(S2+S1)----->此时快指针比慢指针多走了n圈。
(2)V快=2V慢---->S1+D=n(S2+S1)—>D=(n-1)环+S2
当相遇时,从头到入环的距离,等于首次相遇往后走(n-1)圈再走个S2
(3)此时忽略圈数则 D=S2
想法:引入慢指针other使得其与慢指针slow相遇,得到入环位置。

class Solution {
public:
    bool hasCycle(ListNode *head) {
    
    if(head==NULL) return false;
    if(head->next=NULL) return false;
    ListNode* slow=head,* fast=head;
    
    //如果这个环只有一个元素,当slow=fast=head的时候,也会导致跳出循环.所以先进行一次循环
  do
    {
    //循环跳出:直链表(快指针一下俩步)fast->next或者fast!=null
    //slow==fast
       fast=fast->next->next;
       slow=slow->next;
    }  while(slow!=fast&&fast&&fast->next);
    
   if(fast==slow) return true;
   else return false;
   //可以简化为return fast&&fast->next;
   }
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {

        if(head==nullptr) return nullptr;
        if(head->next==nullptr) return nullptr;
        ListNode* fast=head,*slow=head,*other=head;

       do
        {
            fast=fast->next->next;
            slow=slow->next;
        }while(slow!=fast&&fast&&fast->next);

        if(fast&&fast->next)
        {//有环
            while(fast!=other)
            {
                other=other->next;
                fast=fast->next;
            }
           return fast;
        }else{
            return nullptr;
        }
    }
};

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。

也就是说:判断是否有环,遍历结点如果有1就是快乐数,说明是个环,否则不是。

class Solution {
public:
    int num1(int n)
    { 
        int sum=0;
        while(n>0)
        {
            int bit=n%10;
            sum=sum+bit*bit;
            n/=10;
        }
        return sum;
    }
  
    bool isHappy(int n) {
     int slow=n,fast=n;
     do
     {
         slow=num1(slow);
         fast=num1(num1(fast));
     }while(slow!=fast&&fast!=1);//无环或者是fast赶上1
     return fast==1;
    }
};

代码注意:(1)判断为空时对head和head->next均判断
(2)使用do while循环书写
(3)fast和slow均设置为起始node

206 92 25链表反转

思路一:入栈出栈,知道链表的长度。
不足:需要申请额外的空间。
思路二 双指针(记录断开结点的下一个结点 t=cur->next(),并且让**原链表的头结点去指向反转后列表的头结点 cur->next=pre **,后移pre=cur,然后与后面的结点连接cur=t,当cur==null循环停止)
(1)定义两个指针: pre和cur ;pre指向空,cur指针所指的节点指向pre所指向的节点,也是空。pre记录翻转后链表的头结点。cur为原链表的头结点
在这里插入图片描述
(2)移动pre到cur,把cur移动到cur的next(保证右边的节点不会断)
在这里插入图片描述

(3)cur的next往后走
在这里插入图片描述

(4)循环上述过程,直至 cur指向空。

整体类似于打井,是一个迭代的过程。pre和cur起始都指向空,通过cur.next知道井的大小,然后往下打井,每次先移动cur,此时移动cur.next就可以继续知道后续井的大小,cur往下打井,cur指向pre,然后pre和cur继续向前移动。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
       ListNode* pre = NULL, *cur = head;
        while (cur != NULL) {
            ListNode* t = cur->next;//保存cur的后面一个指针的位置

            cur->next = pre;//反转cur的指向
            pre = cur;//更新pre的值后移
            cur = t;//更新cur的值后移
        }
        return pre;
    }
};

package com.kkb;

public class ExceptionDemo06 {
    // 模拟数据库中已存在账号
    private static String[] names = {"bill","hill","jill"};

    public static void main(String[] args) {
        //调用方法
        try{
            // 可能出现异常的代码
            checkUsername("bill");
            System.out.println("注册成功");//如果没有异常就是注册成功
        }catch(LoginException e){
            //处理异常
            e.printStackTrace();
        }
    }

    //判断当前注册账号是否存在
    //因为是编译期异常,又想调用者去处理 所以声明该异常
    public static boolean checkUsername(String uname) throws LoginException{
        for (String name : names) {
            if(name.equals(uname)){//如果名字在这里面 就抛出登陆异常
                throw new LoginException("亲"+name+"已经被注册了!");
            }
        }
        return true;
    }
}

25.k个一组翻转链表

一个链表每 k 个节点一组进行翻转 返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

思考:(1)找到待翻转的k个结点。(双指针)(2)进行翻转,返回一个翻转后的头结点和尾结点(3)再对下一轮k个节点进行操作,将每一轮翻转的k个节点连接。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode hair(-1,head);
        ListNode pre=head,tail=null;

        while(pre!=null)
        {
            for(int i=0;i<left;i++)
            {
            tail=tail.next();

            if(tail==null)
             {
                return hair.next;
             }
            }//找到k个一组的翻转区域

           
            ListNode[] re=reverse1(head,tail);
            head=re[0];
            tail=re[1];//用数组接收翻转后的头结点和尾结点

            pre.next=head;
            pre=tail;//pre向后移动。pre就是下面一个待搬运节点的头结点
            head=pre.next;
        }

    }


    public ListNode reverse1(ListNode head,ListNode tail)
    {
        ListNode pre=head.next,cur=head;//pre指向head的下一个节点
        while(cur!=tail)
        {
            ListNode t=cur.next;
            cur.next=pre;
            pre=cur;
            cur=t;
        }
        return ListNode[]{tail,head};

    }
}

61旋转链表

倒数第k个结点和倒数第k+1个结点断开。
把倒数第k个结点连接到倒数第二个结点的头部。
思考链表成环,找到倒数第k个结点和倒数第k+1个结点断开,返回倒数第k个结点。
当k>length的时候,k%len=1,减少旋转的次数。
做法(1)将链表成环。
(2)找到倒数第k个结点和倒数第k+1个结点。
(3)将倒数第k个结点和倒数第k+1个结点断开。
(4)返回倒数第k个结点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
      if(head==null||head.next==null)
       return head;

       int len=1;
       ListNode tail=head;
       while(tail.next!=null)
        {
          tail=tail.next;
          len++;
        }//算链表长度
 
        tail.next=head;//成环

        ListNode newtail=head;//找到倒数第k+1结点(新链表尾部) 
        for(int i=0;i<len-k%len-1;i++)
        {
           newtail=newtail.next;
        }
       
       ListNode newhead=newtail.next;//新链表的头部(倒数第k个结点)
       newtail.next=null;//断开环
       return newhead;

    }
}

24.俩俩交换链表中的结点

**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {
       ListNode hair=new ListNode(-1,head);
       ListNode pre=hair;

       while(pre.next!=null&&pre.next.next!=null)
       {
           //判断还剩一个或者俩个结点
           ListNode one=pre.next;
           ListNode two=pre.next.next;

           one.next=two.next;
           two.next=one;
           pre.next=two;//前俩组结点翻转完成

           pre=one;//移动pre往后走
       }
       return hair.next;
    }
}

91 82 83链表的删除

删除第N个结点,也就是找到倒数第N+1个结点。
(1)定义俩个指针,一个指针指向第N个结点(先指向头结点,向后走N步),另一个指针从虚拟头结点开始。
(2)俩个指针同时向前移动,当第一个指针指向空的时候,就找到第N+1个结点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode hair=new ListNode(-1,head);
    ListNode p=head,q=hair;
     while(n-->0)
     {
        p=p.next;
     }

     while(p!=null)
     {
         p=p.next;
         q=q.next;
     }

     q.next=q.next.next;
     //删除第n个结点
     return hair.next;
    }
    
}

删除有序链表的相同元素
(1)相同值必定相邻,可以用cur指向第一个结点。
(2)如果cur后面的值和cur相同,移动cur.next=cur.next.next
(3) 如果不相同,继续移动cur

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
      if(head==null) return null;
      ListNode cur=head;
      while(cur!=null &&cur.next!=null)
      {
          if(cur.val==cur.next.val)
          {
              cur.next=cur.next.next;
          }else{
              cur=cur.next;
          }
      }
     return head;
    }
}

给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
(1)pre指向虚拟头结点,cur从头结点后移。如果cur.next和cur相等,pre和cur继续移动,直到不相等的时候。
(2)删除pre到cur.next的所有结点
(3)pre和cur各向前走一步

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
    if(head==null) return null;
    ListNode hair=new ListNode(-1,head);
    ListNode pre=hair,cur=head;

    while(cur!=null&&cur.next!=null)
    {
        if(cur.val!=cur.next.val)
        {
            pre=pre.next;
            cur=cur.next;
        }
        else{
            while(cur!=null&&cur.next!=null&&cur.val==cur.next.val)
            {
                cur=cur.next;
            }
            pre.next=cur.next;
            cur=cur.next;
        }
        
    }
    return hair.next;
    }
}

请添加图片描述
请添加图片描述

86分割链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。你应当 保留 两个分区中每个节点的初始相对位置。(实现稳定排序)

根据比较x和p的值的大小分成俩个链表。
请添加图片描述

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode partition(ListNode head, int x) {
     ListNode r1,r2,*p1=&r1,*p2=&r2;
     ListNode *p=head,*q;

     while(p)
     {
         q=p->next;
         //big small
         if(p->val<x)
         {
             p->next=p1->next;
             p1->next=p;//插入p,先往后面指,再改变指向
             p1=p;
             //插入p再移动
         }else
         {
             p->next=p2->next;
             p2->next=p;
             p2=p;
         }
         p=q;
     }
     p1->next=r2.next;//连接
     return r1.next;
    }
}

138复制带随机指针的链表

给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个全新节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。

请添加图片描述


class Solution {
    public Node copyRandomList(Node head) {
        if(head==nullptr)
        {
            return nullptr;
        }
        Node*p=head;
        Node* new_head;

        while(p)
        {
            //copy node
            Node *q=new Node(p->val);//定义一个新节点

            q->random=p->random;
            q->next=p->next;
            p->next=q;

            //插入结点
            p=q->next;
            //往后移动,继续插入下一个
        }

        //回到头结点
        p=head;
        while(p)
        {
            if(p->random) q->random=p->random->next;
            (p=p->next) &&(p=p->next);
            //p=p->next;
            //if(p!=nullptr)
            // p=p->next;
        }

        //创建新链表,每次往后指,都是每次走俩步
        new_head=head.next;
        while(p)
        {
            Node* q=p->next;
            p->next=q->next;
            if(p->next) q->next=p->next->next;
            p=p->next;
        }
        return new_head;
    }
}

链表使用总结

​1)对于笔试,不必在乎空间复杂度,一切为了时间。
2)面试的时间复杂度放第一位,但一定要找到空间最优的方法。

技巧
额外数据结构记录(哈希表等)+快慢指针(快指针一次走俩步,慢指针一次走一步,快指针来到终点位置的时候,慢指针刚好走到中间,从而把慢指针后面的放进栈里面

判断一个链表是否为回文单链表

方法(一):栈存放所有元素,往出弹出的顺序就是逆序的顺序,每弹出一个都和原来的顺序比对。

方法(二):把对称轴右边的放在栈里面,每遇到一个往出弹一个和左边比较,直到栈弹空了。

方法 (三):快指针走俩步,慢的走一步,快指针走完后,慢指针来中点。然后让中点往下遍历的时候逆序,中间指向空,右边的往回指。快和慢指向的每个位置都进行比对。
例:将单链表化成左边大,中间相等,右边大的形式

方法(一):

直接将链表中的值保存到一个数组中,然后按照荷兰国旗的划分方式,将数组划分成左边小于那个数,中间等于那个数,右边大于那个数的形式,(荷兰国旗问题用于快速排序中的partition过程)

划分完之后,再把数组中的值用链表的形式连接起来

static class Node {
        int value;
        Node next;
        Node(int value) {
            this.value = value;
              }
    }

    //普通的需要额外空间O(n)且不能达到稳定性的方法
    static Node partitionList_1(Node head, int mid) {
        if (head == null) return null;
        Node cur = head;
        int len = 0;
        while (cur != null) {
            len++;
            cur = cur.next;
        }
        Node nodeArr[] = new Node[len];
        cur = head;
        for (int i = 0; i < nodeArr.length; i++) {
            nodeArr[i] = cur;
            cur = cur.next;
        }
        arrPartition(nodeArr, pivot);
        for (int i = 1; i < nodeArr.length; i++) {
            nodeArr[i - 1].next = nodeArr[i];
        }
        nodeArr[nodeArr.length - 1].next = null;  //一定要记得把最后一个指针指向null
        return nodeArr[0];
    }

    //数组划分的paration
       void arrPartition(Node[] nodeArr, int pivot) {
        int less = -1;
        int more = nodeArr.length;
        int cur = 0;
        while (cur < more) {
            if (nodeArr[cur].value < pivot) {
                swap(nodeArr, ++less, cur++);
            } else if (nodeArr[cur].value > pivot) {
                swap(nodeArr, --more, cur); //注意放到大于区域的时候cur不能++
            } else {
                cur++;
            }
        }
    }

    //交换两个结点
     void swap(Node[] arrNode, int a, int b) {
        Node temp = arrNode[a];
        arrNode[a] = arrNode[b];
        arrNode[b] = temp;
    }

[点击并拖拽以移动]

方法(二):

这个方法是将原来的链表依次划分成三个链表,三个链表分别为small代表的是左边小于的部分,equal代表的是中间相等的部分,big代表的是右边的大于部分;
这三个链表都有自己的两个指针H和T分别代表各自的头部和尾部,分成三个子链表之后,我们只需要遍历链表,然后和给定的值比较,按照条件,向三个链表中添加值就可以了,最后把三个链表连接起来就可以了

   Node partitionList_2(Node head,int piovt){
        if(head == null)return null;
        Node sH = null,sT = null; //小于部分链表的  head 和tail
        Node eH = null,eT = null; //等于部分链表的 head 和tail
        Node bH = null,bT = null; //大于部分链表的 head 和tail

        Node next = null;  //用来保存下一个结点
        //划分到 三个不同的链表
        while(head != null){
            next = head.next;
            head.next = null; //为了链表拼接后最后一个就不用再去赋值其next域为null 了
            if(head.value < piovt){ 
                if(sH == null){ //small部分的第一个结点
                    sH = head;
                    sT = head;
                }else {
                    sT.next = head; //把head放到small最后一个
                    sT = head;  //更新small部分的sT
                }
            }else if(head.value == piovt){
                if(eH == null){
                    eH = head;
                    eT = head;
                }else{
                    eT.next = head;
                    eT = head;
                }
            }else {
                if(bH == null){
                    bH = head;
                    bT = head;
                }else {
                    bT.next = head;
                    bT = head;
                }
            }
            head = next;
        }
        //将三个链表合并(注意边界的判断)
        if(null != sT) { //合并small和equal部分
            sT.next = eH;
            eT = (eT == null) ? sT : eT;
        }
        if(null != eT){
            eT.next = bH;
        }

        return sH != null ? sH : eH != null ? eH : bH;
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

太一TT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值