1、python小试
题目:输出2~100之间的素数
def find_prime_number(start, end):
prime_number_list = []
i = start
while i < end:
j = 2
while j <= i // 2:
if i % j == 0:
break
j += 1
if j > i // 2:
prime_number_list.append(i)
i += 1
return prime_number_list
prime_number = find_prime_number(2, 100)
print(prime_number)
思路:素数就是找不到1之外可以整除的数。假如可以找到,那么必定有一个值不大于原值的1/2。
2、栈和队列
2.1 设计一个有getMin功能的栈
题目:实现一个特殊的栈,在实现栈的基本功能的基础上,再返回栈中最小元素的操作
要求:pop、push、getMin操作的时间复杂度都是O(1)
data_stack_list = []
min_stack_list = []
def push(x):
data_stack_list.append(x)
if len(min_stack_list) == 0:
min_stack_list.append(x)
else:
before_min_data = min_stack_list[-1]
if x < before_min_data:
min_stack_list.append(x)
else:
min_stack_list.append(before_min_data)
def pop():
top_data = data_stack_list.pop()
min_stack_list.pop()
return top_data
def getMin():
if len(min_stack_list) == 0:
return -1
else:
return min_stack_list[-1]
push(4)
push(5)
push(2)
push(3)
push(1)
print(getMin())
pop()
print(getMin())
pop()
print(getMin())
pop()
print(getMin())
pop()
print(getMin())
pop()
print(getMin())
思路:需要两个栈,一个栈正常存放、取出数据,另一个栈每个值都对应第一个栈中每个元素的最小值。
2.2 由两个栈组成队列
题目:编写一个类,用两个栈实现队列,支持队列的基本操作add、poll、peek
stack_1 = []
stack_2 = []
def add(x):
stack_1.append(x)
def pop():
while len(stack_1) > 0:
data = stack_1.pop()
stack_2.append(data)
first_data = stack_2.pop()
while len(stack_2) > 0:
data = stack_2.pop()
stack_1.append(data)
return first_data
def peek():
if len(stack_1) == 0:
return -1
else:
return stack_1[-1]
add(1)
add(2)
add(3)
add(4)
add(5)
print(peek())
pop()
print(peek())
pop()
print(peek())
思路:两个栈,第一个栈负责装数据。当pop数据时,把第一个栈的数据一次放入第二个栈,然后pop第二个栈的栈顶元素,接着把第二个栈的数据再装回第一个栈。
2.3 仅用递归函数和栈操作实现栈的逆序
题目:只能用递归函数实现,不能使用其他数据结构
stack = [1,2,3,4,5]
def get_and_remove_last_element(stack):
data = stack.pop()
if len(stack) == 0:
return data
else:
last = get_and_remove_last_element(stack)
stack.append(data)
return last
def reverse_stack(stack):
data = get_and_remove_last_element(stack)
if len(stack) == 0:
stack.append(data)
else:
reverse_stack(stack)
stack.append(data)
return stack
stack = reverse_stack(stack)
print(stack)
思路:需要两个递归函数。第一个递归函数每次返回栈底的数据,其他放回栈中。第二个递归函数实现栈的逆序。
2.4 生成窗口最大值数组
题目:有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。实现:返回一个数组,数组中每个元素为每次窗口内的最大值,时间复杂度为O(1)。
from collections import deque
arr = [4,3,5,3,4,4,6,7]
w = 3
def get_window_max_arr(arr, w):
queue = deque()
max_arr = []
size = len(arr)
for i in range(0, size):
if len(queue) == 0:
queue.append(i)
else:
while len(queue) > 0 and arr[queue[-1]] < arr[i]:
queue.pop()
queue.append(i)
if queue[0] == i - w:
queue.popleft()
if i >= w - 1:
max_arr.append(arr[queue[0]])
return max_arr
max_arr = get_window_max_arr(arr, w)
print(max_arr)
思路:需要一个双端队列,存放的是数组下标值(注意不是数组元素)。每次扫描一个新的数组元素时,和deque最后一个元素对比,pop队尾元素直到其值不小于该新元素。如果队首坐标值过期则pop队首元素。窗口最大值为队首对应的数组元素值。
2.5 单调栈结构
题目:给定一个不含有重复值的数组arr,找到一个i位置左边和右边离i位置最近且比arr[i]小的位置,返回所有位置相应的信息,时间复杂度为O(1)
import numpy as np
arr = np.array([3,4,1,5,6,2,7])
def find_nearest_index(arr):
size = arr.size
stack = []
nearest_arr = [(-1, -1)] * size
for i in range(size):
if len(stack) == 0:
stack.append(i)
else:
if arr[stack[-1]] < arr[i]:
stack.append(i)
else:
while len(stack) > 0 and arr[stack[-1]] > arr[i]:
data = stack.pop()
if len(stack) > 0:
tup = (stack[-1], i)
else:
tup = (-1, i)
nearest_arr[data] = tup
stack.append(i)
while len(stack) > 0:
data = stack.pop()
if len(stack) > 0:
tup = (stack[-1], -1)
else:
tup = (-1, -1)
nearest_arr[data] = tup
return nearest_arr
nearest_arr = find_nearest_index(arr)
print(nearest_arr)
思路:构造一个单调栈,从栈顶到栈底从大到小排序。当新进入一个元素打破这个排序,对于弹出栈的元素,栈底元素为其左边想要的值,当前压栈值为其右边想要的值。当全部元素完成入栈后,再逐一出栈再判断一遍。
2.6 求最大子矩阵的大小
题目:给定一个整型矩阵map,其中的值只有0和1两种,求其中全是1的所有矩形区域中,最大的矩形区域为1的数量。
import numpy as np
map1 = np.array([[1,0,1,1], [1,1,1,1], [1,1,1,0]])
map2 = np.array([[1,1,1,0]])
def find_biggest_map_number(map):
flag_map = np.zeros_like(map)
for i in range(len(map)):
for j in range(len(map[0])):
if i == 0:
flag_map[i][j] = 0 if map[i][j] == 0 else 1
else:
flag_map[i][j] = flag_map[i - 1][j] + 1 if map[i][j] == 1 else 0
biggest_num = 0
for i in range(len(map)):
for j in range(len(map[0])):
if map[i][j] == 0:
break
flag_num = flag_map[i][j]
good_num = 1
for k in range(1, j + 1):
num = flag_map[i][j - k]
if num >= flag_num:
good_num += 1
else:
break
for k in range(1, len(map[0]) - j):
num = flag_map[i][j + k]
if num >= flag_num:
good_num += 1
else:
break
cur_num = flag_num * good_num
if biggest_num < cur_num:
biggest_num = cur_num
return biggest_num
num = find_biggest_map_number(map1)
print(num)
思路:首先是以行为单位遍历数组,并且以当前行 为矩阵底部的话,计算矩阵的高度是多少。这样计算出二维数组中每个元素对应矩阵的高度height值。 第二遍扫描数组,计算出当前元素左边不少于当前height值的个数,再计算当前元素右边不少于当前height值的个数,然后当前值height值乘以总个数即为矩阵的大小。
3 链表
3.1 打印两个有序链表的公共部分
题目:给定两个有序列表的头指针head1和head2,打印两个链表的公共部分
class Node:
def __init__(self, val, next=None):
self.val = val
self.next = next
head1 = Node(0)
head2 = Node(0)
head11 = Node(1)
head12 = Node(3)
head13 = Node(5)
head14 = Node(7)
head15 = Node(9)
head21 = Node(2)
head22 = Node(3)
head23 = Node(5)
head24 = Node(8)
head25 = Node(10)
head1.next = head11
head11.next = head12
head12.next = head13
head13.next = head14
head14.next = head15
head2.next = head21
head21.next = head22
head22.next = head23
head23.next = head24
head24.next = head25
def find_common(head1, head2):
p = head1
q = head2
common_data = []
while p != None and q != None:
if p.val < q.val:
p = p.next
elif p.val > q.val:
q = q.next
else:
common_data.append(p.val)
p = p.next
q = q.next
return common_data
common_data = find_common(head1, head2)
print(common_data)
思路:因为是有序的链表,所以比较简单,不解读了。
3.2 在单链表中删除倒数第K个节点
题目: 实现函数,可以删除单链表中倒数第K个节点
class Node:
def __init__(self, val, next=None):
self.val = val
self.next = next
head1 = Node(1)
head2 = Node(2)
head3 = Node(3)
head4 = Node(4)
head5 = Node(5)
head6 = Node(6)
head7 = Node(7)
head8 = Node(8)
head9 = Node(9)
head10 = Node(10)
head1.next = head2
head2.next = head3
head3.next = head4
head4.next = head5
head5.next = head6
head6.next = head7
head7.next = head8
head8.next = head9
head9.next = head10
def delete_K_Node(head1, K):
p = head1
while p != None:
K -= 1
p = p.next
if K >= 0 :
return head1
else:
p = head1
while K < -1:
K += 1
p = p.next
p.next = p.next.next
return head1
head_new = delete_K_Node(head1, 3)
while head_new != None:
print(head_new.val)
head_new = head_new.next
思路:链表从前往后数,每数一次减1。如果最后值大于等于0,则不需要删除K节点。否则第一次数完后得到N,第二次再从前往后数,每数一次加1,等到值为-1时,下一个即为要删除的节点。
3.3 两个单链表生成相加链表
题目:给定两个链表的头节点head1和head2,请生成代表两个整数相加值的结果链表
class Node:
def __init__(self, val, next=None):
self.val = val
self.next = next
head11 = Node(9)
head12 = Node(9)
head13 = Node(0)
head11.next = head12
head12.next = head13
head21 = Node(1)
head22 = Node(3)
head21.next = head22
def add_node(head1, head2):
head_stack_1 = []
head_stack_2 = []
p = head1
q = head2
while p != None:
head_stack_1.append(p.val)
p = p.next
while q != None:
head_stack_2.append(q.val)
q = q.next
big_flag = 0
r = None
while len(head_stack_1) > 0 and len(head_stack_2) > 0:
data_1 = head_stack_1.pop()
data_2 = head_stack_2.pop()
sum = data_1 + data_2 + big_flag
if sum >= 10:
big_flag = 1
else:
big_flag = 0
k = Node(sum % 10, r)
r = k
p = head_stack_1 if len(head_stack_1) > 0 else head_stack_2
while len(p) > 0:
data = p.pop()
sum = data + big_flag
k = Node(sum % 10, r)
r = k
if sum >= 10:
k = Node(1, r)
r = k
return r
result = add_node(head11, head21)
while result != None:
print(result.val)
result = result.next
思路:把链表分别放入stack中,然后再取值相加,生成的结果直接加到结果链表中。不可以采用字符串转为int的方法,假如字符串很长可能会溢出。
3.4 将单链表的每K个节点之间逆序
题目:给定一个单链表head,实现一个调整单链表的函数,使得每K个节点之间逆序,如果最后不够K个节点,则不调整最后几个节点。
class Node:
def __init__(self, val, next=None):
self.val = val
self.next = next
head1 = Node(1)
head2 = Node(2)
head3 = Node(3)
head4 = Node(4)
head5 = Node(5)
head6 = Node(6)
head7 = Node(7)
head8 = Node(8)
head1.next = head2
head2.next = head3
head3.next = head4
head4.next = head5
head5.next = head6
head6.next = head7
head7.next = head8
def reverse_list_node(head, K):
stack = []
p = head
new_head = Node(None)
r = new_head
while p != None:
i = 0
q = p
for _ in range(K):
stack.append(p)
if p != None:
p = p.next
i += 1
while len(stack) > 0 and i == K:
r.next = Node(stack.pop().val)
r = r.next
if i < K:
while q != None:
r.next = Node(q.val)
q = q.next
r = r. next
return new_head.next
result = reverse_list_node(head1, 3)
while result != None:
print(result.val)
result = result.next
思路:逆序使用栈结构。每个K个节点装入stack并弹出并连接到结果链表中,如果某次装入stack的长度不够K则直接连接到结果链表中。
3.5 将搜索二叉树转换成双向链表
题目:有一颗搜索二叉树,将其转换为一个有序的双向链表
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val, next=None, last=None):
self.val = val
self.next = next
self.last = last
node1 = Node(1)
node3 = Node(3)
node2 = Node(2, node1, node3)
node5 = Node(5)
node4 = Node(4, node2, node5)
node8 = Node(8)
node9 = Node(9, node8)
node7 = Node(7, left=None, right=node9)
node6 = Node(6, node4, node7)
middle_list = []
def search(root, middle_list):
if root.left is None and root.right is None:
middle_list.append(root.val)
return
if root.left is not None:
search(root.left, middle_list)
middle_list.append(root.val)
if root.right is not None:
search(root.right, middle_list)
def create_double_link(root, middle_list):
search(root, middle_list)
new_head = ListNode(None)
p = new_head
while len(middle_list) > 0:
q = ListNode(middle_list.pop(0))
p.next = q
q.last = p
p = p.next
return new_head.next
result = create_double_link(node6, middle_list)
while result != None:
print(result.val)
result = result.next
思路:中序遍历搜索二叉树,遍历过程中将值放入队列,然后遍历队列构造双向链表。
4 二叉树
4.1 递归方式先序、中序、后续遍历二叉树
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node1 = Node(1)
node3 = Node(3)
node2 = Node(2, node1, node3)
node5 = Node(5)
node4 = Node(4, node2, node5)
node8 = Node(8)
node9 = Node(9, node8)
node7 = Node(7, left=None, right=node9)
node6 = Node(6, node4, node7)
# 先序遍历
def preOrder(root):
if root.left is None and root.right is None:
print(root.val)
return
print(root.val)
if root.left != None:
preOrder(root.left)
if root.right != None:
preOrder(root.right)
# 中序遍历
def inOrder(root):
if root.left is None and root.right is None:
print(root.val)
return
if root.left != None:
inOrder(root.left)
print(root.val)
if root.right != None:
inOrder(root.right)
# 后序遍历
def posOrder(root):
if root.left is None and root.right is None:
print(root.val)
return
if root.left != None:
posOrder(root.left)
if root.right != None:
posOrder(root.right)
print(root.val)
print("---------先序遍历-----------")
preOrder(node6)
print("---------中序遍历-----------")
inOrder(node6)
print("---------后序遍历-----------")
posOrder(node6)
4.2 非递归方式先序、中序、后续遍历二叉树
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node1 = Node(1)
node3 = Node(3)
node2 = Node(2, node1, node3)
node5 = Node(5)
node4 = Node(4, node2, node5)
node8 = Node(8)
node9 = Node(9, node8)
node7 = Node(7, left=None, right=node9)
node6 = Node(6, node4, node7)
# 先序遍历
def preOrder(root):
stack = []
stack.append(root)
while len(stack) > 0:
node = stack.pop()
print(node.val)
if node.right != None:
stack.append(node.right)
if node.left != None:
stack.append(node.left)
# 中序遍历
def inOrder(root):
stack = []
stack.append(root)
return_flag = 0
while len(stack) > 0:
node = stack[-1]
if node.left != None and return_flag == 0:
stack.append(node.left)
else:
print(node.val)
stack.pop()
return_flag = 1
if node.right != None:
stack.append(node.right)
return_flag = 0
# 后序遍历
def posOrder(root):
stack1 = []
stack2 = []
stack1.append(root)
while len(stack1) > 0:
node = stack1.pop()
stack2.append(node)
if node.left != None:
stack1.append(node.left)
if node.right != None:
stack1.append(node.right)
while len(stack2) > 0:
print(stack2.pop().val)
print("---------先序遍历-----------")
preOrder(node6)
print("---------中序遍历-----------")
inOrder(node6)
print("---------后序遍历-----------")
posOrder(node6)
思路:
(1)先序:构造一个栈,压入root,循环从栈中弹出node并打印,然后再将右子树、左子树压入栈
(2)中序:构造一个栈,压入root,循环放入节点的左子树直至没有,逐一弹出并放入右子树。有一个返回标志,防止再压入左子树。
(3)后序:构造两个栈,压入root,循环从栈1中弹出node并放入栈2,弹出后再将node的左子树、右子树放入栈1。最后栈2的元素就是后序的顺序。
4.3 打印二叉树的边界节点
题目:给定一颗二叉树的头节点head,实现二叉树边界节点的逆时针打印。
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node13 = Node(13)
node14 = Node(14)
node11 = Node(11, node13, node14)
node8 = Node(8, left=None, right=node11)
node7 = Node(7)
node4 = Node(4, node7, node8)
node2 = Node(2, left=None, right=node4)
node15 = Node(15)
node16 = Node(16)
node12 = Node(12, node15, node16)
node9 = Node(9, node12)
node10 = Node(10)
node5 = Node(5, node9, node10)
node6 = Node(6)
node3 = Node(3, node5, node6)
node1 = Node(1, node2, node3)
# 求不包括head的高度
def get_height(head, height):
if head is None:
return 0
left_height = get_height(head.left, height + 1) if head.left != None else height
right_height = get_height(head.right, height + 1) if head.right != None else height
return max(left_height, right_height)
height = get_height(node1, 1)
edge_map = [[-1 for x in range(2)] for y in range(height)]
# 求边界数组信息
def set_edge_map(head, height, edge_map):
if head == None:
return
edge_map[height][0] = edge_map[height][0] if edge_map[height][0] != -1 else head.val
edge_map[height][1] = head.val
set_edge_map(head.left, height + 1, edge_map)
set_edge_map(head.right, height+1, edge_map)
set_edge_map(node1, 0, edge_map)
# 顺序打印左边边界
for i in range(height):
print(edge_map[i][0])
# 找到不再边界数组中的叶子节点
def find_leaf_not_in_edge_map(head, height, edge_map):
if head == None:
return
if head.left == None and head.right == None and head.val != edge_map[height][0] and head.val != edge_map[height][1]:
print(head.val)
find_leaf_not_in_edge_map(head.left, height+1, edge_map)
find_leaf_not_in_edge_map(head.right, height+1, edge_map)
find_leaf_not_in_edge_map(node1, 0, edge_map)
# 逆序打印右边边界
for i in range(0, height - 1):
print(edge_map[height-1-i][1])
思路:先求树的高度,然后构造一个存放边界值的二维数组array(需要有一维长度信息即height值。在先序遍历树时,将第一次获得的左边的值放入array[height][0]不再更新,将每次获得的值放入array[height][1]不断更新。然后再寻找没有包含在array中的叶子节点。
4.4 二叉树的序列化和反序列化
题目:给定一颗二叉树头节点head,已知二叉树节点值得类型时32位整型。设计一种二叉树序列化和反序列化得方案
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node1 = Node(1)
node3 = Node(3)
node2 = Node(2, node1, node3)
node5 = Node(5)
node4 = Node(4, node2, node5)
node8 = Node(8)
node9 = Node(9, node8)
node7 = Node(7, left=None, right=node9)
node6 = Node(6, node4, node7)
queue = []
# 二叉树序列化
def serialize_tree(head, queue):
if head == None:
queue.append('#')
return
queue.append(str(head.val))
serialize_tree(head.left, queue)
serialize_tree(head.right, queue)
serialize_tree(node6, queue)
data = queue.pop(0)
root = Node(int(data))
p = root
# 二叉树反序列化
def deserialize_tree(head, p, queue):
if len(queue) == 0:
return
data = queue.pop(0)
if data != '#':
head.left = Node(int(data))
deserialize_tree(head.left, p, queue)
data = queue.pop(0)
if data != '#':
head.right = Node(int(data))
deserialize_tree(head.right, p, queue)
deserialize_tree(root, p, queue)
# root即为所要的二叉树
思路:序列化时,先序遍历二叉树写入queue中,无值就用"#"替代。反序列化时,递归取值,按照先序遍历的方式构造二叉树。
4.5 二叉树按层打印与ZigZag打印
题目:按层遍历,以及S型遍历
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node1 = Node(1)
node3 = Node(3)
node2 = Node(2, node1, node3)
node5 = Node(5)
node4 = Node(4, node2, node5)
node8 = Node(8)
node9 = Node(9, node8)
node7 = Node(7, left=None, right=node9)
node6 = Node(6, node4, node7)
# 按层次打印二叉树
def print_tree_layer(root):
queue = []
queue.append(root)
while len(queue) > 0:
node = queue.pop(0)
print(node.val)
if node.left != None:
queue.append(node.left)
if node.right != None:
queue.append(node.right)
print("------------层次遍历-----------")
print_tree_layer(node6)
# 按zigzag打印二叉树
def print_tree_zigzag(root):
stack1 = [] # 右左
stack2 = [] # 左右
stack1.append(root)
while len(stack1) > 0 or len(stack2) > 0:
while len(stack1) > 0:
node = stack1.pop()
print(node.val)
if node.left != None:
stack2.append(node.left)
if node.right != None:
stack2.append(node.right)
while len(stack2) > 0:
node2 = stack2.pop()
print(node2.val)
if node2.right != None:
stack1.append(node2.right)
if node2.left != None:
stack1.append(node2.left)
print("------------zigzag遍历-----------")
print_tree_zigzag(node6)
思路:层次遍历使用一个queue即可。S型遍历使用两个stack,stack1从右子树往左子树压入数据,弹出时打印,弹出后从左子树往右子树输入stack2;然后stack2弹出时打印,再压入stack1直到stack1和stack2都为空。
4.6 判断t1树是否包含t2树全部的拓扑结构
题目:给定彼此独立的两棵树头节点t1和t2,判断t1树是否包含t2树全部的拓扑结构
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node1_8 = Node(8)
node1_9 = Node(9)
node1_4 = Node(4, node1_8, node1_9)
node1_10 = Node(10)
node1_5 = Node(5, node1_10)
node1_2 = Node(2, node1_4, node1_5)
node1_6 = Node(6)
node1_7 = Node(7)
node1_3 = Node(3, node1_6, node1_7)
node1_1 = Node(1, node1_2, node1_3)
node2_8 = Node(8)
node2_4 = Node(4, node2_8)
node2_5 = Node(5)
node2_2 = Node(2, node2_4, node2_5)
def judge_contain(head1, head2):
if head2 == None:
return 1
if head1 == None and head2 != None:
return 0
if head1.val != head2.val:
left_flag = judge_contain(head1.left, head2)
right_flag = judge_contain(head1.right, head2)
return max(left_flag, right_flag)
else:
left_flag = judge_contain(head1.left, head2.left)
right_flag = judge_contain(head1.right, head2.right)
if left_flag == 1 and right_flag == 1:
return 1
else:
return 0
result = judge_contain(node1_2, node2_2)
print(result)
思路:递归return判断,t2树到达叶子节点则为true,t1树到达叶子节点而t2树未到达则为false。中间过程中,如果节点值不一样,则左子树和t2开始判断,右子树也和t2开始判断,有一个为true则为true;如果节点值一样,则逐一向下判断,只有其左右子树都为true时才为true。
4.7 判断二叉树是否为平衡二叉树
题目:平衡二叉树,要么是一棵空树,要么任何一个节点的左右子树高度差的绝对值不超过1。给定一棵二叉树的头节点head,判断这棵二叉树是否为平衡二叉树。
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node8 = Node(8)
node5 = Node(5, node8)
node4 = Node(4)
node2 = Node(2, node4, node5)
node6 = Node(6)
node7 = Node(7)
node3 = Node(3, node6, node7)
node1 = Node(1, node2, node3)
def get_height(head, height):
if head == None:
return 1
left_height = get_height(head.left, height + 1) if head.left != None else height
right_height = get_height(head.right, height + 1) if head.right != None else height
return max(left_height, right_height)
def judge_balance_tree(head, flag):
if head == None:
return 1
left_height = get_height(head.left, flag) if head.left != None else 0
right_height = get_height(head.right, flag) if head.right != None else 0
if abs(left_height - right_height) <= 1:
flag = min(1, flag)
else:
flag = min(0, flag)
return flag
result = judge_balance_tree(node1, 1)
print(result)
思路:函数一可以获取每个节点的高度。函数二通过左右子树的高度判断是否为平衡二叉树,只要有一个节点不是则整棵树都不是。
4.8 在二叉树中找到两个节点的最近公共祖先
题目:给定一棵二叉树的头节点head,以及这棵树中的两个节点o1和o2,青返回o1和o2的最近公共祖先节点。
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node4 = Node(4)
node5 = Node(5)
node2 = Node(2, node4, node5)
node8 = Node(8)
node7 = Node(7, node8)
node6 = Node(6)
node3 = Node(3, node6, node7)
node1 = Node(1, node2, node3)
map = {}
map.update({node1.val : []})
def preOrder(head, map):
if head == None:
return
print(head.val)
if head.left != None:
map.update({head.left.val : [head.val] + map.get(head.val)})
preOrder(head.left, map)
if head.right != None:
map.update({head.right.val : [head.val] + map.get(head.val)})
preOrder(head.right, map)
preOrder(node1, map)
def find_nearest_ancetor(head1, head2):
path1 = map.get(head1.val)
path2 = map.get(head2.val)
for data1 in path1:
for data2 in path2:
if data1 == data2:
return data1
result = find_nearest_ancetor(node6, node8)
print('---------ancetor--------------')
print(result)
思路:先序遍历二叉树,构造一个dict,key为某个节点,value为key对应的所有按顺序的祖先列表。寻找两个节点的祖先时,就是依次在两个列表中。
4.9 二叉树节点间的最大距离
题目:从二叉树的节点A出发,可以向上或者向下走,但沿途的节点只能经过一次,当到达节点B时,路径上的节点数叫做A到B的距离。
class Node:
def __init__(self, val, left=None, right=None):
self.val = val
self.left = left
self.right = right
node4 = Node(4)
node5 = Node(5)
node2 = Node(2, node4, node5)
node6 = Node(6)
node7 = Node(7)
node3 = Node(3, node6, node7)
node1 = Node(1, node2, node3)
map = {}
map.update({node1.val : []})
def preOrder(head, map):
if head == None:
return
print(head.val)
if head.left != None:
map.update({head.left.val : [head.val] + map.get(head.val)})
preOrder(head.left, map)
if head.right != None:
map.update({head.right.val : [head.val] + map.get(head.val)})
preOrder(head.right, map)
preOrder(node1, map)
def find_node_distance(head1, head2):
path1 = [head1.val] + map.get(head1.val)
path2 = [head2.val] + map.get(head2.val)
for i in range(1, len(path1) + 1):
for j in range(0, len(path2)):
if path1[i - 1] == path2[j]:
return i + j
print('----------distance------------')
result = find_node_distance(node5, node6)
print(result)
思路:先找到两点间的最近公共祖先,然后计算两个节点到公共祖先的距离再求和。
4.10 通过先序和中序数组生成后序数组
题目:一颗二叉树所有节点值都不同,给定这棵树的先序和中序数组,不要重建整棵树直接生成后序数组。
pre_order = [1,2,4,5,8,3,6,7]
mid_order = [4,2,8,5,1,6,3,7]
post_order = []
def find_post_order(pre_order, mid_order, post_order):
if pre_order == None or len(pre_order) == 0:
return
first_elem = pre_order[0]
post_order.append(first_elem)
for i in range(len(mid_order)):
if first_elem == mid_order[i]:
break
pre_left = pre_order[1 : i + 1]
pre_right = pre_order[i+1:]
mid_left = mid_order[0 : i]
mid_right = mid_order[i+1:]
find_post_order(pre_right, mid_right, post_order)
find_post_order(pre_left, mid_left, post_order)
find_post_order(pre_order, mid_order, post_order)
post_order.reverse()
print(post_order)
思路:通过先序找到head节点,然后在中序中根据head分成左右两边,然后先序的右边和中序的右边继续递归,接着先序的左边和中序的左边继续递归。
5 递归和动态规划
5.1 斐波那契数列问题
题目:给定整数N,代表台阶数,一次可以跨2个或1个台阶,有多少种走法。
def calc_step(n):
if n == 0:
return 0
elif n == 1:
return 1
elif n == 2:
return 2
else:
return calc_step(n-1) + calc_step(n-2)
result = calc_step(5)
print(result)
思路:先确定前两步的走法数量,后续就是前一步跳1个台阶或者前两步跳2个台阶,所以是前一步走法+前两步走法。
5.2 矩阵的最小路径和
题目:给定一个矩阵m,从左上角开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。
# 时间复杂度为O(MN)2
def get_min_path_num(matrix, i, j):
if i == 0 and j == 0:
path = matrix[i][j]
return path
elif i == 0 and j > 0:
path = get_min_path_num(matrix, i, j - 1) + matrix[i][j]
return path
elif i > 0 and j == 0:
path = get_min_path_num(matrix, i - 1, j) + matrix[i][j]
return path
else:
up = get_min_path_num(matrix, i - 1, j)
left = get_min_path_num(matrix, i, j - 1)
path = min(up, left) + matrix[i][j]
return path
# 时间复杂度为O(MN)
def get_min_path_num2(matrix):
dp = [[-1 for j in range(len(matrix[0]))] for i in range(len(matrix))]
dp[0][0] = matrix[0][0]
for i in range(1, len(matrix[0])):
dp[0][i] = dp[0][i-1] + matrix[0][i]
for j in range(1, len(matrix)):
dp[j][0] = dp[j-1][0] + matrix[j][0]
for i in range(1, len(matrix)):
for j in range(1, len(matrix[0])):
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + matrix[i][j]
return dp[len(matrix)-1][len(matrix[0])-1]
matrix = [[1,3,5,9], [8,1,3,4], [5,0,6,1], [8,8,4,0]]
result = get_min_path_num(matrix, len(matrix) - 1, len(matrix[0]) - 1)
print(result)
result2 = get_min_path_num2(matrix)
print(result2)
思路:先确定左上角的值,然后确定第一排和第一列的值,其余的值通过找上面和左边的最小值得到。我这边有两种方法,其中第一种方法时间复杂度为O(MN)2,第二种方法时间复杂度为O(MN)。
备注:递归的时间复杂度=递归的次数 * 每次递归中的操作次数。
5.3 换钱的最少货币数
题目:给定数组arr,arr中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,代表要找的钱数,求组成aim的最少货币数。
arr = [5, 2, 3]
aim = 20
def find_least_money_num(arr, aim):
dp = [-1] * (aim + 1)
dp[0] = 0
for data in arr:
if data <= aim:
dp[data] = 1
for i in range(aim + 1):
for j in arr:
if i - j >= 0 and dp[i-j] >= 0:
if dp[i] != -1:
dp[i] = min(dp[i-j] + 1, dp[i])
else:
dp[i] = dp[i-j] + 1
result = dp[aim] if dp[aim] != None else -1
return result
result = find_least_money_num(arr, aim)
print(result)
思路:动态规划方法,从0到aim逐一求解最优质。
动态规划(避免暴力递归)的套路步骤:
1)找到什么可变参数可以代表一个递归状态,也就是哪些参数一旦确定,返回值就确定了。
2)把可变参数的所有组合映射成一张表,有1个可变参数就是一维表,2个可变参数就是二维表...
3)最终答案要的是表中的哪个位置,在表中标出。
4)根据递归过程的base case,把这张表最简单、不需要依赖其他位置的那些位置填好值。
5)填好表,返回最终答案在表中位置的值。
5.4 机器人到达指定位置方法数
题目:假设有排成一行的N个位置,记为1~N,N一定大于或等于2。开始时机器人在其中的M位置上,机器人可以往左走或往右走,如果机器人来到1位置,那么下一步只能往右来到2位置;如果机器人来到N位置,那么下一步只能往左来到N-1位置,规定机器人必须走K步,最终能来到P位置的方法有多少种。给定四个参数N、M、K、P,返回方法数。
# 暴力递归法,时间复杂度过高
method_num = 0
def find_walk_method_num(n, m, k, p, cur_pos):
global method_num
if k == 0:
if cur_pos == m:
method_num += 1
return
if cur_pos == 1:
find_walk_method_num(n, m, k - 1 ,p, 2)
elif cur_pos == n:
find_walk_method_num(n, m, k - 1, p, n - 1)
else:
find_walk_method_num(n, m, k - 1, p, cur_pos - 1)
find_walk_method_num(n, m, k - 1, p, cur_pos + 1)
# 动态规划方法,构造二维数组逐一求解
def find_walk_method_num2(n, m, k, p):
dp = [[0 for j in range(n)] for i in range(k + 1)]
dp[0][p - 1] = 1
for i in range(1, k + 1):
for j in range(n):
if j == 0:
dp[i][j] = dp[i - 1][j + 1]
elif j == n - 1:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j + 1]
return dp[k][m - 1]
print("--------------method1------------------")
find_walk_method_num(7, 4, 9, 5, 5)
print(method_num)
print("--------------method2------------------")
result = find_walk_method_num2(7, 4, 9, 5)
print(result)
思路:找到可变参数 “当前位置” 和 “剩余步数”,然后构造二维数组,根据dp的套路计算出二维数组中所有的值。
5.5 换钱的方法数
题目:给定数组arr,arr中所有的值都为正数且不重复,每个值代表一种面值的货币,每种面值的货币可以使用任意张。再给定一个整数aim,代表要找的钱数,求换钱有多少种方法。
arr = [5, 10, 25, 1]
aim = 15
# 暴力递归
def change_money(arr, index, aim):
res = 0
if index == len(arr):
if aim == 0:
return 1
else:
return 0
for i in range(0, aim // arr[index] + 1, 1):
res += change_money(arr, index + 1, aim - arr[index] * i)
return res
print('---------------暴力求解法---------------------')
result = change_money(arr, 0, aim)
print(result)
# 记忆优化搜索
map = {}
def change_money2(arr, index, aim):
res = 0
global map
if index == len(arr):
if aim == 0:
return 1
else:
return 0
for i in range(0, aim // arr[index] + 1, 1):
backup_res = map.get(str(index + 1) + '_' + str(aim - arr[index] * i))
if backup_res != None:
res += backup_res
else:
cur_res = change_money2(arr, index + 1, aim - arr[index] * i)
map.update({str(index + 1) + '_' + str(aim - arr[index] * i): cur_res})
res += cur_res
return res
print('---------------记忆优化搜索---------------------')
result = change_money2(arr, 0, aim)
print(result)
思路:第一种方法,就是每种货币组合的穷举,当第一张货币n为m张时,用其他货币组成aim-n*m的方法数,不停的递归。
第二种方法就是增加了记忆方法,解决第一种方法在递归时重复计算的问题。比如aim=1000,arr=[5,10,20,50],2张5元和0张10元 或者 0张5元1张10元,都需要计算剩余20、50组成990元的方法数。
5.6 最长递增子序列
题目:给定数组arr,返回arr的最长递增子序列。
arr = [2,1,5,3,6,4,8,9,7]
def find_longest_ascend(arr):
dp = [0] * len(arr)
dp[0] = 1
map = {}
biggest_index = 0
biggest_value = 0
res = []
for i in range(1, len(arr)):
max_num = 0
for j in range(i):
if arr[j] < arr[i] and dp[j] >= max_num:
max_num = dp[j]
map.update({i : j})
dp[i] = max_num + 1
if dp[i] >= biggest_value:
biggest_value = dp[i]
biggest_index = i
index = biggest_index
while index != None:
res.append(arr[index])
index = map.get(index)
res.reverse()
return res
result = find_longest_ascend(arr)
print(result)
思路:构造dp数组,每个代表以当前点结尾的最长子序列长度。每次计算时,遍历当前值(值1)之前的所有值,找到比值1小且最接近的值2,让值1做值2的后面一位数,值1的dp值为值2对应的dp值+1。
5.7 最长公共子序列问题
题目:给定两个字符串str1和str2,返回两个字符串的最长公共子序列。
str1 = '1A2C3D4B56'
str2 = 'B1D23CA45B6A'
# dp not pefect版本
def find_same_str(str1, str2):
len1 = len(str1)
len2 = len(str2)
biggest_len = 0
res = []
dp = [[0 for j in range(len1)] for i in range(len2)]
for i in range(len1):
if str2[0] == str1[i]:
dp[0][i] = 1
for i in range(len2):
if str1[0] == str2[i]:
dp[i][0] = 1
for i in range(0, len2):
for j in range(0, len1):
if str2[i] == str1[j]:
max_num = 0
for ii in range(i):
for jj in range(j):
max_num = max(dp[ii][jj], max_num)
dp[i][j] = max_num + 1
if dp[i][j] > biggest_len:
biggest_len = dp[i][j]
for i in range(0, len2):
for j in range(0, len1):
if dp[len2 - i - 1][len1 - j - 1] == biggest_len:
res.append(str2[len2 - i - 1])
biggest_len -= 1
break
if biggest_len == 0:
break
res.reverse()
return ''.join(res)
print('----------动态规划非完美版-----------------')
result = find_same_str(str1, str2)
print(result)
def find_same_str2(str1, str2):
len1 = len(str1)
len2 = len(str2)
res = []
dp = [[0 for j in range(len1)] for i in range(len2)]
for i in range(len1):
dp[0][i] = (dp[0][i-1] + 1 if i > 0 else 1) if str2[0] == str1[i] else dp[0][i-1]
for i in range(1, len2):
for j in range(len1):
if str2[i] == str1[j]:
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) + 1 if j > 0 else dp[i-1][j] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1]) if j > 0 else dp[i-1][j]
biggest_num = dp[len2-1][len1-1]
for i in range(len2):
for j in range(len1):
if dp[len2-i-1][len1-j-1] == biggest_num:
if str2[len2-i-1] == str1[len1-j-1]:
res.append(str2[len2-i-1])
biggest_num -=1
else:
break
res.reverse()
return ''.join(res)
print('----------动态规划完美版-----------------')
result = find_same_str2(str1, str2)
print(result)
思路:
方法1:构建dp二维数组,第一行和第一行则值相等时设为1。然后从(2,2)位置开始遍历,每次取以(0,0)点为左上角,以当前点为左下角的矩形区域内较大值+1得到当前dp的值。dp构建完毕后,从dp数组右下角开始搜索需要的元素。
方法2:构建dp二维数组,初始化第一行,后续每行元素的值取dp数组上边和左边最大值,如果字符相等再加1。dp构建完毕后,从右下角开始搜索,每次找字符相等的元素。
5.8 最长公共子串问题
题目:给定两个字符串str1和str2,返回两个字符串的最长公共子串。
str1 = '1AB2345CD'
str2 = '12345EF'
def find_common_substring(str1, str2):
len1 = len(str1)
len2 = len(str2)
biggest_num = 0
res = []
dp = [[0 for j in range(len1)] for i in range(len2)]
for i in range(len1):
if str2[0] == str1[i]:
dp[0][i] = 1
for i in range(1, len2):
for j in range(len1):
if j == 0 or str2[i] != str1[j]:
dp[i][j] = dp[i-1][j]
else:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1] + 1)
if dp[i][j] > biggest_num:
biggest_num = dp[i][j]
flag = 0
for i in range(len2):
for j in range(len1):
if dp[i][j] == biggest_num and flag == 0:
for k in range(biggest_num):
res.append(str2[i-k])
flag = 1
res.reverse()
return ''.join(res)
result = find_common_substring(str1, str2)
print(result)
思路:标准dp套路,第一排初始化时值相同就赋1,后续几排取“上方值”和“斜对角+1”的较大值。然后在dp二维数组中找到最大的值,返回一遍斜对角的值就可以了。
5.9 最小编辑代价
题目:给定两个字符串str1和str2,再给定三个整数ic、dc和rc,分别代表插入、删除和替换一个字符的代价,返回将str1和str2编辑成str2d额最小代价。
str1 = 'ab12cd3'
str2 = 'abcdf'
ic = 5
dc = 3
rc = 2
def calc_edit_cost(str1, str2, ic, dc, rc):
len1 = len(str1)
len2 = len(str2)
dp = [[0 for j in range(len2 + 1)] for i in range(len1 + 1)]
dp[0][0] = 0
for i in range(1, len2+1):
dp[0][i] = dp[0][i-1] + ic
for i in range(1, len1+1):
dp[i][0] = dp[i-1][0] + dc
for i in range(1, len1+1):
for j in range(1, len2+1):
rc_cost = dp[i-1][j-1] if str1[i-1] == str2[j-1] else dp[i-1][j-1] + rc
ic_cost = dp[i][j-1] + ic
dc_cost = dp[i-1][j] + dc
dp[i][j] = min(rc_cost, ic_cost, dc_cost)
return dp[len1][len2]
result = calc_edit_cost(str1, str2, ic, dc, rc)
print(result)
思路:构建dp数组,横纵维度各多出一个位置。每个位置,从上边删除得到,从左边插入得到,从斜对角替换得到(如果相同就不用替换了),每次寻找3个方向最小值得到dp值。
6 字符串问题
6.1 判断两个字符串是否互为旋转词
题目:如果一个字符串为str,把字符串str前面任意的部分挪到后面形成的字符串叫做str的旋转词。给定两个字符串a和b,判断a和b是否互为旋转词。
str1 = '2ab1'
str2 = 'ab12'
def is_rotate(str1, str2):
if len(str1) != len(str2):
return 0
str = str1 + str1
for i in range(len(str)):
for j in range(len(str2)):
if i+j < len(str) and str[i+j] != str2[j]:
break
if j == len(str2) - 1:
return 1
return 0
result = is_rotate(str1, str2)
print(result)
思路:将某一个字符串*2拼接在一起,然后判断另一个字符串是否在这个拼接的大字符串里面。
6.2 翻转字符串
题目:给定一个字符类型的数组chas,请在单词间做逆序调整。只要做到单词的顺序逆序即可,对空格的位置没有特别要求。
chas = ['d','o','g',' ','l','o','v','e','s',' ','p', 'i','g']
def reverse_word(chas):
left = 0
right = len(chas) - 1
while left < right:
temp = chas[left]
chas[left] = chas[right]
chas[right] = temp
left += 1
right -= 1
return chas
def reverse_chas(chas):
res = []
i = j = 0
reversed_chas = reverse_word(chas)
while j < len(reversed_chas):
if reversed_chas[j] != ' ':
j += 1
else:
new_words = reverse_word(reversed_chas[i:j])
res += new_words + [' ']
i = j + 1
j += 1
new_words = reverse_word(reversed_chas[i:j])
res += new_words
return res
result = reverse_chas(chas)
print(result)
思路:头尾两个指针实现整个数组翻转,然后再针对单个单词再翻转一次。
6.3 数组中两个字符串的最小距离
题目:给定一个字符串数组strs,再给定两个字符串str1和str2,返回在strs中str1和str2的最小距离,如果str1或str2为null,或不在strs中,返回-1。
strs = ['13', '3', '3', '23', '2', '3', '99', '13']
str1 = '13'
str2 = '23'
def find_min_distance(strs, str1, str2):
if str1 == None or str2 == None:
return -1
min_distance = -1
str1_index = str2_index = -1
for i in range(len(strs)):
if strs[i] == str1:
str1_index = i
if str2_index != -1:
distance = str1_index - str2_index
if min_distance == -1:
if str1_index != -1 and str2_index != -1:
min_distance = distance
elif min_distance > distance:
min_distance = distance
if strs[i] == str2:
str2_index = i
if str1_index != -1:
distance = str2_index - str1_index
if min_distance == -1:
if str1_index != -1 and str2_index != -1:
min_distance = distance
elif min_distance > distance:
min_distance = distance
return min_distance
result = find_min_distance(strs, str1, str2)
print(result)
思路:用两个index分别标记str1和str2在strs中的位置,每次识别到其中一个就开始计算两者间的距离,取最短距离即可。
6.4 字符串的转换路径问题
题目:给定两个字符串,记为start和to,再给定一个字符串列表list,list中一定包含to,list中没有重复字符串。所有的字符串都是小写的。规定start每次只能改变一个字符,最终的目标是彻底变成to,但是每次变成的新字符串必须在list中存在,请返回所有最短的变换路径。
start = 'abc'
end = 'cab'
list = ['cab', 'acc', 'cbc', 'ccc', 'cac', 'cbb', 'aab', 'abb']
total_path_list = []
# 计算两个字符串的差异个数
def calc_diff(str1, str2):
num = 0
for i in range(len(str1)):
if str1[i] != str2[i]:
num += 1
return num
# 求出所有的路径
def get_all_path(start, end, list, path_list):
if start == end:
total_path_list.append(path_list.copy())
return
if len(path_list) == 0:
path_list.append(start)
for str in list:
if calc_diff(start, str) == 1:
if str in path_list:
return
cur_path = path_list + [str]
get_all_path(str, end, list, cur_path)
# 求最短的路径
def get_shortest_path(start, end, list):
shortest_paths = []
min_path_num = -1
get_all_path(start, end, list, [])
for path in total_path_list:
if min_path_num == -1:
min_path_num = len(path)
elif min_path_num > len(path):
min_path_num = len(path)
for path in total_path_list:
if len(path) == min_path_num:
shortest_paths.append(path)
return shortest_paths
paths = get_shortest_path(start, end, list)
for path in paths:
print(path)
思路:首先通过查找start所有可能的下一个节点next,然后将next作为start继续查找下一个节点直到为end。通过递归操作找出所有的路径,并且避免陷入死循环。然后在所有路径中找到最短的路径。
6.5 括号字符串的有效性和最长有效长度
题目:给定一个括号字符串str,返回最长的有效括号子串。
def judge_valid_bracket(str):
stack = []
for i in range(len(str)):
if str[i] != '(' and str[i] != ')':
return 0
if str[i] == '(':
stack.append(str[i])
if str[i] == ')':
if len(stack) == 0:
return 0
chas = stack.pop()
if chas != '(' or chas == None:
return 0
if len(stack) == 0:
return 1
else:
return 0
def find_longest_valid_bracket(str):
size = len(str)
longest_num = 0
for i in range(size):
for j in range(i+1, size + 1):
str_new = str[i:j]
if judge_valid_bracket(str_new) == 1:
if j - i + 1 > longest_num:
longest_num = j - i
return longest_num
str = '()(()()('
result = find_longest_valid_bracket(str)
print(result)
思路:首先构造一个函数判断一个str是否为有效括号字符串,通过stack的方法。然后遍历字符串,通过两个指针控制start和end,依次判断是否为有效括号,找出最长的长度。
6.6 找到字符串的最长无重复字符子串
题目:给定一个字符串str,返回str的最长无重复字符子串的长度。
def find_distinct_len(str):
distinct_len = 0
letter_set = set()
for i in range(len(str)):
if str[i] not in letter_set:
letter_set.add(str[i])
distinct_len += 1
else:
return 0
return distinct_len
def find_longest_len(str):
longest_len = 0
for i in range(len(str)):
for j in range(i+1, len(str) + 1):
str_len = j - i
if str_len <= longest_len:
continue
sub_str = str[i : j]
distinct_len = find_distinct_len(sub_str)
if distinct_len > longest_len:
longest_len = distinct_len
return longest_len
str = 'abcd'
result = find_longest_len(str)
print(result)
思路:两个指针遍历数组,每个子串分别判断是否无重复,每次无重复的子串记录长度,下次遍历时如果子串长度小于该长度则不需要判断,算小小的优化。
6.7 最小包含子串的长度
题目:给定字符串str1和str2,求str1的子串中含有str2所有字符的最小子串长度
def judge_contain_str(str1, str2):
if len(str1) < len(str2):
return 0
i = 0
j = 0
while i < len(str1):
if str1[i] == str2[j]:
j += 1
if j == len(str2):
return 1
i += 1
return 0
def find_shortest_substr_len(str1, str2):
shortest_len = 0
for i in range(len(str1)):
for j in range(i+1, len(str1)+1):
if shortest_len > 0 and shortest_len <= j - i:
continue
sub_str = str1[i : j]
res = judge_contain_str(sub_str, str2)
if res == 1:
if shortest_len == 0 or shortest_len > j - i:
shortest_len = j - i
return shortest_len
str1 = '12345'
str2 = '344'
result = find_shortest_substr_len(str1, str2)
print(result)
思路:str2不是取set集合。移动两个指针,判断str2的指针是否到结尾来判断包含关系。
6.8 回文最少分割数
题目:给定一个字符串str,返回把str全部切成回文子串的最小分割数。
def judge_huiwen(str):
size = len(str)
for i in range(size // 2):
if str[i] != str[size - 1 - i]:
return 0
return 1
def find_huiwen_split_num(str):
if str == '' or str == None:
return 0
str_len = len(str)
split_num = 0
i = 0
while i < str_len:
for j in range(1, str_len + 1):
sub_str = str[i : str_len - j + 1]
if judge_huiwen(sub_str) == 1:
split_num += 1
i = str_len - j + 1
break
return split_num - 1
str = 'A'
result = find_huiwen_split_num(str)
print(result)
思路:首先构造判断回文的函数,然后逐步判断长度缩小的子串(从结尾开始缩小)是否为回文,最后仅剩一个字符时肯定为回文,下一轮迭代从上一个回文的下一个字符开始判断,继续逐步缩小子串再判断,直到字符串全部遍历完毕。
7 数组和矩阵问题
7.1 转圈打印矩阵
题目:给定一个整型矩阵matrix,请按照转圈的方式打印它。
matrix1 = [[1,2,3],[4,5,6], [7,8,9], [10,11,12]]
matrix2 = [[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15], [16,17,18,19,20]]
matrix3 = [[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16]]
def print_matrix(matrix):
width = len(matrix[0])
height = len(matrix)
min_len = max(width, height)
for k in range(min_len // 2):
for i in range(0, width - 1 - k * 2):
print(matrix[k][i + k])
for i in range(height - 1 - k * 2):
print(matrix[i + k][width - 1 - k])
for i in range(width - 1 - k * 2):
print(matrix[height - 1 - k][width - i - 1 - k])
for i in range(height - 1 - k * 2):
print(matrix[height - i - 1 - k][k])
print_matrix(matrix3)
思路:一圈一圈打印。
7.2 将正方形顺时针转动90度
题目:给定一个N*N的矩阵matrix,把这个矩阵调整成顺时针转动90度后的形式。
matrix = [[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16]]
def rotate_90(matrix):
size = len(matrix)
matrix_new = [[-1 for j in range(size)] for i in range(size)]
for i in range(size):
for j in range(size):
matrix_new[j][size - 1 - i] = matrix[i][j]
return matrix_new
result = rotate_90(matrix)
print(result)
思路:计算映射关系,(x,y) -> (y, N-1-x)
7.3 之字形打印矩阵
题目:给定一个矩阵matrix,按照“之”字形订地方式打印这个矩阵。
matrix = [[1,2,3,4], [5,6,7,8], [9,10,11,12]]
def print_matrix(matrix):
i = j = 0
height = len(matrix)
width = len(matrix[0])
print(matrix[i][j])
flag = 1
for sum in range(1, height + width - 1):
if flag == 1: # 正向
flag = -1
for i in range(sum + 1):
row = i
column = sum - i
if row < height and column < width:
print(matrix[row][column])
if row >= height:
break
else: # 反向
flag = 1
stack = []
for i in range(sum + 1):
row = i
column = sum - i
if row < height and column < width:
stack.append(matrix[row][column])
if row >= height:
break
while len(stack) > 0:
print(stack.pop())
print_matrix(matrix)
思路:全部按照从右上方到左下方的方向打印,分奇偶行,如果是偶数行就反方向输出。
7.4 找到无序数组中最小的k个数
题目:给定一个无序的整型数组arr,找到其中最小的k个数。
arr = [1,2,7,3,6,9,8,4,4,2,1,10,0]
k = 4
def find_min_k_number(arr, k):
stack1 = []
stack2 = []
for i in range(len(arr)):
if len(stack1) == 0:
stack1.append(arr[i])
else:
if stack1[-1] > arr[i]:
while len(stack1) > 0 and stack1[-1] > arr[i]:
stack2.append(stack1.pop())
stack1.append(arr[i])
while len(stack1) < k and len(stack2) > 0:
stack1.append(stack2.pop())
stack2.clear()
else:
if len(stack1) < k:
stack1.append(arr[i])
return stack1
result = find_min_k_number(arr, k)
print(result)
思路:通过两个stack模拟最大堆,每次对比栈顶元素,大于则直接忽略,小于则替换堆里面的元素。
7.5 需要排序的最短子数组长度
题目:给定一个无序数组arr,求出需要排序的最短子数组长度。
arr = [1,5,3,4,2,6,7]
def get_shortest_length(arr):
min_index = max_index = -1
arr_min = arr[-1]
arr_max = arr[0]
for i in range(1, len(arr)):
if arr[i] >= arr_max:
arr_max = arr[i]
else:
max_index = i
for i in range(len(arr) - 1, 0, -1):
if arr[i] <= arr_min:
arr_min = arr[i]
else:
min_index = i - 1
return max_index - min_index
result = get_shortest_length(arr)
print(result)
思路:找出错误位置的最左边和最右边,从右往左遍历,遍历的过程记录右侧出现过的数的最小值,记录排序出错的最左边的位置。从左往右遍历,遍历的过程记录左侧出现过的数的最大值,记录排序出错的最右边的位置。
7.6 最长的可整合子数组的长度
题目:给定一个整型数组arr,请返回其中最大可整合子数组的长度。
arr = [5,5,3,2,6,4,3]
# 判断是否为整合数组
def judge_combine(arr):
arr_max = arr_min = arr[0]
data_set = set()
data_set.add(arr[0])
for i in range(1, len(arr)):
if arr[i] in data_set:
return 0
if arr[i] > arr_max:
arr_max = arr[i]
if arr[i] < arr_min:
arr_min = arr[i]
if arr_max - arr_min == len(arr) - 1:
return 1
else:
return 0
def find_longest_length(arr):
longest_len = 0
for end in range(len(arr), 0 , -1):
for start in range(0, end):
distance = end - start
if distance <= longest_len:
break
sub_str = arr[start : end]
res = judge_combine(sub_str)
if res == 1 and distance > longest_len:
longest_len = distance
return longest_len
result = find_longest_length(arr)
print(result)
思路:可整合数组的判断,不能有重复,且最大值和最小值的差刚好是数组的长度。然后遍历数组,移动start和end指针判断是否为可整合数组,保留最大的长度。
7.7 未排序正数数组中累加和为给定值的最长子数组长度
题目:给定一个数组arr,该数组无序,但每个值均为正数,再给定一个正数k。求arr的所有子数组中所有元素相加和为k的最长子数组长度。
arr = [1,2,1,1,1]
k = 3
def find_longest_len(arr, k):
left = right = 0
arr_len = 0
while left < len(arr) and right < len(arr):
sub_str = arr[left : right + 1]
str_sum = sum(sub_str)
if str_sum == k:
if right - left + 1 > arr_len:
arr_len = right - left + 1
right += 1
elif str_sum < k:
right += 1
else:
left += 1
return arr_len
result = find_longest_len(arr, k)
print(result)
思路:两个指针left和right指定子串长度,如果子串和小于k则right右移一位,子串和大于k则left右移一位,如果和等于k则记录下长度。
7.8 未排序数组中累加和为给定值的最长子数组系列问题
题目:给定一个无序数组arr,其中元素可正、可负、可0。给定一个整数k,求arr所有的子数组中累加和为k的最长子数组长度。
arr = [-4,-2,1,-3,2,6,7]
k = 5
def find_longest_len(arr, k):
map = {}
sum = 0
longest_len = 0
for i in range(len(arr)):
sum += arr[i]
if map.get(sum - k) is not None:
str_len = i - map.get(sum - k)
if str_len > longest_len:
longest_len = str_len
if map.get(sum) is None:
map.update({sum : i})
return longest_len
result = find_longest_len(arr, k)
print(result)
思路:某一段子串arr[i...j]的和为arr[0...j]与arr[0...i]之间的差,构造map记录每个sum第一次出现的坐标,然后找到sum-k在arr中的位置。
变换题型:
(1)给定一个无序数组arr,其中元素可正、可负、可0。求arr所有的子数组中正数与负数个数相等的最长子数组长度。
(2)给定一个无序数组arr,其中元素只是1或0。求arr所有的子数组中0和1个数相等的最长子数组长度。
7.9 奇数下标都是奇数或者偶数下标都是偶数
题目:给定一个长度不大于2的数组arr,实现一个函数调整arr,要么让所有的偶数下标都是偶数,要么让所有的奇数下标都是奇数。
arr = [2,2,2,2,2,1,1]
def adjust_arr(arr):
arr_len = len(arr)
arr_new = [None] * arr_len * 2
arr_new2 = [None] * arr_len
q_index = 0
o_index = 1
for i in range(len(arr)):
if (i + 1) % 2 == 1: # 奇数位
if arr[i] % 2 == 1: # 是奇数
arr_new[q_index] = arr[i]
q_index += 2
else:
arr_new[o_index] = arr[i]
o_index += 2
else: # 偶数位
if arr[i] % 2 == 0: # 是偶数
arr_new[o_index] = arr[i]
o_index += 2
else:
arr_new[q_index] = arr[i]
q_index += 2
index = 0
for i in range(len(arr_new)):
if arr_new[i] != None:
arr_new2[index] = arr_new[i]
index += 1
return arr_new2
result = adjust_arr(arr)
print(result)
思路:构建1个长度是2*len的arr_new以及奇数位置指针和偶数位置指针,然后遍历原数组,在arr_new中奇数位放入奇数,偶数位放入偶数。最后在arr_new中去掉None就得到了调整后的数组。
7.10 子数组的最大累加和问题
题目:给定一个数组arr,返回子数组的最大累加和。
def find_biggest_sum(arr):
min_index = 0
max_index = 0
arr_min = arr[0]
arr_max = arr[0]
sum = arr[0]
biggest_sum = sum
for i in range(1, len(arr)):
sum += arr[i]
if sum > arr_max:
arr_max = sum
max_index = i
if sum < arr_min:
arr_min = sum
min_index = i
if max_index > min_index:
cur_biggest_sum = arr_max - arr_min
else:
cur_biggest_sum = arr_max
if cur_biggest_sum > biggest_sum:
biggest_sum = cur_biggest_sum
return biggest_sum
arr = [1,-2,3,5,-2,6,-1]
result = find_biggest_sum(arr)
print(result)
思路:arr[i...j]是最大累加和,则arr[...i]是最小累加和,arr[...j]是最大累加和,max_index一定要在min_index的后面才能最大值减最小值,否则只保留最大值。每移动一步,计算当前步的局部最大累加和,最后取全局的最大累加和。
7.11 数组中子数组的最大累乘积
题目:给定一个double类型的数组arr,其中的元素可正、可负、可0,返回子数组累乘积的最大乘积。
def find_max_product(arr):
arr_max = arr[0]
arr_min = arr[0]
res = arr[0]
for i in range(1, len(arr)):
max_end = arr_max * arr[i]
min_end = arr_min * arr[i]
arr_max = max(max_end, min_end, arr[i])
arr_min = min(max_end, min_end, arr[i])
res = max(res, arr_max)
return res
arr = [-2.5,4,0,3,0.5,8,-1]
result = find_max_product(arr)
print(result)
思路:不断地累乘积,最大值可能是(之前最小值*当前值(负值),之前最大值*当前值,当前值)的最大值。
7.12 不包含本位置值的累乘数组
题目:给定一个整型数组arr,返回不包含本位置得累乘数组。
arr1 = [2,3,1,4]
arr2 = [2,0,1,4]
arr3 = [2,0,1,0,4]
def get_cum_multiply(arr):
product = 1
non_zero_product = 1
zero_num = 0
arr_new = [0] * len(arr)
for i in range(len(arr)):
product *= arr[i]
if arr[i] != 0:
non_zero_product *= arr[i]
else:
zero_num += 1
if product != 0:
for i in range(len(arr)):
arr_new[i] = product / arr[i]
else:
for i in range(len(arr)):
if zero_num == 1 and arr[i] == 0:
arr_new[i] = non_zero_product
else:
arr_new[i] = 0
return arr_new
result = get_cum_multiply(arr3)
print(result)
思路:先得到所有值累乘得结果,然后分数组中0有0个、1个、1个以上的情况分别进行处理。
7.13 求最短通路值
题目:用1个整型矩阵matrix表示一个网络,1代表有路,0代表无路,每一个位置只要不越界,都有上下左右4个方向,求从最左上角到最右下角的最短通路值。
import sys
matrix = [[1,0,1,1,1], [1,0,1,0,1], [1,1,1,0,1], [0,0,1,0,1]]
matrix2 = [[1,0], [1,1]]
def find_shortest_num(matrix, i, j, path_set):
if i == len(matrix) - 1 and j == len(matrix[0]) - 1:
return 1
path_set.add(str(i) + '_' + str(j))
up_sum = down_sum = left_sum = right_sum = sys.maxsize
# 上
if i - 1 >= 0 and matrix[i - 1][j] == 1 and str(i - 1) + '_' + str(j) not in path_set:
up_sum = find_shortest_num(matrix, i - 1, j, path_set)
# 下
if i + 1 <= len(matrix) - 1 and matrix[i + 1][j] == 1 and str(i + 1) + '_' + str(j) not in path_set :
down_sum = find_shortest_num(matrix, i + 1, j, path_set)
# 左
if j - 1 >= 0 and matrix[i][j - 1] == 1 and str(i) + '_' + str(j - 1) not in path_set:
left_sum = find_shortest_num(matrix, i, j - 1, path_set)
# 右
if j + 1 <= len(matrix[0]) - 1 and matrix[i][j + 1] == 1 and str(i) + '_' + str(j + 1) not in path_set:
right_sum = find_shortest_num(matrix, i, j + 1, path_set)
shortest_sum = min(up_sum, down_sum, left_sum, right_sum) + 1
return shortest_sum
path_set = set()
shortest_sum = find_shortest_num(matrix, 0, 0, path_set)
print(shortest_sum)
思路:递归搜索,在每个点上查看上下左右:
(1)是否有1
(2)是否越界
(3)是否曾经到过(否则会陷入死循环)
如果没有则找到下一个点继续搜索,直到到达最右下角。
8、其他
8.1 求两个数的最大公约数
M = 10
N = 5
def gcd(M, N):
min_num = min(M, N)
max_num = 1
for i in range(2, min_num + 1):
if M % i == 0 and N % i == 0:
max_num = i
return max_num
max_num = gcd(M, N)
print(max_num)
def gcd2(M, N):
if N == 0:
return M
else:
return gcd2(N, M % N)
max_num = gcd2(M, N)
print(max_num)
思路:两种方法,方法一:从1开始逐一找公约数,比较费时。方法二:使用辗转相除法,如果q和r分别是m除以n的商及余数,即m=nq+r,那么m和n的最大公约数等于n和r的最大公约数。
8.2 有关阶乘的问题
题目:给定一个非负整数N,返回N!结果的末尾为0的数量。
# 原始方法
def get_zero_num(num):
if num < 0:
return 0
res = 0
for i in range(5, num + 1, 5):
cur = i
while cur % 5 == 0:
res += 1
cur /= 5
return res
# result = get_zero_num(1000000000)
# print(result)
# 进阶方法
def get_zero_num2(num):
if num < 0:
return 0
res = 0
cur = 5
while cur < num:
res += int(num / cur)
cur *= 5
return res
result2 = get_zero_num2(1000000000)
print(result2)
思路:0的个数和因子5的个数相关。方法1是从5到num(每一步加5)分别计算每个数因子5的个数。方法2中,分别计算1个因子5的数量,2个因子5(25)的数量,3个因子5(125)的数量...,然后数量累加即可。
8.3 判断一个点是否在矩形内部
题目:在二维坐标系中,给定4个点代表的矩形,再给定一个点(x,y),判断(x,y)是否在矩形中。
def judge_in_rectangle(x1, y1, x2, y2, x3, y3, x4, y4, x, y):
res1 = (x - x1) * (x4 - x1) + (y - y1) * (y4 - y1)
res2 = (x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)
res3 = (x - x3) * (x4 - x3) + (y - y3) * (y4 - y3)
res4 = (x - x3) * (x2 - x3) + (y - y3) * (y2 - y3)
if res1 >= 0 and res2 >= 0 and res3 >= 0 and res4 >= 0:
return 1
else:
return 0
result = judge_in_rectangle(0, 2, 2, 2, 2, 0, 0, 0, 2.1, 1.9)
print(result)
思路:
8.4 判断一个点是否在三角形内部
题目:给定3个点代表三角形,再给定一个点(x,y),判断(x,y)是否在三角形中。
import math
def get_len(x1, y1, x2, y2):
return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
def get_area(x1, y1, x2, y2, x3, y3):
a = get_len(x1, y1, x2, y2)
b = get_len(x1, y1, x3, y3)
c = get_len(x2, y2, x3, y3)
p = (a + b + c) / 2
return math.sqrt(p * (p - a) * (p - b) * (p - c))
def similar(a, b):
if abs(a - b) <= 0.00001:
return 1
def judge_in_side(x1, y1, x2, y2, x3, y3, x, y):
s = get_area(x1, y1, x2, y2, x3, y3)
s1 = get_area(x1, y1, x2, y2, x, y)
s2 = get_area(x1, y1, x3, y3, x, y)
s3 = get_area(x2, y2, x3, y3, x, y)
s_sum = s1 + s2 + s3
if (s_sum > s and similar(s_sum, s) == 1) or s_sum <= s:
return 1
else:
return 0
result = judge_in_side(0, 0, 1, 1, 2, 0, 1, 0.5)
print(result)
思路:利用海伦公式可以根据边长计算三角形面积,如果点在三角形内部则理论上点和三组三角形两个点构成的三角形面积之和等于原三角形面积,计算上考虑double精度的情况。
8.5 设计有setAll功能的哈希表
题目:哈希表常见的三个操作put、get和containsKey时间复杂度为O(1)。现在想加一个setAll功能,就是把所有记录的value都设成统一的值,时间复杂度也是O(1)。
import time
class Data:
def __init__(self, data):
self.data = data
self.one_time = time.time()
class MyValue:
my_dict = {}
one_time = None
one_value = None
def put(self, key, value):
one_data = Data(value)
self.my_dict.update({key : one_data})
def containsKey(self, key):
if key in self.my_dict:
return 1
else:
return 0
def setAll(self, value):
self.one_time = time.time()
self.one_value = value
def get(self, key):
if self.containsKey(key) == 0:
return None
my_data = self.my_dict.get(key)
if self.one_time is None or my_data.one_time > self.one_time:
return my_data.data
else:
return self.one_value
my_value = MyValue()
print(my_value.get("haha"))
my_value.put("haha", 10)
my_value.put("hoho", 10)
print(my_value.get("haha"))
my_value.setAll(20)
print(my_value.get("haha"))
print(my_value.get("hoho"))
思路:用记录每条数据的生成时间,setAll可以设置全局变量的时间,如果每条记录的时间大于全局时间则返回自己的value,否则返回全局变量的value。
8.6 最大的leftMax与rightMax之差的绝对值
题目:给定一个整型数组arr,可以划分成左右两个部分。求这么多划分方案中,左部分中的最大值减去右部分最大值的绝对值,最大是多少?
def get_max_difference(arr):
arr_len = len(arr)
left_max_arr = [-1] * arr_len
right_max_arr = [-1] * arr_len
cur_left_max = -1
for i in range(arr_len):
cur_left_max = max(cur_left_max, arr[i])
left_max_arr[i] = cur_left_max
cur_right_max = -1
for i in range(arr_len - 1, -1, -1):
cur_right_max = max(cur_right_max, arr[i])
right_max_arr[i] = cur_right_max
cur_max = -1
for i in range(1, arr_len - 1):
diff = abs(left_max_arr[i] - right_max_arr[i])
cur_max = max(cur_max, diff)
return cur_max
arr = [2,7,3,1,1]
result = get_max_difference(arr)
print(result)
思路:两个数组,数组一是在从左往右遍历时记录累计最大值,数组二是在从右往左遍历时记录累计最大值,最后遍历时计算数组一和数组二的差后取最大的差值。
8.7 累加出整个范围所有的数最少还需几个数
题目:给定一个有序的正数数组arr和一个正数range,如果可以自由选择arr中的数字,想累加得到1~range范围上所有的数,返回arr最少还缺几个数。
arr = [3,17,21,78]
K = 67
def find_num(arr, K):
lack_num_arr = []
cur_max = 0
for num in arr:
while cur_max < num - 1:
lack_num_arr.append(cur_max + 1)
cur_max = cur_max + cur_max + 1
cur_max += num
if cur_max >= K:
break
while cur_max < K:
lack_num_arr.append(cur_max + 1)
cur_max = cur_max + cur_max + 1
return lack_num_arr
result = find_num(arr, K)
print(result)
print(len(result))
思路:如果已经搞定了1~touch范围上的所有数,下一个缺的数就是touch+1,有了touch+1之后,就可以搞定1~touch+touch+1范围上的所有数。随着touch的扩大,最终会达到或超过range。
8.8 认识过滤器
题目:不安全网页的黑名单包含100亿个黑名单网页,每个网页的URL最多占用64B。现在想要实现一个网页过滤系统,利用该系统可以根据网页的URL判断该网页是否在黑名单上。
答:利用布隆过滤器。
它由很长的二进制向量和一系列(多个)hash函数组成,主要用于判断一个元素是否在一个集合中(存在一定的误差)。hash函数可以调用包中的hash方法,通过不同的seed可以得到一系列的hash函数。通过hash值对向量长度取余数可以得到hash对应向量的位置信息。
添加元素:
(1)将要添加的元素给K个哈希函数
(2)得到对应于位数组上的k个位置
(3)将这k个位置设为1
查询元素:
(1)将要查询的元素给k个哈希函数
(2)得到对应于位数组上的k个位置
(3)如果k个位置有一个为0,则肯定不在集合中
(4)如果k个位置全部为1,则可能在集合中。
8.9 只用2GB内存在20亿个整数中找到出现次数最多的数
题目:有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数。
答:哈希表存放32位整数的结果,则key占用4B,value也是4B,则哈希表中一条记录需要占用8B。当哈希表记录数为20亿个时,需要至少16GB的内存。
解决方法是把20亿个数的大文件用哈希函数分成16个小文件,则同一种数不可能被散列到不同的小文件上,同事每个小文件中不同的数一定不会大于2亿种(1.6GB内存)。然后对每个小文件用哈希表来统计其中每种数出现的次数,这样就得到了16个小文件中各自出现次数最多的数,还有各自的次数统计。接下来只要选出这16个小文件各自的第一名中谁出现的次数最多即可。
8.10 40亿个非负整数中找到未出现的数
题目:32位无符号整数的范围是0~4294967295,现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然有未出现过的数。可以使用最多1GB的内存,怎么找到所有未出现过的数?
答:假如使用哈希表记录40亿条,存一个32位整数需要4B,则最差情况需要40亿*4B=160亿字节=16GB。解决方案是使用bit map存放出现过的数,申请一个长度为4294967295的bit类型的数组bitArr,8个bit为1B,此时占用16GB/32=500MB。然后遇到某个数,就把bitArr相应位置的值设置为1。最后遍历完bitArr之后,没出现过的数就都可以找出来了。
8.11 找到100亿个URL中重复的URL
题目:有一个包含100亿个URL的大文件,假设每个URL占用64B,请找出其中所有重复的URL。
答:把大文件通过哈希函数分配到机器上,或者通过哈希函数把大文件拆分成小文件,然后处理每一个小数量的集合的重复文件。
8.12 搜索词汇的TOP K问题
题目:某搜索公司一天的用户搜索词汇是海量的,请设计一种求出每天热门top 100词汇的可行办法:
答:哈希分流 + 堆排序