剑指offer题集分类归纳
一: 链表(5道)
面试题18:删除链表的节点
简单
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
注意:此题对比原题有改动
示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.
尾节点法:
#Definition for singly-linked list.
#class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
dummy = ListNode(0) # 设置伪结点
dummy.next = head
if head.val == val:
return head.next # 头结点是要删除的点,直接返回
while head and head.next:
if head.next.val == val # 找到了要删除的结点,删除
head.next = head.next.next
return dummy.next
else:
head = head.next
双指针法:
class Solution:
def deleteNode(self, head: ListNode, val: int) -> ListNode:
if not head:
return head
if head.val == val:
return head.next
pre = head
cur = head.next
while cur:
if cur.val == val:
pre.next = cur.next
return head
else: #删除节点为尾节点的情况
pre = cur
cur = cur.next
复杂度分析:
时间复杂度 O(N) : N 为链表长度,删除操作平均需循环 N/2次,最差 N次。
空间复杂度 O(1) : cur, pre 占用常数大小额外空间。
。
面试题22 [ 链表中倒数第k个节点]
简单
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
双指针法:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
former, latter = head, head
for _ in range(k):
former = former.next
while former:
former, latter = former.next, latter.next
return latter
复杂度分析:
时间复杂度 O(N)O(N) : NN 为链表长度;总体看, former 走了 NN 步, latter 走了 (N-k)(N−k) 步。
空间复杂度 O(1)O(1) : 双指针 former , latter 使用常数大小的额外空间。
面试题24. [反转链表]
难度:简单
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
preNode = None
currNode = head
while currNode:
nextNode = currNode.next #用nextNode记录currNode的指向
currNode.next = preNode #关键步骤 反转箭头
preNode = currNode
currNode = nextNode
return preNode
1.首先函数进入开头的两个if语句,分别是用来判断当前节点和下一个节点是否为NULL,尤其是第二个,在后面递归过程中也会用到。
2.然后开始进入递归,注意传给 self.reverseList( ) 的参数为 head.next ,也就是说链表的会一直往后传递,直到找到最后一个节点(也就是head.val *==* 5的节点,后文简述为节点5)。此时,因为不满足第二个if语句,返回节点5。
3.函数在第二步返回到递归的上一层,headNode 等于返回的节点5 , 也就是节点5作为反转的链表头,完成反转的第一步。
4. 当前节点head为节点4 , head.next指向节点5, head.next.next指向None。 head.next.next= head 让原来指向None的节点5,改为指向节点4,完成了5—>None到5—>4的反转;然后head.next = None , 作用在于截断节点4到节点5的指针,避免形成4—>5—>4的环。
5.同理,返回上一层,当前节点head为节点3,让节点4指向节点3,再截断节点3到节点4的指针。
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next: # 递归中止条件
return head
headNode = self.reverseList(head.next)
head.next.next = head
head.next = None
return headNode
[面试题52. 两个链表的第一个公共节点]
难度:简单
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
双指针法:
解题思路:
我们使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点;当 node2 到达链表 headB 的末尾时,重新定位到链表 headA 的头结点。
这样,当它们相遇时,所指向的结点就是第一个公共结点。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
a1, b1 = headA, headB
while a1 != b1:
a1 = a1.next if a1 else headB
b1 = b1.next if b1 else headA
return a1
复杂度分析
- 时间复杂度:O(M+N) 。
- 空间复杂度:O(1)。
[面试题35. 复杂链表的复制] (不会)
难度中等
请实现 copyRandomList
函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next
指针指向下一个节点,还有一个 random
指针指向链表中的任意节点或者 null
。
示例 1:
输入:head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
输出:[[7,null],[13,0],[11,4],[10,2],[1,0]]
二 数组(5道)
[面试题03. 数组中重复的数字]
难度简单
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
index = 0
while index < len(nums):
if nums[index] == index:
index += 1
else:
if nums[index] == nums[nums[index]]:
return nums[index]
else:
nums[nums[index]], nums[index] = nums[index], nums[nums[index]]
return -1
说明:题目中给出了数组的长度范围,因此不需要对数组的长度做非空判断。
复杂度分析:
时间复杂度:O(N) ,这里 N是数组的长度。虽然 for 循环里面套了 while,但是每一个数来到它应该在的位置以后,位置就不会再变化。这里用到的是均摊复杂度分析的方法:如果在某一个位置 while 循环体执行的次数较多,那么一定在后面的几个位置,根本不会执行 while 循环体内的代码,也就是说最坏的情况不会一直出现。也就是说最坏复杂度的情况不会一直出现。
空间复杂度:O(1) 。
[面试题04. 二维数组中的查找]
难度简单
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if len(matrix) == 0:
return False
row = len(matrix) - 1
column = len(matrix[0]) - 1
i = row
j = 0
while i >= 0 and j <= column:
if target < matrix[i][j]:
i -= 1
elif target > matrix[i][j]:
j += 1
else:
return True
return False
复杂度分析:
时间复杂度 O(M+N) :其中,N 和 M 分别为矩阵行数和列数,此算法最多循环 M+N 次。
空间复杂度 O(1) : i, j 指针使用常数大小额外空间。
[面试题29. 顺时针打印矩阵]
难度简单
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
[面试题53 - I. 在排序数组中查找数字 I]
难度简单
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
"二分法"
class Solution:
def search(self, nums: List[int], target: int) -> int:
i = 0
j = len(nums) - 1
# 搜索右边界 right
while i <= j:
m = (i + j)//2
if nums[m] <= target:
i = m + 1
else:
j = m - 1
right = i
# 若数组中无 target ,则提前返回
if j >= 0 and nums[j] != target:
return 0
# 搜索左边界 right
i = 0
while i <= j:
m = (i + j)//2
if nums[m] < target: #注意边界条件 没有=才能确定左边界
i = m + 1
else:
j = m - 1
left = j
return right-left-1
复杂度分析:
- 时间复杂度 O(log N) : 二分法为对数级别复杂度。
- 空间复杂度 O(1): 几个变量使用常数大小的额外空间。
[面试题53 - II. 0~n-1中缺失的数字]
难度简单
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
“排序数组中的搜索问题,首先想到 二分法 解决。"
class Solution:
def missingNumber(self, nums: List[int]) -> int:
i = 0
j = len(nums) - 1
while i <= j:
m = (i + j) // 2
if nums[m] == m: # 关键
i = m + 1
else:
j = m - 1
return i
杂度分析:
- 时间复杂度 O(log N) : 二分法为对数级别复杂度。
- 空间复杂度 O(1): 几个变量使用常数大小的额外空间。
[面试题29. 顺时针打印矩阵]
难度简单
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
思路:
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int