当前位置: 首页 > news >正文

链表与数组面试常见问题详解与实现

1. 反转单向链表

问题描述:将单向链表所有节点反转

思路

  1. 使用三个指针:prev(前一个节点)、current(当前节点)、next_node(下一个节点)

  2. 遍历链表,每次将当前节点的next指向prev

  3. 移动三个指针直到链表末尾

class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextdef reverse_list(head):prev = Nonecurrent = headwhile current:next_node = current.next  # 保存下一个节点current.next = prev       # 反转指针prev = current            # 移动prevcurrent = next_node       # 移动currentreturn prev  # prev成为新的头节点# 测试
# 创建链表: 1->2->3->4->5
head = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5))))# 反转链表
reversed_head = reverse_list(head)# 打印结果: 5->4->3->2->1
current = reversed_head
while current:print(current.val, end="->" if current.next else "")current = current.next

2. 检测链表中的环

问题描述:判断链表中是否存在环

思路(Floyd判圈算法/快慢指针)

  1. 使用两个指针,slow每次移动一步,fast每次移动两步

  2. 如果存在环,fast最终会追上slow

  3. 如果fast遇到None,说明没有环

def has_cycle(head):if not head or not head.next:return Falseslow = headfast = head.nextwhile slow != fast:if not fast or not fast.next:return False  # 到达链表尾部,无环slow = slow.nextfast = fast.next.nextreturn True  # slow == fast,存在环# 测试
# 创建有环链表: 1->2->3->4->5->3 (5指向3)
node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
node5 = ListNode(5)node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node3  # 形成环print(has_cycle(node1))  # 输出: True# 创建无环链表: 1->2->3->4->5
node5.next = None  # 断开环
print(has_cycle(node1))  # 输出: False

3. 合并两个有序链表

问题描述:合并两个升序链表为一个新的升序链表

思路

  1. 创建哑节点(dummy)作为新链表的起点

  2. 比较两个链表的当前节点值

  3. 将较小值节点连接到新链表

  4. 当一个链表遍历完后,直接连接另一个链表的剩余部分

def merge_two_lists(l1, l2):dummy = ListNode(-1)  # 哑节点current = dummywhile l1 and l2:if l1.val <= l2.val:current.next = l1l1 = l1.nextelse:current.next = l2l2 = l2.nextcurrent = current.next# 连接剩余部分current.next = l1 if l1 else l2return dummy.next  # 返回真正的头节点# 测试
# 链表1: 1->3->5
l1 = ListNode(1, ListNode(3, ListNode(5)))
# 链表2: 2->4->6
l2 = ListNode(2, ListNode(4, ListNode(6)))merged = merge_two_lists(l1, l2)# 打印结果: 1->2->3->4->5->6
current = merged
while current:print(current.val, end="->" if current.next else "")current = current.next

4. 数组与链表的优缺点比较

特性数组链表
内存分配连续内存块分散内存,动态分配
随机访问O(1) - 通过索引直接访问O(n) - 需要遍历
插入/删除O(n) - 需要移动元素O(1) - 已知位置时
头部操作O(n)O(1)
尾部操作O(1) - 已知位置时O(1) - 有尾指针时
空间开销仅存储数据额外存储指针
缓存友好性高 - 空间局部性好低 - 内存不连续
大小调整固定大小或昂贵扩容动态调整大小
适用场景随机访问频繁、数据量固定频繁插入删除、数据量变化大

5. 实现LRU缓存(结合哈希表与双向链表)

LRU原理:最近最少使用策略,淘汰最久未使用的数据

数据结构

  • 双向链表:存储缓存条目,头部是最新使用的,尾部是最久未使用的

  • 哈希表:O(1)时间访问任意节点

class DLinkedNode:def __init__(self, key=0, value=0):self.key = keyself.value = valueself.prev = Noneself.next = Noneclass LRUCache:def __init__(self, capacity):self.capacity = capacityself.cache = {}self.size = 0# 使用哑节点简化操作self.head = DLinkedNode()self.tail = DLinkedNode()self.head.next = self.tailself.tail.prev = self.headdef _add_node(self, node):"""添加节点到链表头部"""node.prev = self.headnode.next = self.head.nextself.head.next.prev = nodeself.head.next = nodedef _remove_node(self, node):"""从链表中移除节点"""prev = node.prevnext_node = node.nextprev.next = next_nodenext_node.prev = prevdef _move_to_head(self, node):"""移动节点到头部"""self._remove_node(node)self._add_node(node)def _pop_tail(self):"""移除尾部节点"""node = self.tail.prevself._remove_node(node)return nodedef get(self, key):"""获取缓存值"""node = self.cache.get(key)if not node:return -1# 移动到头部表示最近使用self._move_to_head(node)return node.valuedef put(self, key, value):"""添加缓存"""if key in self.cache:# 更新值并移动到头部node = self.cache[key]node.value = valueself._move_to_head(node)else:# 创建新节点node = DLinkedNode(key, value)self.cache[key] = nodeself._add_node(node)self.size += 1# 检查容量if self.size > self.capacity:# 移除尾部节点(最久未使用)tail = self._pop_tail()del self.cache[tail.key]self.size -= 1# 测试
cache = LRUCache(2)
cache.put(1, 1)
cache.put(2, 2)
print(cache.get(1))  # 返回 1
cache.put(3, 3)      # 淘汰 key 2
print(cache.get(2))  # 返回 -1 (未找到)
cache.put(4, 4)      # 淘汰 key 1
print(cache.get(1))  # 返回 -1 (未找到)
print(cache.get(3))  # 返回 3
print(cache.get(4))  # 返回 4

6. 找出链表的中间节点

问题描述:返回链表的中间节点(偶数长度时返回第二个中间节点)

思路(快慢指针)

  1. slow每次移动一步,fast每次移动两步

  2. 当fast到达尾部时,slow正好在中间

def middle_node(head):slow = fast = headwhile fast and fast.next:slow = slow.nextfast = fast.next.nextreturn slow# 测试
# 链表: 1->2->3->4->5
head = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5)))))
print(middle_node(head).val)  # 输出: 3# 链表: 1->2->3->4->5->6
head.next.next.next.next.next = ListNode(6)
print(middle_node(head).val)  # 输出: 4 (第二个中间节点)

7. 判断链表是否为回文结构

问题描述:判断链表是否正序和倒序读都一样

思路

  1. 找到中间节点(快慢指针)

  2. 反转后半部分链表

  3. 比较前半部分和反转后的后半部分

  4. 恢复链表(可选)

def is_palindrome(head):if not head or not head.next:return True# 找到中间节点slow = fast = headwhile fast and fast.next:slow = slow.nextfast = fast.next.next# 反转后半部分second_half = reverse_list(slow)# 比较两部分first = headsecond = second_halfresult = Truewhile second and first:if first.val != second.val:result = Falsebreakfirst = first.nextsecond = second.next# 恢复链表(可选)reverse_list(second_half)return result# 测试
# 回文链表: 1->2->3->2->1
head = ListNode(1, ListNode(2, ListNode(3, ListNode(2, ListNode(1)))))
print(is_palindrome(head))  # 输出: True# 非回文链表: 1->2->3->4->5
head = ListNode(1, ListNode(2, ListNode(3, ListNode(4, ListNode(5)))))
print(is_palindrome(head))  # 输出: False

8. 数组实现队列/栈 vs 链表实现

栈(Stack)实现比较

操作数组实现链表实现
入栈(push)O(1) 摊还时间(动态数组)O(1)
出栈(pop)O(1)O(1)
查看栈顶(peek)O(1)O(1)
空间复杂度O(n)O(n)
优点缓存友好,访问快动态大小,无扩容开销
缺点扩容开销大每个元素额外指针空间

数组实现栈

class ArrayStack:def __init__(self):self.stack = []def push(self, item):self.stack.append(item)def pop(self):if not self.is_empty():return self.stack.pop()return Nonedef peek(self):if not self.is_empty():return self.stack[-1]return Nonedef is_empty(self):return len(self.stack) == 0def size(self):return len(self.stack)

链表实现栈

class LinkedListStack:def __init__(self):self.top = Nonedef push(self, item):new_node = ListNode(item)new_node.next = self.topself.top = new_nodedef pop(self):if self.top:item = self.top.valself.top = self.top.nextreturn itemreturn Nonedef peek(self):if self.top:return self.top.valreturn Nonedef is_empty(self):return self.top is None

队列(Queue)实现比较

操作数组实现链表实现
入队(enqueue)O(1) 摊还时间O(1)
出队(dequeue)O(n) - 需要移动元素O(1)
查看队首(peek)O(1)O(1)
空间复杂度O(n)O(n)
优点缓存友好高效出队操作
缺点出队效率低每个元素额外指针空间

循环数组实现队列(优化出队)

class CircularQueue:def __init__(self, capacity):self.queue = [None] * capacityself.capacity = capacityself.front = 0self.rear = 0self.size = 0def enqueue(self, item):if self.is_full():raise Exception("Queue is full")self.queue[self.rear] = itemself.rear = (self.rear + 1) % self.capacityself.size += 1def dequeue(self):if self.is_empty():raise Exception("Queue is empty")item = self.queue[self.front]self.front = (self.front + 1) % self.capacityself.size -= 1return itemdef peek(self):if self.is_empty():return Nonereturn self.queue[self.front]def is_empty(self):return self.size == 0def is_full(self):return self.size == self.capacity

链表实现队列

class LinkedListQueue:def __init__(self):self.front = Noneself.rear = Nonedef enqueue(self, item):new_node = ListNode(item)if self.rear is None:self.front = self.rear = new_nodeelse:self.rear.next = new_nodeself.rear = new_nodedef dequeue(self):if self.front:item = self.front.valself.front = self.front.nextif self.front is None:self.rear = Nonereturn itemreturn Nonedef peek(self):if self.front:return self.front.valreturn Nonedef is_empty(self):return self.front is None

总结建议:

  • 栈实现选择

    • 需要高性能且数据量可预测 → 数组实现

    • 数据量变化大或内存充足 → 链表实现

  • 队列实现选择

    • 需要缓存友好且数据量固定 → 循环数组

    • 数据量变化大或需要高效出队 → 链表实现

 

http://www.lryc.cn/news/609988.html

相关文章:

  • 分布式存储性能跃迁指南:RoCE无损网络设计与优化
  • mysql远程登陆失败
  • DC-Mamba:一种用于热红外无人机图像盲超分辨率的退化感知跨模态框架
  • 正则表达式在js中的应用
  • Hadoop MapReduce 3.3.4 讲解~
  • Prometheus-3--Prometheus是怎么抓取Java应用,Redis中间件,服务器环境的指标的?
  • 超详细:2026年博士申请时间线
  • 【Redis】安装Redis,通用命令
  • Redis键值对中值的数据结构
  • 05 基于sklearn的机械学习-梯度下降(下)
  • 解决 “crypto.hash is not a function”:Vite 从 6.x 升级至 7.x 后 `pnpm run dev` 报错问题
  • vue3+vue-flow制作简单可拖拽可增删改流程图
  • JMeter的基本使用教程
  • OpenLayers 详细开发指南 - 第八部分 - GeoJSON 转换与处理工具
  • 《Java Agent与Instrumentation:运行时增强的魔法武器》
  • 为什么ping和dig(nslookup)返回地址不一样,两者的区别
  • 基于C语言实现(控制台 )小区物业管理系统
  • Java常用数据结构入门
  • 推荐广告搜索三种业务的区别
  • 车载通信架构 ---车内通信的汽车网络安全
  • 人工智能之数学基础:条件概率及其应用
  • 跟着顶刊学写论文-摘要1
  • 深入浅出 RabbitMQ:工作队列实战(轮训策略VS公平策略)
  • SpringCloud之Nacos基础认识-服务注册中心
  • 13.Home-面板组件封装
  • Mac桌面仿制项目--让ai一句话生成的
  • mac 技巧
  • 【AI 加持下的 Python 编程实战 2_13】第九章:繁琐任务的自动化(中)——自动批量合并 PDF 文档
  • 大模型×垂直领域:预算、时间、空间三重夹击下的生存法则
  • 2.2 vue2子组件注册使用