0. 概念
介绍“链表”之前,我们先聊聊“数组”。
“数组”(Array)大家应该都比较熟悉,知道它是一种线性表(Linear List)数据结构。它用一组连续的内存空间,来存储一组具有相同类型的数据。顾名思义,“线性表”就是数据排成像一条线一样的结构,线性表上的每个数据最多只有前和后两个方向。
其实除了“数组”,我们所熟知的“链表”,“队列”和“栈”也是线性表结构。
而与之相对立的概念是“非线性表”,比如树,堆,图等。在非线性表中,数据之间就不是简单的前后关系。
“数组”是用一组连续的内存空间,来存储一组具有相同类型的数据。与之对应的,“链表”则是用一组不连续的内存空间,来存储相同类型的数据。
相比于“数组”,“链表”具有五花八门的结构,其中最常见的三种分别是:单链表,双链表和循环链表。
“单链表”是最简单的链表,面试中考查链表的知识,一般都是基于“单链表”来进行考查。
单链表中每个结点,需要一个变量来存放数据,同时还需要一个“指针”指向下一个结点。
1. 常见考点
接下来,介绍5个常见的考查“链表”的编程题。
链表结点定义如下:
class Node:
def __init__(self, x):
self.data = x
self.next = None
1.1 链表反转
给定一个链表的头结点,请反转这个链表,返回反转后新链表的头结点。
def reverse_list(head):
if head is None or head.next is None:
return head
prev, cur = None, head
while cur:
cur.next, prev, cur = prev, cur, cur.next
return prev
1.2 链表中环的检测
给定一个链表的头结点,返回该链表是否是环。
def is_cyclic(head):
if head is None:
return False
slow = fast = head
while slow.next and fast.next.next:
slow, fast = slow.next, fast.next.next
if slow == fast:
return True
return False
1.3 两个有序链表的合并
给定两个有序链表的头结点,请合并这两个链表,返回合并之后新链表的头结点。
def merge_two_list(l1, l2):
ret = cur = Node(0)
while l1 and l2:
if l1.data < l2.data:
cur.next = l1
l1 = l1.next
else:
cur.next = l2
l2 = l2.next
cur = cur.next
cur.next = l1 or l2
return ret.next # ret只是一个哨兵,新链表的头结点应该是ret.next
1.4 删除链表中倒数第k个结点
这题可以拆分成两个函数,第一个函数实现找到倒数第k个结点,第二个函数实现删除结点。
def kth_to_last(head):
if head is None or k < 1:
return None
p1 = p2 = head
for i in range(k): # 先将p1向前移动k位
if p1 is None: # 若中途p1为None,说明k值过大,超出链表长度
return None
p1 = p1.next
while p1: # 然后再同时移动p1和p2,直到p1为None
p1 = p1.next
p2 = p2.next
return p2
def delete_node(head, p):
if head is None or p is None:
raise ValueError
if p.next is None: # p是尾结点,则直接将p删除即可(置为None)
p = None
else: # p不是尾结点,则p结点复制后继结点的值,然后删除后继结点
p.data = p.next.data
p.next = p.next.next
1.5 找链表的中间结点
def mid_node(head):
if head is None or head.next is None:
return head
slow = fast = head
while slow.next and fast.next.next:
slow, fast = slow.next, fast.next.next
return slow