1.反转链表
反转链表有两种做法:迭代法和递归法,这里两种都写一下
1.1 迭代法
定义三个指针pPrev
,pNode
,pNext
,分别指向当前节点的前一节点
、当前节点
、当前节点的后一节点
,只要当前节点不为空,就不断地将当前节点的next
指向前一节点,然后更新当前节点为下一节点,最后返回pPrev
,因为退出条件为当前节点为空节点,故此时pPrev
为最后一个节点。(PS:这个代码的前后相连真的是美)
def reverseLinkedListIter(head):
pPrev = None
pNode = head
while pNode != None:
pNext = pNode.next
pNode.next = pPrev
pPrev = pNode
pNode = pNext
return pPrev
时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( 1 ) O(1) O(1)。
1.2 递归法
递归法的思想是,每次翻转当前节点后面的链表,然后把下一节点的next
指向当前节点,直接看代码。
def reverseLinkedListRecursion(head):
if head == None or head.next == None:
return head
newHead = reverseLinkedListRecursion(head.next)
head.next.next = head
head.next = None
return newHead
这个代码的巧妙之处在于newHead
一直没变,是最后一个节点。
时间复杂度
O
(
N
)
O(N)
O(N),空间复杂度
O
(
N
)
O(N)
O(N)。
2.成对反转链表
题目描述:
乍一看这个题目,我有一种很粗暴的解法,就是把链表按单双数分解成两个链表,然后再重新合并,这种方法肯定是行得通的,此处讲另外两种方法,也分别是迭代法和递归法。
2.1 迭代法
思路也是定义三个指针
def reversePairIter(head):
# 定义一个虚拟节点,方便处理
dummy = ListNode(0)
pPrev = dummy
pNode = head
while pNode != None and pNode.next != None:
pNext = pNode.next
pNode.next = pNext.next
pNext.next = pNode
pPrev.next = pNext
pPrev = pNode
pNode = pNode.next
return dummy.next
下面用一张图来说明一下上面代码的第8~10行的过程,注意,这里1,2,3,4
表示的是原先的顺序,其中pPrev
是已经翻转过的,上面三个步骤分别用图中的带圈1,2,3
表示,
经过交换后,现在的节点顺序如下:
可见,2
和3
对应的节点已经交换了顺序,接下来更新pPrev
和pNode
的值即可继续后续的翻转。
时间复杂度
O
(
N
)
O(N)
O(N),空间复杂度
O
(
1
)
O(1)
O(1)。
2.2 递归法
思路是定义三个指针,分别指向当前节点、下一节点和下下节点,首先将下一节点的next
指向当前节点,然后将当前节点的next
指向下下节点翻转后的返回值(递归),最后返回下一节点(此时下一节点变为头节点)。
def reversePairRecursion(head):
if head == None or head.next == None:
return head
pNext = head.next
pNextNext = pNext.next
pNext.next = head
head.next = reversePairRecursion(pNextNext)
return pNext
时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( N ) O(N) O(N)。
3. 反转链表的第m个至第n个节点
暂空。
4. K个一组反转链表
这个题目我的做法比较笨,先说自己的笨办法,这个办法的过程是这样的:
- 先获取链表的总长度N
- 令
m=N//k
,即有多少组子链表需要反转- 对于整个链表,断成
m
组节点数为k
的子链表,过程中记录各子链表的头节点,产生一个列表heads
,并把各组的尾节点接一个None
,对于剩下长度不够k
的子链表,记下其头节点- 再断开的同时(这一步也可以放后面做),对每个子链表进行反转,将反转后的头节点也记录下,产生一个列表
newHeads
- 连接各段子链表
连接子链表,原先的头为现在的尾,连接规则
heads: [p1 , p2 , p3 , p4 , p5]
| | | |
newHeads: [p1 , p2 , p3 , p4 , p5]
newHeads中的p1为最终反转后的链表的头节点,heads中的p5的下一个节点为剩余的长度不够k的子链表的前序节点
代码敬上:
def reverseKGroupIter(head,k):
def reversedLinkedList(head):
pPrev = None
pNode = head
while pNode != None:
pNext = pNode.next
pNode.next = pPrev
pPrev = pNode
pNode = pNext
return pPrev
if head == None or head.next == None:
return head
# 获取链表的长度
N = 0
pNode = head
while pNode != None:
pNode = pNode.next
N += 1
if N<k:
return head
# m组需要反转的子链表
m = N // k
heads = []
newHeads = []
pNode = head
# 断开链表为 m + 1 个子链表,记录前面m个头节点,最后剩余的长度不够k的子链表的头节点为pNode
for i in range(m):
heads.append(pNode)
for j in range(k):
# 若 j == k-1,此时pNode指向子链表的第k个节点,记录它,然后让它的下一个节点为None
if j == k - 1:
pCurrent = pNode
# 下面两步的顺序不能反,因为此时pcurrent==pNode,若先执行后一步,pNode就丢失了链表后面的节点
# 也正是这个原因,我们必须写else
pNode = pNode.next
pCurrent.next = None
else:
pNode = pNode.next
# 记录翻转后的新子链表的头部
newHeads.append(reversedLinkedList(heads[i]))
# 连接链表节点
for i in range(len(heads)-1):
heads[i].next = newHeads[i+1]
heads[-1].next = pNode
return newHeads[0]
再说一种递归的方法,这个方法看的是leetcode里的题解,递归方法的思路其实和上面的意思差不多,但是不需要拆开链表,大致过程是:
- 定义一个列表,用于保存需要反转的
k
个节点- 往列表里塞节点,塞满
k
个为止,若塞不满k
个,则直接返回当前的head
- 对于列表里的
k
个节点,从后往前,每个节点的next
指向其前一个节点- 对于列表里的第
1
个节点,其next
为后续链表反转后的结果,故可递归调用- 返回值为列表的最后一个节点
代码:
def reverseKGroupRecursion(head,k):
cnt = k
pNode = head
l = []
while cnt > 0:
if pNode != None:
l.append(pNode)
pNode = pNode.next
else:
return head
cnt -= 1
cnt = k - 1
while cnt > 0:
l[cnt].next = l[cnt-1]
cnt -= 1
# 此时pNode为下一段的头节点
l[0].next = reverseKGroupRecursion(pNode,k)
return l[-1]
上面所有代码都通过了leet-code对应的题目。