青青园中葵,朝露待日晞。 阳春布德泽,万物生光辉。 常恐秋节至,焜黄华叶衰。 百川东到海,何时复西归? 少壮不努力,老大徒伤悲!——选自《乐府诗集》
题目:给定一个单链表,将其向左旋转K个位置。例如:head->1->3->4->6->7->0->2->4->5->8->3->1,k=3,则旋转过的链表为:head->8->3->1->1->3->4->6->7->0->2->4->5
今天的题目是前天的题目的引申——python算法-011找出单链表的倒数第k的元素,前天我们学会了找倒数第k个元素。先来理解下今天的题目:不看前面的题目,光从这题出发,我写文章时就一下蹦出很多方法(当然这些不是我当时写的方法,现想的,当做新题):
- 想法一:准备K个指针,先分别指向第1~K个节点,在一起向后遍历,直到第K个元素到达最后一个节点;这时开始从第K个指针开始,将指针指向的节点插入到头结点后,也就是第一个节点之前,直到把所有指向的节点都放到前面,用字符表示就是如下:
K=3
=========================
p1 p2 p3
| | |
v v v
H->1->3->4->6->7->0->2->4
1- ^ ^ ^ ^ ^ ^ ^ ^
| | | | | | | |
2- p1 p2 p3 | | | | |
| | | | | | |
3- p1 p2 p3 | | | |
| | | | | |
4- p1 p2 p3 | | |
| | | | |
5- p1 p2 p3 | |
| | | |
6- p1 p2 p3 |
| | |
7- p1 p2 p3
>>>>>>>>>>>>>>>>>>>>>>>>>>
将p3放到头结点之后
H->4->1->3->4->6->7->0->2
1- ^ ^ ^ ^ ^ ^ ^ ^
| | |
2- | p1 p2
|
3- p3
···········将p2,p1做同样的操作,即可完成。
- 想法二:用一个指针,每次都从头开始遍历,直到最后一个节点,然后将最后一个节点插入到头结点之后,将此操作执行k次即可完成。这个想法实现太简单就不放图了。
上面两个想法都是临时想的,完全根据题来,很符合向右“旋转”k个位置。
但是真的是这样吗?
当然我不是说上面两个想法不能实现题目的要求,而是效率的问题。假如我给你一个有100个节点的链表,我让你把链表向右旋转99个位置呢?那用第一个想法做:我需要99个新的指针,而且需要操作节点插入头结点之后99次!那你会说嫌节点多,我用第二个想法:我只需要1个新的指针,但是我需要从头结点向后遍历99次!而且每次都要遍历全部节点!更可怕的是:我同样需要操作节点插入头结点之后99次!
那你说99次对电脑来说也没多少,一眨眼的事情,可能连眨眼都不用。那我把给你的链表换成100000个节点的呢?又或是10000000个节点呢?如果再多几个零,用的时间都够我吃一顿**MG了。
来看一下我推荐的方法:
我们重新来看下题目:向右“旋转”k个位置,这到底什么意思,真的是旋转吗?不是!它其实是将后面的k个节点构成的链表放到了前面去。这是不一样的!细细体会。
现在问题成了——怎样把后面的k个节点放到前面去。这个问题也可变成——把前N-k个元素构成的元素放到链表尾部(N是链表长度)。接下来就是怎样将链表拆分为两个链表的问题了:
下面我就都以前者为解决方案了,两种是一样的。
如果你看过前天的文章<-o-o->直通车,你就很容易想到那两个方法——顺序遍历两次法和先后指针法。
顺序遍历两次法:先遍历一次链表算出链表长度,然后在遍历到第N-k个位置时,切断分为两个链表,然后在交换位置合并即可。这方法比前面两个想法要好不少,但是相比先后指针法还是要多遍历一次。这方法实现很简单,参考前天的方法,大家自己试一试,不会可以简信、微信我。
先后指针法:利用三个指针slow、fast和pre,slow指向第一个节点,pre指向slow的前驱节点,fast指向第k个节点,,然后同时向后遍历,直到fast指向最后一个节点,此时的slow刚好指向了倒数第k个元素,这时将pre与slow断开,就将链表分为了N-k和k个节点的链表,通过交换位置、合并等操作,就可完成。字符图示如下:
K=3
1->3->4->6->7->0->2->4
1-^ ^ ^ ^ ^ ^ ^ ^
2-| | | | | | | |
3-s | | f | | | |
4- s | | f | | |
5- s | | f | |
6- s | f |
7- pre s f
>>>>>>>>>>>>>>>>>>>>>>>>>>>
7->0->2->4->1->3->4->6
1-^ ^ ^ ^ ^ ^ ^ ^
2-| | | | | | | pre
3-s f
时间复杂度为O(N),空间复杂度为O(1)
代码实现:
def RotatingLastButK(head,k):
slow=head.next
fast=head.next
#处理fast节点
while k-1>0:
fast=fast.next
k-=1
#pre用来指向slow的前驱节点
pre=None
#判断链表是否为空,不为空执行
if head.next is not None:
#开始循环,直到fast指向最后一个节点。
while fast.next is not None:
pre=slow
slow=slow.next
fast=fast.next
pre.next=None # 断开链表
cur=head.next
head.next=slow # 头结点指向slow
fast.next=cur # 将前N-k个节点接在尾部
#返回头结点
return head
主程序:
if __name__ == '__main__':
head=creatLink(10)
print("beforewhirlhead:")
cur = head.next
while cur != None:
print(cur.data)
cur = cur.next
k=int(input("请输入k:\n"))
item=RotatingLastButK(head,k)
#print("链表倒数第%d个元素为:"%k,item)
print("afterwhirlhead:")
cur = head.next
while cur != None:
print(cur.data)
cur = cur.next
运行结果:
多试几个值,尤其是边界值:0,1,2等,结果正确。
全部代码:
import random
class LNode:
def __init__(self,arg):
self.data=arg
self.next=None
"""
题目描述:
给定链表
Head->1->1->3->3->5->7->7->8
k=4
Head->5->7->7->8->1->1->3->3
要求:
方法:先后指针法
"""
def creatLink(x):
i = 1
head = LNode(None)
tmp = None
cur = head
while i <= x:
n = random.randint(1, 9)
tmp = LNode(n)
cur.next = tmp
cur = tmp
i += 1
return head
def RotatingLastButK(head,k):
slow=head.next
fast=head.next
#处理fast节点
while k-1>0:
fast=fast.next
k-=1
#pre用来指向slow的前驱节点
pre=None
#判断链表是否为空,不为空执行
if head.next is not None:
#开始循环,直到fast指向最后一个节点。
while fast.next is not None:
pre=slow
slow=slow.next
fast=fast.next
pre.next=None # 断开链表
cur=head.next
head.next=slow # 头结点指向slow
fast.next=cur # 将前N-k个节点接在尾部
#返回头结点
return head
if __name__ == '__main__':
head=creatLink(10)
print("beforewhirlhead:")
cur = head.next
while cur != None:
print(cur.data)
cur = cur.next
k=int(input("请输入k:\n"))
item=RotatingLastButK(head,k)
#print("链表倒数第%d个元素为:"%k,item)
print("afterwhirlhead:")
cur = head.next
while cur != None:
print(cur.data)
cur = cur.next
今天的算法就到这里,大家自己写一写,加深记忆,前面有快慢指针,这里是前后指针,差不多一个理念。今天算是简单的题都写完了,之后的题会也来越难,大家加油啊!
大家可以想想怎么向左旋转k个位置?是否与今天的题一样呢?
学到脱发!听说地中海发型可以直接入职,哈哈哈。
乐意帮助大家的DKider,祝大家头发却来越少!号:Dkider。Github:Gesujian。GitHub上有更多的代码,可以自己研究哈。
我学习我快乐。