├── .gitignore ├── README.md ├── chapter_2 ├── section_3 │ ├── 3_find_duplicate.py │ ├── 4_find_in_matrix.py │ ├── 5_replace_space.py │ ├── 6_print_list_reverse.py │ ├── 7_build_tree.py │ ├── 8_next_tree_node.py │ ├── 9_implement_queue.py │ ├── 9_implement_stack.py │ └── README.md └── section_4 │ ├── 10_fibonacci.py │ ├── 11_min_in_rotated.py │ ├── 12_path_in_matrix.py │ ├── 13_range_of_robot.py │ ├── 14_cut_rope.py │ ├── 15_count_one_in_bits.py │ └── README.md ├── chapter_3 ├── section_3 │ ├── 16_pow.py │ ├── 17_print_n_bit.py │ ├── 18_remove_listnode.py │ ├── 19_regular_matching.py │ ├── 21_reorder_array.py │ └── README.md └── section_4 │ ├── 22_kth_tail_listnode.py │ ├── 23_linked_list_cycle.py │ ├── 24_reverse_linklist.py │ ├── 25_merge_both_list.py │ ├── 26_is_subtree.py │ └── README.md ├── chapter_4 ├── section_2 │ ├── 27_mirror_tree.py │ ├── 28_symmetric_tree.py │ ├── 29_spiral_matrix.py │ └── README.md ├── section_3 │ ├── 30_min_stack.py │ ├── 31_valid_stack_seq.py │ ├── 32_print_tree.py │ ├── 33_postorder_tree.py │ ├── 33_varify_postorder.py │ ├── 34_path_sum.py │ └── README.md └── section_4 │ ├── 35_copy_random_list.py │ ├── 36_bst2linkedlist.py │ ├── 37_serialize_tree.py │ ├── 38_str_permutations.py │ └── README.md ├── chapter_5 ├── section_2 │ ├── 39_mojority_num.py │ ├── 40_kth_smallest.py │ ├── 41_median_in_stream.py │ ├── 42_maximum_sub.py │ ├── 43_num_of_one.py │ ├── 45_min_num.py │ ├── 46_num2strways.py │ ├── 47_max_gift.py │ ├── 48_longest_sub_not_repeat.py │ └── README.md └── section_3 │ ├── 49_ugly_num.py │ ├── 50_first_only.py │ ├── 51_inverse_pair.py │ ├── 52_intersection_of_2lists.py │ └── README.md ├── chapter_6 ├── section_3 │ ├── 53_missing_num.py │ ├── 54_kth_larger_bst.py │ ├── 55_balaned_tree.py │ ├── 55_depth_of_tree.py │ ├── 56_single_numII.py │ ├── 56_single_numIII.py │ ├── 57_find_num_with_sum.py │ ├── 58_reverse_words.py │ ├── 59_slide_window_max.py │ └── README.md ├── section_4 │ ├── 60_dice.py │ ├── 61_poke.py │ ├── 62_remain_cycle.py │ ├── 63_stock_profit.py │ └── README.md └── section_5 │ ├── 64_sum_n.py │ ├── 65_plus_bit.py │ ├── 66_mul_matrix.py │ └── README.md └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | .env* 2 | 3 | # PyCharm 4 | .idea/ 5 | 6 | .pyc* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sword-for-offer 2 | 3 | 使用Python3用pythonic的方式实现《剑指Offer 第二版》中的题目。拒绝直接“翻译”java等实现。代码有些并非原创,搬运了一些LeetCode中大神的优秀解法,比如:如何用一行代码实现`顺时针打印矩阵`。 4 | 基本所有题都包含对应网站链接。建议优先选择LeetCode答题,相对来说testcase比较全面。源码包含doctest,如想了解更多LeetCode题解,请移步[我的博客](https://darktiantian.github.io/%E5%89%91%E6%8C%87Offer/)。 5 | 6 | ### 第2章 面试需要的基础知识 7 | 8 | ### 2.3 [数据结构](/chapter_2/section_3) 9 | > #### [3 数组中重复的数字](/chapter_2/section_3#3-数组中重复的数字) 10 | > #### [4 二维数组中的查找](/chapter_2/section_3#4-二维数组中的查找) 11 | > #### [5 替换空格](/chapter_2/section_3#5-替换空格) 12 | > #### [6 从尾到头打印链表](/chapter_2/section_3#6-从尾到头打印链表) 13 | > #### [7 重建二叉树](/chapter_2/section_3#7-重建二叉树) 14 | > #### [9 用两个栈实现队列](/chapter_2/section_3#9-用两个栈实现队列) 15 | > #### [9.1 用两个队列实现栈](/chapter_2/section_3#9_1-用两个队列实现栈) 16 | 17 | ### 2.4 [算法和数据操作](/chapter_2/section_4) 18 | > #### [10 斐波那契数列](/chapter_2/section_4#10-斐波那契数列) 19 | > #### [11 旋转数组的最小数字](/chapter_2/section_4#11-旋转数组的最小数字) 20 | > #### [12 矩阵中的路径](/chapter_2/section_4#12-矩阵中的路径) 21 | > #### [13 机器人的运动范围](/chapter_2/section_4#13-机器人的运动范围) 22 | > #### [14 剪绳子](/chapter_2/section_4#14-剪绳子) 23 | > #### [15 二进制中1的个数](/chapter_2/section_4#15-二进制中1的个数) 24 | 25 | ### 第3章 高质量的代码 26 | 27 | ### 3.3 [代码的完整性](/chapter_3/section_3) 28 | > #### [16 数值的整数次方](/chapter_3/section_3#16-数值的整数次方) 29 | > #### [17 打印从1到最大的n位数](/chapter_3/section_3#17-打印从1到最大的n位数) 30 | > #### [18 删除链表中的节点](/chapter_3/section_3#18-删除链表中的节点) 31 | > #### [19 正则表达式](/chapter_3/section_3#19-正则表达式) 32 | > #### [20 表示数值的字符串](/chapter_3/section_3#20-表示数值的字符串) 33 | > #### [21 调整数组顺序使奇数位于偶数前面](/chapter_3/section_3#21-调整数组顺序使奇数位于偶数前面) 34 | 35 | ### 3.4 [代码的鲁棒性](/chapter_3/section_4) 36 | > #### [22 链表中倒数第k个节点](/chapter_3/section_4#22-链表中倒数第k个节点) 37 | > #### [23 链表中环的入口节点](/chapter_3/section_4#23-链表中环的入口节点) 38 | > #### [24 反转链表](/chapter_3/section_4#24-反转链表) 39 | > #### [25 合并两个有序链表](/chapter_3/section_4#25-合并两个有序链表) 40 | > #### [26 树的子结构](/chapter_3/section_4#26-树的子结构) 41 | 42 | ### 第4章 解决面试题的思路 43 | 44 | ### 4.2 [画图让抽象问题形象化](/chapter_4/section_2) 45 | > #### [27 二叉树的镜像](/chapter_4/section_2#27-二叉树的镜像) 46 | > #### [28 对称的二叉树](/chapter_4/section_2#28-对称的二叉树) 47 | > #### [29 顺时针打印矩阵](/chapter_4/section_2#29-顺时针打印矩阵) 48 | 49 | ### 4.3 [举例让抽象问题具体化](/chapter_4/section_3) 50 | > #### [30 包含min函数的栈](/chapter_4/section_3#30-包含min函数的栈) 51 | > #### [31 栈的压入、弹出序列](/chapter_4/section_3#31-栈的压入、弹出序列) 52 | > #### [32 从上到下打印二叉树](/chapter_4/section_3#32-从上到下打印二叉树) 53 | > #### [32.1 分层从上到下打印二叉树](/chapter_4/section_3#32_1-分层从上到下打印二叉树) 54 | > #### [32.2 之字形打印二叉树](/chapter_4/section_3#32_2-之字形打印二叉树) 55 | > #### [33 是否是二叉搜索树的后序遍历](/chapter_4/section_3#33-是否是二叉搜索树的后序遍历) 56 | > #### [34 二叉树和为某一值的路径](/chapter_4/section_3#34-二叉树和为某一值的路径) 57 | 58 | ### 4.4 [分解让复杂问题简单化](/chapter_4/section_4) 59 | > #### [35 复杂链表的复制](/chapter_4/section_4#35-复杂链表的复制) 60 | > #### [36 二叉搜索树与双向链表](/chapter_4/section_4#36-二叉搜索树与双向链表) 61 | > #### [37 序列化二叉树](/chapter_4/section_4#37-序列化二叉树) 62 | > #### [38 字符串的排列](/chapter_4/section_4#38-字符串的排列) 63 | 64 | ### 第5章 优化时间和空间效率 65 | 66 | ### 5.2 [时间效率](/chapter_5/section_2) 67 | > #### [39 数组中出现次数超过一半的数字](/chapter_5/section_2#39-数组中出现次数超过一半的数字) 68 | > #### [40 最小的k个数](/chapter_5/section_2#40-最小的k个数) 69 | > #### [41 数据流中的中位数](/chapter_5/section_2#41-数据流中的中位数) 70 | > #### [42 连续子数组的最大和](/chapter_5/section_2#42-连续子数组的最大和) 71 | > #### [43 1~n整数中1出现的次数](/chapter_5/section_2#43-1~n整数中1出现的次数) 72 | > #### [44 数字序列中某一位的数字](/chapter_5/section_2#44-数字序列中某一位的数字) 73 | > #### [45 把数组排成最小的数字](/chapter_5/section_2#45-把数组排成最小的数字) 74 | > #### [46 把数字翻译成字符串](/chapter_5/section_2#46-把数字翻译成字符串) 75 | > #### [47 礼物的最大价值](/chapter_5/section_2#47-礼物的最大价值) 76 | > #### [48 最长不含重复字符的子字符串](/chapter_5/section_2#48-最长不含重复字符的子字符串) 77 | 78 | ### 5.3 [时间效率与空间效率的平衡](/chapter_5/section_3) 79 | > #### [49 丑数](/chapter_5/section_3#49-丑数) 80 | > #### [50 第一个只出现一次的字符](/chapter_5/section_3#50-第一个只出现一次的字符) 81 | > #### [51 数组中的逆序对](/chapter_5/section_3#51-数组中的逆序对) 82 | > #### [52 两个链表的第一个公共节点](/chapter_5/section_3#52-两个链表的第一个公共节点) 83 | 84 | ### 第6章 面试中的各项能力 85 | 86 | ### 6.2 [知识迁移能力](/chapter_6/section_3) 87 | > #### [53 在排序数组中查找数字](/chapter_6/section_3#53-在排序数组中查找数字) 88 | > #### [53 0~n-1中缺失的数字](/chapter_6/section_3#53-0~n-1中缺失的数字) 89 | > #### [53 数组中数值和下标相等的元素](/chapter_6/section_3#53-数组中数值和下标相等的元素) 90 | > #### [54 二叉搜索树的第k大节点](/chapter_6/section_3#54-二叉搜索树的第k大节点) 91 | > #### [55 二叉树的深度](/chapter_6/section_3#55-二叉树的深度) 92 | > #### [55_1 平衡二叉树](/chapter_6/section_3#55_1-平衡二叉树) 93 | > #### [56 数组中只出现一次的两个数字。](/chapter_6/section_3#56-数组中只出现一次的两个数字。) 94 | > #### [56_1 数组中出现一次的数字,其余元素出现三次。](/chapter_6/section_3#56_1-数组中出现一次的数字,其余元素出现三次。) 95 | > #### [57 和为s的数字](/chapter_6/section_3#57-和为s的数字) 96 | > #### [57_1 和为s的连续正数序列](/chapter_6/section_3#57_1-和为s的连续正数序列) 97 | > #### [58 翻转字符串](/chapter_6/section_3#58-翻转字符串) 98 | > #### [58_1 左旋转字符串](/chapter_6/section_3#58_1-左旋转字符串) 99 | > #### [59 滑动窗口的最大值](/chapter_6/section_3#59-滑动窗口的最大值) 100 | 101 | ### 6.3 [抽象建模能力](/chapter_6/section_4) 102 | > #### [60 n个骰子的点数](/chapter_6/section_4#60-n个骰子的点数) 103 | > #### [61 扑克牌中的顺子](/chapter_6/section_4#61-扑克牌中的顺子) 104 | > #### [62 圆圈中最后剩下的数字](/chapter_6/section_4#62-圆圈中最后剩下的数字) 105 | > #### [63 股票的最大利润](/chapter_6/section_4#63-股票的最大利润) 106 | 107 | ### 6.4 [发散思维能力](/chapter_6/section_5) 108 | > #### [64 求1+2+···+n](/chapter_6/section_5#64-求1+2+···+n) 109 | > #### [65 不用加减乘除做加法](/chapter_6/section_5#65-不用加减乘除做加法) 110 | > #### [66 构建乘积数组](/chapter_6/section_5#66-构建乘积数组) 111 | -------------------------------------------------------------------------------- /chapter_2/section_3/3_find_duplicate.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/16 7:14 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 0~n-1组成的数组中找出重复的任意一个数字。 9 | >>> duplicate([2, 3, 1, 0, 2, 5, 3]) 10 | (True, 2) 11 | 12 | # test second 13 | # >>> find_duplicate([2, 3, 5, 4, 3, 2, 6, 7]) 14 | # 3 15 | >>> find_duplicate([2, 3, 5, 4, 1, 2, 6, 7]) 16 | 2 17 | """ 18 | 19 | 20 | def duplicate(nums: list) -> int: 21 | for i, num in enumerate(nums): 22 | while i != num: 23 | if num == nums[num]: 24 | return True, num 25 | else: 26 | nums[i], nums[num] = nums[num], nums[i] 27 | num = nums[i] 28 | 29 | return False, None 30 | 31 | 32 | def find_duplicate(nums: list) -> int: 33 | """ 34 | 不能修改原数组,范围为1~n 35 | 时间复杂度:O(nlogn) 36 | 空间复杂度:O(1) 37 | """ 38 | def count_range(i, j): 39 | return sum(i<=num<=j for num in nums) 40 | 41 | lo = 1 42 | hi = len(nums) - 1 # n为范围 43 | while lo <= hi: 44 | mid = (lo + hi) // 2 45 | # print(lo, mid, hi) 46 | count = count_range(lo, mid) 47 | if lo == hi: 48 | if count > 1: 49 | return lo 50 | else: 51 | break 52 | if count > mid-lo+1: 53 | hi = mid 54 | else: 55 | lo = mid + 1 56 | return -1 57 | 58 | 59 | -------------------------------------------------------------------------------- /chapter_2/section_3/4_find_in_matrix.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 1:30 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 在从左到右从上到下递增的矩阵中,判断目标数是否存在 9 | 10 | >>> m = [[1, 2, 8, 9], [2, 4, 9, 12], [4, 7, 10, 13], [6, 8, 11, 15]] 11 | >>> search_in_matrix(7, m) 12 | True 13 | >>> search_in_matrix(5, m) 14 | False 15 | """ 16 | 17 | # 思路:从右上角顶点出发。 18 | 19 | def search_in_matrix(target: int, matrix: 'List[List[int]]') -> 'bool': 20 | 21 | row = 0 22 | col = len(matrix[0]) - 1 23 | while col >= 0 and row <= len(matrix) - 1: 24 | if matrix[row][col] > target: 25 | col -= 1 26 | elif matrix[row][col] < target: 27 | row += 1 28 | else: 29 | return True 30 | return False 31 | 32 | 33 | -------------------------------------------------------------------------------- /chapter_2/section_3/5_replace_space.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 2:26 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 替换空格 9 | >>> s = 'We are happy.' 10 | >>> replace_space(s) 11 | 'We%20are%20happy.' 12 | >>> s1 = 'We are very happy!' 13 | >>> replace_space(s1) 14 | 'We%20are%20very%20%20happy!' 15 | """ 16 | 17 | def replace_space(s: str) -> str: 18 | return ''.join(c if c!=' ' else '%20' for c in s) 19 | -------------------------------------------------------------------------------- /chapter_2/section_3/6_print_list_reverse.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 2:35 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 从尾到头打印链表。 10 | >>> l1 = (1, 2, 3, 4, 5) 11 | >>> l1 = construct_linklist(l1) 12 | >>> print_list_reverse(l1) 13 | [5, 4, 3, 2, 1] 14 | >>> l2 = construct_linklist([3, 4, 8, 2, 7]) 15 | >>> print_list_reverse(l2) 16 | [7, 2, 8, 4, 3] 17 | """ 18 | from utils import construct_linklist, ListNode 19 | 20 | 21 | def print_list_reverse(head: ListNode) -> list: 22 | stack, h = [], head 23 | while h: 24 | stack.append(h.val) 25 | h = h.next 26 | return stack[::-1] -------------------------------------------------------------------------------- /chapter_2/section_3/7_build_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 3:03 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 根据前序遍历和中序遍历重建二叉树。 9 | >>> preorder = [3, 9, 20, 15, 7] 10 | >>> inorder = [9, 3, 15, 20, 7] 11 | >>> t1 = buildTree(preorder, inorder) 12 | >>> preorder_traversal(t1) == preorder 13 | True 14 | >>> inorder_traversal(t1) == inorder 15 | True 16 | 17 | # test t2 18 | >>> t2 = buildTree2(preorder, inorder) 19 | >>> preorder_traversal(t2) == preorder 20 | True 21 | >>> inorder_traversal(t2) == inorder 22 | True 23 | """ 24 | 25 | from utils import TreeNode, preorder_traversal, inorder_traversal 26 | 27 | def buildTree(preorder: 'List[int]', inorder: 'List[int]') -> TreeNode: 28 | """ 29 | 解法1:解法一有个问题,在一个极端情况下,例如前序遍历为[1, 2, 3, 4, 5] 30 | 中序遍历为[5, 4, 3, 2, 1],这种情况只有左子树,那么在中序遍历使用index寻找根节点时, 31 | 时间复杂度为O(n²) 32 | """ 33 | if preorder == []: 34 | return None 35 | root_val = preorder[0] 36 | root = TreeNode(root_val) 37 | cut = inorder.index(root_val) 38 | root.left = buildTree(preorder[1:cut+1], inorder[:cut]) 39 | root.right = buildTree(preorder[cut+1:], inorder[cut+1:]) 40 | return root 41 | 42 | 43 | def buildTree2(preorder: 'List[int]', inorder: 'List[int]') -> TreeNode: 44 | """ 45 | 解法2:解决上述为题,并且没有多余的切片产生的内存,空间复杂度也更小了。 46 | """ 47 | preo, inor = list(preorder), list(inorder) 48 | def build(stop): 49 | if inor and inor[-1] != stop: 50 | root = TreeNode(preo.pop()) 51 | root.left = build(root.val) 52 | inor.pop() 53 | root.right = build(stop) 54 | return root 55 | preo.reverse() 56 | inor.reverse() 57 | return build(None) 58 | 59 | -------------------------------------------------------------------------------- /chapter_2/section_3/8_next_tree_node.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/16 10:20 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | def GetNext(pNode): 9 | # write code here 10 | if not pNode: 11 | return None 12 | # 有右子树,右子树中最左节点 13 | if pNode.right: 14 | pre = pNode.right 15 | while pre.left: 16 | pre = pre.left 17 | return pre 18 | while pNode.next: 19 | parent = pNode.next 20 | if parent.left == pNode: 21 | return parent 22 | pNode = parent 23 | return None -------------------------------------------------------------------------------- /chapter_2/section_3/9_implement_queue.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 3:28 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 用两个栈实现队列 9 | 10 | test 1 11 | >>> q1 = MyQueue1() 12 | >>> q1.push(1) 13 | >>> q1.push(2) 14 | >>> q1.peek() 15 | 1 16 | >>> q1.pop() 17 | 1 18 | >>> q1.empty() 19 | False 20 | 21 | # test q2 22 | >>> q2 = MyQueue2() 23 | >>> q2.push(1) 24 | >>> q2.push(3) 25 | >>> q2.peek() 26 | 1 27 | >>> q2.pop() 28 | 1 29 | >>> q2.peek() 30 | 3 31 | >>> q2.empty() 32 | False 33 | >>> q2.pop() 34 | 3 35 | """ 36 | 37 | 38 | class MyQueue1: 39 | """ 40 | push: O(1), pop/peek: O(n) 41 | 使用一个入栈用来接收数据,使用一个出栈用来返回数据。 42 | """ 43 | def __init__(self): 44 | self.in_stack, self.out_stack = [], [] 45 | 46 | def push(self, x: int) -> None: 47 | self.in_stack.append(x) 48 | 49 | def move(self) -> None: 50 | if self.out_stack == []: 51 | while self.in_stack: 52 | self.out_stack.append(self.in_stack.pop()) 53 | 54 | def pop(self) -> int: 55 | self.move() 56 | return self.out_stack.pop() 57 | 58 | def peek(self) -> int: 59 | self.move() 60 | return self.out_stack[-1] 61 | 62 | def empty(self) -> bool: 63 | return self.in_stack == self.out_stack == [] 64 | 65 | 66 | class MyQueue2: 67 | """ 68 | push: O(n), pop/peek: O(1) 69 | """ 70 | def __init__(self): 71 | self.s1, self.s2 = [], [] 72 | 73 | def push(self, x: int) -> None: 74 | while self.s1: 75 | self.s2.append(self.s1.pop()) 76 | self.s1.append(x) 77 | while self.s2: 78 | self.s1.append(self.s2.pop()) 79 | 80 | def pop(self) -> int: 81 | return self.s1.pop() 82 | 83 | def peek(self) -> int: 84 | return self.s1[-1] 85 | 86 | def empty(self) -> bool: 87 | return not self.s1 -------------------------------------------------------------------------------- /chapter_2/section_3/9_implement_stack.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 3:46 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 两个队列实现栈。 9 | # test s1 10 | >>> s1 = MyStack() 11 | >>> s1.push(1) 12 | >>> s1.push(2) 13 | >>> s1.top() 14 | 2 15 | >>> s1.pop() 16 | 2 17 | >>> s1.top() 18 | 1 19 | 20 | # test s2 21 | >>> s2 = MyStack2() 22 | >>> s2.push(1) 23 | >>> s2.push(2) 24 | >>> s2.top() 25 | 2 26 | >>> s2.pop() 27 | 2 28 | >>> s2.pop() 29 | 1 30 | >>> s2.empty() 31 | True 32 | """ 33 | 34 | from collections import deque 35 | 36 | 37 | class MyStack: 38 | """ 39 | 解法1:push:O(n), pop/top:O(1). 40 | """ 41 | def __init__(self): 42 | self.q1, self.q2 = deque(), deque() 43 | 44 | def push(self, x: int) -> None: 45 | self.q2.append(x) 46 | while self.q1: 47 | self.q2.append(self.q1.popleft()) 48 | self.q1, self.q2 = self.q2, self.q1 49 | 50 | def pop(self) -> int: 51 | return self.q1.popleft() 52 | 53 | def top(self) -> int: 54 | return self.q1[0] 55 | 56 | def empty(self) -> bool: 57 | return not self.q1 58 | 59 | 60 | class MyStack2: 61 | """ 62 | 解法2: 队列旋转。 63 | """ 64 | def __init__(self): 65 | self.q = deque() 66 | 67 | def push(self, x: int) -> None: 68 | self.q.append(x) 69 | # self.q.rotate(1) 这里是用了双端队列的特性,所以这么写是不符合题意的。 70 | # self.q.rotate(1-len(self.q)) 这里和下面循环是一样的效果 71 | for _ in range(len(self.q) - 1): 72 | self.q.append(self.q.popleft()) 73 | 74 | def pop(self) -> int: 75 | return self.q.popleft() 76 | 77 | def top(self) -> int: 78 | return self.q[0] 79 | 80 | def empty(self) -> bool: 81 | return not self.q -------------------------------------------------------------------------------- /chapter_2/section_3/README.md: -------------------------------------------------------------------------------- 1 | ### 2. 实现Singleton模式 2 | 3 | 使用`__new__`控制实例创建过程 4 | 5 | ```python 6 | class Singleton: 7 | _instance = None 8 | def __new__(cls, *args, **kw): 9 | if not cls._instance: 10 | cls._instance = super().__new__(cls) 11 | return cls._instance 12 | 13 | class MyClass(Singleton): 14 | pass 15 | ``` 16 | 使用decorator 17 | 18 | ```python 19 | from functools import wraps 20 | def singleton(cls): 21 | instances = {} 22 | @wraps(cls) 23 | def get_instance(*args, **kw): 24 | if cls not in instances: 25 | instances[cls] = cls(*args, **kw) 26 | return instances[cls] 27 | return get_instance 28 | 29 | @singleton 30 | class Myclass: 31 | pass 32 | ``` 33 | 34 | 使用元类 35 | 36 | ```python 37 | class Singleton(type): 38 | def __init__(self, *args, **kwargs): 39 | self.__instance = None 40 | super().__init__(*args, **kwargs) 41 | 42 | def __call__(self, *args, **kwargs): 43 | if self.__instance is None: 44 | self.__instance = super().__call__(*args, **kwargs) 45 | return self.__instance 46 | else: 47 | return self.__instance 48 | # Example 49 | class Spam(metaclass=Singleton): 50 | def __init__(self): 51 | print('Creating Spam') 52 | ``` 53 | 54 | ### 3 数组中重复的数字 55 | 56 | #### [牛客网传送门](https://www.nowcoder.com/practice/623a5ac0ea5b4e5f95552655361ae0a8?tpId=13&tqId=11203&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) 57 | 58 | 书中参数还传了一个数组用来保存重复的数字,身为一个Pythoner,直接返回tuple。 59 | 60 | ```python 61 | def duplicate(nums: list) -> int: 62 | for i, num in enumerate(nums): 63 | while i != num: 64 | if num == nums[num]: 65 | return True, num 66 | else: 67 | nums[i], nums[num] = nums[num], nums[i] 68 | num = nums[i] 69 | return False, None 70 | ``` 71 | 72 | ### 3_1 数组中重复的数字,不能修改数组 73 | 74 | #### [AcWing传送门](https://www.acwing.com/problem/content/15/) 75 | 76 | 元素范围变成了1~n。此方法有缺陷,不能找出所有的重复的数字,因为在1~2的范围里有1和2两个数字,这个范围的数字也出现两次,不能确定是每个数字各出现一次还是某个数字出现了两次。 77 | 78 | ```python 79 | def find_duplicate(nums: list) -> int: 80 | def count_range(i, j): 81 | return sum(i<=num<=j for num in nums) 82 | 83 | lo = 1 84 | hi = len(nums) - 1 # n为范围 85 | while lo <= hi: 86 | mid = (lo + hi) // 2 87 | count = count_range(lo, mid) 88 | if lo == hi: 89 | if count > 1: 90 | return lo 91 | else: 92 | break 93 | if count > mid-lo+1: 94 | hi = mid 95 | else: 96 | lo = mid + 1 97 | return -1 98 | ``` 99 | 100 | ### 4 二维数组中的查找 101 | 102 | #### [牛客网传送门](https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 103 | 104 | #### [LeetCode传送门]() 105 | 106 | 选取右上角为起始点。 107 | 108 | ```python 109 | def find(target, array): 110 | row = 0 111 | col = len(array[0]) - 1 112 | while col >= 0 and row <= len(array)-1: 113 | if array[row][col] > target: 114 | col -= 1 115 | elif array[row][col] < target: 116 | row += 1 117 | else: 118 | return True 119 | return False 120 | ``` 121 | 122 | 更为优雅的方法。 123 | 124 | ```python 125 | def searchMatrix(self, matrix, target): 126 | j = -1 127 | for row in matrix: 128 | while j + len(row) and row[j] > target: 129 | j -= 1 130 | if row[j] == target: 131 | return True 132 | return False 133 | ``` 134 | 135 | ### 5 替换空格 136 | 137 | #### [牛客网传送门](https://www.nowcoder.com/practice/4060ac7e3e404ad1a894ef3e17650423?tpId=13&tqId=11155&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 138 | 139 | ```python 140 | def replaceSpace(self, s): 141 | return ''.join(c if c!=' ' else '%20' for c in s) 142 | ``` 143 | 144 | ### 6 从尾到头打印链表 145 | 146 | #### [牛客网传送门](https://www.nowcoder.com/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 147 | 148 | ```python 149 | def printListFromTailToHead(self, listNode): 150 | stack, h = [], listNode 151 | while h: 152 | stack.append(h.val) 153 | h = h.next 154 | return stack[::-1] 155 | ``` 156 | 157 | ### 7 重建二叉树 158 | 159 | #### [LeetCode传送门](https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/description/) 160 | 161 | 说明:根据前序遍历和中序遍历重建二叉树,假设遍历结果中不包含重复的数字。 162 | ```python 163 | def buildTree(preorder, inorder): 164 | if preorder == []: 165 | return None 166 | root_val = preorder[0] 167 | root = TreeNode(root_val) 168 | cut = inorder.index(root_val) 169 | root.left = buildTree(preorder[1:cut+1], inorder[:cut]) 170 | root.right = buildTree(preorder[cut+1:], inorder[cut+1:]) 171 | return root 172 | ``` 173 | 方法二:空间复杂度更低的解法。 174 | 175 | ```python 176 | def buildTree(self, preorder: 'List[int]', inorder: 'List[int]') -> 'TreeNode': 177 | def build(stop): 178 | if inorder and inorder[-1] != stop: 179 | root = TreeNode(preorder.pop()) 180 | root.left = build(root.val) 181 | inorder.pop() 182 | root.right = build(stop) 183 | return root 184 | preorder.reverse() 185 | inorder.reverse() 186 | return build(None) 187 | ``` 188 | 189 | ### 8 二叉树的下一个节点 190 | 191 | #### [牛客网传送门](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=3) 192 | 193 | ```python 194 | def GetNext(self, pNode): 195 | # write code here 196 | if not pNode: 197 | return None 198 | # 有右子树,右子树中最左节点 199 | if pNode.right: 200 | pre = pNode.right 201 | while pre.left: 202 | pre = pre.left 203 | return pre 204 | while pNode.next: 205 | parent = pNode.next 206 | if parent.left == pNode: 207 | return parent 208 | pNode = parent 209 | return None 210 | ``` 211 | 212 | ### 9 用两个栈实现队列 213 | 214 | #### [LeetCode传送门](https://leetcode.com/problems/implement-queue-using-stacks/description/) 215 | 216 | ```python 217 | class MyQueue: 218 | 219 | def __init__(self): 220 | self.s1 = [] 221 | self.s2 = [] 222 | 223 | def push(self, x: int) -> None: 224 | while self.s1: 225 | self.s2.append(self.s1.pop()) 226 | self.s1.append(x) 227 | while self.s2: 228 | self.s1.append(self.s2.pop()) 229 | 230 | def pop(self) -> int: 231 | return self.s1.pop() 232 | 233 | def peek(self) -> int: 234 | return self.s1[-1] 235 | 236 | def empty(self) -> bool: 237 | return self.s1 == [] 238 | ``` 239 | 240 | ### 9_1 用两个队列实现栈 241 | 242 | #### [LeetCode传送门](https://leetcode.com/problems/implement-stack-using-queues/description/) 243 | 244 | 两个队列 245 | ```python 246 | class MyStack: 247 | 248 | def __init__(self): 249 | from collections import deque 250 | self.q1, self.q2 = deque(), deque() 251 | 252 | def push(self, x: int) -> None: 253 | self.q2.append(x) 254 | while self.q1: 255 | self.q2.append(self.q1.popleft()) 256 | self.q1, self.q2 = self.q2, self.q1 257 | 258 | def pop(self) -> int: 259 | return self.q1.popleft() 260 | 261 | def top(self) -> int: 262 | return self.q1[0] 263 | 264 | def empty(self) -> bool: 265 | return not self.q1 266 | 267 | ``` 268 | 单队列旋转 269 | ```python 270 | class MyStack: 271 | 272 | def __init__(self): 273 | from collections import deque 274 | self.q = deque() 275 | 276 | def push(self, x: int) -> None: 277 | self.q.append(x) 278 | # self.q.rotate(1) 这里是用了双端队列的特性 279 | # self.q.rotate(1-len(self.q)) 这里和下面循环是一样的效果 280 | for _ in range(len(self.q)-1): 281 | self.q.append(self.q.popleft()) 282 | 283 | def pop(self) -> int: 284 | return self.q.popleft() 285 | 286 | def top(self) -> int: 287 | return self.q[0] 288 | 289 | def empty(self) -> bool: 290 | return not self.q 291 | ``` 292 | -------------------------------------------------------------------------------- /chapter_2/section_4/10_fibonacci.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 4:00 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 斐波那契数列 9 | >>> fibonacci(3) 10 | 3 11 | >>> fibonacci(5) 12 | 8 13 | >>> fibonacci2(5) 14 | 8 15 | >>> fibonacci2(30) 16 | 1346269 17 | """ 18 | 19 | import functools 20 | 21 | 22 | def fibonacci(n: int) -> int: 23 | a = b = 1 24 | for _ in range(n-1): 25 | a, b = b, a+b 26 | return b 27 | 28 | @functools.lru_cache() 29 | def fibonacci2(n: int) -> int: 30 | """ 31 | 递归时注意使用备忘,否则将计算多次fibonacci2(1) 32 | """ 33 | if n < 2: 34 | return 1 35 | return fibonacci2(n-1) + fibonacci2(n-2) 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /chapter_2/section_4/11_min_in_rotated.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 4:13 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 在一个排序数组旋转后的结果中,找出最小元素。 9 | >>> find_min([3, 4, 5, 1, 2]) 10 | 1 11 | >>> find_min([3, 4, 5, 0, 1, 2]) 12 | 0 13 | """ 14 | 15 | def find_min(nums: 'List[int]') -> int: 16 | l, r = 0, len(nums)-1 17 | if nums[l] < nums[r]: # 此时升序,直接返回 18 | return nums[l] 19 | while l <= r: 20 | mid = (l+r) // 2 21 | if nums[mid] > nums[l]: 22 | l = mid 23 | elif nums[mid] < nums[r]: 24 | r = mid 25 | else: 26 | return nums[r] -------------------------------------------------------------------------------- /chapter_2/section_4/12_path_in_matrix.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 4:19 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 矩阵中的路径 10 | >>> hasPath('ABEESFCSADME', rows=3, cols=4, path='SEE') 11 | True 12 | >>> hasPath('abtgcfcsjdeh', rows=3, cols=4, path='bfce') 13 | True 14 | >>> hasPath('abtgcfcsjdeh', rows=3, cols=4, path='abfb') 15 | False 16 | >>> hasPath('ABCESFCSADEE', rows=3, cols=4, path='SEE') 17 | True 18 | >>> hasPath('ABCESFCSADEE', rows=3, cols=4, path='ABCESEEEFS') 19 | True 20 | """ 21 | 22 | 23 | 24 | def hasPath(matrix: str, rows: int, cols: int, path: str) -> 'bool': 25 | """ 26 | 回溯法,此题牛客网中的testcase不全,排行榜的答案不一定是正确的。 27 | 如doctest中的第一个示例,应该是True,但如果返回False,也能通过牛客网的测试用例。 28 | """ 29 | for i in range(rows): 30 | for j in range(cols): 31 | if matrix[i * cols + j] == path[0]: 32 | if spread(list(matrix), rows, cols, path[1:], i, j): 33 | return True 34 | return False 35 | 36 | 37 | def spread(matrix: str, rows: int, cols: int, path: str, i: int, j: int) -> 'bool': 38 | if not path: 39 | return True 40 | matrix[i * cols + j] = '-' 41 | spreaded = False 42 | for x, y in ((i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)): 43 | if 0 <= x < rows and 0 <= y < cols and matrix[x * cols + y] == path[0]: 44 | if spread(matrix, rows, cols, path[1:], x, y): 45 | spreaded = True 46 | return spreaded -------------------------------------------------------------------------------- /chapter_2/section_4/13_range_of_robot.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 4:26 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 机器人的运动范围 10 | >>> movingCount(2, 3, 4) 11 | 6 12 | """ 13 | 14 | 15 | def movingCount(threshold: int, rows: int, cols: int) -> int: 16 | # write code here 17 | visited = [[False] * cols for _ in range(rows)] 18 | 19 | def get_sum(x, y): 20 | return sum(map(int, str(x) + str(y))) 21 | 22 | def movingCore(threshold, rows, cols, i, j): 23 | if get_sum(i, j) <= threshold: 24 | visited[i][j] = True 25 | for x, y in ((i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1)): 26 | if 0 <= x < rows and 0 <= y < cols and not visited[x][y]: 27 | movingCore(threshold, rows, cols, x, y) 28 | 29 | movingCore(threshold, rows, cols, 0, 0) 30 | return sum(sum(visited, [])) -------------------------------------------------------------------------------- /chapter_2/section_4/14_cut_rope.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 4:44 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 剪绳子 9 | >>> cut_rope(8) 10 | 18 11 | """ 12 | 13 | 14 | def cut_rope(length: int) -> int: 15 | if length < 2: 16 | return 0 17 | if length == 2: 18 | return 1 19 | if length == 3: 20 | return 2 21 | 22 | # 尽可能剪出3 23 | timesOf3 = length // 3 24 | # 如果最后余1,则留一段4分成两半 25 | if length - timesOf3 * 3 == 1: 26 | timesOf3 -= 1 27 | timesOf2 = (length - timesOf3 * 3) // 2 28 | return (3 ** timesOf3) * (2 ** timesOf2) -------------------------------------------------------------------------------- /chapter_2/section_4/15_count_one_in_bits.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 4:50 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 二进制中1的个数 9 | >>> hamming_weigth(11) 10 | 3 11 | >>> all(bin(n).count('1')==hamming_weigth(n) for n in range(30)) 12 | True 13 | """ 14 | 15 | 16 | def hamming_weigth(n: int) -> int: 17 | """ 18 | 一个数n和它-1做与运算,就相当于干掉了最右边的1. 19 | """ 20 | bits = 0 21 | while n: 22 | bits += 1 23 | n = (n-1) & n 24 | return bits -------------------------------------------------------------------------------- /chapter_2/section_4/README.md: -------------------------------------------------------------------------------- 1 | ### 10 斐波那契数列 2 | 3 | #### [LeetCode传送门](https://leetcode.com/problems/fibonacci-number/) 4 | 5 | ```python 6 | def fibonacci(n): 7 | a = b = 1 8 | for _ in range(n-1): 9 | a, b = b, a+b 10 | return b 11 | ``` 12 | 13 | ### 11 旋转数组的最小数字 14 | 15 | #### [LeetCode传送门](https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/description/) 16 | 17 | 说明:通过一个递增数组旋转后的数组,找出最小元素。 18 | 19 | 思路:通过二分法不断缩小范围,由于mid是整除,最后l==mid,并且nums[mid] > nums[r]的。 20 | ```python 21 | def find_min(nums): 22 | l, r = 0, len(nums)-1 23 | if nums[l] < nums[r]: 24 | return nums[l] 25 | while l <= r: 26 | mid = (l+r) // 2 27 | if nums[mid] > nums[l]: 28 | l = mid 29 | elif nums[mid] < nums[r]: 30 | r = mid 31 | else: 32 | return nums[r] 33 | ``` 34 | 35 | ### 12 矩阵中的路径 36 | 37 | #### [LeetCode传送门](https://leetcode.com/problems/word-search/) 38 | 39 | **这道题一定一定要去LeetCode上做,牛客网和AcWing的TestCase都差了太多,毫不夸张地说牛客网此题Python排行榜中的答案很多都是错误的**。我写了一篇关于此题的博客,讲述了如何逐步地将一开始的错误代码改正确。这也是一开始我为什么要强调同样的题,要去LeetCode做的原因。[矩阵中的路径,你真的写对了么?](https://darktiantian.github.io/%E7%9F%A9%E9%98%B5%E4%B8%AD%E5%8D%95%E8%AF%8D%E7%9A%84%E8%B7%AF%E5%BE%84%EF%BC%8C%E5%BE%88%E5%A4%9A%E4%BA%BA%E9%83%BD%E9%94%99%E4%BA%86/) 40 | 41 | ```python 42 | def exist(self, g: List[List[str]], word: str) -> bool: 43 | R, C = len(g), len(g[0]) 44 | 45 | def spread(i, j, w): 46 | if not w: 47 | return True 48 | original, g[i][j] = g[i][j], '-' 49 | spreaded = False 50 | for x, y in ((i-1, j), (i+1, j), (i, j-1), (i, j+1)): 51 | if (0<=x=5`时,`2(n-2)>n`并且`3(n-3)>n`,而且`3(n-3) >= 2(n-2)`,所以尽可能多剪长度为3的绳子。如果长度为4的时候,`2*2>3*1`,所以4的时候就剪成`2*2`的两段。 92 | 93 | ```python 94 | def cut_rope(length): 95 | if length < 2: 96 | return 0 97 | if length == 2: 98 | return 1 99 | if length == 3: 100 | return 2 101 | 102 | # 尽可能剪出3 103 | timesOf3 = length // 3 104 | # 如果最后余1,则留一段4分成两半 105 | if length-timesOf3*3 == 1: 106 | timeOf3 -= 1 107 | timesOf2 = (length-timesOf3*3) // 2 108 | return (3**timesOf3) * (2**timesOf2) 109 | ``` 110 | 111 | ### 15 二进制中1的个数 112 | 113 | #### [LeetCode传送门](https://leetcode.com/problems/number-of-1-bits/description/) 114 | 115 | 方法一:常规解法,使用1与n作与运算,如果不是0说明,含有一个1。 116 | ```python 117 | def hamming_weight(n): 118 | count = 0 119 | for _ in range(32): 120 | count += (n&1==1) 121 | n >>= 1 122 | return count 123 | ``` 124 | 方法二:关键点是,一个数n和n-1的与运算操作,相当于去掉了最右面的1。 125 | ```python 126 | def hamming_weigth(n): 127 | bits = 0 128 | while n: 129 | bits += 1 130 | n = (n-1) & n 131 | return bits 132 | ``` -------------------------------------------------------------------------------- /chapter_3/section_3/16_pow.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 5:04 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 实现pow函数 9 | >>> my_pow(2, 10) 10 | 1024 11 | >>> a, b = randint(-10, 10), randint(10, 10) 12 | >>> c = int(math.pow(a, b)) 13 | >>> d = my_pow(a, b) 14 | >>> c == d 15 | True 16 | """ 17 | 18 | from random import randint 19 | import math 20 | 21 | 22 | def my_pow(x: int, n: int): 23 | if n < 0: 24 | return 1 / pow_with_unsigned(x, -n) 25 | else: 26 | return pow_with_unsigned(x, n) 27 | 28 | 29 | def pow_with_unsigned(x: int, n: int): 30 | if n == 1: 31 | return x 32 | if n == 0: 33 | return 1 34 | 35 | res = pow_with_unsigned(x, n >> 1) 36 | res *= res 37 | 38 | if n & 1 == 1: 39 | res *= x 40 | 41 | return res -------------------------------------------------------------------------------- /chapter_3/section_3/17_print_n_bit.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/17 12:23 AM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | """ 9 | 10 | def print_n(n: int): 11 | 12 | n = 10 ** (n) 13 | for i in range(1, n): 14 | print(i) 15 | 16 | print_n(1) 17 | 18 | print_n(2) -------------------------------------------------------------------------------- /chapter_3/section_3/18_remove_listnode.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 5:17 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 删除链表中的节点,给定的节点不是尾节点。 10 | >>> head = construct_linklist([1, 2, 3, 4, 9]) 11 | >>> print(pretty_linklist(head)) 12 | 1->2->3->4->9 13 | >>> to_del = head.next.next 14 | >>> delete_node(to_del) 15 | >>> print(pretty_linklist(head)) 16 | 1->2->4->9 17 | """ 18 | 19 | from utils import construct_linklist, pretty_linklist 20 | 21 | def delete_node(node: 'ListNode'): 22 | node.val = node.next.val 23 | node.next = node.next.next -------------------------------------------------------------------------------- /chapter_3/section_3/19_regular_matching.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 5:32 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 正则表达式匹配 9 | >>> match('aaa', 'a.a') 10 | True 11 | >>> match('aaa', 'ab*ac*a') 12 | True 13 | >>> match('aaa', 'aa.a') 14 | False 15 | >>> match('aaa', 'ab*a') 16 | False 17 | """ 18 | 19 | def match(s: str, pattern: str) -> 'bool': 20 | if not pattern: return not s 21 | f_match = bool(s) and pattern[0] in {s[0], '.'} 22 | # 一定要用bool(s),否则会输出'',牛客网又忽略了这个TestCase 23 | if len(pattern) > 1 and pattern[1] == '*': 24 | return (match(s, pattern[2:]) or 25 | (f_match and match(s[1:], pattern))) 26 | else: 27 | return f_match and match(s[1:], pattern[1:]) -------------------------------------------------------------------------------- /chapter_3/section_3/21_reorder_array.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 5:40 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 调整数组顺序使奇数位于偶数面前 10 | 11 | >>> ary = list(range(15)) 12 | >>> random.shuffle(ary) 13 | >>> reorder_array(ary) 14 | >>> check_odd_even(ary) and sorted(ary)==sorted(list(range(15))) 15 | True 16 | 17 | # test second 18 | >>> random.shuffle(ary) 19 | >>> ary2 = reorder_array2(ary) 20 | >>> check_odd_even(ary2) and sorted(ary2) == sorted(list(range(15))) 21 | True 22 | 23 | # test third 24 | >>> random.shuffle(ary) 25 | >>> ary3 = reorder_array3(ary) 26 | >>> check_odd_even(ary3) and sorted(ary3)==sorted(list(range(15))) 27 | True 28 | """ 29 | 30 | import random 31 | from collections import deque 32 | from itertools import dropwhile 33 | 34 | 35 | ### 这道题在牛客网上有些不一样,牛客网要求奇偶的相对位置不变,所以解法1不能通过牛客网测试。 36 | ### 但是解法1却是时间和空间复杂度最优解。 37 | 38 | def reorder_array(array: 'List[int]') -> 'List[int]': 39 | """ 40 | 解法1:双指针。Time: O(n), Space: O(1) 41 | """ 42 | l, r = 0, len(array)-1 43 | while l < r: 44 | while l < r and array[l]&1 == 1: 45 | l += 1 46 | while l < r and array[r]&1 == 0: 47 | r -= 1 48 | array[l], array[r] = array[r], array[l] 49 | 50 | def reorder_array2(array: 'List[int]') -> 'List[int]': 51 | """ 52 | 解法2:排序。Time: O(nlogn), Space: O(1) 53 | """ 54 | return sorted(array, key=lambda x:x&1==0) 55 | 56 | def reorder_array3(array: 'List[int]') -> 'List[int]': 57 | """ 58 | 解法3:双端队列插入,Time: O(n), Space: O(n) 59 | """ 60 | q = deque() 61 | n = len(array) 62 | for i in range(n): 63 | if array[-i-1] & 1 == 1: # 从后找奇数 64 | q.appendleft(array[-i-1]) 65 | if array[i] & 1 == 0: #从前找偶数 66 | q.append(array[i]) 67 | return q 68 | 69 | 70 | def check_odd_even(ary: 'List[int]') -> 'bool': 71 | return all(n&1==0 for n in dropwhile(lambda x: x&1==1, ary)) 72 | -------------------------------------------------------------------------------- /chapter_3/section_3/README.md: -------------------------------------------------------------------------------- 1 | ### 16 数值的整数次方 2 | 3 | #### [LeetCode传送门](https://leetcode.com/problems/powx-n/description/) 4 | 5 | ```python 6 | def myPow(self, x: float, n: int) -> float: 7 | 8 | def pow_with_unsigned(x, n): 9 | if n == 0: 10 | return 1 11 | if n == 1: 12 | return x 13 | ans = pow_with_unsigned(x, n>>1) 14 | ans *= ans 15 | if n & 1 == 1: 16 | ans *= x 17 | return ans 18 | 19 | if n < 0: 20 | return 1 / pow_with_unsigned(x, -n) 21 | else: 22 | return pow_with_unsigned(x, n) 23 | ``` 24 | 25 | ### 17 打印从1到最大的n位数 26 | 27 | #### 404. 28 | 29 | 打印呗,反正Python的int没有长度限制。 30 | 31 | ```python 32 | def print_n(n: int): 33 | n = 10 ** (n) 34 | for i in range(1, n): 35 | print(i) 36 | ``` 37 | 38 | ### 18 删除链表中的节点 39 | 40 | #### [LeetCode传送门](https://leetcode.com/problems/delete-node-in-a-linked-list/description/) 41 | 42 | 开始看到这题的思路是,要是能拿到父节点就好了,然后这道题需要别的思路,其关键在于复制 43 | ```python 44 | def deleteNode(self, node): 45 | node.val = node.next.val # 4->1->1->9 46 | node.next = node.next.next # 4->1->9 47 | ``` 48 | 49 | ### 19 正则表达式 50 | 51 | #### [LeetCode传送门](https://leetcode.com/problems/regular-expression-matching/description/) 52 | 53 | * 先考虑没有`*`的情况,通过一个递归逐个字符判断 54 | 55 | ```python 56 | def match(text, pattern): 57 | if not pattern: return not text 58 | first_match = bool(text) and pattern[0] in {text[0], '.'} 59 | return first_match and match(text[1:], pattern[1:]) 60 | ``` 61 | 62 | * 当`*`出现时,一定会在前面跟一个其他字符,所以一定会出现在pattern[1]的位置。一种情况是我们忽略这对pattern,因为可以出现0次;另一种情况是匹配上这个字符,用递归的方式匹配下一个。 63 | 64 | * 一定要用f_match = bool(s),否则结果可能输出`''` 65 | 66 | ```python 67 | def match(self, s, pattern): 68 | if not pattern: return not s 69 | f_match = bool(s) and pattern[0] in {s[0], '.'} 70 | if len(pattern) > 1 and pattern[1] == '*': 71 | return (self.match(s, pattern[2:]) or 72 | (f_match and self.match(s[1:], pattern))) 73 | else: 74 | return f_match and self.match(s[1:], pattern[1:]) 75 | ``` 76 | 77 | ### 20 表示数值的字符串 78 | 79 | #### [LeetCode传送门](https://leetcode.com/problems/valid-number/discuss/23728/A-simple-solution-in-Python-based-on-DFA) 80 | 81 | 此处留坑,[排名第一的python答案](https://leetcode.com/problems/valid-number/discuss/23728/A-simple-solution-in-Python-based-on-DFA)暂时没有理解。 82 | 83 | ### 21 调整数组顺序使奇数位于偶数前面 84 | 85 | #### [牛客网传送门](https://www.nowcoder.com/practice/beb5aa231adc45b2a5dcc5b62c93f593?tpId=13&tqId=11166&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) 86 | #### [AcWing传送门](https://www.acwing.com/problem/content/30/) 87 | 88 | 时间:O(n), 空间O(1) 89 | 90 | ```python 91 | def reOrderArray(self, array): 92 | l, r = 0, len(array)-1 93 | while l < r: 94 | while l < r and array[l]&1 == 1: 95 | l += 1 96 | while l < r and array[r]&1 == 0: 97 | r -= 1 98 | array[l], array[r] = array[r], array[l] 99 | ``` 100 | 101 | 看了一下没有通过牛客网的测试用例,因为题目有些不同,牛客网要求奇数和奇数,偶数和偶数之前的相对位置不变。 102 | 103 | ```python 104 | def reOrderArray(array): 105 | return sorted(array, key=lambda x:x&1==0) 106 | ``` 107 | 108 | 不使用sort 109 | 110 | ```python 111 | def reOrderArray(self, array): 112 | from collections import deque 113 | q = deque() 114 | n = len(array) 115 | for i in range(n): 116 | if array[-i-1] & 1 == 1: # 从后找奇数 117 | q.appendleft(array[-i-1]) 118 | if array[i] & 1 == 0: #从前找偶数 119 | q.append(array[i]) 120 | return q 121 | ``` 122 | 123 | -------------------------------------------------------------------------------- /chapter_3/section_4/22_kth_tail_listnode.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 6:48 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 链表中倒数第k个节点 10 | >>> l1 = construct_linklist([1, 2, 3, 4, 5]) 11 | >>> find_kth_to_tail(l1, 3) is l1.next.next 12 | True 13 | >>> find_kth_to_tail(l1, 7) 14 | >>> find_kth_to_tail(l1, 1) is l1.next.next.next.next 15 | True 16 | 17 | """ 18 | 19 | from utils import construct_linklist 20 | 21 | def find_kth_to_tail(head: 'ListNode', k: int) -> 'ListNode': 22 | fast = slow = head 23 | for _ in range(k): 24 | if fast: 25 | fast = fast.next 26 | else: 27 | return None 28 | while fast: 29 | slow, fast = slow.next, fast.next 30 | return slow -------------------------------------------------------------------------------- /chapter_3/section_4/23_linked_list_cycle.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 6:57 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 链表中环的入口节点 10 | >>> h1 = construct_linklist([1, 2, 3, 4, 5, 6]) 11 | >>> h1.next.next.next.next.next.next = h1.next.next 12 | >>> detect_cycle(h1) is h1.next.next 13 | True 14 | 15 | """ 16 | 17 | from utils import construct_linklist 18 | 19 | 20 | def detect_cycle(head: 'ListNode') -> 'ListNode': 21 | fast = slow = head 22 | # 检测是否有环 23 | while fast and fast.next: 24 | slow, fast = slow.next, fast.next.next 25 | if slow is fast: 26 | break 27 | else: 28 | return None 29 | # 找出入口节点 30 | h = head 31 | while h is not slow: 32 | h, slow = h.next, slow.next 33 | return h -------------------------------------------------------------------------------- /chapter_3/section_4/24_reverse_linklist.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 7:04 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 反转链表。 9 | >>> h1 = construct_linklist([1, 2, 3, 4, 5]) 10 | >>> print(pretty_linklist(h1)) 11 | 1->2->3->4->5 12 | >>> h2 = reverse_list(h1) 13 | >>> print(pretty_linklist(h2)) 14 | 5->4->3->2->1 15 | """ 16 | 17 | from utils import construct_linklist, pretty_linklist 18 | 19 | def reverse_list(head: 'ListNode') -> 'ListNode': 20 | prev = None 21 | while head: 22 | head.next, prev, head = prev, head, head.next 23 | return prev -------------------------------------------------------------------------------- /chapter_3/section_4/25_merge_both_list.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 7:10 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 合并两个有序链表。 10 | >>> h1 = construct_linklist([1, 2, 4]) 11 | >>> h2 = construct_linklist([1, 3, 4]) 12 | >>> print(pretty_linklist(mergeTwoLists(h1, h2))) 13 | 1->1->2->3->4->4 14 | 15 | >>> h1 = construct_linklist([1, 2, 4]) 16 | >>> h2 = construct_linklist([1, 3, 4]) 17 | >>> print(pretty_linklist(mergeTwoLists2(h1, h2))) 18 | 1->1->2->3->4->4 19 | """ 20 | 21 | from utils import ListNode, construct_linklist, pretty_linklist 22 | 23 | 24 | def mergeTwoLists(l1: 'ListNode', l2: 'ListNode') -> 'ListNode': 25 | """ 26 | 解法1:迭代。 27 | """ 28 | l = head = ListNode(0) 29 | while l1 and l2: 30 | if l1.val <= l2.val: 31 | l.next, l1 = l1, l1.next 32 | else: 33 | l.next, l2 = l2, l2.next 34 | l = l.next 35 | l.next = l1 or l2 36 | return head.next 37 | 38 | 39 | def mergeTwoLists2(l1: 'ListNode', l2: 'ListNode') -> 'ListNode': 40 | """ 41 | 解法2:递归。 42 | """ 43 | if not l1 or not l2: 44 | return l1 or l2 45 | if l1.val < l2.val: 46 | l1.next = mergeTwoLists(l1.next, l2) 47 | return l1 48 | else: 49 | l2.next = mergeTwoLists(l1, l2.next) 50 | return l2 -------------------------------------------------------------------------------- /chapter_3/section_4/26_is_subtree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 7:45 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 是否是树的子结构 9 | >>> t1 = '8,8,9,$,$,2,4,$,$,7,$,$,7,$,$' 10 | >>> t1 = deserialize_tree(t1) 11 | >>> t2 = '8,9,$,$,2,$,$' 12 | >>> t2 = deserialize_tree(t2) 13 | >>> is_subtree(t1, t2) 14 | True 15 | 16 | >>> t1 = TreeNode(1) 17 | >>> t1.left, t1.right = TreeNode(2), TreeNode(3) 18 | >>> t1.left.left = TreeNode(4) 19 | >>> t2 = TreeNode(2) 20 | >>> t2.left = TreeNode(5) 21 | >>> is_subtree(t1, t2) 22 | False 23 | """ 24 | 25 | from utils import TreeNode, deserialize_tree 26 | 27 | # 注意:此题和LeetCode中572不一样,572中 28 | # 3 29 | # / \ 30 | # 4 5 31 | # / \ 32 | # 1 2 33 | # / 34 | # 0 35 | 36 | # 和子树,返回值为False,因为2还有左子树。而书中则认为是True。 37 | # 4 38 | # / \ 39 | # 1 2 40 | 41 | 42 | 43 | def is_subtree(s: 'TreeNode', t: 'TreeNode') -> 'bool': 44 | 45 | def is_same(t1, t2): 46 | if not t2: 47 | return True 48 | if not t1: 49 | return False 50 | return t1.val == t2.val and is_same(t1.left, t2.left) and is_same(t1.right, t2.right) 51 | 52 | if not s or not t: return False 53 | stack = s and [s] 54 | while stack: 55 | node = stack.pop() 56 | if node: 57 | if is_same(node, t): 58 | return True 59 | stack.append(node.right) 60 | stack.append(node.left) 61 | return False 62 | -------------------------------------------------------------------------------- /chapter_3/section_4/README.md: -------------------------------------------------------------------------------- 1 | ### 22 链表中倒数第k个节点 2 | 3 | #### [牛客网传送门](https://www.nowcoder.com/practice/529d3ae5a407492994ad2a246518148a?tpId=13&tqId=11167&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=1) 4 | 5 | #### [AcWing传送门](https://www.acwing.com/problem/content/32/) 6 | 7 | 8 | 思路:两个指针,快指针先走k-1步,然后两个一起走,快指针走到尾节点时,慢指针在倒数第k个节点。 9 | 需考虑k=0时和fast已经走到尾节点的情况。 10 | 11 | ```python 12 | def FindKthToTail(self, head, k): 13 | fast = slow = head 14 | for _ in range(k): 15 | if not fast: 16 | return None 17 | fast = fast.next 18 | while fast: 19 | slow, fast = slow.next, fast.next 20 | return slow 21 | ``` 22 | 23 | ### 23 链表中环的入口节点 24 | #### [LeetCode传送门](https://leetcode.com/problems/linked-list-cycle-ii/description/) 25 | 26 | * 首先判断此链表是否有环。 27 | * 然后再相交点和头结点一起走,一定会在入口相遇。 28 | 29 | 30 | ```python 31 | def detectCycle(self, head): 32 | fast = slow = head 33 | # 检测是否有环 34 | while fast and fast.next: 35 | slow, fast = slow.next, fast.next.next 36 | if slow is fast: 37 | break 38 | else: 39 | return None 40 | # 找出入口节点 41 | while head is not slow: 42 | head, slow = head.next, slow.next 43 | return head 44 | ``` 45 | 46 | ### 24 反转链表 47 | 48 | #### [LeetCode传送门](https://leetcode.com/problems/reverse-linked-list/description/) 49 | 50 | ```python 51 | def reverseList(self, head): 52 | prev = None 53 | while head: 54 | head.next, prev, head = prev, head, head.next 55 | return prev 56 | ``` 57 | 58 | ### 25 合并两个有序链表 59 | 60 | #### [LeetCode传送门](https://leetcode.com/problems/merge-two-sorted-lists/description/) 61 | 62 | 方法1:iteratively 迭代 63 | 64 | ```python 65 | def mergeTwoLists(l1, l2): 66 | l = head = ListNode(0) 67 | while l1 and l2: 68 | if l1.val <= l2.val: 69 | l.next, l1 = l1, l1.next 70 | else: 71 | l.next, l2 = l2, l2.next 72 | l = l.next 73 | l.next = l1 or l2 74 | return head.next 75 | ``` 76 | 77 | 方法2:recursively 递归 78 | 79 | ```python 80 | def mergeTwoLists(l1, l2): 81 | # 判断是否存在None 82 | if not l1 or not l2: 83 | return l1 or l2 84 | if l1.val < l2.val: 85 | l1.next = mergeTwoLists(l1.next, l2) 86 | return l1 87 | else: 88 | l2.next = mergeTwoLists(l1, l2.next) 89 | return l2 90 | ``` 91 | 92 | ### 26 树的子结构 93 | 94 | #### [LeetCode传送门](https://leetcode.com/problems/subtree-of-another-tree/description/) 95 | 96 | 二刷的时候突然发现,此题和LeetCode中不同。LeetCode中子树`4-1-2`返回False因为2下边还有节点,所以不一样;而书中认为True,不考虑2下边的节点。 97 | 98 | ``` 99 | 3 100 | / \ 101 | 4 5 102 | / \ 103 | 1 2 104 | / 105 | 0 106 | ``` 107 | 108 | ```python 109 | def is_subtree(s: 'TreeNode', t: 'TreeNode') -> 'bool': 110 | 111 | def is_same(t1, t2): 112 | if not t2: 113 | return True 114 | if not t1: 115 | return False 116 | return t1.val == t2.val and is_same(t1.left, t2.left) and is_same(t1.right, t2.right) 117 | 118 | if not s or not t: return False 119 | stack = s and [s] 120 | while stack: 121 | node = stack.pop() 122 | if node: 123 | if is_same(node, t): 124 | return True 125 | stack.append(node.right) 126 | stack.append(node.left) 127 | return False 128 | ``` -------------------------------------------------------------------------------- /chapter_4/section_2/27_mirror_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 10:12 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 二叉树的镜像 9 | >>> t1 = '8,6,5,$,$,7,$,$,10,9,$,$,11,$,$' 10 | >>> t2 = '8,10,11,$,$,9,$,$,6,7,$,$,5,$,$' 11 | >>> t1 = deserialize_tree(t1) 12 | >>> t2 = deserialize_tree(t2) 13 | >>> mirror(t1) 14 | >>> is_same_tree(t1, t2) 15 | True 16 | 17 | """ 18 | 19 | from utils import deserialize_tree, TreeNode, is_same_tree 20 | 21 | 22 | def mirror(root: TreeNode): 23 | if root: 24 | root.left, root.right = root.right, root.left 25 | mirror(root.left) 26 | mirror(root.right) -------------------------------------------------------------------------------- /chapter_4/section_2/28_symmetric_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 10:33 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 对称的二叉树 9 | >>> t1 = '8,6,5,$,$,7,$,$,6,7,$,$,5,$,$' 10 | >>> t2 = '8,6,5,$,$,7,$,$,9,7,$,$,5,$,$' 11 | >>> t3 = '7,7,7,$,$,7,$,$,7,7,$,$,$' 12 | >>> t1 = deserialize_tree(t1) 13 | >>> t2 = deserialize_tree(t2) 14 | >>> t3 = deserialize_tree(t3) 15 | >>> isSymmetric(t1) 16 | True 17 | >>> isSymmetric(t2) 18 | False 19 | >>> isSymmetric(t3) 20 | False 21 | """ 22 | 23 | from utils import deserialize_tree 24 | 25 | def isSymmetric(root: 'TreeNode') -> 'bool': 26 | 27 | def symmetric(p1, p2): 28 | if p1 and p2: 29 | return (p1.val == p2.val and symmetric(p1.left, p2.right) and 30 | symmetric(p1.right, p2.left)) 31 | else: 32 | return p1 is p2 33 | 34 | if not root: 35 | return True 36 | return symmetric(root.left, root.right) -------------------------------------------------------------------------------- /chapter_4/section_2/29_spiral_matrix.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 10:40 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 顺时针打印矩阵 9 | >>> m = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] 10 | >>> spiral_order(m) 11 | [1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10] 12 | 13 | >>> m = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [13, 14, 15, 16]] 14 | >>> anti_clock_wise(m) 15 | [1, 5, 9, 13, 14, 15, 16, 12, 8, 4, 3, 2, 6, 10, 11, 7] 16 | >>> anti_clock_wise([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) 17 | [1, 4, 7, 8, 9, 6, 3, 2, 5] 18 | """ 19 | 20 | # 这是我最喜欢的写法了,原创来自LeetCode一位大神, 21 | # 相信如果你经常刷LeetCode,应该知道我说的是谁 22 | 23 | def spiral_order(matrix: 'List[List[int]]') -> list: 24 | return (matrix and list(matrix.pop(0)) + 25 | spiral_order(list(zip(*matrix))[::-1])) 26 | 27 | 28 | # 下面为一个变种,逆时针打印。 29 | 30 | def anti_clock_wise(matrix: 'List[List[int]]') -> list: 31 | if not matrix: 32 | return [] 33 | clock_wise = list(zip(*(matrix[::-1]))) 34 | a = list(clock_wise.pop(0))[::-1] 35 | b = anti_clock_wise(clock_wise) 36 | return a + b -------------------------------------------------------------------------------- /chapter_4/section_2/README.md: -------------------------------------------------------------------------------- 1 | ### 27 二叉树的镜像 2 | 3 | #### [牛客网传送门](https://www.nowcoder.com/practice/564f4c26aa584921bc75623e48ca3011?tpId=13&tqId=11171&tPage=1&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 4 | 5 | #### [AcWing传送门](https://www.acwing.com/problem/content/37/) 6 | 7 | 8 | ```python 9 | def Mirror(self, root): 10 | if root: 11 | root.left, root.right = root.right, root.left 12 | self.Mirror(root.left) 13 | self.Mirror(root.right) 14 | ``` 15 | 16 | 迭代 17 | 18 | ```python 19 | def Mirror(self, root): 20 | stack = root and [root] 21 | while stack: 22 | n = stack.pop() 23 | if n: 24 | n.left, n.right = n.right, n.left 25 | stack += n.right, n.left 26 | ``` 27 | ### 28 对称的二叉树 28 | 29 | #### [LeetCode传送门](https://leetcode.com/problems/symmetric-tree/description/) 30 | 31 | ``` 32 | 1 33 | / \ 34 | 2 2 35 | / \ / \ 36 | 3 4 4 3 37 | ``` 38 | 39 | 方法一:recursively. 40 | 41 | ```python 42 | def isSymmetric(self, root: 'TreeNode') -> 'bool': 43 | 44 | def symmetric(p1, p2): 45 | if p1 and p2: 46 | return (p1.val == p2.val and symmetric(p1.left, p2.right) and 47 | symmetric(p1.right, p2.left)) 48 | else: 49 | return p1 is p2 50 | 51 | if not root: 52 | return True 53 | return symmetric(root.left, root.right) 54 | ``` 55 | 56 | 方法二:iteratively. 57 | 58 | ```python 59 | def isSymmetric(self, root: 'TreeNode') -> 'bool': 60 | stack = root and [(root.left, root.right)] 61 | while stack: 62 | p1, p2 = stack.pop() 63 | if not p1 and not p2: continue 64 | if not p1 or not p2: return False 65 | if p1.val != p2.val: return False 66 | stack.append((p1.left, p2.right)) 67 | stack.append((p1.right, p2.left)) 68 | return True 69 | ``` 70 | 71 | ### 29 顺时针打印矩阵 72 | 73 | #### [LeetCode传送门](https://leetcode.com/problems/spiral-matrix/description/) 74 | 75 | 这里注意一点`matrix.pop(0)`需要转成list,因为zip函数中的每个元素是一个tuple,如果不转变成了一个`tuple+list`,会抛出异常。 76 | 77 | ```python 78 | def spiralOrder(self, matrix): 79 | return (matrix and list(matrix.pop(0)) + 80 | self.spiralOrder(list(zip(*matrix))[::-1])) 81 | ``` 82 | 83 | 此题有个变形,如果逆时针该如何打印。这样的话情况稍微复杂一些。 84 | 85 | ```python 86 | def anti_clock_wise(self, matrix) 87 | if not matrix: 88 | return [] 89 | clock_wise = list(zip(*(matrix[::-1]))) 90 | a = list(clock_wise.pop(0))[::-1] 91 | b = self.anti_clock_wise(clock_wise) 92 | return a + b 93 | ``` -------------------------------------------------------------------------------- /chapter_4/section_3/30_min_stack.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 10:55 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 在O(1)时间复杂度获得最小值得栈。 9 | >>> ms = MinStack() 10 | >>> ms.push(-2) 11 | >>> ms.push(0) 12 | >>> ms.push(-3) 13 | >>> ms.getMin() 14 | -3 15 | >>> ms.pop() 16 | >>> ms.top() 17 | 0 18 | >>> ms.getMin() 19 | -2 20 | """ 21 | 22 | 23 | class MinStack: 24 | 25 | def __init__(self): 26 | self._stack = [] 27 | 28 | def push(self, x: int) -> None: 29 | cur_min = self.getMin() 30 | if x < cur_min: 31 | cur_min = x 32 | self._stack.append((x, cur_min)) 33 | 34 | def pop(self) -> None: 35 | self._stack.pop() 36 | 37 | def top(self) -> int: 38 | if not self._stack: 39 | return None 40 | else: 41 | return self._stack[-1][0] 42 | 43 | def getMin(self) -> int: 44 | if not self._stack: 45 | return float('inf') 46 | else: 47 | return self._stack[-1][1] -------------------------------------------------------------------------------- /chapter_4/section_3/31_valid_stack_seq.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 10:58 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 根据入栈和出栈顺序,判断是否能够清空该栈。 9 | >>> pushed = [1, 2, 3, 4, 5] 10 | >>> popped = [4, 5, 3, 2, 1] 11 | >>> validateStackSequences(pushed, popped) 12 | True 13 | >>> validateStackSequences(pushed, [4, 3, 5, 1, 2]) 14 | False 15 | """ 16 | 17 | def validateStackSequences(pushed: 'List[int]', popped: 'List[int]') -> 'bool': 18 | stack = [] 19 | j = 0 20 | for num in pushed: 21 | stack.append(num) 22 | while stack and stack[-1] == popped[j]: 23 | stack.pop() 24 | j += 1 25 | return j == len(popped) -------------------------------------------------------------------------------- /chapter_4/section_3/32_print_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 11:05 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 从上到下打印二叉树 9 | >>> t1 = '8,6,5,$,$,7,$,$,10,9,$,$,11,$,$' 10 | >>> t1 = deserialize_tree(t1) 11 | >>> PrintFromTopToBottom(t1) 12 | [8, 6, 10, 5, 7, 9, 11] 13 | >>> levelOrder(t1) 14 | [[8], [6, 10], [5, 7, 9, 11]] 15 | >>> zigzagLevelOrder(t1) 16 | [[8], [10, 6], [5, 7, 9, 11]] 17 | """ 18 | 19 | from utils import deserialize_tree 20 | 21 | 22 | # 从上到下打印 23 | def PrintFromTopToBottom(root: 'TreeNode') -> list: 24 | from collections import deque 25 | queue = deque([root]) 26 | res = [] 27 | while queue: 28 | cur = queue.popleft() 29 | if cur: 30 | res.append(cur.val) 31 | queue.append(cur.left) 32 | queue.append(cur.right) 33 | return res 34 | 35 | # 分层打印 36 | def levelOrder(root: 'TreeNode') -> 'List[List[int]]': 37 | ans, level = [], root and [root] 38 | while level: 39 | ans.append([n.val for n in level]) 40 | level = [k for n in level for k in (n.left, n.right) if k] 41 | return ans 42 | 43 | # 之字形打印 44 | def zigzagLevelOrder(root: 'TreeNode') -> 'List[List[int]]': 45 | ans, level, order = [], root and [root], 1 46 | while level: 47 | ans.append([n.val for n in level][::order]) 48 | order *= -1 49 | level = [kid for n in level for kid in (n.left, n.right) if kid] 50 | return ans -------------------------------------------------------------------------------- /chapter_4/section_3/33_postorder_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 11:13 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 二叉树后序遍历 9 | >>> t1 = '8,6,5,$,$,7,$,$,10,9,$,$,11,$,$' 10 | >>> t1 = deserialize_tree(t1) 11 | >>> postorder_traversal(t1) 12 | [5, 7, 6, 9, 11, 10, 8] 13 | >>> postorder_traversal2(t1) 14 | [5, 7, 6, 9, 11, 10, 8] 15 | >>> postorder_traversal3(t1) 16 | [5, 7, 6, 9, 11, 10, 8] 17 | >>> postorder_traversal4(t1) 18 | [5, 7, 6, 9, 11, 10, 8] 19 | """ 20 | 21 | from utils import deserialize_tree 22 | 23 | def postorder_traversal(root: 'TreeNode') -> list: 24 | """ 25 | 解法1:根右左,再倒序。 26 | """ 27 | res, stack = [], [root] 28 | while stack: 29 | node = stack.pop() 30 | if node: 31 | res.append(node.val) 32 | stack.append(node.left) 33 | stack.append(node.right) 34 | return res[::-1] 35 | 36 | 37 | def postorder_traversal2(root: 'TreeNode') -> list: 38 | """ 39 | 解法2:使用元组的形式判断当前节点是否访问过 40 | """ 41 | res, stack = [], [(root, False)] 42 | while stack: 43 | node, visited = stack.pop() 44 | if node: 45 | if visited: 46 | res.append(node.val) 47 | else: 48 | stack.append((node, True)) 49 | stack.append((node.right, False)) 50 | stack.append((node.left, False)) 51 | return res 52 | 53 | 54 | def postorder_traversal3(root: 'TreeNode') -> list: 55 | """ 56 | 解法3:空间复杂度最低的方法。 57 | """ 58 | res, stack, node, last = [], [], root, None 59 | while stack or node: 60 | if node: 61 | stack.append(node) 62 | node = node.left 63 | else: 64 | node = stack[-1] 65 | if not node.right or last == node.right: 66 | node = stack.pop() 67 | res.append(node.val) 68 | last, node = node, None 69 | else: 70 | node = node.right 71 | return res 72 | 73 | 74 | def postorder_traversal4(root: 'TreeNode') -> list: 75 | """ 76 | 解法4:生成器。 77 | """ 78 | def dfs(node): 79 | if node: 80 | yield from dfs(node.left) 81 | yield from dfs(node.right) 82 | yield node.val 83 | 84 | return list(dfs(root)) -------------------------------------------------------------------------------- /chapter_4/section_3/33_varify_postorder.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/17 6:01 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 验证后序遍历是否是某个二叉搜索树的遍历结果。 9 | 10 | >>> verify_sequence([5, 7, 6, 9, 11, 10, 8]) 11 | True 12 | >>> verify_sequence([4,6,7,5]) 13 | True 14 | >>> verify_sequence([7, 4, 6, 5]) 15 | False 16 | 17 | """ 18 | from itertools import takewhile, dropwhile 19 | 20 | def verify_sequence(seq: list) -> 'bool': 21 | 22 | if not seq: 23 | return False 24 | p = seq[-1] 25 | left_sub = list(takewhile(lambda x: x < p, seq[:-1])) 26 | right_sub = seq[len(left_sub):-1] 27 | if not all(x>p for x in right_sub): 28 | return False 29 | left = right = True 30 | if left_sub: 31 | left = verify_sequence(left_sub) 32 | if right_sub: 33 | right = verify_sequence(right_sub) 34 | return left and right -------------------------------------------------------------------------------- /chapter_4/section_3/34_path_sum.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 11:26 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 返回二叉树中路径和为某一值得所有路径。 9 | >>> t1 = '10,5,4,$,$,7,$,$,12,$,$' 10 | >>> t1 = deserialize_tree(t1) 11 | >>> pathSum(t1, 22) 12 | [[10, 5, 7], [10, 12]] 13 | """ 14 | 15 | from utils import deserialize_tree 16 | 17 | def pathSum(root: 'TreeNode', sum: int) -> 'List[List[int]]': 18 | if not root: 19 | return [] 20 | val, *kids = root.val, root.left, root.right 21 | if any(kids): 22 | return [[val] + path 23 | for kid in kids if kid 24 | for path in pathSum(kid, sum-val)] 25 | return [[val]] if val==sum else [] 26 | -------------------------------------------------------------------------------- /chapter_4/section_3/README.md: -------------------------------------------------------------------------------- 1 | ### 30 包含min函数的栈 2 | 3 | #### [LeetCode传送门](https://leetcode.com/problems/min-stack/description/) 4 | 5 | ```python 6 | class MinStack: 7 | 8 | def __init__(self): 9 | self._stack = [] 10 | 11 | def push(self, x: int) -> None: 12 | cur_min = self.getMin() 13 | if x < cur_min: 14 | cur_min = x 15 | self._stack.append((x, cur_min)) 16 | 17 | def pop(self) -> None: 18 | self._stack.pop() 19 | 20 | def top(self) -> int: 21 | if not self._stack: 22 | return None 23 | else: 24 | return self._stack[-1][0] 25 | 26 | def getMin(self) -> int: 27 | if not self._stack: 28 | return float('inf') 29 | else: 30 | return self._stack[-1][1] 31 | ``` 32 | 33 | ### 31 栈的压入、弹出序列 34 | 35 | #### [LeetCode传送门](https://leetcode.com/problems/validate-stack-sequences/) 36 | 37 | ```python 38 | def validateStackSequences(self, pushed: 'List[int]', popped: 'List[int]') -> 'bool': 39 | stack = [] 40 | j = 0 41 | for num in pushed: 42 | stack.append(num) 43 | while stack and stack[-1] == popped[j]: 44 | stack.pop() 45 | j += 1 46 | return j == len(popped) 47 | ``` 48 | 49 | ### 32 从上到下打印二叉树 50 | 51 | #### [牛客网传送门](https://www.nowcoder.com/practice/7fe2212963db4790b57431d9ed259701?tpId=13&tqId=11175&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 52 | #### [AcWing传送门](https://www.acwing.com/problem/content/41/) 53 | 54 | 双端队列 55 | 56 | ```python 57 | def PrintFromTopToBottom(self, root): 58 | from collections import deque 59 | queue = deque([root]) 60 | res = [] 61 | while queue: 62 | cur = queue.popleft() 63 | if cur: 64 | res.append(cur.val) 65 | queue.append(cur.left) 66 | queue.append(cur.right) 67 | return res 68 | ``` 69 | 70 | ### 32_1 分层从上到下打印二叉树 71 | 72 | #### [LeetCode传送门](https://leetcode.com/problems/binary-tree-level-order-traversal/description/) 73 | 74 | ```python 75 | def levelOrder(self, root: 'TreeNode') -> 'List[List[int]]': 76 | ans, level = [], root and [root] 77 | while level: 78 | ans.append([n.val for n in level]) 79 | level = [k for n in level for k in (n.left, n.right) if k] 80 | return ans 81 | ``` 82 | 83 | ### 32_2 之字形打印二叉树 84 | 85 | #### [LeetCode传送门](https://leetcode.com/problems/binary-tree-zigzag-level-order-traversal/description/) 86 | 87 | ```python 88 | def zigzagLevelOrder(self, root: 'TreeNode') -> 'List[List[int]]': 89 | ans, level, order = [], root and [root], 1 90 | while level: 91 | ans.append([n.val for n in level][::order]) 92 | order *= -1 93 | level = [kid for n in level for kid in (n.left, n.right) if kid] 94 | return ans 95 | ``` 96 | 97 | ### 33 是否是二叉搜索树的后序遍历 98 | 99 | #### [牛客网传送门](https://www.nowcoder.com/practice/a861533d45854474ac791d90e447bafd?tpId=13&tqId=11176&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=2) 100 | 101 | ```python 102 | def VerifySquenceOfBST(self, seq): 103 | from itertools import takewhile 104 | if not seq: 105 | return False 106 | p = seq[-1] 107 | left_sub = list(takewhile(lambda x: x < p, seq[:-1])) 108 | right_sub = seq[len(left_sub):-1] 109 | if not all(x>p for x in right_sub): 110 | return False 111 | left = right = True 112 | if left_sub: 113 | left = self.VerifySquenceOfBST(left_sub) 114 | if right_sub: 115 | right = self.VerifySquenceOfBST(right_sub) 116 | return left and right 117 | ``` 118 | ### 33_1 二叉树的后序遍历 119 | 120 | #### [LeetCode传送门](https://leetcode.com/problems/binary-tree-postorder-traversal/description/) 121 | 122 | 方法一:根右左,再倒序。 123 | 124 | ```python 125 | def postorder_traversal(root): 126 | res, stack = [], [root] 127 | while stack: 128 | node = stack.pop() 129 | if node: 130 | res.append(node.val) 131 | stack.append(node.left) 132 | stack.append(node.right) 133 | return res[::-1] 134 | ``` 135 | 136 | 方法二:思想: 使用`last`作为判断是否该节点的右子树完成遍历,如果一个`node.right`已经刚刚遍历完毕,那么将`last==node.right`,否则将会寻找`node.right`。 137 | 138 | ```python 139 | def postorderTraversal(self, root): 140 | res, stack, node, last = [], [], root, None 141 | while stack or node: 142 | if node: 143 | stack.append(node) 144 | node = node.left 145 | else: 146 | node = stack[-1] 147 | if not node.right or last == node.right: 148 | node = stack.pop() 149 | res.append(node.val) 150 | last, node = node, None 151 | else: 152 | node = node.right 153 | return res 154 | ``` 155 | 156 | 方法三:使用boolean判断一个节点是否被遍历过 157 | 158 | ```python 159 | def postorderTraversal(self, root): 160 | res, stack = [], [(root, False)] 161 | while stack: 162 | node, visited = stack.pop() 163 | if node: 164 | if visited: 165 | res.append(node.val) 166 | else: 167 | stack.append((node, True)) 168 | stack.append((node.right, False)) 169 | stack.append((node.left, False)) 170 | return res 171 | ``` 172 | 173 | 方法四:dfs. 174 | 175 | ```python 176 | def postorderTraversal(self, root: TreeNode) -> List[int]: 177 | 178 | def dfs(n): 179 | if n: 180 | yield from dfs(n.left) 181 | yield from dfs(n.right) 182 | yield n.val 183 | return list(dfs(root)) 184 | ``` 185 | 186 | ### 34 二叉树和为某一值的路径 187 | 188 | #### [LeetCode传送门](https://leetcode.com/problems/path-sum-ii/description/) 189 | 190 | ``` 191 | sum = 22 192 | 193 | 5 194 | / \ 195 | 4 8 196 | / / \ 197 | 11 13 4 198 | / \ / \ 199 | 7 2 5 1 200 | [ 201 | [5,4,11,2], 202 | [5,8,4,5] 203 | ] 204 | ``` 205 | 方法一:iteratively. 举一反三。 206 | 207 | ```python 208 | def pathSum(self, root: 'TreeNode', total: 'int') -> 'List[List[int]]': 209 | stack = root and [(root, [root.val], total)] 210 | ans = [] 211 | while stack: 212 | n, v, t = stack.pop() 213 | if not n.left and not n.right and n.val==t: 214 | ans.append(v) 215 | if n.right: 216 | stack.append((n.right, v+[n.right.val], t-n.val)) 217 | if n.left: 218 | stack.append((n.left, v+[n.left.val], t-n.val)) 219 | return ans 220 | ``` 221 | 222 | recursively. 先找出所有路径,再过滤,实际上和257题一样。不过这并没有把这道题的特性涵盖进去。 223 | 224 | ```python 225 | def pathSum(self, root, sum_val): 226 | paths = self.all_paths(root) 227 | return [path for path in paths if sum(path)==sum_val] 228 | 229 | def all_paths(self, root): 230 | if not root: 231 | return [] 232 | return [[root.val]+path 233 | for kid in (root.left, root.right) if kid 234 | for path in self.all_paths(kid)] or [[root.val]] 235 | ``` 236 | 237 | 方法三:recursively. 238 | 239 | ```python 240 | def pathSum(self, root, sum): 241 | if not root: 242 | return [] 243 | val, *kids = root.val, root.left, root.right 244 | if any(kids): 245 | return [[val] + path 246 | for kid in kids if kid 247 | for path in self.pathSum(kid, sum-val)] 248 | return [[val]] if val==sum else [] 249 | ``` -------------------------------------------------------------------------------- /chapter_4/section_4/35_copy_random_list.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 11:33 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 复杂链表的复制。 9 | >>> n1, n2, n3 = RandomListNode(1), RandomListNode(2), RandomListNode(3) 10 | >>> n4, n5 = RandomListNode(4), RandomListNode(5) 11 | >>> n1.next, n2.next, n3.next, n4.next = n2, n3, n4, n5 12 | >>> n3.random, n2.random = n1, n5 13 | >>> print(pretty_linklist(n1)) 14 | 1->2->3->4->5 15 | >>> head2 = copyRandomList2(n1) 16 | >>> print(pretty_linklist(head2)) 17 | 1->2->3->4->5 18 | >>> check_same(n1, head2) 19 | True 20 | >>> head3 = copyRandomList(n1) 21 | >>> check_same(n1, head3) 22 | True 23 | >>> n3.random = n3 24 | >>> check_same(n1, head3) 25 | False 26 | >>> check_same(n1, head2) 27 | False 28 | >>> check_same(head2, head3) 29 | True 30 | """ 31 | 32 | from utils import ListNode, pretty_linklist 33 | 34 | class RandomListNode(ListNode): 35 | 36 | def __init__(self, x): 37 | super().__init__(x) 38 | self.random = None 39 | 40 | 41 | def copyRandomList(head: 'RandomListNode') -> 'RandomListNode': 42 | """ 43 | 解法1:遍历两次 44 | """ 45 | cp_map = {} 46 | 47 | m = n = head 48 | while m: 49 | cp_map[m] = RandomListNode(m.val) 50 | m = m.next 51 | while n: 52 | cp_map[n].next = cp_map.get(n.next) 53 | cp_map[n].random = cp_map.get(n.random) 54 | n = n.next 55 | 56 | return cp_map.get(head) 57 | 58 | def copyRandomList2(head: 'RandomListNode') -> 'RandomListNode': 59 | """ 60 | 解法2:遍历一次 61 | """ 62 | from collections import defaultdict 63 | cp = defaultdict(lambda: RandomListNode(0)) 64 | cp[None] = None 65 | n = head 66 | while n: 67 | cp[n].val = n.val 68 | cp[n].next = cp[n.next] 69 | cp[n].random = cp[n.random] 70 | n = n.next 71 | return cp[head] 72 | 73 | def check_same(h1, h2): 74 | n1, n2 = h1, h2 75 | while n1 and n2: 76 | if n1.val != n2.val: 77 | return False 78 | if n1.random and n2.random: 79 | if n1.random.val != n2.random.val: 80 | return False 81 | elif n1.random is not n2.random: 82 | return False 83 | n1, n2 = n1.next, n2.next 84 | return n1 is n2 -------------------------------------------------------------------------------- /chapter_4/section_4/36_bst2linkedlist.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 10:12 AM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 二叉搜索树转双向链表 9 | >>> t_str = '10,6,4,$,$,8,$,$,14,12,$,$,16,$,$' 10 | >>> t1 = deserialize_tree(t_str) 11 | >>> correct = inorder_traversal(t1) 12 | >>> correct 13 | [4, 6, 8, 10, 12, 14, 16] 14 | 15 | # test first 16 | >>> ans = convert(t1) 17 | >>> check_ans(ans, correct) 18 | True 19 | 20 | # test second 21 | >>> t1 = deserialize_tree(t_str) 22 | >>> ans2 = convert2(t1) 23 | >>> check_ans(ans, correct) 24 | True 25 | 26 | # test third 27 | >>> t1 = deserialize_tree(t_str) 28 | >>> ans2 = convert3(t1) 29 | >>> check_ans(ans, correct) 30 | True 31 | 32 | """ 33 | 34 | from utils import deserialize_tree, inorder_traversal 35 | 36 | def convert(root: 'TreeNode') -> 'TreeNode': 37 | """ 38 | 解法1:遍历两次。 39 | """ 40 | if not root: 41 | return None 42 | inorder = [] 43 | def dfs(node): 44 | if node: 45 | dfs(node.left) 46 | inorder.append(node) 47 | dfs(node.right) 48 | 49 | dfs(root) 50 | for i, n in enumerate(inorder[:-1]): 51 | n.right = inorder[i+1] 52 | inorder[i+1].left = n 53 | return inorder[0] 54 | 55 | 56 | def convert2(root: 'TreeNode') -> 'TreeNode': 57 | """ 58 | 解法2:递归。 59 | """ 60 | def convert_tree(node): 61 | if not node: 62 | return None 63 | if node.left: 64 | left = convert_tree(node.left) 65 | while left.right: 66 | left = left.right 67 | left.right = node 68 | node.left = left 69 | if node.right: 70 | right = convert_tree(node.right) 71 | while right.left: 72 | right = right.left 73 | right.left = node 74 | node.right = right 75 | return node 76 | 77 | if not root: 78 | return root 79 | root = convert_tree(root) 80 | while root.left: 81 | root = root.left 82 | return root 83 | 84 | def convert3(root: 'TreeNode') -> 'TreeNode': 85 | """ 86 | 解法3:Morris Traversal. 87 | """ 88 | cur = root 89 | pre = ans = None 90 | while cur: 91 | while cur.left: 92 | q = cur.left 93 | while q.right: 94 | q = q.right 95 | q.right = cur # 补齐右指针 96 | cur.left, cur = None, cur.left # 拆掉左指针 97 | cur.left = pre 98 | if pre is None: 99 | ans = cur # 这里是为了找到链表的头,只执行一次 100 | else: 101 | pre.right = cur 102 | pre, cur = cur, cur.right 103 | return ans 104 | 105 | def check_ans(root: 'TreeNode', right) -> 'bool': 106 | asc, desc = [], [] 107 | pre = None 108 | while root: 109 | asc.append(root.val) 110 | pre, root = root, root.right 111 | 112 | while pre: 113 | desc.append(pre.val) 114 | pre = pre.left 115 | desc.reverse() 116 | return asc==right==desc -------------------------------------------------------------------------------- /chapter_4/section_4/37_serialize_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 8:27 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 序列化反序列化二叉树。 10 | >>> t1 = TreeNode(1) 11 | >>> t1.left, t1.right = TreeNode(2), TreeNode(3) 12 | >>> t1.left.left, t1.right.left, t1.right.right = TreeNode(4), TreeNode(5), TreeNode(6) 13 | >>> t2 = copy.copy(t1) 14 | >>> t3_str = serialize_tree(t1) 15 | >>> t3 = deserialize_tree(t3_str) 16 | >>> is_same_tree(t2, t3) 17 | True 18 | 19 | """ 20 | 21 | import copy 22 | from utils import TreeNode, is_same_tree 23 | 24 | def serialize_tree(root: 'TreeNode') -> str: 25 | if not root: 26 | return '$' 27 | return (str(root.val) + ',' + serialize_tree(root.left) + 28 | ',' + serialize_tree(root.right)) 29 | 30 | def deserialize_tree(data: str) -> 'TreeNode': 31 | nodes = data.split(',')[::-1] 32 | return deserialize_tree_util(nodes) 33 | 34 | def deserialize_tree_util(nodes): 35 | val = nodes.pop() 36 | if val == '$': 37 | return None 38 | root = TreeNode(int(val)) 39 | root.left = deserialize_tree_util(nodes) 40 | root.right = deserialize_tree_util(nodes) 41 | return root -------------------------------------------------------------------------------- /chapter_4/section_4/38_str_permutations.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 4:38 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 字符串的全排列,可能包含重复的字符,因此要去重。 10 | >>> s1 = 'abc' 11 | >>> s2 = 'aacd' 12 | >>> ans1 = sorted(list(set(''.join(x) for x in permutations(s1)))) 13 | >>> ans2 = sorted(list(set(''.join(x) for x in permutations(s2)))) 14 | >>> ans1 == my_permutation(s1) 15 | True 16 | >>> ans2 == my_permutation(s2) 17 | True 18 | """ 19 | 20 | from itertools import permutations 21 | 22 | def my_permutation(ss: str) -> 'List[str]': 23 | ans = [''] 24 | for s in ss: 25 | ans = [p[:i] + s + p[i:] 26 | for p in ans for i in range((p+s).index(s)+1)] 27 | return sorted(ans) if ss else [] -------------------------------------------------------------------------------- /chapter_4/section_4/README.md: -------------------------------------------------------------------------------- 1 | ### 35 复杂链表的复制 2 | 3 | #### [LeetCode传送门](https://leetcode.com/problems/copy-list-with-random-pointer/description/) 4 | 5 | 方法一:遍历两次。 6 | 7 | ```python 8 | def copyRandomList(self, head: 'Node') -> 'Node': 9 | cp = {None: None} 10 | m = n = head 11 | while m: 12 | cp[m] = Node(m.val, None, None) 13 | m = m.next 14 | while n: 15 | cp[n].next = cp[n.next] 16 | cp[n].random = cp[n.random] 17 | n = n.next 18 | return cp[head] 19 | ``` 20 | 21 | Time-O(n), Memory-O(n). 这种方式是相当于把第一次迭代的过程委托给了`defaultdict`,通过创建一个默认的对象,再去修改它的label值。 22 | 23 | ```python 24 | def copyRandomList(self, head: 'Node') -> 'Node': 25 | cp = collections.defaultdict(lambda: Node(0, None, None)) 26 | cp[None] = None 27 | h = head 28 | while h: 29 | cp[h].val = h.val 30 | cp[h].next = cp[h.next] 31 | cp[h].random = cp[h.random] 32 | h = h.next 33 | return cp[head] 34 | ``` 35 | 36 | ### 36 二叉搜索树与双向链表 37 | 38 | #### LeetCode有此题,但是不是免费的。[牛客网传送门](https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&tqId=11179&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 39 | #### [AcWing传送门](https://www.acwing.com/problem/content/87/) 40 | 41 | 方法一:中序遍历,再构造链表。 42 | 43 | ```python 44 | def convert(self, root): 45 | from itertools import tee 46 | def dfs(node): 47 | if node: 48 | yield from dfs(node.left) 49 | yield node 50 | yield from dfs(node.right) 51 | 52 | a, b = tee(dfs(root)) 53 | ans = next(b, None) 54 | for f, s in zip(a, b): 55 | f.right = s 56 | s.left = f 57 | return ans 58 | ``` 59 | 60 | 方法二:分别递归处理左子树和右子树。 61 | 62 | ```python 63 | def Convert(self, root): 64 | 65 | def convert_tree(node): 66 | if not node: 67 | return None 68 | if node.left: 69 | left = convert_tree(node.left) 70 | while left.right: 71 | left = left.right 72 | left.right = node 73 | node.left = left 74 | if node.right: 75 | right = convert_tree(node.right) 76 | while right.left: 77 | right = right.left 78 | right.left = node 79 | node.right = right 80 | return node 81 | 82 | if not root: 83 | return root 84 | root = convert_tree(root) 85 | while root.left: 86 | root = root.left 87 | return root 88 | ``` 89 | 90 | 方法三:Morris Traversal. 91 | 92 | ```python 93 | def Convert(self, root): 94 | cur = root 95 | pre = ans = None 96 | while cur: 97 | while cur.left: 98 | q = cur.left 99 | while q.right: 100 | q = q.right 101 | q.right = cur # 补齐右指针 102 | cur.left, cur = None, cur.left # 拆掉左指针 103 | cur.left = pre 104 | if pre is None: 105 | ans = cur # 这里是为了找到链表的头,只执行一次 106 | else: 107 | pre.right = cur 108 | pre, cur = cur, cur.right 109 | return ans 110 | ``` 111 | ### 37 序列化二叉树 112 | 113 | #### [LeetCode传送门](https://leetcode.com/problems/serialize-and-deserialize-binary-tree/description/) 114 | 115 | ```python 116 | class Codec: 117 | 118 | def serialize(self, root): 119 | if not root: 120 | return '$' 121 | return str(root.val) + ',' + self.serialize(root.left) + ',' + self.serialize(root.right) 122 | 123 | def deserialize(self, data): 124 | 125 | def deserialize_tree(nodes): 126 | val = next(nodes) 127 | if val == '$': 128 | return None 129 | root = TreeNode(val) 130 | root.left = deserialize_tree(nodes) 131 | root.right = deserialize_tree(nodes) 132 | return root 133 | nodes = iter(data.split(',')) 134 | return deserialize_tree(nodes) 135 | ``` 136 | 137 | ### 38 字符串的排列 138 | 139 | #### [LeetCode传送门](https://leetcode.com/problems/permutations/description/)这是一道类似的题,求的是数组的全排列,不过在Python中都一样,都是可迭代对象。 140 | 141 | 使用itertools 142 | 143 | ```python 144 | def Permutation(self, ss): 145 | # write code here 146 | from itertools import permutations 147 | if not ss: 148 | return [] 149 | return sorted(list(set([''.join(x) for x in permutations(ss)]))) 150 | ``` 151 | 这里注意几点: 152 | * 为什么要判断`if not ss`,是因为如果`ss=''`的时时候,返回了`['']`而不是`[]`。因为这里返回了一个空的`tuple`,所以在列表推导式中是有一个元素的。 153 | 154 | ```shell 155 | >>> list(permutations('', 0)) 156 | [()] 157 | ``` 158 | * 为什么使用`set`去重,因为当`ss='aa'`的时候,牛客网的测试用例要求返回一个元素,即`['aa']`。 159 | * 排序也是为了满足测试用例。 160 | 161 | 自己实现。这里拆成两个方法的原因还是因为`ss=''`的时候会影响递归循环。 162 | 163 | ```python 164 | def Permutation(self, ss): 165 | if not ss: 166 | return [] 167 | return self.permute(ss) 168 | 169 | def permute(self, ss): 170 | return sorted(list(set([h + p 171 | for i, h in enumerate(ss) 172 | for p in self.permute(ss[:i]+ss[i+1:]) 173 | ]))) or [""] 174 | ``` 175 | 176 | 方法二:迭代。 177 | 178 | ```python 179 | def Permutation(self, ss): 180 | ans = [''] 181 | for s in ss: 182 | ans = [p[:i] + s + p[i:] 183 | for p in ans for i in range((p+s).index(s)+1)] 184 | return sorted(ans) if ss else [] 185 | ``` 186 | 187 | -------------------------------------------------------------------------------- /chapter_5/section_2/39_mojority_num.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 4:56 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 数组中超过一半的数。 9 | >>> nums = [1, 2, 3, 2, 2, 2, 5, 4, 2] 10 | >>> majority_element(nums) 11 | 2 12 | >>> majority_element2(nums) 13 | 2 14 | >>> majority_element3(nums) 15 | 2 16 | 17 | """ 18 | 19 | # 牛客网中把此题改了,这个超过一半的数可能不存在,这样就没法用波义尔摩尔投票的经典解法了。 20 | # 我们不管它,按照书中的解法为准。 21 | 22 | 23 | def majority_element(nums: 'List[int]') -> int: 24 | """ 25 | 解法1:排序。 26 | """ 27 | return sorted(nums)[len(nums)//2] 28 | 29 | def majority_element2(nums: 'List[int]') -> int: 30 | """ 31 | 解法2:使用Counter. 32 | """ 33 | from collections import Counter 34 | c = Counter(nums) 35 | # return max(c.keys(), key=c.get) 36 | return c.most_common(1)[0][0] 37 | 38 | def majority_element3(nums: 'List[int]') -> int: 39 | """ 40 | 解法3:波义尔摩尔投票法。 41 | """ 42 | count = 0 43 | candidate = None 44 | for num in nums: 45 | if count == 0: 46 | candidate = num 47 | count += (1 if num == candidate else -1) 48 | return candidate -------------------------------------------------------------------------------- /chapter_5/section_2/40_kth_smallest.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:06 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 最小的K个数 9 | >>> nums = [4, 5, 1, 2, 6, 7, 3, 8] 10 | >>> GetSmallNumbers_Solution(nums, 4) 11 | [1, 2, 3, 4] 12 | """ 13 | 14 | 15 | def GetSmallNumbers_Solution(tinput: list, k: int) -> list: 16 | # write code here 17 | l, r = 0, len(tinput) - 1 18 | if k > len(tinput) or k < 1: return [] # for passing the damn testcases in niuke 19 | while True: 20 | pos = partition(tinput, l, r) 21 | if pos < k - 1: 22 | l = pos + 1 23 | elif pos > k - 1: 24 | r = pos - 1 25 | else: 26 | return sorted( 27 | tinput[:pos + 1]) # sorted for passing the damn testcases of niuke 28 | 29 | 30 | def partition(nums: list, l: int, r: int) -> int: 31 | from random import randint 32 | p = randint(l, r) # 消除依赖 33 | nums[r], nums[p] = nums[p], nums[r] 34 | for i, v in enumerate(nums[l:r + 1], l): 35 | if nums[i] <= nums[r]: 36 | nums[l], nums[i] = nums[i], nums[l] 37 | l += 1 38 | return l - 1 # the pivot inde -------------------------------------------------------------------------------- /chapter_5/section_2/41_median_in_stream.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:11 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 找出数据流中的中位数。 9 | >>> mf = MedianFinder() 10 | >>> mf.addNum(41) 11 | >>> mf.findMedian() 12 | 41.0 13 | >>> mf.addNum(35) 14 | >>> mf.findMedian() 15 | 38.0 16 | >>> mf.addNum(62) 17 | >>> mf.addNum(4) 18 | >>> mf.addNum(97) 19 | >>> mf.findMedian() 20 | 41.0 21 | """ 22 | 23 | import heapq as hq 24 | 25 | 26 | class MedianFinder: 27 | 28 | def __init__(self): 29 | self.lo, self.hi = [], [] # lo is max_heap, hi is min_heap 30 | 31 | def addNum(self, num: int): 32 | hq.heappush(self.lo, -num) 33 | hq.heappush(self.hi, -hq.heappop(self.lo)) 34 | 35 | if len(self.lo) < len(self.hi): 36 | hq.heappush(self.lo, -hq.heappop(self.hi)) 37 | 38 | def findMedian(self): 39 | if len(self.lo) == len(self.hi): 40 | return (-self.lo[0] + self.hi[0]) / 2.0 41 | else: 42 | return float(-self.lo[0]) -------------------------------------------------------------------------------- /chapter_5/section_2/42_maximum_sub.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:17 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 连续子数组的最大和 10 | >>> nums = [1, -2, 3, 10, -4, 7, 2, -5] 11 | >>> maxSubArray(nums) 12 | 18 13 | >>> maxSubArray2(nums) 14 | 18 15 | """ 16 | 17 | def maxSubArray(nums: list) -> int: 18 | """ 19 | 解法1:书中的思想 20 | """ 21 | cp_nums = nums[:] 22 | for i in range(1, len(nums)): 23 | if cp_nums[i-1] > 0: 24 | cp_nums[i] += cp_nums[i-1] 25 | return max(cp_nums) 26 | 27 | 28 | def maxSubArray2(nums: list) -> int: 29 | """ 30 | 解法2:同1,简便写法。 31 | """ 32 | from itertools import accumulate 33 | return max(accumulate(nums, lambda x, y: x+y if x > 0 else y)) -------------------------------------------------------------------------------- /chapter_5/section_2/43_num_of_one.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:21 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 1~n整数中1出现的次数。 9 | >>> countDigitOne(12) 10 | 5 11 | """ 12 | 13 | 14 | def countDigitOne(n: int) -> int: 15 | counter, i = 0, 1 16 | while i <= n: 17 | divider = i * 10 18 | counter += (n // divider) * i + min(max(n % divider - i + 1, 0), i) 19 | i *= 10 20 | return counter 21 | -------------------------------------------------------------------------------- /chapter_5/section_2/45_min_num.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:26 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 把数组排成最小的数字 9 | >>> nums = [3, 32, 321] 10 | >>> PrintMinNumber(nums) 11 | '321323' 12 | """ 13 | 14 | from functools import cmp_to_key 15 | 16 | def PrintMinNumber(numbers): 17 | nums = list(map(str, numbers)) 18 | nums.sort(key=cmp_to_key(lambda x, y: ((x+y)>(y+x)) - ((y+x)>(x+y)))) 19 | return ''.join(nums) -------------------------------------------------------------------------------- /chapter_5/section_2/46_num2strways.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/18 2:46 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 数字翻译成字符串有多少种 9 | >>> num_decodings('226') 10 | 3 11 | """ 12 | 13 | 14 | 15 | def num_decodings(s: str) -> int: 16 | # w tells the number of ways 17 | # v tells the previous number of ways 18 | # d is the current digit 19 | # p is the previous digit 20 | v, w, p = 0, int(s>''), '' 21 | for d in s: 22 | v, w, p = w, int(d>'0')*w + (9>> m = [[1, 10, 3, 8], [12, 2, 9, 6], [5, 7, 4, 11], [3, 7, 16, 5]] 10 | >>> get_max_value(m) 11 | 53 12 | """ 13 | import itertools 14 | 15 | 16 | def get_max_value(g: 'List[List[int]]') -> int: 17 | R, C = len(g), len(g[0]) 18 | cur = list(itertools.accumulate(g[0])) 19 | for i in range(1, R): 20 | tmp = [] 21 | for j in range(C): 22 | left = tmp[-1] if j > 0 else float('-inf') 23 | tmp.append(max(cur[j], left) + g[i][j]) 24 | cur = tmp 25 | return cur[-1] -------------------------------------------------------------------------------- /chapter_5/section_2/48_longest_sub_not_repeat.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:28 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 最长不含重复字符的子字符串 10 | 11 | >>> s = 'arabcacfr' 12 | >>> lengthOfLongestSubstring(s) 13 | 4 14 | >>> lengthOfLongestSubstring('abba') 15 | 2 16 | 17 | """ 18 | 19 | 20 | def lengthOfLongestSubstring(s: str) -> str: 21 | res, start = 0, 0 22 | dic = {} 23 | for end in range(len(s)): 24 | if s[end] in dic: 25 | start = max(start, dic[s[end]]+1) 26 | # start = dic[s[end]] + 1 27 | dic[s[end]] = end 28 | res = max(res, end-start+1) 29 | return res -------------------------------------------------------------------------------- /chapter_5/section_2/README.md: -------------------------------------------------------------------------------- 1 | ### 39 数组中出现次数超过一半的数字 2 | 3 | #### [LeetCode传送门](https://leetcode.com/problems/majority-element/description/) 4 | 5 | 方法一:排序. Time-O(nlogn), Space-O(n) 6 | 7 | ```python 8 | def majority_element(nums): 9 | return sorted(nums)[len(nums)//2] 10 | ``` 11 | 12 | 方法二:Counter Time-O(n), Space-O(n) 13 | 14 | ```python 15 | def majority_element(nums): 16 | from collections import Counter 17 | c = Counter(nums) 18 | # return max(c.keys(), key=c.get) 19 | return c.most_common(1)[0][0] 20 | ``` 21 | 22 | 方法三:Boyer-Moore Voting Algorithm. 书中的算法说的就是这个,这详情请看[波义尔摩尔投票](https://darktiantian.github.io/%E6%B3%A2%E4%B9%89%E5%B0%94%E6%91%A9%E5%B0%94%E6%8A%95%E7%A5%A8%E7%AE%97%E6%B3%95%EF%BC%88Boyer-Moore-Voting-Algorithm%EF%BC%89/)。 23 | 24 | ```python 25 | def majorityElement(self, nums): 26 | count = 0 27 | candidate = None 28 | for num in nums: 29 | if count == 0: 30 | candidate = num 31 | count += (1 if num == candidate else -1) 32 | return candidate 33 | ``` 34 | 35 | ### 40 最小的k个数 36 | 37 | #### 相似题目,但是求最大的k个数[LeetCode传送门](https://leetcode.com/problems/kth-largest-element-in-an-array/) 38 | 39 | #### [牛客网传送门](https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&tqId=11182&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking&tPage=2) 40 | 41 | ```python 42 | def GetLeastNumbers_Solution(self, tinput, k): 43 | # write code here 44 | l, r = 0, len(tinput)-1 45 | if k > len(tinput) or k < 1: return [] # for passing the damn testcase 46 | while True: 47 | pos = self.partition(tinput, l, r) 48 | if pos < k-1: 49 | l = pos + 1 50 | elif pos > k-1: 51 | r = pos - 1 52 | else: 53 | return sorted(tinput[:pos+1]) # sorted for passing the damn testcase 54 | 55 | def partition(self, nums, l, r): 56 | from random import randint 57 | p = randint(l, r) 58 | nums[r], nums[p] = nums[p], nums[r] 59 | for i, v in enumerate(nums[l:r+1], l): 60 | if nums[i] <= nums[r]: 61 | nums[l], nums[i] = nums[i], nums[l] 62 | l += 1 63 | return l-1 # the pivot index 64 | ``` 65 | 66 | 使用堆,不改变原数组 67 | 68 | ```python 69 | def GetLeastNumbers_Solution(self, tinput, k): 70 | import heapq as hq 71 | if k > len(tinput) or k <= 0: return [] 72 | heap = [-x for x in tinput[:k]] 73 | hq.heapify(heap) 74 | for num in tinput[k:]: 75 | if -heap[0] > num: 76 | hq.heapreplace(heap, -num) 77 | return sorted(-x for x in heap) 78 | ``` 79 | 80 | ### 41 数据流中的中位数 81 | 82 | #### [LeetCode传送门](https://leetcode.com/problems/find-median-from-data-stream/description/) 83 | 84 | 思路:使用两个堆,最大堆存储较小的数据,最小堆存储较大的数据。添加数字时,先添加到最大堆,然后最大堆返回一个最大的数字给最小堆,最后为了平衡,可能需要最小堆还给最大堆一个最小值,以保证最大堆的长度>=最小堆的长度。由于headpq是最小堆,所以使用取反实现最大堆。添加数字:Time-O(logn),取出中位数:Time-O(1)。 85 | 86 | ```python 87 | Adding number 41 88 | MaxHeap lo: [41] // MaxHeap stores the largest value at the top (index 0) 89 | MinHeap hi: [] // MinHeap stores the smallest value at the top (index 0) 90 | Median is 41 91 | ======================= 92 | Adding number 35 93 | MaxHeap lo: [35] 94 | MinHeap hi: [41] 95 | Median is 38 96 | ======================= 97 | Adding number 62 98 | MaxHeap lo: [41, 35] 99 | MinHeap hi: [62] 100 | Median is 41 101 | ======================= 102 | Adding number 4 103 | MaxHeap lo: [35, 4] 104 | MinHeap hi: [41, 62] 105 | Median is 38 106 | ======================= 107 | Adding number 97 108 | MaxHeap lo: [41, 35, 4] 109 | MinHeap hi: [62, 97] 110 | Median is 41 111 | ======================= 112 | Adding number 108 113 | MaxHeap lo: [41, 35, 4] 114 | MinHeap hi: [62, 97, 108] 115 | Median is 51.5 116 | ``` 117 | ```python 118 | import heapq as hq 119 | 120 | class MedianFinder: 121 | 122 | def __init__(self): 123 | self.lo, self.hi = [], [] # lo is max_heap, hi is min_heap 124 | 125 | def addNum(self, num): 126 | hq.heappush(self.lo, -num) 127 | hq.heappush(self.hi, -hq.heappop(self.lo)) 128 | 129 | if len(self.lo) < len(self.hi): 130 | hq.heappush(self.lo, -hq.heappop(self.hi)) 131 | 132 | def findMedian(self): 133 | if len(self.lo) == len(self.hi): 134 | return (-self.lo[0]+self.hi[0]) / 2.0 135 | else: 136 | return float(-self.lo[0]) 137 | ``` 138 | 139 | ### 42 连续子数组的最大和 140 | 141 | #### [LeetCode传送门](https://leetcode.com/problems/maximum-subarray/description/) 142 | 143 | 方法一:书中的思想。 144 | 145 | ```python 146 | def maxSubArray(self, nums): 147 | cp_nums = nums[:] 148 | for i in range(1, len(nums)): 149 | if cp_nums[i-1] > 0: 150 | cp_nums[i] += cp_nums[i-1] 151 | return max(cp_nums) 152 | ``` 153 | 154 | 方法二:one-liner。注意`accumulate`是把函数放到后面的。 155 | 156 | ```python 157 | def maxSubArray(self, nums): 158 | from itertools import accumulate 159 | return max(accumulate(nums, lambda x, y: x+y if x > 0 else y)) 160 | ``` 161 | 162 | ### 43 1~n整数中1出现的次数 163 | 164 | #### [LeetCode传送门](https://leetcode.com/problems/number-of-digit-one/description/) 165 | 166 | ```python 167 | def countDigitOne(self, n): 168 | countr, i = 0, 1 169 | while i <= n: 170 | divider = i * 10 171 | countr += (n // divider) * i + min(max(n % divider - i + 1, 0), i) 172 | i *= 10 173 | return countr 174 | ``` 175 | 176 | ### 44 数字序列中某一位的数字 177 | 178 | #### [LeetCode传送门](https://leetcode.com/problems/nth-digit/) 179 | 180 | ```python 181 | def findNthDigit(self, n): 182 | start, step, size = 1, 9, 1 183 | while n > size * step: 184 | n, start, step, size = n-size*step, start*10, step*10, size+1 185 | return int(str(start + (n-1)//size)[(n-1) % size]) 186 | ``` 187 | ### 45 把数组排成最小的数字 188 | 189 | #### [牛客网传送门](https://www.nowcoder.com/practice/8fecd3f8ba334add803bf2a06af1b993?tpId=13&tqId=11185&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 190 | #### [AcWing传送门](https://www.acwing.com/problem/content/54/) 191 | 192 | python2的写法。 193 | 194 | ```python 195 | def PrintMinNumber(self, numbers): 196 | return ''.join(sorted(map(str, numbers), 197 | lambda x, y: cmp(x+y, y+x))) 198 | ``` 199 | 匿名函数作为sort的参数,在python2中有这个参数。 200 | 201 | > cmp specifies a custom comparison function of two arguments (iterable elements) which should return a negative, zero or positive number depending on whether the first argument is considered smaller than, equal to, or larger than the second argument: cmp=lambda x,y: cmp(x.lower(), y.lower()). The default value is None. 202 | 203 | 作为sort的参数,cmp提供了一个自定义的比较两个元素的方法,如果返回-1表示前者小于后者。python3中取消了这个参数,但是提供了一种key的转换。而内置函数可以通过运算符实现。 204 | 205 | ```python 206 | cmp(a, b) 207 | ``` 208 | 等同于 209 | 210 | ```python 211 | (a>b) - (a(y+x)) - ((y+x)>(x+y)))) 221 | return ''.join(nums) 222 | ``` 223 | 224 | ### 46 把数字翻译成字符串 225 | 226 | #### [LeetCode传送门](https://leetcode.com/problems/decode-ways/) 227 | 228 | ```python 229 | def numDecodings(self, s: str) -> int: 230 | # w tells the number of ways 231 | # v tells the previous number of ways 232 | # d is the current digit 233 | # p is the previous digit 234 | v, w, p = 0, int(s>''), '' 235 | for d in s: 236 | v, w, p = w, int(d>'0')*w + (9 int: 249 | R, C = len(g), len(g[0]) 250 | cur = list(itertools.accumulate(g[0])) 251 | for i in range(1, R): 252 | tmp = [] 253 | for j in range(C): 254 | left = tmp[-1] if j > 0 else float('-inf') 255 | tmp.append(max(cur[j], left) + g[i][j]) 256 | cur = tmp 257 | return cur[-1] 258 | ``` 259 | ### 48 最长不含重复字符的子字符串 260 | 261 | #### [LeetCode传送门](https://leetcode.com/problems/longest-substring-without-repeating-characters/description/) 262 | 263 | 方法二:找到重复值时,更新start的值,为什么使用max,因为start有可能大于`dic[s[end]]+1`,比如当`s='abba'`,end走到最后的时候,上一次start因为b做了更新变为了2。 264 | 265 | ```python 266 | def lengthOfLongestSubstring(self, s): 267 | ans = start = 0 268 | pos = {} # last index of element 269 | for end, c in enumerate(s): 270 | if c in pos: 271 | start = max(start, pos[c]+1) 272 | pos[c] = end 273 | ans = max(ans, end-start+1) 274 | return ans 275 | ``` 276 | 277 | -------------------------------------------------------------------------------- /chapter_5/section_3/49_ugly_num.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:32 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 丑数 9 | >>> nthUglyNumber(5) 10 | 5 11 | >>> nthUglyNumber(1) 12 | 1 13 | >>> nthUglyNumber(4) 14 | 4 15 | >>> nthUglyNumber(1500) 16 | 859963392 17 | """ 18 | 19 | 20 | def nthUglyNumber(n: int) -> int: 21 | """ 22 | 书中的解法 23 | """ 24 | q = [1] 25 | t2, t3, t5 = 0, 0, 0 26 | for i in range(n-1): 27 | a2, a3, a5 = q[t2]*2, q[t3]*3, q[t5]*5 28 | to_add = min(a2, a3, a5) 29 | q.append(to_add) 30 | if a2 == to_add: 31 | t2 += 1 32 | if a3 == to_add: 33 | t3 += 1 34 | if a5 == to_add: 35 | t5 += 1 36 | return q[-1] -------------------------------------------------------------------------------- /chapter_5/section_3/50_first_only.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:36 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 第一个只出现一次的字符 9 | >>> s = 'google' 10 | >>> firstUniqChar(s) 11 | 'l' 12 | """ 13 | 14 | def firstUniqChar(s: str) -> str: 15 | from collections import Counter 16 | c = Counter(s) 17 | for i, ch in enumerate(s): 18 | if c[ch] == 1: 19 | return ch 20 | return '' -------------------------------------------------------------------------------- /chapter_5/section_3/51_inverse_pair.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:41 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 数组中的逆序对 10 | >>> InversePairs([7, 5, 6, 4]) 11 | 5 12 | """ 13 | 14 | from collections import deque 15 | 16 | 17 | def InversePairs(data: list) -> int: 18 | count = 0 19 | 20 | def merge(left, right): 21 | nonlocal count 22 | q = deque() # 双端队列是为了更快地从头取出 23 | l, r = len(left) - 1, len(right) - 1 24 | while l >= 0 and r >= 0: 25 | if left[l] > right[r]: 26 | count += r + 1 27 | q.appendleft(left[l]) 28 | l -= 1 29 | else: 30 | q.appendleft(right[r]) 31 | r -= 1 32 | # q.extendleft(left[l:-1:-1] or right[r:-1:-1]) 33 | q = left[:l + 1] + right[:r + 1] + list(q) 34 | return q 35 | 36 | def merge_sort(ary: list): 37 | if len(ary) <= 1: return ary 38 | mid = len(ary) // 2 39 | left = merge_sort(ary[:mid]) 40 | right = merge_sort(ary[mid:]) 41 | return merge(left, right) 42 | 43 | merge_sort(data) 44 | return count % 1000000007 -------------------------------------------------------------------------------- /chapter_5/section_3/52_intersection_of_2lists.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 5:45 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 两个链表的第一个公共节点。 10 | >>> h1 = construct_linklist([4, 2, 1, 5, 5, 7]) 11 | >>> h2 = construct_linklist([8, 3]) 12 | >>> h2.next.next= h1.next.next 13 | >>> intersection = h1.next.next 14 | >>> intersection is getIntersectionNode(h1, h2) 15 | True 16 | 17 | """ 18 | 19 | from utils import construct_linklist 20 | 21 | def getIntersectionNode(headA: 'ListNode', headB: 'ListNode') -> 'ListNode': 22 | p1, p2 = headA, headB 23 | while p1 is not p2: 24 | p1 = p1.next if p1 else headB 25 | p2 = p2.next if p2 else headA 26 | return p1 27 | -------------------------------------------------------------------------------- /chapter_5/section_3/README.md: -------------------------------------------------------------------------------- 1 | ### 49 丑数 2 | 3 | #### [LeetCode传送门](https://leetcode.com/problems/ugly-number-ii/) 4 | 5 | ```python 6 | def nthUglyNumber(self, n: int) -> int: 7 | q = [1] 8 | t2 = t3 = t5 = 0 9 | for _ in range(n-1): 10 | a2, a3, a5 = q[t2]*2, q[t3]*3, q[t5]*5 11 | to_add = min(a2, a3, a5) 12 | q.append(to_add) 13 | if a2 == to_add: 14 | t2 += 1 15 | if a3 == to_add: 16 | t3 += 1 17 | if a5 == to_add: 18 | t5 += 1 19 | return q[-1] 20 | ``` 21 | 22 | ### 50 第一个只出现一次的字符 23 | 24 | #### [LeetCode传送门](https://leetcode.com/problems/first-unique-character-in-a-string/description/)有一点小区别,LeetCode输出索引,书中输出值。 25 | 26 | ``` 27 | s = "leetcode" 28 | return 0. 29 | s = "loveleetcode", 30 | return 2. 31 | ``` 32 | 33 | Time-O(N), Space-O(N)。暂时没发现更快的算法了。 34 | 35 | ```python 36 | def firstUniqChar(self, s): 37 | from collections import Counter 38 | c = Counter(s) 39 | for i, ch in enumerate(s): 40 | if c[ch] == 1: 41 | return i 42 | return -1 43 | ``` 44 | 45 | ### 51 数组中的逆序对 46 | 47 | #### [牛客网传送门](https://www.nowcoder.com/practice/96bd6684e04a44eb80e6a68efc0ec6c5?tpId=13&tqId=11188&tPage=2&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 48 | 49 | #### [AcWing传送门](https://www.acwing.com/problem/content/61/) 50 | 51 | 这里使用了双端队列感觉不太合适,因为还要显式地转成list,否则没法对剩余的left或right做切片。也试了将其改为stack,但是stack来回reverse又太麻烦。 52 | 53 | ```python 54 | def InversePairs(self, data): 55 | self.count = 0 56 | 57 | def merge(left, right): 58 | q = deque() 59 | l, r = len(left)-1, len(right)-1 60 | while l >= 0 and r >= 0: 61 | if left[l] > right[r]: 62 | self.count += r + 1 63 | q.appendleft(left[l]) 64 | l -= 1 65 | else: 66 | q.appendleft(right[r]) 67 | r -= 1 68 | # q.extendleft(left[l:-1:-1] or right[r:-1:-1]) 69 | q = left[:l+1] + right[:r+1] + list(q) 70 | return q 71 | 72 | def merge_sort(ary): 73 | if len(ary) <= 1: return ary 74 | mid = len(ary) // 2 75 | left = merge_sort(ary[:mid]) 76 | right = merge_sort(ary[mid:]) 77 | return merge(left, right) 78 | 79 | merge_sort(data) 80 | return self.count % 1000000007 81 | ``` 82 | ### 52 两个链表的第一个公共节点 83 | 84 | #### [LeetCode传送门](https://leetcode.com/problems/intersection-of-two-linked-lists/description/) 85 | 86 | ```python 87 | def getIntersectionNode(self, headA, headB): 88 | p1, p2 = headA, headB 89 | while p1 is not p2: 90 | p1 = p1.next if p1 else headB 91 | p2 = p2.next if p2 else headA 92 | return p1 93 | ``` -------------------------------------------------------------------------------- /chapter_6/section_3/53_missing_num.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 6:44 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 0~n中缺失的数字 9 | >>> nums = [0, 1, 2, 3, 5, 6] 10 | >>> find_missing(nums) 11 | 4 12 | >>> random.shuffle(nums) 13 | >>> missingNumber(nums) 14 | 4 15 | 16 | >>> missingNumber2(nums) 17 | 4 18 | """ 19 | 20 | import random 21 | 22 | def find_missing(nums: list) -> int: 23 | """ 24 | 解法1:限定有序的情况 25 | """ 26 | lo, hi = 0, len(nums) - 1 27 | while lo <= hi: 28 | mid = (lo + hi) >> 1 29 | if nums[mid] != mid: 30 | if mid == 0 or nums[mid - 1] == mid - 1: 31 | return mid 32 | hi = mid - 1 33 | else: 34 | lo = mid + 1 35 | return lo 36 | 37 | 38 | def missingNumber(nums: list) -> int: 39 | """ 40 | 解法2:数学公式。 41 | """ 42 | n = len(nums) 43 | expected_sum = n*(n+1) // 2 44 | actual_sum = sum(nums) 45 | return expected_sum - actual_sum 46 | 47 | 48 | def missingNumber2(nums: 'List[int]') -> 'int': 49 | """ 50 | 解法3:异或 51 | """ 52 | missing = len(nums) 53 | for i, num in enumerate(nums): 54 | missing ^= i ^ num 55 | return missing -------------------------------------------------------------------------------- /chapter_6/section_3/54_kth_larger_bst.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 6:52 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 二叉搜索树的第K大节点 9 | >>> t1 = '5,3,2,$,$,4,$,$,7,6,$,$,8,$,$' 10 | >>> t1 = deserialize_tree(t1) 11 | >>> kth_largest(t1, 2) 12 | 7 13 | """ 14 | 15 | from utils import deserialize_tree 16 | 17 | def kth_largest(root: 'TreeNode', k: int) -> int: 18 | """ 19 | 牛客网是求最小,比较简单,书中求最大,那么只要将中序遍历的左右互换。 20 | """ 21 | stack, ans = [], None 22 | while True: 23 | while root: 24 | stack.append(root) 25 | root = root.right 26 | cur = stack.pop() 27 | k -= 1 28 | ans = cur.val 29 | root = cur.left 30 | if k == 0: 31 | return ans -------------------------------------------------------------------------------- /chapter_6/section_3/55_balaned_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 7:04 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 判断是否为平衡二叉树 9 | >>> t1 = '1,2,4,$,$,5,7,$,$,$,3,$,6,$,$' 10 | >>> t1 = deserialize_tree(t1) 11 | >>> isBalanced(t1) 12 | True 13 | >>> t2 = '1,2,4,$,$,5,7,8,$,$,$,$,3,$,6,$,$' 14 | >>> t2 = deserialize_tree(t2) 15 | >>> isBalanced(t2) 16 | False 17 | >>> is_balanced2(t1) 18 | True 19 | >>> is_balanced2(t2) 20 | False 21 | """ 22 | 23 | from collections import defaultdict 24 | 25 | from utils import deserialize_tree 26 | 27 | def isBalanced(root: 'TreeNode') -> 'bool': 28 | 29 | """ 30 | 解法1:深度遍历。 31 | """ 32 | balanced = True 33 | 34 | def dfs(node): 35 | nonlocal balanced 36 | if not node: 37 | return 0 38 | left = dfs(node.left) 39 | right = dfs(node.right) 40 | if not balanced or abs(left - right) > 1: 41 | balanced = False 42 | return max(left, right) + 1 43 | 44 | dfs(root) 45 | return balanced 46 | 47 | def is_balanced2(root: 'TreeNode') -> 'bool': 48 | """ 49 | 解法2:后序遍历 50 | """ 51 | stack, node = [], root 52 | last, depths = None, defaultdict(int) 53 | while stack or node: 54 | if node: 55 | stack.append(node) 56 | node = node.left 57 | else: 58 | node = stack[-1] 59 | if not node.right or last == node.right: 60 | node = stack.pop() 61 | left, right = depths[node.left], depths[node.right] 62 | if abs(left - right) > 1: 63 | return False 64 | depths[node] = 1 + max(left, right) 65 | last, node = node, None 66 | else: 67 | node = node.right 68 | return True 69 | -------------------------------------------------------------------------------- /chapter_6/section_3/55_depth_of_tree.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 6:59 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 二叉树的深度。 9 | >>> t1 = '1,2,4,$,$,5,7,$,$,$,3,$,6,$,$' 10 | >>> t1 = deserialize_tree(t1) 11 | >>> max_depth(t1) 12 | 4 13 | >>> max_depth2(t1) 14 | 4 15 | """ 16 | 17 | from collections import deque 18 | 19 | from utils import deserialize_tree 20 | 21 | 22 | def max_depth(root: 'TreeNode') -> int: 23 | if not root: 24 | return 0 25 | return max(max_depth(root.left), max_depth(root.right)) + 1 26 | 27 | def max_depth2(root: 'TreeNode') -> 'int': 28 | q = root and deque([(root, 1)]) 29 | d = 0 30 | while q: 31 | node, d = q.popleft() 32 | if node.right: 33 | q.append((node.right, d+1)) 34 | if node.left: 35 | q.append((node.left, d+1)) 36 | return d -------------------------------------------------------------------------------- /chapter_6/section_3/56_single_numII.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 7:35 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 找出数组中出现一次的元素,其它元素出现三次。 9 | >>> nums = [1, 2, 3, 2, 2, 1, 1, 4, 4, 4] 10 | >>> single_number(nums) 11 | 3 12 | """ 13 | 14 | 15 | def single_number(nums: list, n=3) -> int: 16 | ans = 0 17 | for i in range(32): 18 | count = 0 19 | for num in nums: 20 | if ((num >> i) & 1): 21 | count += 1 22 | ans |= ((count%n!=0) << i) 23 | return convert(ans) 24 | 25 | def convert(x): 26 | if x >= 2**31: 27 | x -= 2**32 28 | return x -------------------------------------------------------------------------------- /chapter_6/section_3/56_single_numIII.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 7:27 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 数组中两个唯一出现一次的元素,其余均出现两次。 9 | >>> nums = [2, 4, 3, 6, 3, 2, 5, 5] 10 | >>> singleNumber(nums) 11 | (4, 6) 12 | """ 13 | 14 | # 思想:将这两个元素分到两个组,由于这两个数不相等,所以亦或结果不为0, 15 | # 也就是说二进制中至少有一位1,记为第n位。我们以第n位是否为1,把数组分为两个子数组。 16 | 17 | 18 | def singleNumber(nums: list) -> tuple: 19 | total_xor = get_xor(nums) 20 | mask = 1 21 | while total_xor & mask == 0: 22 | mask <<= 1 23 | p1 = [num for num in nums if num & mask == 0] 24 | p2 = [num for num in nums if num & mask != 0] 25 | return get_xor(p1), get_xor(p2) 26 | 27 | 28 | def get_xor(nums: list) -> int: 29 | from functools import reduce 30 | return reduce(lambda x, y: x ^ y, nums) -------------------------------------------------------------------------------- /chapter_6/section_3/57_find_num_with_sum.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 7:44 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 找出和为s的数字。 10 | >>> nums = [1, 2, 4, 7, 11, 15] 11 | >>> FindNumbersWithSum(nums, 15) 12 | (4, 11) 13 | """ 14 | 15 | def FindNumbersWithSum(array: list, tsum: int) -> tuple: 16 | l, r = 0, len(array)-1 17 | while l < r: 18 | if array[l] + array[r] < tsum: 19 | l += 1 20 | elif array[l]+array[r] > tsum: 21 | r -= 1 22 | else: 23 | return array[l], array[r] 24 | return [] -------------------------------------------------------------------------------- /chapter_6/section_3/58_reverse_words.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 7:46 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 反转一句话的单词。 9 | >>> s = "the sky is blue" 10 | >>> reverse_words(s) 11 | 'blue is sky the' 12 | """ 13 | 14 | def reverse_words(s: str) -> str: 15 | return ' '.join(s.split()[::-1]) -------------------------------------------------------------------------------- /chapter_6/section_3/59_slide_window_max.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 7:48 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 滑动窗口的最大值。 10 | >>> nums = [2, 3, 4, 2, 6, 2, 5, 1] 11 | >>> maxInWindows(nums, 3) 12 | [4, 4, 6, 6, 6, 5] 13 | 14 | """ 15 | 16 | 17 | def maxInWindows(nums: list, size: int) -> int: 18 | return [max(nums[i:i+size]) 19 | for i in range(len(nums)-size+1) if size!=0 ] -------------------------------------------------------------------------------- /chapter_6/section_3/README.md: -------------------------------------------------------------------------------- 1 | ### 53 在排序数组中查找数字 2 | 3 | #### [LeetCode传送门](https://leetcode.com/problems/find-first-and-last-position-of-element-in-sorted-array/description/)。相似题目,LeetCode要返回两个索引,书中求个数。 4 | 5 | ```python 6 | Input: nums = [5,7,7,8,8,10], target = 8 7 | Output: [3,4] 8 | Input: nums = [5,7,7,8,8,10], target = 6 9 | Output: [-1,-1] 10 | ``` 11 | 方法一:标准库写法。这里不需要target去整个nums中判断。 12 | 13 | ```python 14 | def searchRange(self, nums: List[int], target: int) -> List[int]: 15 | from bisect import bisect, bisect_left 16 | lo = bisect_left(nums, target) 17 | if target in nums[lo:lo+1]: 18 | return lo, bisect(nums, target)-1 19 | else: 20 | return -1, -1 21 | ``` 22 | 方法二:自己实现。bisect_right的方式采用+1的形式。 23 | 24 | ```python 25 | def searchRange(self, nums: List[int], target: int) -> List[int]: 26 | 27 | def search(n): 28 | lo, hi = 0, len(nums) 29 | while lo < hi: 30 | mid = (lo + hi) // 2 31 | if nums[mid] >= n: 32 | hi = mid 33 | else: 34 | lo = mid + 1 35 | return lo 36 | lo = search(target) 37 | if target in nums[lo:lo+1]: 38 | return lo, search(target+1)-1 39 | else: 40 | return -1, -1 41 | ``` 42 | 43 | ### 53 0~n-1中缺失的数字 44 | 45 | #### [LeetCode传送门](https://leetcode.com/problems/missing-number/description/) 46 | 47 | 相似题目,LeetCode是未排序,书中是已排序。所以可以利用排序的特性使时间复杂度小于O(n)。即找出第一个下标与值不相等的元素,再-1就是缺失的元素。 48 | 49 | #### [AcWing传送门](https://www.acwing.com/problem/content/64/) 50 | 51 | 方法一:数学公式。 52 | 53 | ```python 54 | def missingNumber(self, nums): 55 | n = len(nums) 56 | expected_sum = n*(n+1) // 2 57 | actual_sum = sum(nums) 58 | return expected_sum - actual_sum 59 | ``` 60 | 61 | 方法二:XOR. 62 | 63 | | index | 0 | 1 | 2 | 64 | | ----- | ---- | ---- | ---- | 65 | | value | 3 | 0 | 1 | 66 | 67 | ```python 68 | def missingNumber(self, nums: 'List[int]') -> 'int': 69 | missing = len(nums) 70 | for i, num in enumerate(nums): 71 | missing ^= i ^ num 72 | return missing 73 | ``` 74 | 75 | 方法三:利用书中已排序的特性。 76 | 77 | ```python 78 | def getMissingNumber(self, nums): 79 | lo, hi = 0, len(nums)-1 80 | while lo <= hi: 81 | mid = (lo + hi) >> 1 82 | if nums[mid] != mid: 83 | if mid==0 or nums[mid-1]==mid-1: 84 | return mid 85 | hi = mid - 1 86 | else: 87 | lo = mid + 1 88 | return lo 89 | ``` 90 | 91 | ### 53 数组中数值和下标相等的元素 92 | 93 | #### [AcWing传送门](https://www.acwing.com/problem/content/65/) 94 | 95 | ```python 96 | def getNumberSameAsIndex(self, nums): 97 | lo, hi = 0, len(nums)-1 98 | while lo <= hi: 99 | mid = (lo + hi) >> 1 100 | if nums[mid] < mid: 101 | lo = mid + 1 102 | elif nums[mid] > mid: 103 | hi = mid - 1 104 | else: 105 | return mid 106 | return -1 107 | ``` 108 | 109 | ### 54 二叉搜索树的第k大节点 110 | 111 | #### [牛客网传送门](https://www.nowcoder.com/practice/ef068f602dde4d28aab2b210e859150a?tpId=13&tqId=11215&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 112 | #### [AcWing传送门](https://www.acwing.com/problem/content/66/) 113 | 114 | 注意:牛客网上是求第k小节点,这里被坑了一次,然后返回值居然要求返回节点对象,而不是节点值,这里的答案按书中返回。如果是牛客网上需要把节点添加到`res`中,然后`return res[k-1]` 115 | 116 | ```python 117 | def kth_largest(self, root: TreeNode, k: int) -> int: 118 | stack, ans = [], None 119 | while stack or root: 120 | while root: 121 | stack.append(root) 122 | root = root.right 123 | root = stack.pop() 124 | k -= 1 125 | ans = root 126 | root = root.left 127 | if k == 0: 128 | return ans 129 | ``` 130 | 131 | ### 55 二叉树的深度 132 | 133 | #### [LeetCode传送门](https://leetcode.com/problems/maximum-depth-of-binary-tree/description/) 134 | 135 | ``` 136 | 3 137 | / \ 138 | 9 20 139 | / \ 140 | 15 7 141 | return 3 142 | ``` 143 | 144 | 方法一:recursively 145 | 146 | ```python 147 | def max_depth(root): 148 | if not root: 149 | return 0 150 | # return max(max_depth(root.left), max_depth(root.right)) + 1 151 | return max(map(max_depth, (root.left, root.right))) + 1 152 | ``` 153 | 154 | 方法二:iteratively. BFS with deque 155 | 156 | ```python 157 | def maxDepth(self, root: 'TreeNode') -> 'int': 158 | q = root and collections.deque([(root, 1)]) 159 | d = 0 160 | while q: 161 | node, d = q.popleft() 162 | if node.right: 163 | q.append((node.right, d+1)) 164 | if node.left: 165 | q.append((node.left, d+1)) 166 | return d 167 | ``` 168 | 169 | ### 55_1 平衡二叉树 170 | 171 | #### [LeetCode传送门](https://leetcode.com/problems/balanced-binary-tree/description/) 172 | 173 | 方法一:递归+递归。 174 | 175 | ```python 176 | def isBalanced(self, root): 177 | if not root: 178 | return True 179 | return self.isBalanced(root.left) and self.isBalanced(root.right) and \ 180 | abs(self.max_depth(root.left)-self.max_depth(root.right)) <= 1 181 | 182 | def max_depth(self, root): 183 | if not root: 184 | return 0 185 | return max(self.max_depth(root.left), self.max_depth(root.right)) + 1 186 | ``` 187 | 188 | 方法二:上诉两种方法中都包含了一些无意义的重复遍历。这里采用后序遍历,边遍历边判断,不会重复节点。受此思想启发,添加一种后序遍历二叉树的方法。 189 | 190 | ```python 191 | def isBalanced(self, root): 192 | stack, node, last = [], root, None 193 | depths = collections.defaultdict(int) 194 | while stack or node: 195 | if node: 196 | stack.append(node) 197 | node = node.left 198 | else: 199 | node = stack[-1] 200 | if not node.right or last == node.right: 201 | node = stack.pop() 202 | left, right = depths[node.left], depths[node.right] 203 | if abs(left - right) > 1: 204 | return False 205 | depths[node] = 1 + max(left, right) 206 | last, node = node, None 207 | else: 208 | node = node.right 209 | return True 210 | ``` 211 | 212 | 方法三:dfs. 算深度的时候判断左右是否深度超过1. 这里变量不能把self去掉,否则`[1,2,2,3,3,null,null,4,4]`会错误的返回`True`而不是`False`。也可以使用`nonlocal` 213 | 214 | ```python 215 | def isBalanced(self, root: 'TreeNode') -> 'bool': 216 | self.balanced = True 217 | 218 | def dfs(node): 219 | if not node: 220 | return 0 221 | left = dfs(node.left) 222 | right = dfs(node.right) 223 | if abs(left-right) > 1 and self.balanced: 224 | self.balanced = False 225 | return max(left, right) + 1 226 | 227 | dfs(root) 228 | return self.balanced 229 | ``` 230 | 231 | ### 56 数组中只出现一次的两个数字。 232 | 233 | #### 找出数组中两个唯一出现一次的元素,其余元素均出现两次。[LeetCode传送门](https://leetcode.com/problems/single-number-iii/description/) 234 | 235 | ``` 236 | Input: [1,2,1,3,2,5] 237 | Output: [3,5] 238 | ``` 239 | 240 | 思想:将这两个元素分到两个组,由于这两个数不相等,所以亦或结果不为0,也就是说二进制中至少有一位1,记为第n位。我们以第n位是否为1,把数组分为两个子数组。 241 | 242 | ```python 243 | def singleNumber(self, nums: List[int]) -> List[int]: 244 | from functools import reduce 245 | def get_single(nums): 246 | return reduce(operator.xor, nums) 247 | 248 | total_xor = get_single(nums) 249 | mask = 1 250 | while total_xor&mask == 0: 251 | mask <<= 1 252 | n1 = [num for num in nums if num&mask==0] 253 | n2 = [num for num in nums if num&mask!=0] 254 | return get_single(n1), get_single(n2) 255 | ``` 256 | 257 | ### 56_1 数组中出现一次的数字,其余元素出现三次。 258 | 259 | #### [LeetCode传送门](https://leetcode.com/problems/single-number-ii/description/) 260 | 261 | ``` 262 | Input: [2,2,3,2] 263 | Output: 3 264 | ``` 265 | 266 | 方法一:找出单独元素每一位的值。如果把所有数字的二进制每一位加起来,如果某一位可以被3整除,则表示单独元素的该位为0,否则为1。以下使用`count`来表示每一位`1`的个数。假设`count%3!=0`为True,说明该元素`i`位为1,然后是用`|=`更新ans在第`i`个位置的值,这里也可以使用`+=`,但是效率稍慢。`convert`的作用是因为python中的int是个对象,且没有最大限制,不是在第32位使用1来表示负数。 267 | 268 | ```python 269 | def singleNumber(self, nums, n=3): 270 | ans = 0 271 | for i in range(32): 272 | count = 0 273 | for num in nums: 274 | if ((num >> i) & 1): 275 | count += 1 276 | ans |= ((count%n!=0) << i) 277 | return self.convert(ans) 278 | 279 | def convert(self, x): 280 | if x >= 2**31: 281 | x -= 2**32 282 | return x 283 | ``` 284 | 285 | 这里有个状态机的解法,不明觉厉,留坑。[讨论1](https://leetcode.com/problems/single-number-ii/discuss/43294/Challenge-me-thx?page=2)和[讨论2](https://leetcode.com/problems/single-number-ii/discuss/43295/Detailed-explanation-and-generalization-of-the-bitwise-operation-method-for-single-numbers) 286 | 287 | ```python 288 | def singleNumber(self, nums): 289 | ones, twos = 0, 0; 290 | for i in range(len(nums)): 291 | ones = (ones ^ nums[i]) & ~twos 292 | twos = (twos ^ nums[i]) & ~ones 293 | return ones 294 | ``` 295 | 296 | ### 57 和为s的数字 297 | #### [牛客网传送门](https://www.nowcoder.com/practice/390da4f7a00f44bea7c2f3d19491311b?tpId=13&tqId=11195&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 298 | #### [AcWing传送门](https://www.acwing.com/problem/content/71/) 299 | 300 | 看牛客网上的描述,如果有多对数字和为s,要求返回乘积最小的一对。乍一看以为牛客网又乱改题,但是仔细一想,如果两个和为s的数,而且是在递增数组中很明显,边缘的数字乘积要小,例如`8X8>1X15`。所以还是和书中解法一样。 301 | 302 | ```python 303 | def FindNumbersWithSum(self, array, tsum): 304 | l, r = 0, len(array)-1 305 | while l < r: 306 | if array[l] + array[r] < tsum: 307 | l += 1 308 | elif array[l]+array[r] > tsum: 309 | r -= 1 310 | else: 311 | return array[l], array[r] 312 | return [] 313 | ``` 314 | 315 | ### 57_1 和为s的连续正数序列 316 | 317 | #### [牛客网传送门](https://www.nowcoder.com/practice/c451a3fd84b64cb19485dad758a55ebe?tpId=13&tqId=11194&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 318 | 319 | #### [AcWing传送门](https://www.acwing.com/problem/content/72/) 320 | 321 | ```python 322 | def findContinuousSequence(self, tsum): 323 | end = (tsum + 1) // 2 324 | lo, hi, cur_sum = 1, 2, 3 325 | ans = [] 326 | while lo < end: 327 | if cur_sum < tsum: 328 | hi += 1 329 | cur_sum += hi 330 | else: 331 | if cur_sum == tsum: 332 | ans.append(list(range(lo, hi+1))) 333 | cur_sum -= lo 334 | lo += 1 335 | return ans 336 | ``` 337 | 338 | ### 58 翻转字符串 339 | 340 | #### [LeetCode传送门](https://leetcode.com/problems/reverse-words-in-a-string/description/) 341 | 342 | ```python 343 | Input: "the sky is blue", 344 | Output: "blue is sky the". 345 | ``` 346 | 方法一:如果面试官是一个Pythoner,那么就让你过了。 347 | 348 | ```python 349 | def reverse_words(s): 350 | return ' '.join(reversed(s.split())) 351 | ``` 352 | 353 | 如果你的面试官是一个只写Java或者C,看见代码就不平衡了,凭啥可以写到一行,非要你实现reverse。 354 | 355 | ```python 356 | def reverseWords(self, s: str) -> str: 357 | 358 | def hp_reversed(s): 359 | s = list(s) 360 | for i in range(len(s)//2): 361 | # s[i], s[-i-1] = s[-i-1], s[i] 362 | s[i], s[~i] = s[~i], s[i] 363 | return ''.join(s) 364 | s = hp_reversed(s) 365 | return ' '.join(hp_reversed(word) for word in s.split()) 366 | ``` 367 | 368 | 实现`split`,hp_reverse, 369 | 370 | ```python 371 | def reverseWords(self, s: str) -> str: 372 | 373 | def hp_reversed(s): 374 | s = list(s) 375 | for i in range(len(s)//2): 376 | s[i], s[~i] = s[~i], s[i] 377 | return ''.join(s) 378 | 379 | s = hp_reversed(s) 380 | ans = word = '' 381 | for r, c in enumerate(s): 382 | if c != ' ': 383 | word += c 384 | if (c== ' ' or r==len(s)-1) and word: 385 | ans += hp_reversed(word) + ' ' 386 | word = '' 387 | return ans[:-1] 388 | ``` 389 | 390 | ### 58_1 左旋转字符串 391 | 392 | #### [牛客网传送门](https://www.nowcoder.com/practice/12d959b108cb42b1ab72cef4d36af5ec?tpId=13&tqId=11196&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking) 393 | #### [AcWing传送门](https://www.acwing.com/problem/content/74/) 394 | 395 | 切片,书中的方法个人觉得Python并不适用。 396 | 397 | ```python 398 | def LeftRotateString(self, s, n): 399 | if not s: 400 | return '' 401 | n = n % len(s) 402 | return s[n:] + s[:n] 403 | ``` 404 | 405 | ### 59 滑动窗口的最大值 406 | 407 | #### [牛客网传送门](https://www.nowcoder.com/practice/1624bc35a45c42c0bc17d17fa0cba788?tpId=13&tqId=11217&tPage=4&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 408 | #### [AcWing传送门](https://www.acwing.com/problem/content/75/) 409 | 410 | 得益于python的切片。Time: O(n*k). k=n-size 411 | 412 | ```python 413 | def maxInWindows(self, nums, size): 414 | return [max(nums[i:i+size]) 415 | for i in range(len(nums)-size+1) if size!=0 ] 416 | ``` 417 | 418 | 方法二:常规写法,只是将书中的代码封装了一下,Time: O(n). 419 | 420 | ```python 421 | def maxInWindows(self, nums, size): 422 | from collections import deque 423 | q = deque() 424 | ans = [] 425 | def pop_less(i): 426 | # nums[i] 索引和值都比队列尾的元素大,队列尾的元素就没有必要存在了 427 | while q and nums[i]>=nums[q[-1]]: 428 | q.pop() 429 | q.append(i) 430 | 431 | for i in range(size): 432 | pop_less(i) 433 | 434 | for i in range(size, len(nums)): 435 | ans.append(nums[q[0]]) 436 | pop_less(i) 437 | while q and q[0]<= i-size: 438 | q.popleft() 439 | ans.append(nums[q[0]]) 440 | return ans 441 | ``` -------------------------------------------------------------------------------- /chapter_6/section_4/60_dice.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 7:50 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | n个骰子的点数。 10 | >>> dice_probability_dict(1) 11 | {1: 0.16667, 2: 0.16667, 3: 0.16667, 4: 0.16667, 5: 0.16667, 6: 0.16667} 12 | >>> ans = dice_probability_dict(2) 13 | >>> for k, v in ans.items(): # doctest: +ELLIPSIS 14 | ... print(k, v) 15 | 2 0.02778 16 | 3 0.05556 17 | 4 0.08333 18 | 5 0.11111 19 | 6 0.13889 20 | ... 21 | 22 | """ 23 | 24 | def dice_probability_dict(n: int, val=6) -> dict: 25 | from collections import defaultdict 26 | dp = defaultdict(int) 27 | dp.update({k: 1 for k in range(1, val+1)}) # 初始化第一个骰子 28 | for i in range(n-1): # 根据第i个骰子更新第i+1个骰子 29 | new_dp = defaultdict(int) 30 | for j in range(n*(i+1), val*n+1): # n个骰子最小值为n*(i+1),最大值为val*n 31 | # 第i+1个骰子和为j(实际为j+1,因为数组下标从0开始)的次数,等于第i个 32 | # 骰子j-1 ~ j-6次数的总和 33 | new_dp[j] = sum(dp[j-k] for k in range(1, val+1)) 34 | dp = new_dp 35 | 36 | # 这时的dp就是得到我们想要的了,只不过值为出现的次数,我们算一下概率 37 | count = {k: round(float(times / (val**n)), 5) 38 | for k, times in dp.items()} 39 | return count -------------------------------------------------------------------------------- /chapter_6/section_4/61_poke.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 8:02 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 扑克牌中的顺子。 10 | >>> nums = [0, 0, 1, 2, 3] 11 | >>> IsContinuous(nums) 12 | True 13 | >>> nums2 = [3, 4, 6, 8, 0] 14 | >>> IsContinuous(nums2) 15 | False 16 | """ 17 | 18 | 19 | def IsContinuous(numbers: list) -> 'bool': 20 | 21 | if not numbers: 22 | return False 23 | joker_count = numbers.count(0) 24 | left_cards = sorted(numbers)[joker_count:] 25 | need_joker = 0 26 | for i in range(len(left_cards)-1): 27 | if left_cards[i+1] == left_cards[i]: 28 | return False 29 | need_joker += (left_cards[i+1]-left_cards[i]-1) 30 | return need_joker <= joker_count -------------------------------------------------------------------------------- /chapter_6/section_4/62_remain_cycle.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 8:04 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 圆圈中最后剩下的数字 9 | >>> LastRemaining_Solution(5, 3) 10 | 3 11 | """ 12 | 13 | 14 | def LastRemaining_Solution(n: int, m: int) -> int: 15 | if n<=0 or m<=0: 16 | return -1 17 | last_num = 0 18 | for i in range(2, n+1): 19 | last_num = (last_num+m) % i 20 | return last_num -------------------------------------------------------------------------------- /chapter_6/section_4/63_stock_profit.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 8:08 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 股票的最大利润 9 | >>> prices = [9, 11, 8, 5, 7, 12, 16, 14] 10 | >>> maxProfit(prices) 11 | 11 12 | """ 13 | 14 | def maxProfit(prices: 'List[int]') -> int: 15 | ans, min_buy = 0, float('inf') 16 | for price in prices: 17 | if price < min_buy: 18 | min_buy = price 19 | elif price-min_buy > ans: 20 | ans = price - min_buy 21 | return ans -------------------------------------------------------------------------------- /chapter_6/section_4/README.md: -------------------------------------------------------------------------------- 1 | ### 60 n个骰子的点数 2 | 3 | #### [AcWing传送门](https://www.acwing.com/problem/content/76/) 4 | 5 | ``` 6 | [[1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 1, 2, 3, 4, 5, 6, 5, 4, 3, 2, 1]] 7 | ``` 8 | 9 | `dp[0][j]==1`表示第一个骰子和为`j+1`的次数为1,因为数组下标从0开始。 10 | 11 | ```python 12 | def dice_probability(n, val=6): 13 | dp = [[0]*val*n for _ in range(n)] 14 | dp[0][:val] = [1] * val # 初始化第一个骰子 15 | 16 | for i in range(n-1): # 根据第i个骰子更新第i+1个骰子 17 | for j in range(i, len(dp[i+1])): 18 | # 第i+1个骰子和为j(实际为j+1,因为数组下标从0开始)的次数,等于第i个 19 | # 骰子j-1 ~ j-6次数的总和 20 | dp[i+1][j] = sum([dp[i][j-k] for k in range(1, val+1)]) 21 | 22 | # 整理数据成为dict,key表示和,value表示出现的次数 23 | # count = {i+1: times for i, times in enumerate(dp[-1])} 24 | # 计算概率 25 | count = {i+1: round(float(times / (val**n)), 5) 26 | for i, times in enumerate(dp[-1]) if times!=0} 27 | return count 28 | ``` 29 | 30 | 感觉用dict来表示更加明确,没有数组下标从0开始的混淆。按照AcWing中的返回写出一种解法。 31 | 32 | ```python 33 | from collections import defaultdict 34 | from itertools import repeat 35 | 36 | def numberOfDice(self, n): 37 | last_p = defaultdict(int) 38 | last_p.update(dict(zip(range(1, 7), repeat(1)))) 39 | for i in range(2, n+1): 40 | new_p = defaultdict(int) 41 | for j in range(i, i*6+1): 42 | new_p[j] = sum(last_p[j-k] for k in range(1, 7)) 43 | # print(new_p) 44 | last_p = new_p 45 | return list(last_p.values()) 46 | ``` 47 | 48 | ### 61 扑克牌中的顺子 49 | 50 | #### [牛客网传送门](https://www.nowcoder.com/practice/762836f4d43d43ca9deb273b3de8e1f4?tpId=13&tqId=11198&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 51 | #### [AcWing传送门](https://www.acwing.com/problem/content/77/) 52 | 53 | 开始以为还要用个dict来映射值,后来发现直接传得卡牌的值。思想就是先排序,然后查joker的数量,看剩下牌的差值加起来能不能用已有的joker连起来。 54 | 55 | ```python 56 | def IsContinuous(self, numbers): 57 | if not numbers: 58 | return False 59 | joker_count = numbers.count(0) 60 | left_cards = sorted(numbers)[joker_count:] 61 | need_joker = 0 62 | for i in range(len(left_cards)-1): 63 | if left_cards[i+1] == left_cards[i]: 64 | return False 65 | need_joker += (left_cards[i+1]-left_cards[i]-1) 66 | return need_joker <= joker_count 67 | ``` 68 | 69 | 使用标准库,更加优雅,原理相同。 70 | 71 | ```python 72 | from itertools import tee 73 | 74 | def IsContinuous(self, numbers): 75 | if not numbers: 76 | return False 77 | joker_count = numbers.count(0) 78 | left_cards = sorted(numbers)[joker_count:] 79 | need_joker = 0 80 | c1, c2 = tee(left_cards) 81 | next(c2, None) 82 | for s, g in zip(c1, c2): 83 | if g == s: 84 | return False 85 | need_joker += g - 1 - s 86 | return need_joker <= joker_count 87 | ``` 88 | 89 | 90 | 91 | ### 62 圆圈中最后剩下的数字 92 | 93 | #### [牛客网传送门](https://www.nowcoder.com/practice/f78a359491e64a50bce2d89cff857eb6?tpId=13&tqId=11199&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 94 | #### [AcWing传送门](https://www.acwing.com/problem/content/78/) 95 | 96 | 方法一:其实不需要环形链表,使用一个list足矣,每次将其旋转`rot`位,一开始想将要把第m个数旋转到list首部,然后再`pop(0)`,后来想到直接可以通过切片取出来,节省了`pop(0)`的O(n)复杂度。 97 | 98 | ```python 99 | def LastRemaining_Solution(self, n, m): 100 | if n<=0 or m<=0: 101 | return -1 102 | seats = range(n) 103 | while seats: 104 | rot = (m-1) % len(seats) 105 | seats, last = seats[rot+1:] + seats[:rot], seats[rot] 106 | return last 107 | ``` 108 | 109 | 方法二:书中的推导过程过于复杂,这里学到了一个稍微简单的推导过程。参考[约瑟夫环问题](https://blog.oldj.net/2010/05/27/joseph-ring/)。[我自己的拙见](https://darktiantian.github.io/%E7%BA%A6%E7%91%9F%E5%A4%AB%E7%8E%AF%E9%97%AE%E9%A2%98%EF%BC%88Josephus-problem%EF%BC%89/)。 110 | 111 | ```python 112 | def LastRemaining_Solution(self, n, m): 113 | if n<=0 or m<=0: 114 | return -1 115 | last_num = 0 116 | for i in range(2, n+1): 117 | last_num = (last_num+m) % i 118 | return last_num 119 | ``` 120 | 121 | ### 63 股票的最大利润 122 | 123 | #### [LeetCode传送门](https://leetcode.com/problems/best-time-to-buy-and-sell-stock/description/) 124 | 125 | 方法一:Brute Force.其实就是求最高峰点和前面最低谷点的差。 126 | 127 | ```python 128 | def maxProfit(self, prices: List[int]) -> int: 129 | profit, min_buy = 0, float('inf') 130 | for p in prices: 131 | min_buy = min(min_buy, p) 132 | profit = max(profit, p-min_buy) 133 | return profit 134 | ``` 135 | 136 | 方法二:标准的卡登算法。此题为53.连续数组最大和的变形,如果价格比之前小,则舍弃,否则一起计算连续子数组的和。 137 | 138 | ```python 139 | def maxProfit(self, prices: List[int]) -> int: 140 | cur = sofar = 0 141 | for i in range(1, len(prices)): 142 | cur += prices[i] - prices[i-1] 143 | cur = max(0, cur) 144 | sofar = max(cur, sofar) 145 | return sofar 146 | ``` 147 | 148 | 方法三:使用标准库的卡登算法。 149 | 150 | ```python 151 | def maxProfit(self, prices: List[int]) -> int: 152 | from itertools import tee 153 | cur = profit = 0 154 | a, b = tee(prices) 155 | next(b, None) 156 | for before, today in zip(a, b): 157 | cur += today - before 158 | cur = max(0, cur) 159 | profit = max(profit, cur) 160 | return profit 161 | ``` 162 | 163 | -------------------------------------------------------------------------------- /chapter_6/section_5/64_sum_n.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 8:10 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | """ 8 | 求1+...n, 不能用循环等。 9 | >>> sum_solution(10) 10 | 55 11 | """ 12 | 13 | 14 | 15 | 16 | def sum_solution(n: int) -> int: 17 | return n and (n+sum_solution(n-1)) -------------------------------------------------------------------------------- /chapter_6/section_5/65_plus_bit.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/10 8:12 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 不用加减乘除做加法。 10 | >>> getSum(1, 2) 11 | 3 12 | >>> a, b = 2**31, (-2**5) 13 | >>> getSum2(a, b) == a+b 14 | True 15 | >>> getSum2(-12, -8) 16 | -20 17 | >>> getSum(9, -18) 18 | -9 19 | 20 | """ 21 | 22 | import numpy as np 23 | 24 | def getSum(a: int, b: int) -> int: 25 | 26 | while b != 0: 27 | a, b = np.int32(a ^ b), np.int32((a & b) << 1) 28 | return int(a) 29 | 30 | 31 | 32 | def getSum2(a, b): 33 | # 32 bits integer max 34 | MAX = 0x7FFFFFFF # 2**31-1 35 | # 32 bits interger min 36 | MIN = 0x80000000 # -2**31 37 | # mask to get last 32 bits 38 | mask = 0xFFFFFFFF # 2*32-1 39 | while b != 0: 40 | # ^ get different bits and & gets double 1s, << moves carry 41 | a, b = (a ^ b) & mask, ((a & b) << 1) & mask 42 | # if a is negative, get a's 32 bits complement positive first 43 | # then get 32-bit positive's Python complement negative 44 | return a if a <= MAX else ~(a ^ mask) -------------------------------------------------------------------------------- /chapter_6/section_5/66_mul_matrix.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/21 1:10 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | """ 9 | 构建乘积数组 10 | >>> multiply(list(range(1, 6))) 11 | [120, 60, 40, 30, 24] 12 | >>> a = multiply(list(range(4, 10))) 13 | >>> b = check(list(range(4, 10))) 14 | >>> a == b 15 | True 16 | >>> multiply([1, 2, 3, 0, 5]) 17 | [0, 0, 0, 30, 0] 18 | >>> multiply([1, 0, 3, 0, 5]) 19 | [0, 0, 0, 0, 0] 20 | """ 21 | 22 | from itertools import accumulate 23 | from operator import mul 24 | from functools import reduce 25 | import random 26 | 27 | def multiply(A: list) -> list: 28 | from itertools import accumulate 29 | from operator import mul 30 | C = [1] # 第一个元素相当于没有 31 | C += accumulate(A[:-1], mul) # `+=`支持右边生成器,`+`不支持 32 | D = [1] 33 | D += accumulate(A[:0:-1], mul) 34 | D.reverse() 35 | return [C[i] * D[i] for i in range(len(A))] 36 | 37 | 38 | def check(A): 39 | 40 | def helper_mul(x, y): 41 | if x == 0: 42 | x = 1 43 | if y == 0: 44 | y = 1 45 | return x * y 46 | 47 | print(A, A.count(0)) 48 | if A.count(0) > 1: 49 | return [0] * len(A) 50 | 51 | total = reduce(helper_mul, A) 52 | has_0 = 0 in A 53 | ans = [] 54 | for num in A: 55 | if num == 0: 56 | ans.append(total) 57 | elif has_0: 58 | ans.append(0) 59 | else: 60 | ans.append(total // num) 61 | 62 | return ans 63 | 64 | if __name__ == '__main__': 65 | 66 | for _ in range(1000): 67 | 68 | a = random.sample(range(1, 15), 3) 69 | b = random.sample(range(-10, 20), 1) 70 | d = random.sample(range(-5, 5), 1) 71 | c = a + b + d 72 | # c = [0, 2, 3, 5] 73 | ans1 = multiply(c) 74 | ans2 = check(c) 75 | print(ans1, ans2) 76 | assert ans1 == ans2 77 | -------------------------------------------------------------------------------- /chapter_6/section_5/README.md: -------------------------------------------------------------------------------- 1 | ### 64 求1+2+···+n 2 | 3 | #### [牛客网传送门](https://www.nowcoder.com/practice/7a0da8fc483247ff8800059e12d7caf1?tpId=13&tqId=11200&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 4 | #### [AcWing传送门](https://www.acwing.com/problem/content/80/) 5 | 6 | 这题对python不是很友好,感觉`and`也属于条件判断语句。`reduce``sum`之类的属于作弊行为,这里就不贴了。 7 | 8 | ```python 9 | def Sum_Solution(self, n): 10 | return n and (n+self.Sum_Solution(n-1)) 11 | ``` 12 | 13 | ### 65 不用加减乘除做加法 14 | 15 | #### [LeetCode传送门](https://leetcode.com/problems/sum-of-two-integers/description/) 16 | 17 | 此题由于Python中的int没有长度限制,在负数出现的情况,会导致结果与预期不同。详情见[Python负数位运算](https://darktiantian.github.io/371-Sum-of-Two-Integers-Python/) 18 | 19 | ```python 20 | def getSum(self, a, b): 21 | # 32 bits integer max 22 | MAX = 0x7FFFFFFF # 2**31-1 23 | # 32 bits interger min 24 | MIN = 0x80000000 # -2**31 25 | # mask to get last 32 bits 26 | mask = 0xFFFFFFFF # 2*32-1 27 | while b != 0: 28 | # ^ get different bits and & gets double 1s, << moves carry 29 | a, b = (a ^ b) & mask, ((a & b) << 1) & mask 30 | # if a is negative, get a's 32 bits complement positive first 31 | # then get 32-bit positive's Python complement negative 32 | return a if a <= MAX else ~(a ^ mask) 33 | ``` 34 | 35 | 或者可以将其转成32位整数。 36 | 37 | ```python 38 | import numpy as np 39 | 40 | class Solution(object): 41 | def getSum(self, a, b): 42 | while b != 0: 43 | a, b = np.int32(a ^ b), np.int32((a & b) << 1) 44 | return int(a) 45 | ``` 46 | 47 | ### 66 构建乘积数组 48 | 49 | #### [牛客网传送门](https://www.nowcoder.com/practice/94a4d381a68b47b7a8bed86f2975db46?tpId=13&tqId=11204&tPage=3&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking) 50 | 51 | #### [AcWing传送门](https://www.acwing.com/problem/content/82/) 52 | 53 | 思路:不能使用除法。如书中所说,以i为分割点,将B拆成C,D两部分,左边是`A[0] x A[1] x...x A[i-1]`右边则为`A[i+1] x ...x A[n-1]` ,C[i] = C[i-1] * A[i-1] 54 | 55 | 使用`accumulate`,牛客网居然不能AC,老是说我语法错误或数组越界?AcWing是可以的。我在自己的testcase中使用随机生成的方式验证了是可以的。 56 | 57 | ```python 58 | def multiply(self, A): 59 | from itertools import accumulate 60 | from operator import mul 61 | C = [1] # 第一个元素相当于没有 62 | C += accumulate(A[:-1], mul) # `+=`支持右边生成器,`+`不支持 63 | D = [1] 64 | D += accumulate(A[:0:-1], mul) 65 | D.reverse() 66 | return [C[i] * D[i] for i in range(len(A))] 67 | ``` 68 | 不使用标准库的方法。 69 | 70 | ```python 71 | def multiply(self, A): 72 | C = [1] # 第一个元素相当于没有 73 | for i in range(len(A)-1): 74 | C.append(C[-1] * A[i]) 75 | D = [1] 76 | for j in range(len(A)-1, 0, -1): 77 | D.append(D[-1] * A[j]) 78 | D.reverse() 79 | return [C[i] * D[i] for i in range(len(A))] 80 | ``` -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | # @Time : 2019/3/9 2:37 PM 4 | # @Author : xiaoliji 5 | # @Email : yutian9527@gmail.com 6 | 7 | 8 | class ListNode: 9 | 10 | def __init__(self, x: int): 11 | self.val = x 12 | self.next = None 13 | 14 | def construct_linklist(nodes: 'iterable')-> 'LinkedList': 15 | vals = list(nodes) 16 | head = ListNode(0) 17 | h = head 18 | for val in vals: 19 | h.next = ListNode(val) 20 | h = h.next 21 | return head.next 22 | 23 | def pretty_linklist(head: 'LinkedList') -> str: 24 | ans = [] 25 | h = head 26 | while h: 27 | ans.append(str(h.val)) 28 | h = h.next 29 | return '->'.join(ans) 30 | 31 | 32 | class TreeNode: 33 | 34 | def __init__(self, x: int): 35 | self.val = x 36 | self.left = None 37 | self.right = None 38 | 39 | def preorder_traversal(root: TreeNode) -> list: 40 | def dfs(node): 41 | if node: 42 | yield node.val 43 | yield from dfs(node.left) 44 | yield from dfs(node.right) 45 | 46 | return list(dfs(root)) 47 | 48 | def inorder_traversal(root: TreeNode) -> list: 49 | def dfs(node): 50 | if node: 51 | yield from dfs(node.left) 52 | yield node.val 53 | yield from dfs(node.right) 54 | 55 | return list(dfs(root)) 56 | 57 | def deserialize_tree(data): 58 | nodes = data.split(',')[::-1] 59 | return deserialize_tree_util(nodes) 60 | 61 | def deserialize_tree_util(nodes): 62 | val = nodes.pop() 63 | if val == '$': 64 | return None 65 | root = TreeNode(int(val)) 66 | root.left = deserialize_tree_util(nodes) 67 | root.right = deserialize_tree_util(nodes) 68 | return root 69 | 70 | def is_same_tree(p: 'TreeNode', q: 'TreeNode') -> 'bool': 71 | if p and q: 72 | return (p.val==q.val and is_same_tree(p.left, q.left) and 73 | is_same_tree(p.right, q.right)) 74 | else: 75 | return p is q --------------------------------------------------------------------------------