【数据结构与算法】03 链表(基础知识+面试高频leetcode题目)

目录

 数组&链表基础

高频面试题(leetcode)

206 反转链表(常考)

方法1:迭代算法(双指针)(cpp&python)

方法2:递归解法

妖魔化的双指针算法。

 24 两两交换链表中的节点

拓展:哑节点

 141环形链表

方法1:快慢指针算法。

方法2:哈希表

142 环形链表2

扩展:哈希表基础及其在c++和python中的基本语法。

set和unordered_set区别以及适用情况

致谢


数组&链表基础

数组定义:

数组是一个固定长度的存储相同数据类型的数据结构,数组中的元素被存储在一段连续的内存空间中。它是最简单的数据结构之一,大多数现代编程语言都内置数组支持。

数组在遍历时速度非常快,但是在删除和增加元素时需要平移大量的元素(删除和增加首末位置除外),复杂度为o(n)。

数组特点:

  1. 使用前需要申请数组长度,声明长度之后不能更改;
  2. 插入和删除操作需要移动大量的元素,效率比较慢;
  3. 只能存储一种类型的数据;

链表定义:

链表是一种物理存储单元上非联系、非顺序的存储结构。数据元素中的逻辑顺序是通过链表中的指针连接次序依次实现的,链表由一系列的节点(链表中每一个元素称为节点)组成,节点可以在运行时动态生成,节点的数据空间一般会包含一个数据域和一个指针域,该指针一般称为next,用来指向下一个节点的位置。

链表特点:

  1. 节点之间通过指针相连;
  2. 每个节点有一个前驱节点和一个后继节点;
  3. 首节点没有前驱节点,尾节点没有后继节点;
  4. n个节点离散分配;

链表图解

链表分类:

单向链表:

链表中的元素只能指向链表中的下一个元素,元素之间不能互相指向。

双向链表:

每个链表既有指向下一个元素的指针,又有指向上一个元素的指针,每个节点都有两个指针。

  

循环链表:

高频面试题(leetcode)

206 反转链表(常考)

206. 反转链表 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/reverse-linked-list/

方法1:迭代算法(双指针)(cpp&python)

  • 定义curr,pre指针,curr指针初始化为head,pre指针为空指针;
  • 定义tmp指针,存储curr的下一节点;
  • curr的next指针指向pre;
  • curr和pre均向前移动一个位置;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode*curr = head,*pre = nullptr;    //定义两个指针
        while(cur){
            ListNode* tmp = cur->next;    // 存储当前的下一个节点数据
            curr->next = pre;    //cur下一节点 指向pre
            pre = curr;    //pre指针前进一位
            curr = tmp;    // cur指针前进一位
        }
        return pre;        
    }
};
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        cur,pre = head,None
        while cur:
            # 同时赋值,省掉tmp
            cur.next,pre,cur = pre,cur,cur.next
        return pre

方法2:递归解法

双指针的办法是调换相邻的两个节点,递归是找到链表的最后一个节点,从尾到首依次反转。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == 0 || head->next == 0) return head;
        ListNode* tmp = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return tmp;
    }
};
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
#        if head is None or head.next is None:return head	# 这个也行
        if not head or not head.next:return head
        tmp = self.reverseList(head.next)
        head.next.next = head
        head.next = None
        return tmp

妖魔化的双指针算法。

  • 定义指针curr,初始化为head;
  • 将head的下一个节点的next指向curr,实现局部反转;
  • 局部反转完成之后,curr和head的next指针同时向前移动一个位置;
  • 循环,一直到curr达到链表的最后一个节点;
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == nullptr) { return nullptr;}
        ListNode* curr = head;
        while (head->next){
            ListNode* tmp = head->next->next;
            head->next->next= curr;
            curr = head->next;
            head->next = tmp;
        }
        return curr;   
    }
};
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def reverseList(self, head: ListNode) -> ListNode:
        if head == None:return None
        curr = head
        while (head.next):
            tmp = head.next.next
            head.next.next = curr
            curr = head.next
            head.next = tmp
        return curr

 24 两两交换链表中的节点

迭代算法,创建哑节点。

  • 创建哑结点,令dummyHead.next = head;tmp表示当前到达的节点,初始化为dummyHead;每次交换tmp后面的两个节点。
  • tmp后面的节点分别为node1和node2,交换之前:tmp->node1->node2,交换之后:tmp->node2->node1;
  • 迭代,当新的链表的头节点为dummyHead.next,停止迭代。

示意图:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0);
        dummyHead->next = head;
        ListNode* tmp = dummyHead;
        while  (tmp->next != nullptr && tmp->next->next != nullptr){
            ListNode* node1 = tmp->next;
            ListNode* node2 = tmp->next->next;
            tmp->next = node2;
            node1->next = node2->next;
            node2->next = node1;
            tmp = node1;           
        }
        return dummyHead->next;
    }
};
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def swapPairs(self, head: ListNode) -> ListNode:
        dummyHead = ListNode(0)
        dummyHead.next = head
        tmp = dummyHead
        while (tmp.next and tmp.next.next):
            node1 = tmp.next
            node2 = tmp.next.next
            tmp.next = node2
            node1.next = node2.next
            node2.next = node1
            tmp = node1
        return dummyHead.next

拓展:哑节点

哑节点是在处理与链表相关的操作时,设置在链表头之前的指向链表头的节点,用于简化与链表头相关的操作。因为LeetCode上的题的测试用例都是在头结点就已经存储数据,所以我们需要一个哑结点,放在头结点的前面。

ListNode dummy = new ListNode(0);
dummy.next = head;
//head是链表的头节点,dummy就是指向链表头部的哑节点。

具体可以参考:

(15条消息) 链表 - 哑节点_Hi-YOLO的博客-CSDN博客_哑节点

 141环形链表

141. 环形链表 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/linked-list-cycle/

方法1:快慢指针算法。

我们定义两个指针,一快一满。慢指针每次只移动一步,而快指针每次移动两步。初始时,慢指针在位置 head,而快指针在位置 head.next。这样一来,如果在移动的过程中,快指针反过来追上慢指针,就说明该链表为环形链表。否则快指针将到达链表尾部,该链表不为环形链表。

步骤示意图:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if (head == nullptr || head->next == nullptr){ return false;}
        ListNode* fast = head->next;
        ListNode* slow = head;
        while (fast != slow){
            if (fast == nullptr || fast->next == nullptr){ return false;}
            fast = fast->next->next;
            slow = slow->next;
        }
        return true;       
    }
};
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if(head == None or head.next == None):return False
        fast = head.next
        slow = head
        while (fast != slow):
            if (fast == None or fast.next == None):
                return False
            fast = fast.next.next
            slow = slow.next
        return True

写一个python简洁版本: 

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        fast = slow = head
        while slow and fast and fast.next.next:
            slow = slow.next
            fast = fast.next.next
            if slow is fast:
                return True
        return False

  

方法2:哈希表

哈希表来存储所有已经访问过的节点。每次我们到达一个节点,如果该节点已经存在于哈希表中,则说明该链表是环形链表,否则就将该节点加入哈希表中。重复这一过程,直到我们遍历完整个链表即可。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> seen;
        while (head != nullptr){
            if (seen.count(head)){
                return true;
            }
            seen.insert(head);
            head = head->next;
        }
        return false;
    }
};
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def hasCycle(self, head: Optional[ListNode]) -> bool:
        if (head == None or head.next == None):return False
        seen = set()
        while (head):
            if head in seen:
                return True
            seen.add(head)
            head = head.next
        return False

142 环形链表2

142. 环形链表 II - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/linked-list-cycle-ii/

方法1:哈希表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> visit;
        if (head == nullptr || head->next == nullptr){
            return nullptr;
        }
        while (head != nullptr){
            if (visit.count(head)){
                return head;
            }
            visit.insert(head);
            head = head->next;
        }
        return nullptr;
    }
};
# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def detectCycle(self, head: ListNode) -> ListNode:
        visit = set()
        if not head or not head.next:return None
        while (head):
            if head in visit:
                return head
            visit.add(head)
            head = head.next
        return None

 注:暂时不明白

扩展:哈希表基础及其在c++和python中的基本语法。

set集合

官方文档:

内置类型 — Python 3.10.3 文档https://docs.python.org/zh-cn/3/library/stdtypes.html?highlight=set#set

[unordered_map - C++ Referenceicon-default.png?t=M276http://[unordered_map - C++ Reference (cplusplus.com)](http://www.cplusplus.com/reference/unordered_map/unordered_map/?kw=unordered_map) 

集合是按照特定顺序存储唯一元素的容器。

在集合中,元素的值也标识它(值本身就是键,类型为 T),并且每个值必须是唯一的。

集合中元素的值不能在容器中修改一次(元素始终为 const),但可以从容器中插入或删除它们。 在内部,集合中的元素始终按照其内部比较对象(比较类型)指示的特定严格弱排序标准进行排序。

set 容器通常比 unordered_set 容器通过键访问单个元素要慢,但它们允许根据它们的顺序对子集进行直接迭代。

集合通常实现为二叉搜索树。

基本函数(c++):

  1. begin()--返回指向第一个元素的迭代器
  2. clear()--清除所有元素
  3. count()--返回某个值元素的个数//值为1或者0
  4. empty()--如果集合为空,返回true
  5. end()--返回指向最后一个元素的迭代器
  6. equal_range()--返回集合中与给定值相等的上下限的两个迭代器
  7. erase()--删除集合中的元素
  8. find()--返回一个指向被查找到元素的迭代器
  9. get_allocator()--返回集合的分配器
  10. insert()--在集合中插入元素
  11. lower_bound()--返回指向大于(或等于)某值的第一个元素的迭代器
  12. key_comp()--返回一个用于元素间值比较的函数
  13. max_size()--返回集合能容纳的元素的最大限值
  14. rbegin()--返回指向集合中最后一个元素的反向迭代器
  15. rend()--返回指向集合中第一个元素的反向迭代器
  16. size()--集合中元素的数目
  17. swap()--交换两个集合变量
  18. upper_bound()--返回大于某个值元素的迭代器
  19. value_comp()--返回一个用于比较元素间的值的函数1.

多看c++官方文档:

set - C++ Reference (cplusplus.com)

基本函数(python)

 多看官方文档:

内置类型 — Python 3.10.2 文档

set和unordered_set区别以及适用情况

(17条消息) set和unordered_set区别以及适用情况_小乾的计算机学习之路-CSDN博客_set unordered_set

致谢

【反转链表】:双指针,递归,妖魔化的双指针 - 反转链表 - 力扣(LeetCode) (leetcode-cn.com)(15条消息) 链表 - 哑节点_Hi-YOLO的博客-CSDN博客_哑节点

(17条消息) Python集合(set)的操作及方法_小七的博客-CSDN博客_python set

(22条消息) 链表(图文详解)_糊涂糖僧的博客-CSDN博客_链表

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值