题054.螺旋矩阵
题意
给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。
eg
输入:
[
[ 1, 2, 3 ],
[ 4, 5, 6 ],
[ 7, 8, 9 ]
]
输出: [1,2,3,6,9,8,7,4,5]
解题
我的方法是按照题意进行遍历,对矩阵进行螺旋式的遍历,然后把遍历到的元素加到list中输出。原本的想法是按上下左右四条直线写四个循环,但看到题解中用更改方向的做法,代码整体看上去会更简洁点。
螺旋式的遍历要考虑两点:
- 螺旋时,遍历方向的转换
- 遍历终止的条件
为了解决上面两个问题,可以用一个 状态矩阵 来记录每个点是否被遍历过:
- 转换方向时,如果原方向的下一个数已被访问过,或者超出了数组的范围,就右转90度,具体做法是定义一个二维矩阵,存储四个方式上横纵坐标的变换(0,+1),(+1,0),(0,-1),(-1,0)。
- 当遍历次数达到原矩阵元素总数时,遍历结束。
代码
if not matrix or not matrix[0]:
return list()
direction=[[0,1],[1,0],[0,-1],[-1,0]]
m,n=len(matrix),len(matrix[0])
l=m*n
res=[]
visited=[[False]*n for _ in range(m)]
did=0 #遍历方向
i,j=0,0
for _ in range(l):
res.append(matrix[i][j])
visited[i][j]=True
#判断是否更改方向
tmpi,tmpj=i+direction[did][0],j+direction[did][1]
if not(0<=tmpi<m and 0<=tmpj<n and not visited[tmpi][tmpj]):
did=(did+1)%4
i+=direction[did][0]
j+=direction[did][1]
return res
矩阵的每个元素都访问了一次,时间复杂度是O(mn)
题059.螺旋矩阵 II
题意
给定一个正整数 n n n,生成一个包含 1 到 n 2 n^2 n2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。
eg
输入: 3
输出:
[
[ 1, 2, 3 ],
[ 8, 9, 4 ],
[ 7, 6, 5 ]
]
解题
本题和上题的螺旋矩阵其实差不多,区别是一个是取值,一个是赋值。
所以参照上例方案,做法如下:
- 维护一个方向数组,包含四个变量:(0,+1),(+1,0),(0,-1),(-1,0)
- 由于赋值范围为1~
n
2
n^2
n2 ,即都是正数,所以定义一个n×n的矩阵,初始化所有值为0,这样判断是否转向时只有两种情况:
- 触碰边界
- 节点原值不为0
- 用cur作为当前赋值的数,每次循环赋值后自增,共循环 n 2 n^2 n2 次
代码
matrix=[[0]*n for _ in range(n)]
direction=[[0,1],[1,0],[0,-1],[-1,0]]
i,j,cur=0,0,1
did=0 #当前方向
for _ in range(n*n):
matrix[i][j]=cur
nexti,nextj=i+direction[did][0],j+direction[did][1]
if not(0<=nexti<n and 0<=nextj<n and not matrix[nexti][nextj]):
did=(did+1)%4
i+=direction[did][0]
j+=direction[did][1]
cur+=1
return matrix
题061.旋转链表
题意
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
eg
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
解题
本题解题的关键点就是 找到链表第l-k个节点 (l为链表长度),然后把它的next设为NULL,并且把原来第一个节点接到最后一个节点后面。
我的解法:链表转成list
如果旋转的是数组,只需分成切片再合并即可,但链表的性质决定了操作的麻烦,所以考虑到学习前几天一道题中对于链表的处理方法,将链表放到list中,这样转换顺序就方便多了。
而且k的值可能比链表长度还长,需要对链表长度取余,考虑这点,我觉得数组是个很好的选择。
思路
-
判断可直接返回链表的可能:
- 链表为空
- 链表只有一项
- k为0
- k为链表长度的整数倍
-
以上四种的前三种可写在开头,但第四种需要在获得链表长度后再操作
-
将链表放到数组中,然后按照原来的顺序连起来,再进行判断:
- k取余后为0:直接输出链表
- k不为0:把第l-k项的next设为空,把第一项链接到最后一项之后,输出链表
具体的代码如下:
if not head or not head.next or k==0:#前三种情况
return head
helper=[]
while head:
helper.append(head)
head=head.next
l=len(helper)
k=k%l
for i in range(l-1):
helper[i].next=helper[i+1]
if k==0:#第四种情况
return helper[0]
else:
helper[l-1].next=helper[0]
helper[l-k-1].next=None
return helper[l-k]
#对上方的判断部分进行修改,可以在最后输出helper[l-k]时模一下l,就把k为l整数倍的情况也考虑进去了
helper[l-1].next=helper[0]
helper[l-k-1].next=None
return helper[(l-k)%l]
时间复杂度为O(n)
法二:遍历两次
- 通过第一次循环获取链表长度,从而得到 l-k 的值
- 第二次遍历到第l-k个节点,进行操作
l,cur=1,head
#一次遍历
while cur.next:
l+=1
cur=cur.next
cur.next=head
#二次遍历
for i in range(l-k%l):
cur=cur.next
head,cur.next=cur.next,Nonne
return head
法三:快慢指针
快慢指针是做链表题时常用的一种方法,这边找的是l-k个节点,也就是从后往前数第k个节点,所以操作如下:
- 快指针先走k步
- 而后快慢指针同时开始移动
这样当快指针达到尾部的时候,慢指针指向的就是第l-k个节点
BUT,
如果 k 值比 l 小的话可以直接这样做,本题还是要考虑 k 值比 l 大的情况,换句话说,还是要对 k 模 l ,所以依然要先进行一次遍历获取链表长度
#获取长度略
k%=l
if k==0:
return head
slow=fast=head
#先行k步
for _ in range(k):
fast=fast.next
#携手并进
while fast.next:
fast=fast.next
slow=slow.next
fast.next=head
head=slow.next
slow.next=None
return head