剑指offer题集分类 Python实现详解

剑指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:

img

输入: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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值