├── .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
--------------------------------------------------------------------------------