链表(List)及经典问题

链表基础知识

  • 链表中的每个节点至少包含两个部分: 数据域和指针域
  • 链表中的每个节点, 通过指针域的值, 形成一个线性结构
  • 查找节点 O(n), 插入节点O(1), 删除节点O(1)
  • 不适合快速的定位数据, 适合动态的插入和删除数据的应用场景

实现方式

实现方式1

struct Node {
   Node(int data) : data(data), next(NULL){}
   int data;
    Node *next;
}

int main(){
  Node *head = Null;
  head = new Node(1);
  head->next = new Node(2)
  head->next-> = new Node(3)
  head->next->next->next = new Node(4)
  //遍历
  Node *p = head
  while(p!=NULL){
     print("%d->",p=>data)  // 1->2->3->4->
     p=p->next
  }
  printf("\n")
  return 0;
}

实现方式2

int data[10]
int next[10]

void add(int ind, int p, int val){
   next[p] = next[ind];
   next[ind] = p;
   data[p] =val;
   return;
}
int main(){
   int head =3;
   data[3] = 0;
   add(3,5,1);
   add(5,2,2);
   add(2,7,3);
   add(7,9,100);
   
   int p = head;
   while(p != 0){
     printf("%d=>", data[p]);  //0->1->2->3->100->
     p = next[p];
   }
   printf("\n");
   return 0 ;
}

链表的典型应用场景

场景一: 操作系统内的动态内存分配
在这里插入图片描述
场景二:LRU缓存淘汰算法
在这里插入图片描述
假设1GB缓存仅能存储4个数据, 那么取数据的时候, 先在这4个数据中查找, 如果查找到了, 那么即 缓存命中, 如果查不到, 就把数据5添加到链表的尾部, 从头部删除数据1, 保持仍是4个数据。

经典面试题 - 链表的访问

141.环形链表

判断链表中是否有环

思路1: 哈希表

  • 依次遍历整个链表, 并创建一个哈希表来存储遍历过的节点
  • 在存入哈希表之前, 先判断哈希表中是否存在该节点。如果不存在, 则存入哈希表。如果已经存在,说明遍历到了重复的点, 则存在环形链表
  • 如果遇到next节点为null的节点, 说明没有环

思路2: 快慢指针

  • 定义两个指针(一个慢指针, 一个快指针)
  • 一开始, 慢指针指向head节点, 快指针指向head.next节点
  • 快指针每次向前移动两步, 慢指针每次向前移动一步, 进行遍历整个链表
  • 当快指针的next节点为null,或者快指针本身节点为null时, 说明该链表没有环, 遍历结束
  • 如果链表有环, 那么快慢指针一定会相遇, 指向同一个节点, 当指向同一节点时, 遍历结束
class Solustion {
public:
    bool hasCircle(listNode *head){
       while(head == nullptr) return false;
       //p 慢指针  q快指针
       ListNode *p = head, *q = head->next;
       while( p != q && q && q->next){
         p = p->next;
         q = q->next->next;
       }
       return q && q->next;
    }
};

142 环形链表2

在一个指针在相遇点, 一个指针回到起点, 同时走, 相遇的位置既是环形的初始点

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        if(head == nullptr) return nullptr;
        ListNode *p = head, *q = head->next;
       while( p != q && q && q->next){
         p = p->next;
         q = q->next->next;
       }
        if(q == nullptr || q->next == nullptr) return nullptr;
        p = q = head;
        do{
          p = p->next;
          q = q->next->next;
        } while(p != q);
        p = head;
        while(p != q){
          p = p->next;
          q = q->next;
        }
        return q;
    }
};

202 快乐数

一个数经过若干次变换规则(等于各位平方和), 若能变换到1, 即为1
题目可以转化为, 判断一个链表是否有环。
如果遍历某个节点为1, 说明没环, 就是快乐数。
如果遍历到重复的节点值, 说明有环, 就不是快乐数。

class Solution {
public:
    int getNext(int x){
      int z = 0;
      while(x) {
        z += ( x % 10 ) * (x % 10);
        x /= 10; 
      }
      return z;
    }
    bool isHappy(int n) {
      int p = n, q = n;
      do {
         p = getNext(p);
         q = getNext(getNext(q));
      } while (p != q && q != 1);
      return q == 1;
    }
};

经典面试题 - 链表的反转

206 反转链表

基于迭代:

  • 定义指针pre, pre指向空
  • 定义cur,cur指向我们的头节点
  • 定义指针next,next指向cur所指向节点的下一个节点
  • 将cur指向pre, 然后移动指针pre到指针cur所在的位置, 移动cur到next所在的位置, 将next指针指西乡节点的下一个节点
  • 不断重复, 当cur指针指向null的时候就完成了整个链表的反转
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
       if(head == nullptr) return head;
       ListNode *pre = nullptr, *cur = head, *p = head->next;
       while(cur){
         cur->next = pre;
         pre = cur;
         (cur = p) && (p = p->next);
       }
       return pre;
    }
};

基于递归:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
       if(head == nullptr || head->next == nullptr) return head;
       ListNode *tail = head->next, *p = reverseList(head->next);
       head->next = tail->next;
       tail->next = head;
       return p;
    }
};

92 反转链表2

将m到n区间的链表进行反转
技巧: 虚拟头节点

class Solution {
public:
    ListNode *reverseN(ListNode *head, int n){
      if(n == 1) return head;
      ListNode *tail = head->next, *p = reverseN(head->next, n-1);
      head->next = tail->next;
      tail->next = head;
      return p;
    }
    ListNode* reverseBetween(ListNode* head, int m, int n) {
     ListNode ret(0, head),*p = &ret;
     int cnt = n - m + 1;
     while(--m) p = p->next;
     p->next = reverseN(p->next,cnt);
     return ret.next;
    }
};

25 K个一组反转链表

class Solution {
public:
    ListNode *__reverseN(ListNode *head, int n){
      if(n == 1) return head;
      ListNode *tail = head->next, *p = __reverseN(head->next, n-1);
      head->next = tail->next;
      tail->next = head;
      return p;
    }
    ListNode *reverseN(ListNode *head, int n){
      ListNode *p = head;
      int cnt = n;
      while(--n && p) p = p->next;
      if(p == nullptr) return head;
      return __reverseN(head, cnt);
    }
    ListNode* reverseKGroup(ListNode* head, int k) {
       ListNode ret(0, head), *p = &ret, *q = p->next;
       while((p->next = reverseN(q, k)) != q){
          p = q;
          q = p->next;
       } 
       return ret.next;
    }
};

61 旋转链表

class Solution {
public:
    ListNode* rotateRight(ListNode* head, int k) {
       if(head == nullptr) return nullptr;
       int n = 1;
       ListNode *p = head;
       while(p->next) p = p->next, n += 1;
       p->next = head;
       k %= n;
       k = n-k;
       while(k--) p = p->next;
       head = p->next;
       p->next = nullptr;
       return head;
    }
};

经典面试题 - 链表节点的删除

19 删除链表的倒数第N个结点

q指针先走n步, 然后q指针和p指针一起走, 知道q指向最后一个null节点, p就走到了倒数第N个结点的前一个结点

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
       ListNode ret(0, head),*p = &ret, *q = head;
       while(n--) q = q->next;
       while(q) p = p->next, q = q->next;
       p->next = p->next->next;
       return ret.next; 
    }
};

83 删除排序列表的重复节点

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
      if(head == nullptr) return nullptr;
      ListNode *p = head;
      while(p->next){
        if(p->val == p->next->val){
          p->next = p->next->next;
        }else{
         p = p->next;
        }
      }
      return head;
    }
};

82 删除排序链表中的重复元素2

class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
      ListNode ret(0, head), *p = &ret, *q;
      while(p->next){
        if(p->next->next && p->next->val == p->next->next->val){
         q = p ->next->next;
         while(q && q->val == p->next->val) q = q->next;
         p->next = q;
        }else{
          p = p->next;
        }
      }
      return ret.next;
    }
};

技巧: 什么时候需要使用虚拟头节点?
虚拟头节点用在链表的首地址有可能发生改变的情况

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值