《剑指Offer》中的一些常见练习题,包含二叉树、链表以及其他的一些常见算法练习题;最近又系统性地做了下,大致整理了一下解题思路,均用Python实现,持续更新中…
1. 二叉树 首先需要定义好二叉树的结构,后续所有关于二叉树的算法默认其已经定义好对应的树结构,所以在节点处有val
、left
、right
属性。
1.1 定义树节点 定义树节点一般如下:
1 2 3 4 5 class TreeNode (object ): def __init__ (x ): self.val = x self.left = None self.right = None
1.2 根据序列生成一颗树 生成树的方法有很多种,通常用到的一般是根据前序遍历的结果生成树 和广度优先遍历结果的结果生成树 。
1 2 3 4 5 6 7 8 9 10 11 def create_tree_by_list_1 (arr ): if len (arr) <= 0 : return None val = arr.pop(0 ) root = None if val != "#" : root = TreeNode(int (val)) root.left = create_tree_by_list_1(arr) root.right = create_tree_by_list_1(arr) return root
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def create_tree_by_list_2 (root, arr, i ): if i < len (arr): if arr[i] == "#" : return None else : root = TreeNode(arr[i]) root.left = create_tree_by_list_2(root, arr, 2 * i + 1 ) root.right = create_tree_by_list_2(root, arr, 2 * i + 2 ) return root else : return None
1.2.1 二叉树序列化和反序列化 序列化:将二叉树序列化为一组元素;反序列化:根据一组元素生成一颗二叉树。
1 2 3 4 5 6 7 8 9 10 11 12 13 def serialize (root ): """ 按照前序遍历顺序,将二叉树序列化为一组元素 """ if not root: return "#" return str (root.val) + "," + serialize(root.left) + "," + serialize(root.right) def deserialize (s ): serialized_list = s.split("," ) create_tree_by_list_1(serialized_list)
1.3 前中后序遍历 均分为递归实现和非递归实现。
1.3.1 前序遍历 前序遍历顺序: root —> left —> right。
1 2 3 4 5 6 7 8 9 10 def pre_order_traversal_1 (root ): """ 递归实现二叉树前序遍历 """ if not root: return None result.append(root.val) pre_order_traversal_1(root.left) pre_order_traversal_1(root.right)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def pre_order_traversal_2 (root ): """ 根据节点入栈出栈进行二叉树前序遍历 """ queue = [root] order = [] while len (queue): tmp = queue.pop() order.append(tmp.val) if tmp.right: queue.append(tmp.right) if tmp.left: queue.append(tmp.left) return order
1.3.2 中序遍历 中序遍历顺序:left —> root —> right。
1 2 3 4 5 6 7 8 9 10 def in_order_traversal_1 (root ): """ 递归实现二叉树中序遍历 """ if not root: return None in_order_traversal_1(root.left) result.append(root.val) in_order_traversal_1(root.right)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def in_order_traversal_2 (root ): """ 非递归实现二叉树中序遍历 """ order = [] if not root: return order queue = [] cur = root while cur or len (queue): while cur: queue.append(cur) cur = cur.left node = queue.pop() order.append(node.val) while not node.right and len (queue): node = queue.pop() order.append(node.val) cur = node.right return order
1.3.3 后序遍历 后序遍历顺序: left —> right —> root。
1 2 3 4 5 6 7 8 9 10 def post_order_traversal_1 (root ): """ 递归实现二叉树后序遍历 """ if not root: return None post_order_traversal_1(root.left) post_order_traversal_1(root.right) result.append(root.val)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def post_order_traversal_2 (root ): """ 非递归实现二叉树后徐遍历: 将前前序遍历顺序由root-->left-->right更改为root-->right-->left,再将结果转置一下,即得到left-->right-->root """ queue = [root] order = [] while len (queue): tmp = queue.pop() order.append(tmp.val) if tmp.left: queue.append(tmp.left) if tmp.right: queue.append(tmp.right) order.reverse() return order
1.4 BFS 和 DFS 1.4.1 BFS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def bfs (root ): if not root: return [] queue = [root] order = [] while len (queue): tmp = queue.pop(0 ) order.append(tmp.val) if tmp.left: queue(tmp.left) if tmp.right: queue(tmp.right) return order
1.4.2 DFS 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def dfs (root ): if not root: return [] queue = [root] order = [] while len (queue): tmp = queue.pop() order.append(tmp.val) if tmp.right: queue.append(tmp.right) if tmp.left: queue.append(tmp.left) return order
1.4.3 将二叉树打印成多行 将二叉树打印成多行,其实就是一个BFS:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def print_tree (root ): if not root: return [] queue = [root] order = [] while len (queue): size = len (queue) tmp = [] for i in queue: tmp.append(i.val) order.append(tmp) for _ in range (size): node_now = queue.pop(0 ) if node_now.left: queue.append(node_now.left) if node_now.right: queue.append(node_now.right) return order
1.4.4 将二叉树按”之”字形打印 按照”之”字形打印二叉树,奇数行从左往右打印,偶数行从右往左打印;方法与1.4.3一样,加一步判断层数的奇偶性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def print_tree (root ): if not root: return [] queue = [root] order = [] while len (queue): size = len (queue) tmp = [] for i in queue: tmp.append(i.val) order.append(tmp) for _ in range (size): node_now = queue.pop(0 ) if node_now.left: queue.append(node_now.left) if node_now.right: queue.append(node_now.right) order = [t if idx % 2 == 0 else t[::-1 ] for idx, t in enumerate (order)] return order
1.5 中序遍历下的下一个节点 给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
考虑如下:
有右子树的:下一个节点就是其右子树的最左边节点;
没有右子树的: a. 是父节点的左子节点,那么父节点就是其下一个节点 b. 是父节点的右子节点,找他的父节点的父节点的父节点…,直到当前节点是父节点的左节点,则返回当前节点的父节点 (如果没有,则为尾节点)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def get_next_node (root ): if not root: return None if root.right: node = root.right while node.left: node = node.left return node while root.father: tmp = root.father if tmf.left = root: return tmp root = tmp return None
1.6 二叉树的深(高)度 输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
1 2 3 4 5 6 7 def tree_depth (root ): if not root: return 0 left = tree_depth(root.left) right = tree_depth(root.right) return max (left, right) + 1
1.6.1 二叉树最小路径 与求深度不一样的是,求树是根节点到叶子节点的最小路径长度:
1 2 3 4 5 6 7 8 def tree_min_depth (root ): if not root: return 0 left = tree_depth(root.left) right = tree_depth(root.right) if left == 0 or right == 0 : return left + right + 1 return min (left, right) + 1
1.6.2 判断是否是平衡二叉树 输入一棵二叉树,判断该二叉树是否是平衡二叉树。
如果左子树和右子树深度相等则是平衡二叉树: a. 遍历每一个节点,根据获取深度的递归函数,根据该节点的左右子树高度差判断是否平衡,然后递归地对左右子树进行判断 b. 上述做法有个缺点是,在判断上层节点时,会重复遍历下层节点;如果改为从下往上遍历,如果子树是平衡数,则返回其高度;否则直接停止迭代。
1 2 3 4 5 6 def is_balanced_tree (root ): if root is None : return True if abs (tree_depth(root.left) - tree_depth(root.right) > 1 ): return False return is_balanced_tree(root.left) and is_balanced_tree(root.right)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 def is_balanced_tree_2 (root ): """ 从下往上判断,如果子树是平衡数,则返回其高度;否则直接迭代返回False """ return get_depth_of_tree(root) != -1 def get_depth_of_tree (root ): """ 返回-1,代表不是平衡树 """ if root is None : return 0 left = get_depth_of_tree(root.left) if left == -1 : return -1 right = get_depth_of_tree(root.right) if right == -1 : return -1 return -1 if abs (left - right) > 1 else max (left, right) + 1
1.7 获取二叉树的镜像 获取二叉树的镜像。根据迭代,不断交换其左右子树即可。
1 2 3 4 5 6 7 8 9 def get_mirror_of_tree (root ): if root is not None : root.left, root.right = root.right, root.left get_mirror_of_tree(root.left) get_mirror_of_tree(root.right) return root
1.7.1 对称二叉树 请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
1 2 3 4 5 6 7 8 9 10 11 12 def solution (root ): return judge_same_tree(root, root) def judge_same_tree (tree1, tree2 ): if tree1 is None and tree2 is None : return True if tree1 is None or tree2 is None : return False if tree1.val != tree2.val: return False judge_same_tree(tree1.left, tree2.right) and judge_same_tree(tree1.right, tree2.left)
1.8 根据前序和中序遍历结果重建二叉树 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def reconstruct_tree (pre, tin ): """ pre: 前序遍历结果 tin: 中序遍历结果 首先根据前序遍历序列中的第一个节点获取到当前树的root节点 再从中序遍历序列中找到root节点对应的idx,该idx以前的便是左子树的节点,以后的便是右子树的节点 递归即可 """ if not pre or not tin: return None root = TreeNode(pre.pop(0 )) idx = tin.index(root.val) root.left = reconstruct_tree(pre, tin[:idx]) root.right = reconstruct_tree(pre, tin[idx+1 :]) return root
1.9 判断子树 输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def solution (root1, root2 ): if not root1 or not root2: return None return judge(root1, root2) or judge(root1.left, root2) or judge(root1.right, root2) def judge (main_tree, sub_tree ): if not sub_tree: return True if not main_tree or main_tree.val != sub_tree.val: return False return judge(main_tree.left, sub_tree.left) and judge(main_tree.right, sub_tree.right)
1.10 二叉树判断路径和 在二叉树中判断路径和是否定于一个数;路径和定义为从root到leaf节点的和;
1 2 3 4 5 6 7 def has_path_sum (root, path_sum ): if root is None : return False if root.left is None and root.right is None and root.val == path_sum: return True return has_path_sum(root.left, path_sum - root.val) or has_path_sum(root.right, path_sum - root.val)
1.10.1 不一定以root和leaf开头结尾 统计路径和等于一个数的路径数量;路径不一定以root开头,也不一定以leaf结尾。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 def has_path_sum2 (root, path_sum ): if root is None : return 0 res = path_sum_start(root, path_sum) + path_sum_start(root.left, path_sum) + path_sum_start(root.right, path_sum) return res def path_sum_start (root, s ): if root is None : return 0 res = 0 if root.val == s: res += 1 res += path_sum_start(root.left, s-root.val) + path_sum_start(root.right, s-root.val) return res
2. 二叉搜索树(BST) 二叉搜索树(BST):根节点大于等于左子树所有节点,小于等于右子树所有节点。二叉查找树中序遍历有序。
2.1 二叉树中第k小的节点 给定一棵二叉搜索树,请找出其中的第k小的结点。例如,(5, 3, 7, 2, 4, 6, 8)中,按结点数值大小顺序第三小结点的值为4。
对于二叉搜索树,中序遍历顺序就是从小到大排序的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 def find_kth_node (root, k ): global result result = [] in_order_traversal(root) if k > len (result) or k <= 0 : return None else : return result[k-1 ] def in_order_traversal (root ): if not root: return None in_order_traversal(root.left) result.append(root.val) in_order_traversal(root.right)
3. 其他常见 3.1 [双指针]和为S的两个数字 输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
左右夹逼,如果和比S小则左边往右挪,如果和比S大则右边往左挪。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def sulution (array, tsum ): if len (array) <= 1 : return [] start = 0 end = len (array) - 1 while start < end: if array[start] + array[end] == tsum: return [array[start], array[end]] if array[start] + array[end] < tsum: start += 1 if array[start] + array[end] > tsum: end -= 1 return []
3.2 和为S的连续正数序列 输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序; e.g. 9~16、 18,19,20,21,22的和均为100
1)由于我们要找的是和为S的连续正数序列,因此这个序列是个公差为1的等差数列,而这个序列的中间值代表了平均值的大小。假设序列长度为n, 那么这个序列的中间值可以通过(S / n)得到,知道序列的中间值和长度,也就不难求出这段序列了。
2)满足条件的n分两种情况: n为奇数时,序列中间的数正好是序列的平均值,所以条件为:(n & 1) == 1 && sum % n == 0; n为偶数时,序列中间两个数的平均值是序列的平均值,而这个平均值的小数部分为0.5,所以条件为:(sum % n) * 2 == n.
3)由题可知n >= 2,那么n的最大值是多少呢?我们完全可以将n从2到S全部遍历一次,但是大部分遍历是不必要的。为了让n尽可能大, 我们让序列从1开始,根据等差数列的求和公式:S = (1 + n) * n / 2,得到.
最后举一个例子,假设输入sum = 100,我们只需遍历n = 13~2的情况(按题意应从大到小遍历),n = 8时,得到序列 [9, 10, 11, 12, 13, 14, 15, 16];n = 5时,得到序列[18, 19, 20, 21, 22]。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import mathdef find_continuous_sequence (tsum ): ans = [] for n in range (int (math.sqrt(2 * tsum)), 1 , -1 ): if (n % 2 == 1 and tsum % n == 0 ) or ((tsum % n) * 2 == n): result = [] res_min = int ((tsum * 1.0 / n) - (n - 1 ) * 1.0 / 2 ) res_max = int ((tsum * 1.0 / n) + (n - 1 ) * 1.0 / 2 ) for j in range (res_min, res_max + 1 ): result.append(j) ans.append(result) return ans
3.3 连续子数组的最大和 计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和。
1 2 3 4 5 6 7 8 9 10 11 12 13 def find_greatest_sum_of_sub_array (arr ): max_sum, cur_sum = -1000000 , 0 for i in arr: if cur_sum <= 0 : cur_sum = i else : cur_sum += i if cur_sum > max_sum: max_sum = cur_sum return max_sum
3.4 最小的K个数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def get_least_numbers (array, k ): least_numbers_list = array[:k] max_n = max (least_numbers_list) for n in array[k:]: if n < max_n: least_numbers_list.remove(max_n) least_numbers_list.append(n) max_n = max (least_numbers_list) least_numbers_list.sort() return least_numbers_list
3.5 二进制中1的个数 输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
1 2 3 4 5 6 7 8 9 10 def number_of1 (n ): count = 0 if n < 0 : n = n & 0xffffffff while n != 0 : count += 1 n = n & (n-1 ) return count
3.6 调整数组顺序使奇数位于偶数前面 题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分, 所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路
要保证奇数和奇数、偶数和偶数之间的相对位置不变。故只能对调或者顺次移动
1 2 3 4 5 6 7 8 9 10 11 12 def solution (array ): array_len = len (array) for i in range (array_len): for j in range (array_len-1 , i, -1 ): if array[j] % 2 == 1 and array[j-1 ] % 2 == 0 : temp = array[j-1 ] array[j-1 ] = array[j] array[j] = temp return array
3.7 孩子们的游戏(圆圈中最后剩下的人) 每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。 其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友 要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数….这样下去….直到 剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼 品呢?(注:小朋友的编号是从0到n-1)
2.3 数学推理: 假设在N0=N的时候,第一次出列的孩子在队列中序号为K,那么这个孩子出列后,剩余N-1个孩子的序号是 0,1,2….K-1, K+1,K+2,….N-1,这个序列要调整成N-K-1,N-K,N-K+1,…N-2, 0, 1, …,N-K-2,主要变化在: 原来的K+1到N-1的每个序号减去(K+1),因为原来K+1的序号变成了0,原来的N-1就就变成了(N-1)-(K+1)=N-K-2, 那么原来的0的序号变成了(N-K-1),那么原来的0到K-1的每个序号加上(N-K-1),因此原来的0变成了(N-K-1)。
数学规律总结:设置变化前有N个元素,出列小孩序号为K,那么K=(M-1)%N,设置剩余小孩调整前原始序号为X, 那么重新调整后,新序号为f(X)=(X-K-1)% N,将K值带入:f(X)=X0=(X-(M-1)%N-1)%N=(X-M)%N, 那么已知X0新序号推原序号就是X=(X0+M)% N
1 2 3 4 5 6 def solution (n, m ): if n == 0 : return -1 if n == 1 : return 0 return (solution(n-1 , m) + m) % n
3.8 二维数组中的查找 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
可以从右上角开始查找,如果:
如果数组中的值大于target,则列数减1
如果数组中的值小于target,则行数加1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 def solution (target, array ): row = len (array) - 1 col = len (array[0 ]) - 1 r = 0 c = col while r <= row and c >= 0 : if array[r][c] <= target: r += 1 elif array[r][c] >= target: c -= 1 else : return True return False
3.9 旋转数组的最小数字 题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
思路
因为是一个非递减排序数组的一个旋转,所以从头到尾开始遍历,遇到第一个不满足递增规则的,如5—>1,则返回1即可
1 2 3 4 5 6 7 8 9 def solution (rotate_array ): if len (rotate_array) <= 0 : return 0 res = -1 for num in rotate_array: if num < res: return num res = num
3.10 顺时针打印矩阵 题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵:
则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
思路
依次打印出:
1: 1, 2, 3, 4
2: 8, 12, 16
3: 13, 14, 15
4: 9, 5
5: 6, 7
6: 11
7: 10
每次只打印矩阵的第0行,再打印矩阵的第0行后将矩阵按照逆时针旋转90度,然后再打印矩阵的第0行。重复进行下去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 def solution (matrix ): result = [] while matrix: result.append(matrix[0 ]) if not matrix: break matrix = translate_matrix(matrix) return result def translate_matrix (m ): """ 将矩阵按照逆时针旋转90度 :param m: :return: """ row = len (m) col = len (m[0 ]) translated_m = [] for c in range (col): tmp = [] for r in range (row): tmp.append(m[r][c]) translated_m.append(tmp) translated_m.reverse() return translated_m
3.11 数组中出现次数超过一半的数字 题目描述
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。 由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路
‘分治法’,先将第一个数字设置为1,下一个数字如果相同,则加1;否则减1,如果次数为0;则将下一个数字次数置换成1
先找出出现次数最多的数字
再判定其出现次数是否大于数组长度的一半
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 def solution (numbers ): result = numbers[0 ] t = 1 for i in range (1 , len (numbers)): if t == 0 : result = numbers[i] t = 1 if numbers[i] == result: t += 1 else : t -= 1 t = 0 for i in range (len (numbers)): if numbers[i] == result: t += 1 if t > len (numbers) / 2 : return result else :
3.12 将数组排成最小的数 题目描述
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。 例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
思路 对list内的数据进行排序,按照 将a和b转为string后 若 int(str(a) + str(b) < str(b) + str(a)) , 则a在前, 如 [‘2’, ‘21’]因为212 < 221 所以排序后为[‘21’, ‘2’]
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 from functools import cmp_to_keydef solution_on_py3 (numbers ): if not numbers: return "" lmb = lambda n1, n2: int (str (n1) + str (n2)) - int (str (n2) + str (n1)) numbers.sort(key=cmp_to_key(lmb)) return '' .join([str (i) for i in numbers]) def solution_on_py2 (numbers ): if not numbers: return "" lmb = lambda n1, n2: int (str (n1) + str (n2)) - int (str (n2) + str (n1)) array = sorted (numbers, cmp=lmb) return '' .join([str (i) for i in array]) if __name__ == "__main__" : input_l = ['3' , '32' , '321' ] print (solution_on_py3(input_l))
3.13 丑数 题目描述
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
思路
穷举法:对于因子2,3,5都维护一个index值;每次append丑数列表中的丑数由如下规则确定:
初始化2,3,5对应的index_list均为[0],[0],[0]
在丑数列表中找到2,3,5对应index对应的元素值 * 2/3/5
分别判定除以2, 3, 5对应的余数是否为0,如果为0,则对应2,3,5的indxe值加1,例如对于6这个丑数,2和3对应的index值均加上了1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def solution (index ): if index <= 0 : return 0 ugly_list = [1 ] index_two = 0 index_three = 0 index_five = 0 for i in range (index - 1 ): new_ugly = min (ugly_list[index_two] * 2 , ugly_list[index_three] * 3 , ugly_list[index_five] * 5 ) ugly_list.append(new_ugly) if new_ugly % 2 == 0 : index_two += 1 if new_ugly % 3 == 0 : index_three += 1 if new_ugly % 5 == 0 : index_five += 1 return ugly_list[-1 ]
3.14 数组中只出现一次的数字 题目描述
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
思路
如果问题简化为1个数出现在两个集合A,B中,则其并集 - 交集所得结果便是只出现在集合A或只出现在集合B中的元素。
所以不断二分递归下去,直到集合中只有一个元素,然后再对两个集合做这个操作,最终会将出现两次的数据消除掉,只剩下出现一次的数字。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 def solution (array ): return list (dc(array, 0 , len (array)-1 )) def dc (arr, start, end ): res = set () if start > end: return res if start == end: return set (arr[start:end+1 ]) mid = (start + end) / 2 s1 = dc(arr, start, mid) s2 = dc(arr, mid+1 , end) return s1.union(s2).difference(s1.intersection(s2))
3.15 数组中的重复数字 题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
思路 如果数组元素与将该元素作为idx, 在位于该idx的元素是否相等,如果不相等则做一下交换; 如果相等,则返回这个元素,说明这是一个重复数字
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 def duplicate (numbers, duplication ): for i in range (len (numbers)): if numbers[i] != i: temp = numbers[numbers[i]] if temp == numbers[i]: duplication[0 ] = numbers[i] return True else : numbers[numbers[i]] = numbers[i] numbers[i] = temp return False
3.16 构建乘积数组 题目描述
给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1] …A[i-1] A[i+1]… A[n-1]。不能使用除法。
思路
下三角用连乘可以很容求得,上三角,从下向上也是连乘。
因此我们的思路就很清晰了,先算下三角中的连乘,即我们先算出B[i]中的一部分,然后倒过来按上三角中的分布规律,把另一部分也乘进去。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 def solution (A ): leng = len (A) if leng <= 0 : return None B = [1 ] * leng B[0 ] = 1 for i in range (1 , leng): B[i] = B[i-1 ] * A[i-1 ] temp = 1 for j in range (leng-2 , -1 , -1 ): temp *= A[j+1 ] B[j] *= temp return B
3.17 字符流中第一个不重复的字符 题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符”go”时,第一个只出现一次的字符是”g”。当从该字符流中读出前六个符“google”时,第一个只出现一次的字符是”l”。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution (object ): def __init__ (self ): self.s = "" self.dict = {} def get_first_appear_str (self ): for s in self.s: if self.dict [s] == 1 : return s return "#" def insert (self, char ): self.s += char if char in self.dict : self.dict [char] += 1 else : self.dict [char] = 1
4. 链表 4.1 [双指针]链表中倒数第k个节点 输入一个链表,输出该链表中倒数第k个结点。
可以用两个指针:p1 和 p2 先让p1跑k-1个节点,然后让p2开始跑;当p1跑到最后一个节点时,p2对应的节点就是倒数第k个节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 def solution (head, k ): p1 = head p2 = head i = 0 node_count = 0 while p1 is not None : p1 = p1.next node_count += 1 if i >= k: p2 = p2.next i += 1 if node_count < k: return None return p2
4.2 反转链表 输入一个链表,反转链表后,输出新链表的表头。
1 2 3 4 5 6 7 8 9 10 11 12 def solution (pHead ): if not pHead or pHead.next is None : return pHead last = None while pHead: temp = pHead.next pHead.next = last last = pHead pHead = temp return last
4.3 两个链表的第一个公共节点 输入两个链表,找出它们的第一个公共结点。
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;(a+b) 同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。(b+a) 这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 def solution (pHead1, pHead2 ): l1 = pHead1 l2 = pHead2 while l1 != l2: l1 = pHead2 if l1.next is None else l1.next l2 = pHead1 if l2.next is None else l2.next return l1 def solution2 (pHead1, pHead2 ): """ 先保存链表1的节点,再遍历链表2,如果在链表1中出现,则返回跳出循环。否则返回None :param pHead1: :param pHead2: :return: """ l = [] while pHead1: l.append(pHead1) pHead1 = pHead1.next while pHead2: if pHead2 in l: return pHead2 break pHead2 = pHead2.next return None
4.4 合并两个排序的链表 输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
1 2 3 4 5 6 7 8 9 10 11 12 13 def solution (pHead1, pHead2 ): if pHead1 is None : return pHead2 if pHead2 is None : return pHead1 if pHead1.val <= pHead2.val: pHead1.next = solution(pHead1.next , pHead2) return pHead1 else : pHead2.next = solution(pHead1, pHead2.next ) return pHead2
4.5 复杂链表的复制 题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 class RandomListNode : def __init__ (self, x ): self.label = x self.next = None self.random = None def iter_node (node ): while node: yield node node = node.next def clone_list (p_head ): mem = dict () for i, n in enumerate (iter_node(p_head)): mem[id (n)] = i lst = [RandomListNode(n.label) for n in iter_node(p_head)] for t, f in zip (iter_node(p_head), lst): if t.next : f.next = lst[mem[id (t.next )]] if t.random: f.random = lst[mem[id (t.random)]] return lst[0 ] if lst else None
4.6 二叉搜索树与双向链表 题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 def node_list (root_of_tree ): """ 得到其中序遍历结果 :param root_of_tree: :return: """ if not root_of_tree: return [] return node_list(root_of_tree.left) + [root_of_tree] + node_list(root_of_tree) def convert (root_of_tree ): res = node_list(root_of_tree) if len (res) == 0 : return None if len (res) == 1 : return root_of_tree res[0 ].left = None res[0 ].right = res[1 ] res[-1 ].left = res[-2 ] res[-1 ].right = None for i in range (1 , len (res)-1 ): res[i].left = res[i-1 ] res[i].right = res[i+1 ] return res[0 ]
4.7 链表中环的入口节点 题目描述
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路
有两个:
遍历整个链表,将节点缓存起来,第一个重复的节点就是环的入口(时间、空间复杂度都是O(n));
第二个方法相对比较复杂:
a. 如果将环抽象为如下形式,从链表起点A开始,经过环的入口B,假设从A—>B的距离为x;
1 2 3 4 5 A -----> B ------> C ^ | | | | | <----------
b. 假设快指针fast一次移动2步,慢指针一次移动1步,当快慢指针在环中的C点相遇时,假设整个环B—>C—>B的距离为c;从环的入口B到环中相遇节点C的距离为a.
c. 可以得到如下信息,
slow = x + n*c + a(n代表慢指针走过了n次环)
fast = x + m*c + a(m代表快指针走过了m次环)
有:
2 * slow = fast ==> x = (m-2n)c - a ==> x = (m-2n-1)c + c - a
什么意思呢,即A—>B的距离 = 数个环的长度(可能为0) + c - a(即相遇点C继续走到B的距离)
d. 所以,可以再让一个指针从起点A开始走,让一个指针从相遇点C开始继续往后走,2个指针速度一样,那么两个指针的相遇点一定到达环的入口点。时间复杂度为O(n), 空间复杂度为O(1)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 def solution1 (p_head ): temp_list = [] p = p_head while p: if p in temp_list: return p else : temp_list.append(p) p = p.next return None def solution2 (p_head ): if p_head is None or p_head.next is None or p_head.next .next is None : return None fast = p_head.next .next slow = p_head.next while fast != slow: if fast.next is not None and fast.next .next is not None : fast = fast.next .next slow = slow.next else : return None fast = p_head while fast != slow: fast = fast.next slow = slow.next return slow