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

浙公网安备 33010602011771号