同数组一样,链表也是一种线性数据结构,不同的是它通过引用字段将所有分离的元素链接在一起。
在操作上,链表通常可以O(1)的时间复杂度完成插入和删除元素的操作,同时它不像数组,无法通过索引立即访问任意元素,只能通过头结点遍历的方式来访问,所以通常访问元素是O(n)时间复杂度。 这两点和数组正好相反。
有两种常用的链表:单链表
和双链表。
1、单链表定义
每个结点包含自身的值和到下个结点的引用。
以下是python中链表结点的类定义
class Node(object):
def __init__(self, x):
self.val = x
self.next = None
例题一:删除链表中的节点 (题目来源 leetcode 237题)
请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点,你将只被给定要求被删除的节点。
现有一个链表 -- head = [4,5,1,9],它可以表示为:
输入: head = [4,5,1,9], node = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
这里的注意点是,只给了要删除的节点。
刚看到这个题目是有点懵,因为我们一般情况是给了链表的头节点,要删除某个节点时,只需从头部遍历到要删除节点的前一个节点,然后修改next指向,便可完成操作。所以这道题是有点tricky的。
def deleteNode(self, node):
"""
:type node: ListNode
:rtype: void Do not return anything, modify node in-place instead.
"""
node.val = node.next.val
node.next = node.next.next
其实这里思路转换了下,既然无法通过常规方法删除节点,我们通过把要删除节点的下个节点值向前覆盖当前节点,通过修改指针指向下个节点的下个节点,就实现了下个节点前移覆盖当前节点的操作,实际上就是等于删除了 node节点
例题二:判断链表中是否存在环
解法两种,一种是借助哈希表 但这种空间复杂度O(N)时间复杂度也是O(N),一种是经典的快慢指针 技巧 ,不需要额外的空间
class Solution(object):
def hasCycle(self, head):
"""
:type head: ListNode
:rtype: bool
"""
slow = quick = head
while quick and quick.next:
slow = slow.next
quick = quick.next.next
if quick == slow:
return True
return False
例题三,环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。
说明:不允许修改给定的链表。
这道题,同样有两种常用解法,和上题类似,第一种解法,借助哈希表,但空间复杂度高,第二种用快慢指针和一个小技巧。
public class Solution {
//方法一
// public ListNode detectCycle(ListNode head) {
// Set<ListNode> sets = new HashSet<ListNode>();
// ListNode node = head;
// while(node!=null)
// {
// if(sets.contains(node))
// return node;
// sets.add(node);
// node = node.next;
// }
// return null;
// }
///方法二
public ListNode detectCycle(ListNode head) {
ListNode quick=head,slow=head;
boolean flag = false;
while(quick!=null && quick.next!=null)
{
quick = quick.next.next;
slow = slow.next;
if(quick == slow)
{
flag = true;
break;
}
}
if(flag == true)
{
ListNode p = head;
while(p!=slow)
{
p=p.next;
slow= slow.next;
}
return p;
}
else
return null;
}
}
上面是Java代码,python代码类似,第一种方法,HashSet 换成 集合set即可,由于保存了每个遍历的节点,所以当存在环时,只要再次出现访问过的节点,就是环的入口位置。
第二种方法,先用快慢指针判断是否存在环,如果存在,再用一个指针从头开始,和慢指针同步向前移动,当两者重合时,即是环的入口位置。
题目四:反转一个单链表。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if head == None:
return head
prev,current,next = None,head,None
while current:
next = current.next
current.next = prev
prev = current
current = next
return prev
核心思想就是使用三个指针,遍历一遍链表,逐个去翻转,每次翻转前要保存后下个节点。
题目5: 移除链表元素 leetcode 203
删除链表中等于给定值 val 的所有节点。
示例:
输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3->4->5
def removeElements(self, head: ListNode, val: int) -> ListNode:
if head == None:
return head
while head:
if head.val == val:
head = head.next
else:
break
tmp = head
while tmp.next:
if tmp.next.val == val:
tmp.next = tmp.next.next
else:
tmp = tmp.next
return head
主要是考虑头元素是 val的情况
题目5:奇偶链表 (Leetcode 328)
给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。
请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
示例 1:
输入: 1->2->3->4->5->NULL 输出: 1->3->5->2->4->NULL
这道题还是有点考察逻辑思维和链表的掌握程度的。
def oddEvenList(self, head: ListNode) -> ListNode:
if head == None or head.next == None or head.next.next == None:
return head
odd,even,evenhead = head,head.next,head.next
while even and even.next:
odd.next = even.next
odd = odd.next
even.next = odd.next
even = even.next
odd.next = evenhead
return head
核心思想是奇数位置的下一位肯定是偶数,偶数的下一个位置肯定是奇数,每次奇奇连接,或偶偶连接后,要把odd 指针或even指针移动到奇队列末尾 或 偶队列末尾,遍历结束后分成了奇链表和偶链表,最后奇链表的末尾连接到偶链表的首节点 即可。