剑指offer简单题刷题记录(Python)

剑指 Offer 03. 数组中重复的数字

1、使用哈希表(set)

 1 class Solution:
 2     def findRepeatNumber(self, nums: List[int]) -> int:
 3         """
 4         为什么不是初始化一个列表用于存储num?
 5         列表数据有序,可重复,查找某个元素方式为逐个遍历:
 6         时间复杂度为列表的长度,即从第一个元素遍历到最后一个元素为止,O(len(list))。
 7         集合数据无序,不可重复,查找某个元素方式为哈希:
 8         即某个元素通过哈希计算,他的位置永远固定,查询时通过哈希即可一次找到该元素。时间复杂度为O(1)。
 9         """
10         dict = set()
11         for num in nums:
12             if num not in dict:
13                 dict.add(num)
14             else:
15                 return num
16         return -1

2、原地置换

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        # 一个萝卜一个坑,如果坑位上已经有一个萝卜了,那再有一个相同的萝卜就是多余的萝卜,即重复的数字
        # 索引值=数组值则表明坑位上有相应萝卜
        N = len(nums)
        for i in range(N):
            while nums[i] != i:         # 发现这个坑里的萝卜不是自己家的
                temp = nums[i]          # 看看你是哪家的萝卜
                if nums[temp] == temp:  # 看看你家里有没有和你一样的萝卜
                    return temp         # 发现你家里有了和你一样的萝卜,那你就多余了,上交国家
                else:                   # 你家里那个萝卜和你不一样
                    nums[temp], nums[i] = nums[i], nums[temp]   # 把你送回你家去,然后把你家里的那个萝卜拿回来

剑指 Offer 05. 替换空格

1、直接使用Python的replace函数

class Solution:
    def replaceSpace(self, s: str) -> str:
        s = s.replace(" ","%20")
        return s

2、逐个遍历

class Solution:
    def replaceSpace(self, s: str) -> str:
        lyst = []
        for n in s: # 遍历字符串
            if n == " ": # 判断本次遍历的元素是否为空格
                lyst.append('%20')
            else:
                lyst.append(n)
        return "".join(lyst)

剑指 Offer 06. 从尾到头打印链表

 1、辅助栈

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        stack = []
        while head:
            stack.append(head.val) # 将每个节点的值都添加到stack中
            head = head.next
        return stack[::-1] # 逆序打印stack

剑指 Offer 09. 用两个栈实现队列

参考:https://www.bilibili.com/video/BV1kb4y1y7Md?from=search&seid=1926139226475447499&spm_id_from=333.337.0.0

class CQueue:

    def __init__(self):
        self.stack1 = [] # 数据栈
        self.stack2 = [] #辅助栈


    def appendTail(self, value: int) -> None:
        self.stack1.append(value)


    def deleteHead(self) -> int:
        if self.stack2: # 如果辅助栈stack2不为空,则直接返回stack弹出来的数值
            return self.stack2.pop()
        # 如果辅助栈为空,则进行下一个if的判断
        if not self.stack1: # 如果数据栈stack1为空,则表明队列中没有元素,返回-1
            return -1
        # 如果数据栈不为空,则开始将数据栈pop出来的元素依次添加到辅助栈中,下面while执行完之后,辅助栈中的元素完成数据栈中元素倒置,即栈底变为栈顶,数据栈为空
        while self.stack1:
            self.stack2.append(self.stack1.pop())
        # 返回辅助栈的栈顶元素,即之前数据栈中的栈底元素
        return self.stack2.pop()




# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()

剑指 Offer 10- I. 斐波那契数列

 1、标准递归方式(超出了时间限制)

class Solution:
    def fib(self, n: int) -> int:
        if n == 0:
            return 0
        elif n == 1:
            return 1
        else:
            return (self.fib(n-1) + self.fib(n - 2)) % 1000000007

2、记忆化递归法(在递归法的基础上,新建一个长度为 nn 的数组,用于在递归时存储 f(0)f(0) 至 f(n)f(n) 的数字值,重复遇到某数字则直接从数组取用,避免了重复的递归计算。)

class Solution:
    def fib(self, n: int) -> int:
        # 用数组记录数列的每一个值
        # 边界值是否需要处理?如果n=0或1,for i in range(2, n+1)是不会进入到循环中,所以可以不用进行特殊值处理
        '''
        if n == 0:
            return 0
        if n == 1:
            return 1
        '''
        dp = [0, 1]
        for i in range(2, n+1):
            dp.append(dp[i - 1] + dp[i - 2])
        return dp[n] % 1000000007

3、动态规划

class Solution:
    def fib(self, n: int) -> int:
        a, b = 0, 1
        for _ in range(n):
            a, b = b, a + b
        return a % 1000000007
        '''
        1、如果n=0,不会进入到for循环,直接返回初始的a取余,即0
        2、如果n=1,则进行循环后,a=1(即b),b=1(a+b),返回a取余
        3、如果n=2,则进行循环,第一次:a=1(即b),b=1(a+b);第二次:a=1(b),b=2(a+b),返回a取余
        '''

剑指 Offer 10- II. 青蛙跳台阶问题

1、动态规划

class Solution:
    def numWays(self, n: int) -> int:
        # 跳台阶的问题就是一个斐波那契数列
        # 设跳上n级台阶有f(n)种跳法,青蛙最后一步可以跳1级台阶,也可以跳2级台阶。当为1级台阶:剩 n-1个台阶,此情况共有f(n-1) 种跳法;当为2级台阶:剩 n-2个台阶,此情况共有f(n-2)种跳法。所以f(n)=f(n−1)+f(n−2) 。
        a = 1 # f[0] = 1
        b = 1 # f[1] = 1
        for i in range(n):
            a, b = b, a + b
        return a % 1000000007

剑指 Offer 11. 旋转数组的最小数字

 1、二分法

class Solution:
    def minArray(self, numbers: List[int]) -> int:
        # 遇到“排序”字眼,可以优先选择:二分法
        left = 0
        right = len(numbers) - 1
        while left < right:
            mid = (left + right) // 2
            if numbers[mid] > numbers[right]: # 当mid索引的值大于right索引的值,说明最小值应该在mid索引的右侧
                left = mid + 1
            elif numbers[mid] < numbers[right]: # 当mid索引的值小于right索引的值,说明最小值应该在mid索引的左侧,为什么不是mid-1,因为mid也有可能是数据中最小的值
                right = mid
            else: # 当mid索引的值等于right索引的值,无法说明当前最小值在那一侧,所以使right-1,再开始一轮比较
                right = right - 1 
        return numbers[left]

剑指 Offer 15. 二进制中1的个数

1、逐位判断,按位与

class Solution:
    def hammingWeight(self, n: int) -> int:
        # 逐位判断:按位与,0&1=0,1&1=1
        count = 0
        while n:
            count += n & 1 # 若n&1=1,则表明n的最后一位是1,计数器加1
            n >>= 1 # n为无符号整数,>>表示右移若干位,左补0 
        return count

2、巧用n&(n-1),参考https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/solution/mian-shi-ti-15-er-jin-zhi-zhong-1de-ge-shu-wei-yun/

class Solution:
    def hammingWeight(self, n: int) -> int:
        '''
        n−1解析:二进制数字n最右边的1变成0,此1右边的0都变成1。
        n&(n−1) 解析:二进制数字n最右边的1变成0,其余不变。
        二进制与操作:0&0=0,0&1=0,1&0=0,1&1=1
        举例:
        n:      10101000
        n-1:    10100111
        n&(n-1):10100000
        '''
        count = 0
        while n:
            count += 1
            n &= n - 1
        return count

剑指 Offer 17. 打印从1到最大的n位数

 1、基础解法(未考虑大数的情况)

class Solution:
    def printNumbers(self, n: int) -> List[int]:
        max_ = 10 ** n
        res = []
        for i in range(1,max_):
            res.append(i)
        return res

2、考虑大数的情况

 

class Solution:
    def printNumbers(self, n: int) -> [int]:
        def dfs(x):
            if x == n: # 终止条件:已固定完所有位
                res.append(''.join(num)) # 拼接 num 并添加至 res 尾部
                return
            for i in range(10): # 遍历 0 - 9
                num[x] = str(i) # 固定第 x 位为 i
                dfs(x + 1) # 开启固定第 x + 1 位
        
        num = ['0'] * n # 起始数字定义为 n 个 0 组成的字符列表
        res = [] # 数字字符串列表
        dfs(0) # 开启全排列递归
        return ','.join(res)  # 拼接所有数字字符串,使用逗号隔开,并返回

 

剑指 Offer 18. 删除链表的节点

1、双指针

# 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:
        # 初始化两个游标,cur指向当前值,pre指向cur的前一个值
        cur = head
        pre = None
        while cur is not None:
            # 判断cur的值是否是需要删除的值
            if cur.val == val:
                # 设节点cur的前驱节点为pre,后继节点为cur.next则执行pre.next=cur.next即可实现删除cur节点。
                # 如果cur的值等于head的值,即删除的元素是头节点
                if cur == head:
                    head = cur.next
                # 删除的值不是头节点
                else:
                    pre.next = cur.next
                # 删除完了之后跳出循环
                break
            # 如果cur的值不是需要删除的值,则pre和cur都依次向后移
            else:
                pre = cur
                cur = cur.next
        return head  

 

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

1、辅助数组

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        # 初始化两个数组,一个存奇数,一个存偶数
        lyst1 = []
        lyst2 = []
        for i in nums:
            if i % 2 != 0:
                lyst1.append(i)
            else:
                lyst2.append(i)
        return lyst1 + lyst2

2、头尾双指针

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        # 初始化两个指针i,j,i从头到尾寻找偶数,j从尾到头寻找奇数
        i, j = 0, len(nums) - 1
        while i < j:
            # 为什么外层已经有while i<j,内层的while还需要再判断一次:因为第二步和第三步循环过程中,有可能遇到 i == j 的边界情况,此时就应终止,不然 i 就跑到 j 右边了,这和本文方法定义不符。且对于【1,3,5,7】这种来说,如果不判断的话,会列表越界报错。
            '''x&1位运算等价于x%2 取余运算,即皆可用于判断数字奇偶性。'''
            while i < j and nums[i] % 2 != 0: # i<j且索引为i的值是奇数,则i加1,往后找偶数
                i += 1
            while i < j and nums[j] % 2 == 0: # i<j且索引为j的值是偶数,则j减1,往后找奇数
                j -= 1
            # 跳出第一、第二步循环后,i指向偶数,j指向奇数,所以交换
            nums[i], nums[j] = nums[j], nums[i]
        return nums

剑指 Offer 22. 链表中倒数第k个节点

 1、常规解法

# 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:
        # 先计算链表的长度n,然后(n-k)指向的节点就是倒数第k个节点
        # 计算链表长度n
        cur1 = head
        n = 0
        while cur1 is not None:
            n += 1
            cur1 = cur1.next
        # 找倒数第k个节点
        cur2 = head
        num = n - k
        for _ in range(num):
            cur2 = cur2.next
        return cur2       

 2、双指针

# 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:
        # 双指针
        fast, slow = head, head
        # fast指针先走k步
        for _ in range(k):
            fast = fast.next
        # 当fast指针指向None时,跳出循环,表明链表的节点已遍历完,此时slow指向的节点就是倒数第k个
        while fast:
            fast, slow = fast.next, slow.next
        return slow

剑指 Offer 24. 反转链表

 1、双指针

# 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:
        # 双指针
        pre, cur = None, head  # 初始化两个指针,一个指向头节点,一个指向None
        while cur: # 当cur指向None时,跳出循环
            tmp = cur.next  # 临时指针指向cur的后继节点
            cur.next = pre  # 断开原来cur的后继节点,使其指向pre
            pre = cur  # pre指向cur
            cur = tmp  # cur指向tmp,即一次循环内cur的后继节点
        return pre

2、头插法

# 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:
        # 头插法,往cur节点前插入节点
        cur = None
        while head:
            newNode = ListNode(head.val)  #将原始列表的节点的val形成一个节点
            head = head.next # head指向下一个节点
            newNode.next = cur # newNode.next指向cur
            cur = newNode # cur指向新的节点
        return cur 

剑指 Offer 25. 合并两个排序的链表

1、伪头节点

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        # 增加一个伪节点dum
        cur = dum = ListNode(0)
        while l1 and l2:  # 当l1或l2中有一个为空时,跳出循环
            if l1.val <= l2.val:  # 如果l1.val小于等于l2.val,将l1的节点添加到cur,l1向后移动,cur往后移动
                cur.next = l1
                l1 = l1.next
                cur = cur.next
            else:  # 反之,操作l2
                cur.next = l2
                l2 = l2.next
                cur = cur.next
        # 因为退出while循环时,l1或l2有为空的,如果l1不为空,则把l1剩下的节点都添加到cur节点后面;反之则操作l2 
        cur.next = l1 if l1 else l2
        # 因为有一个伪节点,所以返回的是dum.next
        return dum.next

剑指 Offer 27. 二叉树的镜像

 1、递归法

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def mirrorTree(self, root: TreeNode) -> TreeNode:
        if root == None:
            return None
        # 利用平行赋值的写法
        root.left, root.right = root.right, root.left
        self.mirrorTree(root.left)
        self.mirrorTree(root.right)
        return root

2、辅助栈

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def mirrorTree(self, root: TreeNode) -> TreeNode:
        # 利用栈(或队列)遍历树的所有节点 node ,并交换每个 node 的左 / 右子节点。
        if not root:
            return
        stack = [root]
        while stack:
            node = stack.pop()
            if node.left:
                stack.append(node.left)
            if node.right:
                stack.append(node.right)
            node.left, node.right = node.right, node.left
        return root

剑指 Offer 28. 对称的二叉树

1、递归

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isSymmetric(self, root: TreeNode) -> bool:
        # 判断某节点的左节点L和右节点R是否相等
        def helper(L, R):
            if not L and not R:  # L和R节点都为空,返回true
                return True
            if not L or not R:  # L和R节点有一个为空,返回False                   
                return False
            if L.val != R.val:  # L和R节点不相等,返回False
                return False           
            return helper(L.left, R.right) and helper(L.right, R.left)
        return helper(root.left, root.right) if root else True

剑指 Offer 29. 顺时针打印矩阵

 

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        '''
        打印方向    根据边界打印        边界向内收缩    是否打印完毕
        从左向右    左边界l,右边界r        上边界t加1        是否 t > b
        从上向下    上边界t,下边界b        右边界r减1        是否 l > r
        从右向左    右边界r,左边界l        下边界b减1        是否 t > b
        从下向上    下边界b,上边界t        左边界l加1        是否 l > r
        '''
        # 如果矩阵为空,返回空
        if not matrix:
            return []
        # 开始按照顺时针打印矩阵,顺序是 “从左向右、从上向下、从右向左、从下向上” 
        l, r, t, b, res = 0, len(matrix[0]) - 1, 0, len(matrix) - 1, []
        while True:
            # 从左向右
            for i in range(l, r + 1):
                res.append(matrix[t][i])
            t += 1
            if t > b: break
            # 从上向下
            for i in range(t, b + 1):
                res.append(matrix[i][r])
            r -= 1
            if l > r: break
            # 从右向左
            for i in range(r, l - 1, -1):
                res.append(matrix[b][i])
            b -= 1
            if t > b: break
            # 从下向上
            for i in range(b, t - 1, -1):
                res.append(matrix[i][l])
            l += 1
            if l > r: break     
        return res                   

 

剑指 Offer 30. 包含min函数的栈

 1、辅助栈

class MinStack:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.stack = [] # 数据栈
        self.stack_min = [] # 辅助栈,栈顶始终是数据站中最小的值

    def push(self, x: int) -> None:
        self.stack.append(x) # 将x入数据栈
        if self.stack_min == []: # 如果辅助栈为空,则将x入辅助栈
            self.stack_min.append(x)
        else: # 如果辅助栈不为空,判断x和辅助栈的栈顶元素谁最小:若x最小,将x入辅助栈,否则再入一次栈顶元素到辅助栈,始终保持辅助栈和数据站元素个数一致
            tmp = min(x, self.stack_min[-1])
            self.stack_min.append(tmp)

    def pop(self) -> None: # 同时弹出两个栈的栈顶元素,始终保持辅助栈和数据站元素个数一致
        self.stack.pop()
        self.stack_min.pop()

    def top(self) -> int: # 返回数据站的栈顶元素
        return self.stack[-1]

    def min(self) -> int: # 返回辅助栈的栈顶元素,即数据栈中最小的元素
        return self.stack_min[-1]


# Your MinStack object will be instantiated and called as such:
# obj = MinStack()
# obj.push(x)
# obj.pop()
# param_3 = obj.top()
# param_4 = obj.min()

剑指 Offer 32 - II. 从上到下打印二叉树 II

1、层次遍历,使用queue

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        # 广度遍历(层次遍历),BFS:从上到下,从左到右
        # 队列:先进先出,queue只允许在一端进行插入操作,另一端进行删除操作
        if not root: # 如果树为空,返回空列表
            return [] 
        queue =[root] # 先默认将根节点加入队列
        res = [] # 初始化一个res列表,作为最后的返回
        while queue:
            tmp = [] # 暂存列表,用于存储同一层的节点
            for _ in range(len(queue)): # 循环的次数就是每一层的节点个数
                cur_node = queue.pop(0) # 弹出当前队列的第一个节点
                tmp.append(cur_node.val) # 将当前节点的值加入tmp列表
                # 将当前节点的左右子节点加入队列
                if cur_node.left is not None:
                    queue.append(cur_node.left)
                if cur_node.right is not None:
                    queue.append(cur_node.right)
            res.append(tmp)
        return res

2、层次遍历,使用deque

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        #Python 中使用collections 中的双端队列deque() ,其popleft()方法可达到 O(1)时间复杂度;列表list的pop(0)方法时间复杂度为O(N)。
        if not root: 
            return []
        res, queue = [], collections.deque()
        queue.append(root)
        while queue:
            tmp = []
            for _ in range(len(queue)):
                node = queue.popleft()
                tmp.append(node.val)
                if node.left: 
                    queue.append(node.left)
                if node.right: 
                    queue.append(node.right)
            res.append(tmp)
        return res

剑指 Offer 39. 数组中出现次数超过一半的数字

1、概念法

        # 数组中有一个数字出现的次数超过数组长度的一半,将数组排序,在中间的数必然是需要找到的这个数
        nums.sort()
        n = len(nums) // 2
        return nums[n]

2、摩尔投票法

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        # 摩尔投票法
        votes = 0
        for num in nums:
            if votes == 0: # 如果现在票数为0,则则假设当前数字 num 是众数
                x = num
            if num == x: # 如果现在遍历到的num数等于x,票数加1,否则减1
                votes +=1
            else:
                votes -= 1
        return x

        # 拓展:增加一个验证,验证x是否是数组中出现次数超过一半的数
        count = 0
        for num in nums:
            if num == x: 
                count += 1
        if count > len(nums) // 2:
            return x
        else:
            return False
            
            

剑指 Offer 40. 最小的k个数

1、使用内置函数

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        # 将数组排序,返回数组前k个数就是数组最小的k的数
        arr.sort()
        return arr[:k]

2、快速排序

class Solution:
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        # 将数组排序,返回数组前k个数就是数组最小的k的数
        # 快速排序quick_sort
        def quick_sort(arr, start, end):
            if start >= end: # 退出条件
                return 
            pivot = arr[start] # 选定首个元素作为基准值
            low, high = start, end # 初始化两个指针,分别指向数组首位和末位
            while low < high:
                while low < high and arr[high] >= pivot: # high指针从后向前寻找比基准值小的元素
                    high -= 1
                while low < high and arr[low] <= pivot: # low指针从前向后寻找比基准值小的元素
                    low += 1
                arr[low], arr[high] = arr[high], arr[low] # 交换low和high指针指向的元素
            # 退出循环,交换基准值和low指针指向的元素,完成基准值左边的元素比基准值小,右边的元素比基准值大
            arr[start], arr[low] = arr[low], arr[start]

            quick_sort(arr, start, low - 1)
            quick_sort(arr, low + 1, end)
        quick_sort(arr, 0, len(arr) - 1)
        return arr[:k]

剑指 Offer 42. 连续子数组的最大和

1、动态规划

class Solution:
    def maxSubArray(self, nums: List[int]) -> int:
        curSum =0 # 当前数组的和
        res = nums[0] # 初始化res=nums[0],即res是以nums[0]结尾的连续子数组最大和
        for num in nums: # 开始遍历数组中的每个数字
            if curSum > 0: # 如果当前数组和大于0,则加上此次是num
                curSum += num
            else: # 如果当前数组和小于等于0,则对于数组和是负贡献,使curSum=num
                curSum = num
            res = max(res, curSum) # 更新res,选择res和curSum中最大的值
        return res

 

剑指 Offer 50. 第一个只出现一次的字符

1、哈希表

class Solution:
    def firstUniqChar(self, s: str) -> str:
        dict = {}
        for i in s:
            if i not in dict:  # 如果i不在dict中,则添加i和true进入字典
                dict[i] = True
            else:  # 如果i在dict中,说明i之前出现过,修改i的值为False
                dict[i] = False
        for i in s:
            if dict[i]:  # 从头开始遍历s,如果i的值为true,则返回i
                return i
        return " "

 

剑指 Offer 52. 两个链表的第一个公共节点

1、双指针

# 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:
        '''
      
使用两个指针 node1,node2 分别指向两个链表 headA,headB 的头结点,然后同时分别逐结点遍历,
      当 node1 到达链表 headA 的末尾时,重新定位到链表 headB 的头结点;当 node2 到达链表 headB
      的末尾时,重新定位到链表 headA 的头结点。这样,当它们相遇时,所指向的结点就是第一个公共结点。
''' node1, node2 = headA, headB while node1 != node2: if node1: node1 = node1.next else: node1 = headB if node2: node2 = node2.next else: node2 = headA return node1

 

剑指 Offer 53 - I. 在排序数组中查找数字 I

1、二分法:排序数组中的搜索问题,首先想到二分法解决。

class Solution:
    def search(self, nums: [int], target: int) -> int:
        # target左边的数字小于target,右边的数字大于target,只要找到target左边及右边数字的索引(left、right),则right - left - 1就是target的个数
        # 搜索右边界 right
        i, j = 0, len(nums) - 1
        while i <= j:
            m = (i + j) // 2
            if nums[m] <= target:  # 如果m指向的元素小于等于target,说明有边界在[m+1,j]中,使i=m+1
                i = m + 1
            else: # 如果m指向的元素大于target,说明有边界在[i,m_1]中,使j=m-1
                j = m - 1
        # 退出循环时,有i>j,i指向右边界
        right = i
        # 若【排序】数组中无 target ,则提前返回,因为退出循环时,i指向target右边的值,j指向target值,如果j不等于target就表明数组中没有target
        if j >= 0 and nums[j] != target: 
            return 0
        # 搜索左边界 left
        i = 0 # 此时j指向target值中最右边的一个,所以找左边界只重新初始化i=0
        while i <= j:
            m = (i + j) // 2
            if nums[m] < target: # 如果m指向的值小于target,左边界在[m+1,j]中,使i=m+1
                i = m + 1
            else: # 如果m指向的值大于等于target,左边界在[i,m-1]中,使j=m+1
                j = m - 1
        # 退出循环时,有i>j,j指向左边界,i指向target    
        left = j
        return right - left - 1

2、暴力解法

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        # 暴力
        count = 0
        for i in nums:
            if i > target:
                return count
            if i == target:
                count += 1
        return count

 

剑指 Offer 53 - II. 0~n-1中缺失的数字

 1、二分法

class Solution:
    def missingNumber(self, nums: List[int]) -> int:
        # 遇到排序数组查问问题,优先使用二分法
        left, right = 0, len(nums) - 1 
        while left <= right:
            mid = (left + right) // 2
            if nums[mid] == mid:
                left = mid + 1
            else:
                right = mid - 1
        return left

 

剑指 Offer 54. 二叉搜索树的第k大节点

1、倒中序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        # 二叉搜索树的中序遍历为递增序列.
        stack = []
        # 打印中序遍历的倒序
        def dfs(root):
            if not root: 
                return
            dfs(root.right)  #
            stack.append(root.val) #
            dfs(root.left) #
        dfs(root)
        return stack[k - 1]

        

2、倒中序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def kthLargest(self, root: TreeNode, k: int) -> int:
        # 二叉搜索树的中序遍历为递增序列,中序遍历:左-根-右;如果我们求出此树中序遍历的倒序,即右-根-左,第k个节点就是第k大的节点
        def dfs(cur):
            if not cur:
                return
            dfs(cur.right)
            # 题目中存在限制:1 ≤ k ≤ 二叉搜索树元素个数
            self.k -= 1
            if self.k == 0: # 如果k=0,即已经遍历到第k个数了,直接将该节点的值辅助给res
                self.res = cur.val
            dfs(cur.left)

        self.k = k
        dfs(root)
        return self.res

剑指 Offer 55 - I. 二叉树的深度

1、层次遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        '''
        树的遍历方式总体分为两类:深度优先搜索(DFS)、广度优先搜索(BFS);
        常见的 DFS : 先序遍历、中序遍历、后序遍历;常见的 BFS : 层序遍历(即按层遍历)。
        '''
        # 层次遍历,使用队列queue
        if not root:
            return 0
        queue, res = [root], 0
        while queue:
            res += 1
            for _ in range(len(queue)):
                cur_node = queue.pop(0)
                if cur_node.left:
                    queue.append(cur_node.left)
                if cur_node.right:
                    queue.append(cur_node.right)
        return res

2、后序遍历

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def maxDepth(self, root: TreeNode) -> int:
        '''
        树的遍历方式总体分为两类:深度优先搜索(DFS)、广度优先搜索(BFS);
        常见的 DFS : 先序遍历、中序遍历、后序遍历;常见的 BFS : 层序遍历(即按层遍历)。
        '''
        # 后序遍历,参考https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/solution/mian-shi-ti-55-i-er-cha-shu-de-shen-du-xian-xu-bia/中的动画
        if not root: 
            return 0
        return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

剑指 Offer 55 - II. 平衡二叉树

1、后序遍历 + 剪枝 (从底至顶)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        def recur(root):
            if not root: 
                return 0
            left = recur(root.left) # 先判断以root的左儿子为根节点的左子树是否是平衡二叉树,是则返回左子树的深度,否则返回-1
            if left == -1:  # 如果left的返回值为-1,则表明左子树不是平衡二叉树,提前返回-1,不需要再进行下面的步骤
                return -1
            right = recur(root.right)
            if right == -1: 
                return -1
            # 返回树的深度或-1
            if abs(left - right) <= 1:
                return max(left, right) + 1
            else:
                return -1

        return recur(root) != -1

           

2、先序遍历 + 判断深度 (从顶至底)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def isBalanced(self, root: TreeNode) -> bool:
        if not root: 
            return True
        '''
        abs(self.depth(root.left) - self.depth(root.right)) <= 1:以root为根节点的子树是不是平衡树
        self.isBalanced(root.left):以root的左儿子为根节点的子树是不是平衡树
        self.isBalanced(root.right):以root的右儿子为根节点的子树是不是平衡树
        '''
        return abs(self.depth(root.left) - self.depth(root.right)) <= 1 and \
            self.isBalanced(root.left) and self.isBalanced(root.right)

    # 求以root为根节点的子树的深度
    def depth(self, root):
        if not root: 
            return 0
        return max(self.depth(root.left), self.depth(root.right)) + 1

剑指 Offer 57. 和为s的两个数字

1、双指针

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        # 双指针
        i, j = 0, len(nums) - 1
        while i < j:
            s = nums[i] + nums[j] # 记s为双指针指向的两个元素的和
            if s > target: # s大于target,则j向前移位
                j -= 1
            elif s < target: # s小于target,则i向后移位
                i += 1
            else:
                return nums[i], nums[j]
        return []

 

剑指 Offer 57 - II. 和为s的连续正数序列

1、滑动窗口

class Solution:
    def findContinuousSequence(self, target: int) -> List[List[int]]:
        # 滑动窗口(1、2、3、4、5、6、7、8、9...)
        i, j, s = 1, 2, 3 # 初始化一个左边界、一个右边界、一个元素和
        res =  [] # 结果列表
        while i < j:
            if s == target: # 如果s与target相等,将此时的元素组成列表后再添加到res中
                arr = list(range(i, j + 1))
                res.append(arr)
                # 移动左边界
                s -= i 
                i += 1
            elif s > target: # 移动左边界,减少窗口的大小,进而减少窗口中的数字,使s减小
                s -= i
                i += 1
            else: # 移动右边界,增大窗口大小,进而增加窗口中的数字,使s增大
                j += 1
                s += j
        return res
            

剑指 Offer 58 - I. 翻转单词顺序

1、双指针

class Solution:
    def reverseWords(self, s: str) -> str:
        # 采用双指针,i,j初始均指向最后一个元素
        s = s.strip() # 删除字符串前后空格
        i = j = len(s) - 1
        res = []
        while i >= 0:
            # 跳出循环后,i指向字符串倒数第一个空格
            while i >= 0 and s[i] != ' ':
                i -= 1
            # 此时i指向空格,则将第i+1个到第j个元素加到res([i+1:j+1]:左闭右开)
            res.append(s[i + 1: j + 1])
            while s[i] == ' ': # 跳过字符串中间的空格
                i -= 1
            # 跳出循环后,i指向下一个单词的字母or标点,使j=i
            j = i
        return ' '.join(res) # 用空格拼接并返回

 

剑指 Offer 58 - II. 左旋转字符串

1、字符串切片

class Solution:
    def reverseLeftWords(self, s: str, n: int) -> str:
        return s[n:] + s[:n]

 

剑指 Offer 61. 扑克牌中的顺子

1、集合+遍历

class Solution:
    def isStraight(self, nums: List[int]) -> bool:
        '''
        根据题意,此 55 张牌是顺子的 充分条件 如下:
        除大小王外,所有牌 无重复 ;
        设此 55 张牌中最大的牌为 max,最小的牌为 min(大小王除外),则需满足:
        max - min < 5
        '''
        repeat = set()
        # 初始化最大最小值
        max_ = 0 
        min_ = 13
        for num in nums:
            if num == 0: # 表示遇到大小王
                continue
            max_ = max(max_, num)
            min_ = min(min_, num)
            if num in repeat: # 判断集合 Set是否已经有该num
                return False
            repeat.add(num) # 将遍历到的num加入到集合中
        return max_ - min_ < 5

2、排序+遍历

class Solution:
    def isStraight(self, nums: List[int]) -> bool:
        joker = 0
        nums.sort() # 数组排序
        for i in range(4): # 遍历到倒数第4个元素
            if nums[i] == 0: 
                joker += 1 # 统计大小王数量
            elif nums[i] == nums[i + 1]: 
                return False # 若有重复,提前返回 false
        return nums[4] - nums[joker] < 5 # 最大牌 - 最小牌 < 5 则可构成顺子

剑指 Offer 62. 圆圈中最后剩下的数字

1、本题是著名的 “约瑟夫环” 问题,可使用 动态规划 解决

class Solution:
    def lastRemaining(self, n: int, m: int) -> int:
        x = 0
        for i in range(2, n + 1):
            x = (x + m) % i
        return x

剑指 Offer 65. 不用加减乘除做加法

1、位运算

class Solution:
    def add(self, a: int, b: int) -> int:
        x = 0xffffffff
        a, b = a & x, b & x
        while b != 0:
            a, b = (a ^ b), (a & b) << 1 & x
        return a if a <= 0x7fffffff else ~(a ^ x)

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

1、迭代

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        while root:
            if root.val < p.val and root.val < q.val: # p,q 都在 root 的右子树中
                root = root.right # 遍历至右子节点
            elif root.val > p.val and root.val > q.val: # p,q 都在 root 的左子树中
                root = root.left # 遍历至左子节点
            else: break
        return root

剑指 Offer 68 - II. 二叉树的最近公共祖先

1、递归

class Solution:
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root: # 如果树为空,直接返回
            return
        if root == p or root == q: # 如果 p和q中有等于 root的,那么它们的最近公共祖先即为root(一个节点也可以是它自己的祖先)
            return root
        left = self.lowestCommonAncestor(root.left, p, q) # 递归遍历左子树,只要在左子树中找到了p或q,则先找到谁就返回谁
        right = self.lowestCommonAncestor(root.right, p, q) # 递归遍历右子树,只要在右子树中找到了p或q,则先找到谁就返回谁
        if not left and not right: 
            return # 1.当 left 和 right 同时为空 :说明 root 的左 / 右子树中都不包含 p,q
        if left and right:# 2.当 left 和 right 同时不为空 :说明 p, q 分列在 root 的 异侧 (分别在 左 / 右子树),因此 root 为最近公共祖先,返回 root
            return root
        if not left: 
            return right # 3.当 left 为空 ,right 不为空 :p,q 都不在 root 的左子树中,直接返回 right 。
        if not right: 
            return left # 4.当 right 为空 ,left 不为空 :p,q 都不在 root 的右子树中,直接返回 left 。

 

posted @ 2021-10-14 22:49  钟胜一  阅读(108)  评论(0)    收藏  举报