这里是剑指offer的一些笔记,有几道困难题没做,以后会不上,题解是按照做题序号来的。
- 数组中重复的数字
新建一个标记数组记录每个数字出现的次数。
暴力搜索
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
flags = [0]* len(nums)
for i in range(len(nums)):
flags[nums[i]] += 1
for i in range(len(flags)):
if flags[i] > 1:
return i
改进算法(待定)
- 二维数组中的查找
暴力搜索非常简单,但是复杂度较高,这里可以选择先确定范围再查找,这样可以降低时间复杂度。
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
for row in matrix:
if target in row:
return True
return False
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
i = -2
j = -1
for index, row in enumerate(matrix):
if target >= row[0]:
i = index
if target <= row[-1]:
j = index
if i == -2 or j == -1:
return False
else:
for q in range(0, len(matrix)):
if target in matrix[q]:
return True
return False
- 字符串替换空格
用一个列表存储修改后的字符串,之后再转换成字符串即可。
class Solution:
def replaceSpace(self, s: str) -> str:
temp = list(s)
res = ''
for i in range(len(temp)):
if temp[i] == ' ':
temp[i] = r'%20'
res = res + temp[i]
return res
- 从头到尾打印链表
从前往后遍历链表,添加到一个列表中即可。
# 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]:
res = []
start = head
while(start != None):
res.insert(0, start.val)
start = start.next
return res
- 重建二叉树
这种序列不包含叶子节点信息,需要至少两个遍历方式才能确定整个树。
- 前序、中序、后序三种遍历顺序,需要前序或后序再加上一个中序才能确定一颗二叉树。我们可以先通过前序来找到根节点,再通过根节点将中序序列一分为二,重复这一操作即可。建立一颗二叉树,我们需要知道某一个节点和其左右子节点,再通过递归操作就可以建立一颗完整的二叉树。
- 设置递归,我们需要想好递归函数中的参数传递,这里我们设置三个参数:第一个索引是在前序序列中的索引,用来表示根节点,后两个参数分别是中序序列的索引,用来确定子树的范围。
- 递归终止的条件:左边界> 右边界
- 这里有一个小的技巧,可以事先建立一个字典,方便快速查找根节点在中序序列中的位置。
- 这里有一个需要注意的点:假设前序序列中的第i个点,那么以这点为左右子树的根节点分别是多少?左子树的根节点是第i+1个点,而右子树的点是i+整个左子树的节点数量。
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
dict = {}
for i, node in enumerate(inorder):
if node not in dict:
dict[node] = i
def recurrence(index, left, right):
if left > right:
return None
root = TreeNode()
root.val = preorder[index]
root_index = dict[root.val]
root.left = recurrence(index + 1, left, root_index - 1)
root.right = recurrence(index + root_index - left + 1, root_index + 1, right)
return root
root = recurrence(0, 0, len(inorder) - 1)
return root
- 用两个栈实现队列
两个栈来模拟队列,栈A用来存储数据,栈B用来交换顺序,添加数据直接将数据进栈到A中,删除数据的时候,就需要将A中的数据全部出栈到B中,这样顺序就颠倒了,然后再删除B的栈顶,再将数据入栈到A中即可。
class CQueue:
def __init__(self):
self.stack_A = []
self.stack_B = []
def appendTail(self, value: int) -> None:
self.stack_A.append(value)
def deleteHead(self) -> int:
if not self.stack_A:
return -1
while(self.stack_A):
self.stack_B.append(self.stack_A.pop())
num = self.stack_B.pop()
while(self.stack_B):
self.stack_A.append(self.stack_B.pop())
return num
# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()
上面的思路是最原始的,可以进行一些优化。首先判断栈B是否为空,不为空就直接出栈,这是优先级最高的,因为栈B存储之前从A中出栈的数据;第二再判断栈A是否为空,不为空的话就将A中的数据出栈到B中,最后将B中栈顶出栈即可。注意这个时候不需要将B中的数据再倒回到A中,这样可以减少相应的复杂度。
class CQueue:
def __init__(self):
self.stack_A = []
self.stack_B = []
def appendTail(self, value: int) -> None:
self.stack_A.append(value)
def deleteHead(self) -> int:
if self.stack_B:
return self.stack_B.pop()
if not self.stack_A:
return -1
while(self.stack_A):
self.stack_B.append(self.stack_A.pop())
return self.stack_B.pop()
10-1. 斐波拉契数列
正常的取余操作即可
class Solution:
def fib(self, n: int) -> int:
a = 0
b = 1
c = 0
num = int(1e9 + 7)
if n == 0:
return 0
if n == 1:
return 1
for i in range(n - 1):
c = (a % num + b % num) % num
a = b % num
b = c % num
return c
10-2. 青蛙跳台阶的问题
与上一题类似,但是初始项的值不一样
class Solution:
def numWays(self, n: int) -> int:
a = 1
b = 1
c = 0
num = int(1e9 + 7)
if n == 0:
return 1
if n == 1:
return 1
for i in range(n - 1):
c = (a % num + b % num) % num
a = b % num
b = c % num
return c
- 旋转数组的最小数字
旋转数组局部是递增的,貌似有更好的方法:二分。
class Solution:
def minArray(self, numbers: List[int]) -> int:
a = numbers[0]
for i in range(1, len(numbers)):
if numbers[i] < a:
return numbers[i]
return a
- 矩阵中的路径
典型的DFS,由于可能存在多种路径,因此要注意同一个分支上走过的点不能再走,但是不同的分支上互不影响。这里我们不知道开始的起点是哪,因此每一个点都需要当成起点来进行搜索。DFS只要遍历到深度为K即可,K是指定路径的长度,中间如果对应的点不相同即可终止递归。
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
m = len(board)
n = len(board[0])
flags = [[0]*n for i in range(m)]
def dfs(i, j, k):
if word[k] != board[i][j]:
return False
if k == len(word) - 1:
return True
for deltx, delty in [(-1, 0), (1, 0), (0, 1), (0, -1)]:
if i + deltx >= 0 and i + deltx <= m-1 and j + delty>=0 and j + delty<=n-1:
if flags[i + deltx][j + delty] == 0:
flags[i + deltx][j + delty] = 1
res = dfs(i + deltx, j + delty, k+1)
if res:
return True
flags[i + deltx][j + delty] = 0
return False
for i in range(m):
for j in range(n):
flags[i][j] = 1
res = dfs(i, j, 0)
if res:
return True
flags[i][j] = 0
return False
- 机器人的运动范围
机器人的运动范围,类似于以前的迷宫问题,一般有两种解法:DFS和BFS。这个不需要保存路径,只需要记录能走的节点数量即可。
方法一:DFS,注意走过的节点不用再走了,不然会陷入死循环。
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def count(num):
sum = 0
while(num):
sum += num % 10
num = num // 10
return sum
flags = []
for i in range(m):
flags.append([0]*n)
def dfs(x, y):
if x < 0 or x > m-1 or y < 0 or y > n-1:
return
if count(x) + count(y) > k:
return
if flags[x][y] == 0:
flags[x][y] = 1
dfs(x+1, y)
dfs(x-1, y)
dfs(x, y+1)
dfs(x, y-1)
dfs(0, 0)
res = 0
for i in range(m):
for j in range(n):
res += flags[i][j]
return res
方法二:BFS,这里需要注意一下,如果每次只对队首进行标记,结果会超时,因为以后可能会重复访问扩展节点,应该改成对扩展节点进行标记。
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
def count(num):
sum = 0
while(num):
sum += num % 10
num = num // 10
return sum
flags = []
for i in range(m):
flags.append([0]*n)
queue = [(0, 0)]
flags[0][0] = 1
while(queue):
#flags[queue[0][0]][queue[0][1]] = 1
for dir in [(-1, 0), (1, 0), (0, 1), (0, -1)]:
temp = (queue[0][0] + dir[0], queue[0][1] + dir[1])
if temp[0] >=0 and temp[0] <= m-1 and temp[1] >= 0 and temp[1] <= n-1:
if flags[temp[0]][temp[1]] == 0:
if count(temp[0]) + count(temp[1]) <=k:
queue.append(temp)
flags[temp[0]][temp[1]] = 1
queue.pop(0)
res = 0
for i in range(m):
for j in range(n):
res += flags[i][j]
return res
14-1. 剪绳子
需要注意的是,第一次的话绳子是一定需要拆分的,比如一个长度为2的绳子需要拆分成1和1,尽管长度变小了,但是后面的绳子就不能这样了,需要比较一下,如果结果变小了就不需要拆分。
- 动态规划,状态转移方程需要自己好好想一想,dp[i]表示长度为i的绳子所能取得的最大值,状态转移方程:
max里的两种可能分别表示保持原状态不变和进行剪绳子操作。详细讲解可以参考:
https://leetcode-cn.com/problems/jian-sheng-zi-lcof/solution/xiang-jie-bao-li-di-gui-ji-yi-hua-ji-zhu-dong-tai-/- 数学方法,可以用数学方法来证明将绳子剪成最接近3的小段,最终的乘积最大,然后注意一下边界条件即可。
- 这个题和力扣343整数拆分基本上一样的,这里我使用了DFS加上剪枝的写法,事件复杂度也可以达到最优的,为,不剪枝的话,复杂度为O(n!)。DIct[n]表示n能够拆分出来的最大值,由于可能会有多条路径会共用这样一个状态,因此我们需要将其存储起来复用。带剪枝的DFS的复杂度等于字典的长度*分支数量,这里的Dict最多存储n个状态,而第n个状态需要在前n-1个状态里比较得到,因此最高的时间复杂度就是。
方法一:
class Solution:
def cuttingRope(self, n: int) -> int:
dp = [0]*(n + 1)
dp[2] = 1
for i in range(3, n + 1):
for j in range(1, i):
dp[i] = max(dp[i], max(dp[j]*(i-j), j*(i-j)))
return dp[n]
方法二:
class Solution:
def cuttingRope(self, n: int) -> int:
if n == 2:
return 1
if n == 3:
return 2
res = 1
num = int(n / 3)
res = 3**num
if n - 3*num == 1:
res = int(res / 3) *4
elif n -3*num == 2:
res = res*2
return res
另一个题的写法:
class Solution:
def integerBreak(self, n: int) -> int:
def dfs(n, Dict):
if n == 1:
return 1
if n in Dict:
return Dict[n]
ans = -inf
for i in range(1, n):
ans = max(ans, i*(max(dfs(n-i, Dict), n-i)))
Dict[n] = ans
return ans
return dfs(n , {})
14-2. 剪绳子2
这题在前面的基础上增加了大数取模的要求,在python上是不用考虑这个限制的,如果是其他语言就需要考虑语言的限制了,在乘积的过程中进行取模操作。
class Solution:
def cuttingRope(self, n: int) -> int:
dp = [0]*(n + 1)
dp[2] = 1
for i in range(3, n + 1):
for j in range(1, i):
dp[i] = max(dp[i], max(dp[j]*(i-j), j*(i-j)))
return dp[n] % 1000000007
- 二进制中1的个数
class Solution:
def hammingWeight(self, n: int) -> int:
temp = n
res = 0
while(temp >= 1):
res += temp % 2
temp = int(temp / 2)
return res
- 数值的整数次方
快速幂解法,最简单的做法使用二分来做。注意测试样例还有负数的情况。
class Solution:
def myPow(self, x: float, n: int) -> float:
if n < 0:
x = 1/x
n = -1*n
temp = x
res = 1
while(n):
if n % 2 == 0:
temp = temp*temp
n = n // 2
else:
res = res*temp
n = n - 1
return res
- 打印从1到最大的n位数
常规解法,还有一种可能是大数解法(待定)
class Solution:
def printNumbers(self, n: int) -> List[int]:
temp = 10**n
res = []
for i in range(1, temp):
res.append(i)
return res
- 删除链表的节点
正常的链表删除操作
# 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:
temp = head
if temp.val == val:
return temp.next
while(temp.next != None):
if temp.next.val == val:
temp.next = temp.next.next
return head
temp = temp.next
- 正则表达式匹配
这题完全没有思路
- 表示数值的字符串
自动机,也不会写
- 调整数组顺序使奇数位于偶数前面
题解里面使用的是什么双指针,我觉得没有必要,这题待定。
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
even_nums = []
odd_nums = []
for num in nums:
if num % 2 == 0:
even_nums.append(num)
else:
odd_nums.append(num)
return odd_nums + even_nums
- 链表中倒数第k个节点
题解里面使用的同样是双指针,这题待定。
# 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:
temp = head
res = []
while(temp!= None):
res.append(temp)
temp = temp.next
return res[-k]
- 反转链表
经典题目,不用多说
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
start = head
NewHead = None
while(start != None):
temp = start.next
start.next = NewHead
NewHead = start
start = temp
return NewHead
- 合并两个排序的链表
注意这两个链表没有头节点,利用两个链表的递增性质即可
# 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:
if l1 == None:
return l2
elif l2 == None:
return l1
if l1.val >= l2.val:
head3 = l3 = l2
head1 = l1
head2 = l2.next
else:
head3 = l3 = l1
head1 = l1.next
head2 = l2
while(head1 != None and head2 != None):
if head1.val <= head2.val:
l3.next = head1
l3 = head1
head1 = head1.next
else:
l3.next = head2
l3 = head2
head2 = head2.next
if head1 !=None:
l3.next = head1
elif head2 != None:
l3.next = head2
return head3
- 树的子结构
- 先按照先序遍历的方式来遍历整个树的每一个节点。
- 然后判断每一个以当前节点为根节点的子树是否与该子树相同。
这一题的关键就是要理清楚递归关系,关于这一点官方题解讲的很清楚:
https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/solution/mian-shi-ti-26-shu-de-zi-jie-gou-xian-xu-bian-li-p/
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
def recursion(A, B):
if not B:
return True
if not A:
return False
if A.val != B.val:
return False
return recursion(A.left, B.left) and recursion(A.right, B.right)
if A and B:
return recursion(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
else:
return False
- 二叉树的镜像
遍历二叉树,交换左右子树即可。
# 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:
NewRoot = TreeNode()
if root != None:
NewRoot.val = root.val
NewRoot.left = self.mirrorTree(root.right)
NewRoot.right = self.mirrorTree(root.left)
else:
return None
return NewRoot
- 对称的二叉树
这里提供两种解法,一种是最笨的解法,按照对称的方式建立一颗树,然后对这两棵树以相同的方式进行遍历,比较结果即可,另一种是递归的算法(判断两个对称点是否相等)。
# 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:
NewRoot = self.mirrorTree(root)
res1 = self.readTree(root)
res2 = self.readTree(NewRoot)
if res1 == res2:
return True
else:
return False
def mirrorTree(self, root: TreeNode) -> TreeNode:
NewRoot = TreeNode()
if root != None:
NewRoot.val = root.val
NewRoot.left = self.mirrorTree(root.right)
NewRoot.right = self.mirrorTree(root.left)
else:
return None
return NewRoot
def readTree(self, root: TreeNode) -> List:
res = []
if root != None:
res += [root.val]
res += self.readTree(root.left)
res += self.readTree(root.right)
else:
return [None]
return res
# 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:
if not root:
return True
else:
return self.judge(root.left, root.right)
def judge(self, left: TreeNode, right: TreeNode) -> bool:
if not left and not right:
return True
if not left or not right:
return False
if left.val != right.val:
return False
else:
return self.judge(left.left, right.right) and self.judge(left.right, right.left)
- 顺时针打印矩阵
正常的思维,上下左右四个方向,一圈挨着打印,外圈打印完了就开始打印内圈,最简单的情况就是这个矩阵是一个正方形,一圈打印完后,边长就会减少2,这样我们可以知道一共需要打印多少圈,这里给出正方形的代码,根据边长分奇数和偶数,写这个代码的时候,先写最外圈的情况,然后再加上偏移量:
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if len(matrix) % 2 == 0:
return self.process(matrix)
else:
new_matrix = self.process(matrix)
new_matrix.append(matrix[int(len(matrix) / 2) ][int(len(matrix) / 2) ])
return new_matrix
def process (self, matrix: List[List[int]]) -> List[int]:
res = []
length = len(matrix)
delta = 0
while(delta <= int(length / 2)):
i = 0 + delta
while(i < length - 1 - delta):
res.append(matrix[0 + delta][i])
i = i + 1
i = 0 + delta
while(i < length - 1 - delta):
res.append(matrix[i][length - 1 - delta])
i = i + 1
i = length - 1 - delta
while(i > delta ):
res.append(matrix[length - 1 - delta][i])
i = i - 1
i = length - 1 - delta
while(i > delta):
res.append(matrix[i][0 + delta])
i = i - 1
delta += 1
return res
如果不是正方形,就不能按照上面的代码了,可以根据矩阵的长宽来设定边界,每打印一个方向,相应的边界就会减少,最终程序种子会有4中情况,因为到达最后一个点,可能是从上到下、从下到上,从左到右,从右到左这四种情况。
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if len(matrix) == 0:
return []
l, r, t, b = 0, len(matrix[0]) - 1, 0, len(matrix) - 1
res = []
while(True):
for i in range(l, r + 1):
res.append(matrix[t][i])
t = t + 1
if t > b :
break
for i in range(t, b + 1):
res.append(matrix[i][r])
r = r - 1
if l > r:
break
for i in range(r, l - 1, -1):
res.append(matrix[b][i])
b = b - 1
if t > b:
break
for i in range(b, t - 1, -1):
res.append(matrix[i][l])
l = l + 1
if l > r:
break
return res
- 包含min函数的栈
只能构造一个递减的辅助栈,当该辅助栈为空的时候或者要进栈的元素不大于该辅助栈的栈顶元素的时候进栈(注意这里是不大于);当主栈出栈的时候,如果主栈的出栈元素与辅助栈顶元素相同,则辅助栈一同出栈,这是为了保证一致性。
class MinStack:
def __init__(self):
"""
initialize your data structure here.
"""
self.stack = []
self.stack_A = []
def push(self, x: int) -> None:
self.stack.append(x)
if len(self.stack_A) == 0 or x <= self.stack_A[-1]:
self.stack_A.append(x)
def pop(self) -> None:
if self.stack[-1] == self.stack_A[-1]:
del(self.stack_A[-1])
del(self.stack[-1])
def top(self) -> int:
return self.stack[-1]
def min(self) -> int:
return self.stack_A[-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()
- 栈的压入、弹出序列
按照出栈的顺序进行模拟。类似于贪心的思想,应该算是一道比较简单的题。
class Solution:
def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
stack = []
end = 0
for num in pushed:
stack.append(num)
while(stack and stack[-1] == popped[0]):
stack.pop()
popped.pop(0)
end += 1
return end == len(pushed)
32-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 levelOrder(self, root: TreeNode) -> List[int]:
if not root:
return []
queue = [root]
res = []
while(queue):
res.append(queue[0].val)
if queue[0].left:
queue.append(queue[0].left)
if queue[0].right:
queue.append(queue[0].right)
del queue[0]
return res
32-2. 从上到下打印二叉树 II
这题与上一题相比,添加了一个条件,要求在层序遍历的同时,还要找到树中每一层的节点。找到每一层的节点有两种思路: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 levelOrder(self, root: TreeNode) -> List[List[int]]:
if not root:
return []
queue = [root]
res = []
while(queue):
temp = []
length = len(queue)
for _ in range(length):
temp.append(queue[0].val)
if queue[0].left:
queue.append(queue[0].left)
if queue[0].right:
queue.append(queue[0].right)
del queue[0]
res.append(temp)
return res
32-3. 从上到下打印二叉树 III
相比前面一题,增加了交换顺序的要求,可以用标记记录一下奇数层和偶数层。
# 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]]:
if not root:
return []
queue = [root]
res = []
i = 0
while(queue):
length = len(queue)
temp = []
for _ in range(length):
temp.append(queue[0].val)
if queue[0].left:
queue.append(queue[0].left)
if queue[0].right:
queue.append(queue[0].right)
del queue[0]
if i % 2 == 1:
temp.reverse()
res.append(temp)
i += 1
return res
- 二叉搜索树的后序遍历序列
判断一个序列是否属于二叉树的后序遍历序列。
可以通过递归分治的方法来判断,后序遍历序列我们可以确定根节点,再通过根节点,将序列一分为二,再来判断左子树、根节点、右子树是否满足二叉搜索树的要求。在划分左右子树的时候需要注意一下,从前往后遍历,找到第一个大于根节点的节点,这样就可以将左右子树划分出来了,这种方式保证了左子树一定小于根节点,因此接下来我们只需要保证右子树大于根节点即可。
class Solution:
def verifyPostorder(self, postorder: List[int]) -> bool:
def recursion(nodes):
if len(nodes) <= 1:
return True
rootNode = nodes[-1]
for i in range(len(nodes)): #注意这里不是len(nodes) - 1,这是可能会出现左子树全小于根节点的情况,这样会导致后面的程序出错
if nodes[i] > rootNode:
break
for j in range(i, len(nodes) - 1):
if nodes[j] < rootNode:
return False
if not recursion(nodes[0:i]):
return False
if not recursion(nodes[i:-1]):
return False
return True
if recursion(postorder):
return True
else:
return False
- 二叉树中和为某一值的路径
- 用BFS遍历到每一个节点,然后找到每一个节点的父节点,同时找到叶子节点,这样就能够找到每一条路径了。
- 利用DFS来遍历整个二叉树,这种情况需要注意如何保存路径,我们可以考虑使用一个全局数组来存储路径,但是由于每一条路径长度不一致,这种方法不合适。所以我们得考虑用参数传递的方法来保存路径。
方法一:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
if not root:
return []
parents = {root: None}
leafs = []
queue = [root]
while(queue):
if queue[0].left:
queue.append(queue[0].left)
parents[queue[0].left] = queue[0]
if queue[0].right:
queue.append(queue[0].right)
parents[queue[0].right] = queue[0]
if not queue[0].left and not queue[0].right:
leafs.append(queue[0])
queue.pop(0)
def sum_list(nums):
res = 0
for num in nums:
res += num
return res
ans = []
for leaf in leafs:
s = [leaf.val]
temp = leaf
while(parents[temp]):
s.append(parents[temp].val)
temp = parents[temp]
if sum_list(s) == sum:
s.reverse()
ans.append(s)
return ans
方法二:
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
if not root:
return []
def sum_list(nums):
res = 0
for num in nums:
res += num
return res
ans = []
def dfs(root, route):
if not root.left and not root.right:
if sum_list(route) == sum:
ans.append(route)
return
if root.left:
dfs(root.left, route + [root.left.val])
if root.right:
dfs(root.right, route + [root.right.val])
dfs(root, [root.val])
return ans
-
复杂链表的复制
这是链表的复制操作,很经典的考题。
- 直观看上去,这是典型的深拷贝,可以直接利用python的深拷贝函数。
- 拷贝链表,就需要开辟新的空间来存储这些节点,节点的前后关系是比较好确定的,但是本题中的链表节点除了next指针,还有一个random指针,这个指针是随机指向链表中的一个节点的,因此需要专门使用数据结构来存储这一关系,我们可以使用字典来存储这一关系, 通过字典将链表从前往后依次复制节点,通过字典我们可以将这两个链表的节点一一对应起来,最后再将这些节点连接起来即可。
- 扩展链表,例如将1-2-3转换成1-1-2-2-3-3,然后再将链表分解,由于链表的格式比较规整,所以实现起来较容易。这种方法涉及到链表很多操作,是面试官喜欢考察的一种方法。
方法一:
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
import copy
return copy.deepcopy(head)
方法二:
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
Linklistmap = {}
start = head
while(start):
Linklistmap[start] = Node(start.val, None, None)
start = start.next
Linklistmap[None] = None
start = head
while(start):
Linklistmap[start].next = Linklistmap[start.next]
Linklistmap[start].random = Linklistmap[start.random]
start = start.next
return Linklistmap[head]
方法三:
"""
# Definition for a Node.
class Node:
def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
self.val = int(x)
self.next = next
self.random = random
"""
class Solution:
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return None
start = head
while(start):
temp = start.next
newNode = Node(start.val)
newNode.next = temp
start.next = newNode
start = temp
start = head
while(start):
if start.random:
start.next.random = start.random.next
start = start.next.next
start = head
newHead = head.next
while(start):
temp = start.next
if temp:
start.next = temp.next
start = temp
return newHead
- 二叉搜索树与双向链表
将二叉搜索树转换成双向链表,要求不能建立新的节点,由于链表是按照大小的先后顺序来的,因此我们需要对二叉搜索树进行中序遍历。在具体的实现过程中,我们需要保存当前节点的前一个节点和中序遍历的当前节点。
"""
# Definition for a Node.
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
"""
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
self.pre = None
self.head = None
def dfs(cur):
if not cur:
return
dfs(cur.left)
if not self.pre:
self.head = cur
else:
self.pre.right = cur
cur.left = self.pre
self.pre = cur
dfs(cur.right)
dfs(root)
if not self.head:
return self.head
self.head.left = self.pre
self.pre.right = self.head
return self.head
- 序列化二叉树
直接用标准的中序遍历写法即可,维护一个队列即可。序列和反序列都可以用这种写法。
注意这种输入的序列包含整个树完整的信息是可以只用一种遍历方式来确定整个树的,遍历列表的时候是每3个一起遍历:自身节点和两个子节点。
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
def serialize(self, root):
if not root:
return '[]'
queue = [root]
res = []
while(queue):
temp = queue.pop(0)
if temp :
res.append(str(temp.val))
queue.append(temp.left)
queue.append(temp.right)
else:
res.append('null')
print('[' + ','.join(res) + ']')
return '[' + ','.join(res) + ']'
def deserialize(self, data):
if data == '[]':
return None
res = data[1:-1].split(',')
print(res)
root = TreeNode(int(res[0]))
queue = [root]
i = 1
while(queue):
temp = queue.pop(0)
if res[i] != 'null':
temp.left = TreeNode(int(res[i]))
queue.append(temp.left)
else:
temp.left = None
if res[i + 1] != 'null':
temp.right = TreeNode(int(res[i + 1]))
queue.append(temp.right)
else:
temp.right = None
i = i + 2
return root
# Your Codec object will be instantiated and called as such:
# codec = Codec()
# codec.deserialize(codec.serialize(root))
- 字符的排列
标准的全排列问题,最佳解法也是常用的DFS,具体的实现的时候需要注意用一个标记数组来记录走过的路径,因为同一个分支上走过的路径是不能再走的,而不同的分支却又是相互独立的,因此我们需要一个全局的标记数组来记录这些情况。在遍历的同时需要将路径保存下来,这是这题唯一需要注意的。
1.当递归函数遍历到树的末端的时候,函数结束运行,这里我们可以设置一个参数来记录遍历的深度。
- 由于我们需要记录遍历的路径,因此我们需要使用全局的数据来存储函数递归过程中产生的结果,注意这里使用的是固定长度的列表。
- 最后生成的全排列需要一个去重功能,我们可以直接使用set数据类型,然后最后转换成list即可。
class Solution:
def permutation(self, s: str) -> List[str]:
m = len(s)
flags = [0]*m
route = ['']*m
ans = set()
def dfs(num, count):
if count == num:
ans.add(''.join(route))
return
for i in range(num):
if flags[i] == 0:
route[count] = s[i]
flags[i] = 1
dfs(num, count + 1)
flags[i] = 0
dfs(m, 0)
return list(ans)
- 数组中出现次数超过一半的数字
- 最简单的想法,用空间来换时间,额外定义一个数组将对应大小的数字存储到这个数组对应的位置中,但是这个办法有一个问题,就是如果某个数字太大可能会出现无法存储的情况。要想处理这种情况,可以将数组换成字典,这样就不用考虑元素大小的问题了。
- 针对这个具体的问题有一个投机取巧的办法,因为题目中说有且只有一个数字出现的次数超过数组长度的一半,这样就可以先给数组排个序,然后再取中间的元素即可。
- 摩尔投票法:设定众数为1,非众数为-1,那么整个数组相加最终一定大于0,并且如果前n个数之和为0,那么众数一定存在于后面的数组中。算法实现起来也比较简单,如果投票数为0,则假设当前的数字为众数(前面的数字可以忽略),如果前后两个数字相同则counts加1,否则减1,最终返回前面假设的那个数字即可。
方法一:
class Solution:
def majorityElement(self, nums: List[int]) -> int:
temp = {}
for num in nums:
if num not in temp:
temp[num] = 1
else:
temp[num] += 1
if temp[num] >int(len(nums) / 2):
return num
方法二:
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[int(len(nums) / 2)]
方法三:
class Solution:
def majorityElement(self, nums: List[int]) -> int:
counts = 0
for num in nums:
if counts == 0:
x = num
if num == x:
counts += 1
else:
counts -= 1
return x
- 最小的k个数
就是简单的排序,需要手动实现快排算法、堆排序算法?,这个待定。
class Solution:
def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
arr.sort()
return arr[0:k]
- 数据流中的中位数
这题有多种解法。但是最好的应该建立一个大顶堆和小顶堆。这里要求保证两个堆的长度相差不超过1,其中大顶堆存储较小的那一半数字,小顶堆存储较大的那一半数字,这种方式能够保证在O(1)的时间内找到中位数,如果这两个堆的长度相同,那么取这两个堆的堆顶取平均值即可,如果不相等则取小顶堆的顶堆即可。
具体实现方面,由于python只有小顶堆,没有大顶堆,我们需要对数据进行取反操作操作来模拟大顶堆。在插入数据方面,如果两个堆的长度相等,则把当前数据插入到大顶堆,然后将堆顶元素POP加入到小顶堆中;如果两个堆的长度不相等,则把当前元素插入到小顶堆,然后将
堆顶元素POP加入到大顶堆中,这样的操作可以满足前面的要求。
解法一:(超时)
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.data = []
def addNum(self, num: int) -> None:
self.data.append(num)
def findMedian(self) -> float:
temp = sorted(self.data)
if len(temp) % 2 == 1:
return temp[len(temp) // 2]
else:
return (temp[len(temp) // 2] + temp[len(temp) // 2 - 1]) / 2
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
解法二:
from heapq import *
class MedianFinder:
def __init__(self):
"""
initialize your data structure here.
"""
self.A = [] #小顶堆
self.B = [] #大顶堆
def addNum(self, num: int) -> None:
if len(self.A) == len(self.B):
heappush(self.B, -num)
heappush(self.A, -heappop(self.B))
else:
heappush(self.A, num)
heappush(self.B, -heappop(self.A))
def findMedian(self) -> float:
if len(self.A) == len(self.B):
return (self.A[0] - self.B[0]) /2
else:
return self.A[0]
# Your MedianFinder object will be instantiated and called as such:
# obj = MedianFinder()
# obj.addNum(num)
# param_2 = obj.findMedian()
- 连续子数组的最大和
要求连续子数组的最大和,最简单的暴力搜索我就不说了,这道题应该是一个简单的动态规划,需要写出状态转移方程:
s[i]表示以第i个数结尾的最大子序列和,a[i]表示原数组里的第i个元素,注意s[i]的定义,既然s[i]表示以第i个数结尾的最大子序列和,那么s[i]是一定会包含a[i]的,但如果s[i-1]<0就可以将s[i-1]舍弃,最终整个数组的最大连续子数组和就应该在数组s里产生。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
s = 0
maxnum = -99999999
for num in nums:
if s > 0:
s += num
else:
s = num
maxnum = max(s, maxnum)
return maxnum
- 1~n整数中1出现的次数(待定)
直观上来看出现1的个数就是10的幂次方出现的次数。我个人感觉这题很麻烦,详情还是参考题解:https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/mian-shi-ti-43-1n-zheng-shu-zhong-1-chu-xian-de-2/
class Solution:
def countDigitOne(self, n: int) -> int:
high = n // 10
cur = n % 10
low = 0
digit = 1
res = 0
while(high != 0 or cur != 0):
if cur == 0:
res += high*digit
elif cur == 1:
res += high*digit + 1 + low
else:
res += (high + 1)*digit
low += cur*digit
cur = high % 10
high = high // 10
digit = digit *10
return res
- 数字序列中某一位的数字
首先我们需要找到这个数字所在的具体区间,找到区间之后再去找到区间内具体的数字,最后再找到具体的某一位。
关于具体的位数是有规律的:
,n表示数位,然后进行累加即可,当然我们还可以直接用错位相减的方法求和。
总的来说这种题很麻烦,需要自己找规律和调试。
class Solution:
def findNthDigit(self, n: int) -> int:
digit = 1
start = 1
count = 9*start*digit
while(n > count ):
n = n - count
digit += 1
start = start*10
count = 9*start*digit
num = start + (n - 1) // (digit)
return int(str(num)[ (n-1) % digit ])
- 把数组排成最小的数
应该是字符串比较大小。一种思路是自己实现字符串的比较算法,要注意0的问题,如果前面都相同,后面为0这个时候是后面包含0的字符串比较小;另一种办法就是通过这样的比较方法来确定两个字符串的排列关系。主要是注意一下python中比较函数的写法,这一点和C语言的区别挺大的。
class Solution:
def minNumber(self, nums: List[int]) -> str:
def compare(x, y):
a = x + y
b = y + x
if a < b:
return -1
elif a > b:
return 1
else:
return 0
res = sorted(list(map(str, nums)), key=cmp_to_key(compare))
return ''.join(res)
- 把数字翻译成字符串
相邻的两个数字有两种翻译的方法,可以将两个数字看成一个整体,或者分开翻译。所以这就是一个典型的动态规划问题。
状态转移方程:
在具体的实现上面一共有递归解法和非递归解法,但这两者的本质是一样的,都需要遍历到所有的状态。
解法一:
class Solution:
def translateNum(self, num: int) -> int:
def recursion(num):
if num <10:
return 1
temp = num % 100
if temp <= 9 or temp >= 26:
return recursion(num // 10)
else:
return recursion(num // 10) + recursion(num // 100)
ans = recursion(num)
return ans
解法二:
class Solution:
def translateNum(self, num: int) -> int:
temp = str(num)
if len(temp) == 1:
return 1
dp = [0]*len(temp)
dp[0] = 1
dp[-1] = 1
for i in range(1, len(temp)):
if int(temp[i-1:i+1]) > 9 and int(temp[i-1:i+1]) < 26:
dp[i] = dp[i-1] + dp[i-2]
else:
dp[i] = dp[i-1]
ans = dp[len(temp) - 1]
return ans
- 礼物的最大价值
最简单的动态规划,这个没啥好说的。
class Solution:
def maxValue(self, grid: List[List[int]]) -> int:
length = len(grid) + 2
width = len(grid[0]) + 2
A = [[0 for i in range(width)] for j in range(length)]
S = [[0 for i in range(width)] for j in range(length)]
for i in range(1, len(A) - 1):
for j in range(1, len(A[0]) - 1):
A[i][j] = grid[i-1][j-1]
for i in range(1, len(S)):
for j in range(1, len(S[0])):
S[i][j] = max(S[i-1][j], S[i][j-1]) + A[i][j]
return S[-2][-2]
- 最长不含重复字符的子字符串
比较直观的想法是维护一个双端队列,也就是滑动窗口,当队尾往后移动的时候,如果该元素不在当前窗口中,则加入到当前窗口中,并且更新窗口长度。如果该元素在当前窗口之中,那么就移动队首直到该元素不在当前窗口之中,并且更新窗口长度。
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
if len(s) == 0:
return 0
if len(s) == 1:
return 1
start = 0
end = 0
queue = [s[0]]
maxlength = 0
for i in range(len(s) - 1):
end += 1
if s[end] not in queue:
queue.append(s[end])
else:
while(s[end] in queue):
start += 1
queue.pop(0)
queue.append(s[end])
maxlength = max(maxlength, end - start + 1)
#maxlength = max(maxlength, len(queue))
return maxlength
- 丑数
- 标准的数学题,最简单的思路就是从头开始遍历,然后依次判断每个数是否是丑数即可。
- 利用动态规划来进行求解,我们定义一个数组ugly,这个数组按照从小到大的顺序来存储丑数,假设前面已经有了n-1个丑数,那么第n个丑数如何产生,那肯定是从前n-1个丑数中再乘以2、3、5来生成。我们可以简单地写出状态转移方程:
更新条件:2ugly[i], 3ugly[j], 5*ugly[k]这三个数中有等于当前最小值,对应的坐标往前移动一位即可。
方法一:(超时)
class Solution:
def nthUglyNumber(self, n: int) -> int:
def judge(num):
while(num > 1):
if num % 2 == 0:
num = num // 2
elif num % 3 == 0:
num = num // 3
elif num % 5 == 0:
num = num // 5
else:
return 0
return 1
count = 0
start = 2
if n == 1:
return 1
while(True):
if judge(start):
count += 1
if count == n - 1:
return start
start += 1
方法二:
class Solution:
def nthUglyNumber(self, n: int) -> int:
ugly = [0]*1690
ugly[0] = 1
i = j = k =0
for p in range(1, n):
temp = min(2*ugly[i], 3*ugly[j], 5*ugly[k])
ugly[p] = temp
if 2*ugly[i] == temp:
i += 1
if 3*ugly[j] == temp:
j += 1
if 5*ugly[k] == temp:
k += 1
return ugly[n-1]
- 第一个只出现一次的字符
统计字符出现的次数用字典即可,但题目要求是第一个出现的字符,这个就要求字典添加元素的时候是按照顺序来的。注意第一种的解法是错误的,因为python中dict的元素添加之后是无序的,但不知道为什么能够测试,只能说leetcode上的测试样例太水了。解法二用到了python中的有序字典。解法三直接遍历原序列,不遍历字典,这样就可以保证顺序。
解法一:错误的解法
class Solution:
def firstUniqChar(self, s: str) -> str:
res = {}
for c in s:
if c not in res:
res[c] = 1
else:
res[c] += 1
for key in res:
if res[key] == 1:
return key
return " "
解法二:正确的解法
class Solution:
def firstUniqChar(self, s: str) -> str:
import collections
res = collections.OrderedDict()
for c in s:
if c not in res:
res[c] = 1
else:
res[c] += 1
for key in res:
if res[key] == 1:
return key
return " "
解法三:
class Solution:
def firstUniqChar(self, s: str) -> str:
res = {}
for c in s:
if c not in res:
res[c] = 1
else:
res[c] += 1
for c in s:
if res[c] == 1:
return c
return " "
- 数组中的逆序对
暴力法我就不说了,比较常见的思路应该是归并排序。归并排序的基本思路:分治、比较、归并。逆序对在合并数组的时候计算一下即可。具体实现的时候需要把归并排序的代码改造一下。参考链接:https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/solution/jian-dan-yi-dong-gui-bing-pai-xu-python-by-azl3979/
有一点需要注意的是,python在传递列表的时候,如果直接在函数内部修改了列表元素的话,这个列表在外部也相当于被修改了。
class Solution:
def reversePairs(self, nums: List[int]) -> int:
self.reversecount = 0
def merge(nums, l, mid, r):
i = l
j = mid + 1
temp = []
while(i <= mid and j <= r):
if nums[i] <= nums[j]:
temp.append(nums[i])
i += 1
else:
self.reversecount += mid - i + 1
temp.append(nums[j])
j += 1
while(i <= mid):
temp.append(nums[i])
i += 1
while(j <= r):
temp.append(nums[j])
j += 1
pos = 0
for index in range(l, r + 1):
nums[index] = temp[pos]
pos += 1
def mergesort(nums, l, r):
if l >= r:
return
mid = (l + r) // 2
mergesort(nums, l, mid)
mergesort(nums, mid + 1, r)
merge(nums, l, mid, r)
mergesort(nums, 0, len(nums) - 1)
return self.reversecount
另一种写法:
关于这个全局变量需要说明一下由于这里有两个函数,定义的全局变量无法生效,需要使用类中的self关键字。完整的归并排序还需要回填操作,再对原数组进行修改。
class Solution:
def reversePairs(self, nums: List[int]) -> int:
if not nums:
return 0
self.res = 0
def merge(nums1, nums2):
temp = []
i = 0
j = 0
while(i < len(nums1) and j < len(nums2)):
if nums1[i] <= nums2[j]:
temp.append(nums1[i])
i += 1
else:
self.res += len(nums1) - i
temp.append(nums2[j])
j += 1
if i < len(nums1):
while(i < len(nums1)):
temp.append(nums1[i])
i += 1
if j < len(nums2):
while(j < len(nums2)):
temp.append(nums2[j])
j += 1
return temp
def recursion(nums, l, r):
if l == r:
return nums[l:r+1]
mid = (l + r) // 2
left = recursion(nums, l, mid)
right = recursion(nums, mid+1, r)
return merge(left, right)
nums = recursion(nums, 0, len(nums)-1)
return self.res
- 两个链表的第一个公共节点
- 一种直观的想法,将两条链表进行反转,然后来找公共节点。这里我把题目理解错了,我以为是两条独立的链表,后来发现并不是,这两条链表后面的节点是公共的,所以不能用链表反转的方法。(囧)
- 如果两条链表长度相等,那么同时遍历一定可以可以找到相同的节点,所以我们接下来的操作就是让两个链表长度相等。两个链表同时遍历,如果其中有一条链表先到终点,那么就从另一条链表的第二个节点开始遍历,重复这一操作即可。但是要注意边界条件,比如两个链表中有一个为空,那么直接返回空即可,如果遍历两条链表,同时到达终点都没有找到公共点,那么直接返回空。但这个方法有一个缺点就是一次只能往后移动一步。
- 参考的官方题解:双指针解法,让走的快的指针先到另一个链表上,这样就保证了两个链表能够在同样的位置上开始比较。
解法二:
# 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:
if not headA or not headB:
return None
while(True):
temp1 = headA
temp2 = headB
while(temp1 and temp2):
if temp1 == temp2:
return temp1
else:
temp1 = temp1.next
temp2 = temp2.next
if not temp1 and not temp2:
return None
if not temp1:
headB = headB.next
break
if not temp2:
headA = headA.next
break
解法三:
# 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:
temp1, temp2 = headA, headB
while(temp1 != temp2):
if temp1:
temp1 = temp1.next
else:
temp1 = headB
if temp2:
temp2 = temp2.next
else:
temp2 = headA
return temp1
53-1. 在排序数组中查找数字
- 在有序数组中查找某个元素,一种是直接遍历复杂度为O(n)。
- 另一种是利用二分查找,注意二分查找的前提是数组必须是有序的。这里不是直接判断是否存在该元素值,而是要找到该元素的个数,因此我们需要找到该元素的左边界,当当前元素等于目标时,要将右边界左移直到循环的结束。
方法一:
class Solution:
def search(self, nums: List[int], target: int) -> int:
count = 0
for num in nums:
if num == target:
count += 1
return count
方法二:
class Solution:
def search(self, nums: List[int], target: int) -> int:
count = 0
left = 0
right = len(nums) - 1
while(left <= right):
middle = (left + right) // 2
if nums[middle] > target:
right = middle - 1
elif nums[middle] < target:
left = middle + 1
elif nums[middle] == target:
right = middle - 1
for i in range(left, len(nums)):
if nums[i] == target:
count += 1
return count
53-2. 0~n-1中缺失的数字
- 在一个递增数组中找到缺失的元素,同样一种办法是直接遍历,判断元素是否在该数组中,我看一下题解:还有求和和异或这两种方法,复杂度都是O(n)。
- 另一种是利用有序条件来进行二分查找,如果一个元素都不缺的话,大小为i的元素下标应该是i,如果该元素左边缺一个的话,那么该元素的值大于其下标,如果相等则表明缺失的元素在右边,最终当左边界等于有边界时返回。
方法一:
class Solution:
def missingNumber(self, nums: List[int]) -> int:
length = len(nums)
nums = set(nums)
for i in range(0, length + 2):
if i not in nums:
return i
方法二:
class Solution:
def missingNumber(self, nums: List[int]) -> int:
left = 0
right = len(nums) - 1
while(left <= right):
middle = (left + right) // 2
if nums[middle] > middle:
right = middle - 1
else:
left =middle + 1
return left
方法二另一种写法,这个二分其实只有两种情况,第三种情况不会出现。
class Solution:
def missingNumber(self, nums: List[int]) -> int:
l = 0
r = len(nums) - 1
while(l <= r):
mid = (l + r) //2
if nums[mid] == mid:
l = mid + 1
elif nums[mid] > mid:
r = mid - 1
else:
#l = mid + 1
continue
return l
- 二叉搜索树的第k大节点
- 基本思路,遍历整个二叉树,然后找到第k大的节点。
- 利用二叉搜索树的性质:按照右根左的顺序来遍历就会得到一个递减的数组,这个时候使用深度搜索比较好。
- 再方法二的基础上进一步进行改进,可以不需要遍历所有的节点,只需要遍历到第k个节点就好了。实现起来需要定义一个全局变量来记录遍历节点的个数,在递归出口增加一个条件:当遍历到第k个节点的时候程序终止(剪枝),注意这个剪枝条件放在不同地方,最终的效果也不同。
- 补充,关于在递归函数中使用全局变量的问题,比如二叉树的遍历,我们可以使用一个全局的列表来存储每一个节点,这种写法虽然方便但有一点投机取巧的意味。因此我们还需要设计好递归函数的参数和其返回值的含义。
方法一:
# 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:
if not root:
return None
queue = [root]
res= []
while(queue):
res.append(queue[0].val)
if queue[0].left:
queue.append(queue[0].left)
if queue[0].right:
queue.append(queue[0].right)
del queue[0]
res.sort(reverse=True)
return res[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:
def read(root: TreeNode) -> list:
if not root:
return []
right = read(root.right)
val = root.val
left = read(root.left)
return right + [val] + left
return read(root)[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:
self.counts = k
self.num = 0
def read(root: TreeNode):
if not root:
return
if self.counts == 0:
return
right = read(root.right)
val = root.val
self.counts = self.counts - 1
if self.counts == 0:
self.num = val
left = read(root.left)
read(root)
return self.num
他人题解:
class Solution:
def kthLargest(self, root: TreeNode, k: int) -> int:
self.res, self.k = None, k
def dfs(root):
if not root: return
dfs(root.right)
self.k -= 1
if not self.k: self.res = root.val
if self.k > 0: dfs(root.left)
dfs(root)
return self.res
55-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:
if root == None:
return 0
else:
return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1
55-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 root == None:
return True
else:
if self.isBalanced(root.left) == False:
return False
elif self.isBalanced(root.right) == False:
return False
left_length = self.get_height(root.left)
right_length = self.get_height(root.right)
if abs(left_length - right_length) <= 1:
return True
else:
return False
def get_height(self, root: TreeNode) -> int:
if root == None:
return 0
else:
return max(self.get_height(root.left), self.get_height(root.right)) + 1
56-1. 数组中数字出现的次数
题目要求空间复杂度是O(1),同样得使用位运算。
相同的数异或为0,不同的异或为1。0和任何数异或等于这个数本身。所以,数组里面所有数异或 = 目标两个数异或。由于这两个数不同,所以异或结果必然不为0。假设数组异或的二进制结果为10010,那么说明这两个数从右向左数第2位是不同的,那么可以根据数组里面所有数的第二位为0或者1将数组划分为2个。这样做可以将目标数必然分散在不同的数组中,而且相同的数必然落在同一个数组中。这两个数组里面的数各自进行异或,得到的结果就是答案。
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
res = 0
for num in nums:
res ^= num
temp = 1
while(temp & res == 0):
temp <<= 1
a = 0
b = 0
for num in nums:
if num & temp == 0:
a ^= num
else:
b ^= num
return [a, b]
56-2. 数组中数字出现的次数 II
- 最直观的想法,用一个字典存储每个数字出现的额次数即可。
- 利用题目中给的条件:只有一个数字出现1次,而其他数字都出现了3次,可以先把列表变成集合,这样每个元素就只出现了一次,之后求和再乘以3,接着减去原始列表元素中的和,最后除以2即可。
- 官方解法是位运算,这玩意我实在是不感冒。
解法一:
class Solution:
def singleNumber(self, nums: List[int]) -> int:
counts = {}
for num in nums:
if num not in counts:
counts[num] = 1
else:
counts[num] += 1
for key in counts:
if counts[key] == 1:
return key
解法二:
class Solution:
def singleNumber(self, nums: List[int]) -> int:
return (sum(set(nums))*3-sum(nums))//2
解法三:
待定,数电模拟题
57-1. 和为s的两个数字
- 最简单的思路:暴力搜索,复杂度为,但根据这题的条件,结果显然是超时。考虑到数组属于递增的性质,可以对算法进行优化,外层循环从前往后搜索,内层循环从后往前搜索,这样可以降低时间复杂度,但结果还是超时。这个时候我们还可以再优化,比如当前节点加上最大节点如果小于target值的话,后面的搜索就不需要进行了。
- 利用数组属于递增的性质,可以对算法进行优化,从数组前面和后面同时进行搜索,这样时间复杂度可以降到。
方法一:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
for i in range(len(nums)):
if nums[i] + nums[-1] < target:
continue
for j in range(len(nums)-1, -1, -1):
if i != j:
if nums[i] + nums[j] == target:
return [nums[i], nums[j]]
方法二:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
i = 0
j = len(nums) - 1
while(i != j):
if nums[i] + nums[j] == target:
return [nums[i], nums[j]]
if nums[i] + nums[j] < target:
i += 1
if nums[i] + nums[j] > target:
j -= 1
57-2. 和为s的连续正数序列
连续的数列求和,这一看就可以使用公式来求和,所以这个题本身就是一个因式分解的题目。这题存在一些细节,比如在因式分解的时候,我们可以将因子限定在以内,这点利用反证法可以很快证明。
假设target可以因式分解成(假设p大于q),那么可以很快求出n和d
class Solution:
def findContinuousSequence(self, target: int) -> List[List[int]]:
res = []
target = 2*target
for i in range(2, int(pow(target, 0.5) + 1)):
if target % i == 0:
q = i
p = int(target / i)
if (p - q + 1) % 2 == 0:
n = int((p - q + 1) / 2)
else:
continue
d = q - 1
temp = [ i for i in range(n, n + d + 1)]
res.append(temp)
res.reverse()
return res
58-1. 翻转单词顺序
简单的字符串切割,列表反转,再用空格拼接即可。
class Solution:
def reverseWords(self, s: str) -> str:
temp = s.split()
temp = temp[::-1]
return " ".join(temp)
58-2. 左旋转字符串
遍历字符串,这题没啥好说的。
class Solution:
def reverseLeftWords(self, s: str, n: int) -> str:
return s[n:] + s[:n]
59-1. 滑动窗口的最大值
本题的难点在于如何使用O(1)的时间内找到窗口里的最大值。这里需要维护一个非递增的双端队列,注意是非递增。
维护这个队列的基本操作:
1.当前队列中的最大值等于前一个滑动窗口中的左边界的时候,就说明这个最大值是前一个滑动窗口的最大值,需要剔除。
- 当前队列的尾端大于等于当前元素,则让其入队列,否则将队列中小于该元素的全部出队列,再让其入队列。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
if not nums:
return []
queue = []
for i in range(k):
while(queue and queue[-1] < nums[i]):
queue.pop()
queue.append(nums[i])
res = [queue[0]]
for i in range(k, len(nums)):
if queue[0] == nums[i-k]:
queue.pop(0)
while(queue and queue[-1]<nums[i]):
queue.pop()
queue.append(nums[i])
res.append(queue[0])
return res
59-2. 队列的最大值
接着上一题,同样需要维护一个单调非增的双端队列,用来在O(1)的时间内找到最大值。
class MaxQueue:
def __init__(self):
self.queue_A = []
self.queue_B = []
def max_value(self) -> int:
if self.queue_B:
return self.queue_B[0]
else:
return -1
def push_back(self, value: int) -> None:
self.queue_A.append(value)
if not self.queue_B:
self.queue_B.append(value)
else:
while(self.queue_B and self.queue_B[-1] < value):
self.queue_B.pop()
self.queue_B.append(value)
def pop_front(self) -> int:
if self.queue_A:
if self.queue_A[0] == self.queue_B[0]:
self.queue_B.pop(0)
return self.queue_A.pop(0)
else:
return -1
# Your MaxQueue object will be instantiated and called as such:
# obj = MaxQueue()
# param_1 = obj.max_value()
# obj.push_back(value)
# param_3 = obj.pop_front()
- n个骰子的点数
最简单的思路,n个循环,但是这样肯定超时。我们得考虑一下动态规划的思路,如何从n-1个骰子,变化到n个骰子:
f(s,n) = f(s-1,n-1) + f(s-2, n-1) + f(s-3, n-1) + f(s-4, n-1) + f(s-4,n-1) + f(s-5, n-1) + f(s-6, n-1)
这个其实与斐波拉契数列非常相似了,这种情况就不能使用递归来实现,应该使用迭代。
总的情况为种,但是最终只有6n-n+1种值。
注意实现的时候,我们把s和n的位置换了一下。
解法一:
class Solution:
def twoSum(self, n: int) -> List[float]:
dp = [[0]*70 for i in range(13)]
for i in range(1, 7):
dp[1][i] = 1
for i in range(2, n + 1):
for j in range(i, 6*i + 1):
for cur in range(1, 7):
if j - cur >= i - 1:
dp[i][j] += dp[i-1][j-cur]
sum = 6**n
res = []
for i in range(n, 6*n + 1):
res.append(dp[n][i] / sum)
return res
解法二:
我们可以将将二维数组优化成一位数组,不用考虑到骰子的数量,但是这样写需要注意两点,一个是dp[i]累加的时候需要提前置为0,并且还要倒过来遍历,只有这样才不会让之前初始化的数组白费。
class Solution:
def twoSum(self, n: int) -> List[float]:
dp = [0]*70
for i in range(1, 7):
dp[i] = 1
for i in range(2, n + 1):
for j in range(6*i, i-1, -1):
dp[j] = 0
for cur in range(1, 7):
if j - cur >= i - 1:
dp[j] += dp[j-cur]
sum = 6**n
res = []
for i in range(n, 6*n + 1):
res.append(dp[i] / sum)
return res
- 扑克牌中的顺子
判断是否是连续序列,很基本的题,注意各种边界条件即可。
class Solution:
def isStraight(self, nums: List[int]) -> bool:
if nums == [0]*5:
return True
nums.sort()
res = []
for i in range(len(nums)):
if nums[i] != 0:
res.append(nums[i])
if len(res) == 1:
return True
sum = 0
for i in range(1, len(res)):
if res[i] - res[i-1] > 1:
sum += res[i] - res[i-1] - 1
elif res[i] - res[i-1] == 0:
return False
if sum <= 5 - len(res):
return True
return False
- 圆圈中最后剩下的数字
- 约瑟夫环问题,最简单的思路是直接进行模拟,按照题目中的要求一步步操作即可。
- 数学解法
解法一:
class Solution:
def lastRemaining(self, n: int, m: int) -> int:
res = []
for i in range(n):
res.append(i)
start = 0
while(n > 1):
start = (start + m - 1) % n
del res[start]
n -= 1
return res[0]
解法二:
- 股票的最大利润
这基本属于最简单的DP了,定义一个数组profit,profit[i]表示第i天将股票卖出去能获得的最大利润,要求profit[i]只需要求出前i-1天的最小值即可,最终找出profit数组中的最大就行了。
class Solution:
def maxProfit(self, prices: List[int]) -> int:
minNum = 99999999
maxNum = -99999999
profit = [0]*len(prices)
for i in range(1, len(prices)):
minNum = min(minNum, prices[i-1])
profit[i] = prices[i] - minNum
maxNum = max(maxNum, profit[i])
if maxNum > 0:
return maxNum
else:
return 0
- 求1+2+…+n
这种题就不是在考算法,只是单纯地考语言特性,需要用到位运算什么的,这种题非常无聊。参考:https://leetcode-cn.com/problems/qiu-12n-lcof/comments/
1 and 2返回的是2,0 and 2 返回的是0,注意再python中a and b 要求a和b都不能是等式,在python中赋值操作没有返回值。
class Solution:
def sumNums(self, n: int) -> int:
return n and (n + self.sumNums(n-1))
- 不用加减乘除做加法
题目说的很清楚,不准用加减乘除来做加法,所以我们只能用位运算了。这就需要我们自己来实现二进制加法操作了。两个二进制数a,b相加,先计算正常的相加(二进制的异或操作),再计算进位(二进制的与操作再向左移动一位),重复以上操作直到进位为0,因为加法操作中可能会存在多次进位操作。python写法需要处理python的长整数,C语言写法需要处理C语言的无符号数。
python写法:
class Solution:
def add(self, a: int, b: int) -> int:
a &= 0xffffffff
b &= 0xffffffff
while(b):
temp = a
a = a ^ b
b = ((temp & b) << 1) & 0xffffffff
if a < 0x80000000:#如果是正数的话直接返回
return a
else:
return ~(a^0xffffffff)#是负数的话,转化成其原码
C语言写法:
int add(int a, int b){
int temp = 0;
while(b){
temp = a;
a = a ^ b;
b = (unsigned int) (temp & b) << 1;
}
return a;
}
- 构建乘积数组
- 最简单的方法肯定是暴力法,这个没啥好说的,复杂度,但是超时了。
- 需要求每一个数左边和右边数的乘积和,只需要从左到右和从右到左遍历连乘即可,但是注意需要延迟一步操作。A[i]表示从0到i-1之间数字的乘积,B[i]表示i+1到n-1之间数字的乘积。
解法一:
class Solution:
def constructArr(self, a: List[int]) -> List[int]:
res = []
for i in range(len(a)):
s = 1
for j in range(len(a)):
if i != j:
s = s*a[j]
res.append(s)
return res
解法二:
class Solution:
def constructArr(self, a: List[int]) -> List[int]:
A = [1]*len(a)
B = [1]*len(a)
temp = 1
for i in range(len(a)):
A[i] = temp
temp = temp*a[i]
temp = 1
for i in range(len(a) - 1, -1, -1):
B[i] = temp
temp = temp*a[i]
A[i] = A[i]*B[i]
return A
- 把字符串转换成整数
有限状态自动机
class Solution:
def __init__(self):
self.signed = 1
self.ans = 0
self.state = 'start'
self.max_int = 2**31 - 1
self.min_int = -2**31
self.transpose = {
'start' : ['start', 'signed', 'in_number', 'end'],
'signed' : ['end', 'end', 'in_number', 'end'],
'in_number' : ['end', 'end', 'in_number', 'end'],
'end' : ['end', 'end', 'end', 'end']
}
def get_col(self, c):
if c == ' ':
return 0
elif c == '+' or c == '-':
return 1
elif c in ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']:
return 2
else:
return 3
def get_state(self, c):
col = self.get_col(c)
return self.transpose[self.state][col]
def myAtoi(self, str: str) -> int:
for c in str:
self.state = self.get_state(c)
if self.state == 'signed':
if c == '+':
self.signed = 1
else:
self.signed = -1
elif self.state == 'in_number':
self.ans = self.ans*10 + ord(c) - ord('0')
if self.signed*self.ans > self.max_int:
return self.max_int
if self.signed*self.ans < self.min_int:
return self.min_int
return self.signed*self.ans
68-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':
if p == q:
return p
while(True):
if p.val < root.val and q.val < root.val:
root = root.left
if p.val > root.val and q.val > root.val:
root = root.right
if (p.val <= root.val and q.val >= root.val) or (p.val >= root.val and q.val <= root.val) :
return root
解法二:对第一种解法简单优化一下。
# 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':
if p == q:
return p
while(True):
if p.val < root.val and q.val < root.val:
root = root.left
elif p.val > root.val and q.val > root.val:
root = root.right
else:
return root
68-2. 二叉树的最近公共祖先
- 现在变成了普通的二叉树,这个时候就不知道这两个节点(p,q)的位置了。只有从上到下遍历每一个节点,来找到p和q了。可以这样来提炼一下递归关系,在当前根节点下,如果左子树既没有p也没有q,那么这个最近公共祖先一定在右子树,反之亦然。如果左子树有p或者q并且右子树有p或者q,那么这个公共祖先就是当前根节点。所以这个函数大体上是来表明左右子树中有没有p或q的,以下面的代码为例,如果left或right为空这个时候函数表示没有p和q的意思,如果left和right都不为空同样函数是表示是有p或q的意思,但是当只有一方为空的时候,另一方则表示的是公共祖先的意思,造成这种情况的原因是因为这个函数在不同的条件有不同的递归终止条件,也就是说这个函数在不同的条件下表示不同的意思。(总的来说我认为这个递归很绕,不太好理解)
- 可以通过从根节点到这两个节点的公共路径来找到最近公共祖先。可以通过层序遍历的方法来找到这两个节点,但是保存根节点到这两个节点的路径是个问题,这里可以将每个节点的父节点存储下来,遍历的时候从下往上遍历直到根节点,这样就可以找到路径了。注意存储路径的时候要倒着存储,然后找到这两个数组中第一次不相同的元素前一个就是最近公共祖先。
解法一:
# 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:
if root == None or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if not left:
return right
if not right:
return left
return root
解法二:
# 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:
if p == root: #注意临界条件
return p
if q == root:
return q
queue = [root]
flag_p = 0
flag_q = 0
parents = {}
while(True):
if queue[0] == p:
flag_p = 1
if queue[0] == q:
flag_q = 1
if queue[0].left:
queue.append(queue[0].left)
if queue[0].right:
queue.append(queue[0].right)
parents[queue[0].left] = queue[0]
parents[queue[0].right] = queue[0]
del queue[0]
if flag_p == 1 and flag_q == 1:
break
route_p = [p]
route_q = [q]
temp = p
while(True):
route_p.insert(0, parents[temp])
if parents[temp] == root:
break
temp = parents[temp]
temp = q
while(True):
route_q.insert(0, parents[temp])
if parents[temp] == root:
break
temp = parents[temp]
for i in range(min(len(route_p), len(route_q))):
if route_p[i] != route_q[i]:
return route_p[i-1]
return route_p[min(len(route_p), len(route_q)) - 1]