链表及经典问题

链表的基础知识

链表的结构

节点

  1. 数据域
  2. 指针域

链状结构

通过指针域的值形成了一个线性结构


访问链表的时间复杂度

链表不适合快速的定位数据,适合动态的插入和删除数据的应用场景。

  1. 查找节点 O(n)
  2. 插入节点 O(1)
  3. 删除节点 O(1)

几种经典的链表实现方法

  1. 传统方法 (节点 + 指针)
  2. 使用数组模拟
    1)指针域与数据域分离
    2)利用数组存放下表进行索引

链表的典型应用场景

1.操作系统内的动态内存分配
2.LRU缓存淘汰算法


经典面试题

链表的访问

一. LeetCode #141 环状链表.

  1. 思路
    1)方法一:采用额外的哈希表,遍历链表,如果哈希表中出现了遍历的元素,则认为有环,如果直到NULL,哈希表中都没出现重复元素,则无环。
    2)方法二:利用快慢指针,慢指针一次走一步,快指针一次走两步,如果在到达尾部NULL之前,快指针追上了慢指针,则有环,否则,则无环。
  2. 代码
	bool hasCycle(ListNode *head) {
       if (head == NULL) {
            return false;
        }
        ListNode *p = head, *q = head->next;
        while (p != q && q && q->next) {
            p = p->next;
            q = q->next->next;
        }
        return q && q->next;
    }

二. LeetCode #142 环状链表II.

  1. 思路
    利用快慢指针,慢指针走一步,快指针走两步,找到两个指针的相遇点,无相遇则返回无环的NULL,有相遇的情况下,将慢指针置于起点位置,然后快慢指针每次走一步,再次相遇的点就是入环点。
  2. 代码
//代码1
	ListNode *detectCycle(ListNode *head) {
        if (head == NULL) return NULL;
        ListNode *p = head, *q = head;
        while (q && q->next) {
            p = p->next;
            q = q->next->next;
            if (p == q) {
                p = head;
                while (p != q) {
                    p = p->next;
                    q = q->next;
                }
                return p;
            }
        } 
        return NULL;
    }
//代码二
	ListNode *detectCycle(ListNode *head) {
        if (head == NULL) return NULL;
        ListNode *p = head, *q = head;
        if (q->next == NULL) return NULL;
        do {
            p = p->next;
            q = q->next->next;
        } while(p != q && q && q->next);

        if (q == NULL || q->next == NULL) return NULL;

        p = head;
        while(p != q) p = p->next, q = q->next;

        return q;  
    }
注意: 代码一和代码二思路一样,但是代码二的更好,没有循环中套循环,更优雅。

三. LeetCode #202 快乐数.

  1. 思路
    转换为判断链表是否有环的问题。
  2. 代码
	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;
    }

链表的反转

一. LeetCode #206 反转链表.

  1. 思路
    1)迭代反转
    2)递归反转
  2. 代码
	/*迭代反转*/
	ListNode* reverseList(ListNode* head) {
        if (head == nullptr) return nullptr;
        ListNode *pre = NULL, *cur = head, *p = head->next;
        while (cur) {
            cur->next = pre;
            pre = cur;
            (cur = p) && (p = p->next);
        }
        return pre;
    }
    /*递归反转*/
    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;
    }

二. LeetCode #92 反转链表II.

  1. 思路
    使用虚拟头节点,因为链表的头有可能改变。
  2. 代码
	/*反转链表的前n个节点*/
	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 left, int right) {
        if (head == nullptr) {
            return nullptr;
        }
        ListNode ret(0, head), *p = &ret;
        int m = left;
        //找到反转片段的前一个节点
        while (--m) p = p->next;
        p->next = reverseN(p->next, right - left + 1);
        return ret.next; 
    }

三. LeetCode #25 K个一组翻转链表.

  1. 思路
    先判断是否有K个节点,然后对这K个节点翻转,最后拼接。依然需要虚拟头节点。
  2. 代码
	/*翻转K个链表,不足K个时返回头*/
	ListNode* reverseN(ListNode* head, int K) {
        if (K == 1) {return head;}
        int m = K;
        ListNode* cur = head;
        while (--m && cur) cur = cur->next;
        //不足K个时返回头
        if (cur == nullptr) return head;
        ListNode* tail = head->next, *p = reverseN(head->next, K - 1);
        head->next = tail->next;
        tail->next = head;
        return p;
    }
    ListNode* reverseKGroup(ListNode* head, int k) {
        if (head == nullptr) return head; 
        ListNode ret(0, head), *p = &ret, *q = head;
        //如果p->next == q 说明没有翻转成功,跳出循环
        //不等于则代表翻转成功,此时q就是下一组K节点的前一个节点
        while ((p->next = reverseN(p->next, k)) != q) {
            p = q;
            q = q->next;
        }
        return ret.next;
    }
	//优化reverseN
	ListNode* __reverseN(ListNode* head, int K) {
        if (K == 1) {return head;}
        ListNode* tail = head->next, *p = reverseN(head->next, K - 1);
        head->next = tail->next;
        tail->next = head;
        return p;
    }
    ListNode* reverseN(ListNode* head, int K) {
        int m = K;
        ListNode* cur = head;
        while (--m && cur) cur = cur->next;
        if (cur == nullptr) return head;
        
        return __reverseN(head, K);
    }

四. LeetCode #61 旋转链表.

  1. 思路
    1)求链表的长度 n, 但是要同时找到最后一个节点
    2)将尾节点指向头节点
    3)由于是向右旋转, 则从头节点向右移动 n - k - 1 步就是旋转后的新链表尾节点, 尾节点后一个即是新链表头节点, 断开并返回新头即可
  2. 代码
	ListNode* rotateRight(ListNode* head, int k) {
        if (head == nullptr) return head;
        //求链表的长度 n, 但是要同时找到最后一个节点
        int n = 1;
        ListNode* cur = head;
        while (cur->next) n++, cur = cur->next;
        //将尾节点指向头节点
        cur->next = head;
        //由于是向右旋转, 则从头节点向右移动 n - k - 1 步就是旋转后的新链表尾节点, 尾节点后一个即是新链表头节点, 断开并返回新头即可.
        k = n - k % n;
        //此行可以不加,但是 下面的while循环要改成 k--
        cur = head;
        while (--k) cur = cur->next;
        //此时 cur 即是 旋转后链表的 尾
        ListNode* newhead = cur->next;
        cur->next = NULL;
        return newhead;  
    }

五. LeetCode #24 两两交换链表中的节点.

  1. 思路
    LeetCode #25 题的一个特例,K = 2 时的一个特例.
  2. 代码
	ListNode* __reverseN(ListNode* head, int K) {
        if (K == 1) {return head;}
        ListNode* tail = head->next, *p = reverseN(head->next, K - 1);
        head->next = tail->next;
        tail->next = head;
        return p;
    }
    ListNode* reverseN(ListNode* head, int K) {
        int m = K;
        ListNode* cur = head;
        while (--m && cur) cur = cur->next;
        if (cur == nullptr) return head;
        
        return __reverseN(head, K);
    }
    ListNode* reverseKGroup(ListNode* head, int k) {
        if (head == nullptr) return head; 
        ListNode ret(0, head), *p = &ret, *q = head;
        while ((p->next = reverseN(p->next, k)) != q) {
            p = q;
            q = q->next;
        }
        return ret.next;
    }
    ListNode* swapPairs(ListNode* head) {
        return reverseKGroup(head, 2);
    }

链表的节点删除

一. LeetCode #19 删除链表的倒数第N个节点.

  1. 思路
    利用虚拟头节点和快慢指针。找到删除节点的前一个节点,执行删除即可。
  2. 代码
	ListNode* removeNthFromEnd(ListNode* head, int n) {
        if (head == nullptr) return nullptr;
        ListNode ret(0, head), *p = &ret, *q = head;
        while (n-- && q) q = q->next;
        while (q != nullptr) {
            p = p->next;
            q = q->next;
        }  
        //此时p就是要删除节点的前一个节点
        p->next = p->next->next;
        return ret.next;
    }

二. LeetCode #83 删除排序链表中的重复节点.

  1. 思路
    见代码,很简单,迭代即可。
  2. 代码
	ListNode* deleteDuplicates(ListNode* head) {
        if (head == nullptr) return head;
        ListNode* p = head;
        while (p->next) {
            if (p->val == p->next->val) {
                p->next = p->next->next;
            } else {
                p = p->next;
            }
        } 
        return head;
    }

三. LeetCode #82 删除排序链表中的重复节点II.

  1. 思路
    利用虚拟头节点,因为新链表的头节点有可能发生变化。
  2. 代码
	ListNode* deleteDuplicates(ListNode* head) {
        if (head == nullptr) {
            return nullptr;
        }
        ListNode ret(0, head), *p = &ret, *q;
        while (p->next) {
            if (p->next->next && p->next->next->val == p->next->val) {
                int val = p->next->val;
                q = p->next->next;
                while (q && val == q->val) {
                    q = q->next;
                }
                p->next = q;
            } else {
                p = p->next;
            }
        }
        return ret.next;
    }
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

云镛

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

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

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

打赏作者

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

抵扣说明:

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

余额充值