├── .gitignore ├── algorithms ├── c07_divide │ ├── __init__.py │ ├── binary_search_recur.py │ ├── hanota.py │ └── build_tree.py ├── c10_greedy │ ├── __init__.py │ ├── m01_activity.py │ ├── max_capacity.py │ ├── max_product_cutting.py │ ├── fractional_knapsack.py │ └── coin_change_greedy.py ├── c14_sample │ ├── __init__.py │ ├── m10_code_funny.py │ ├── m12_right_shift.py │ ├── m06_hornerpoly.py │ ├── m04_binary_add.py │ ├── m02_triangle_str.py │ ├── m15_bracket_match.py │ ├── m13_sin_cpu.py │ ├── m03_duplicate_words.py │ ├── m16_circle_queue.py │ ├── m09_max_subarr3.py │ ├── m05_nine_number.py │ ├── m11_rand_permute.py │ ├── m07_max_subarr.py │ ├── m08_max_subarr2.py │ ├── m14_bestsinger.py │ └── m01_math.py ├── c02_tree │ ├── __init__.py │ ├── top_k.py │ ├── binary_tree.py │ ├── binary_tree_bfs.py │ ├── binary_tree_dfs.py │ └── heap.py ├── c13_ai │ ├── __init__.py │ └── basic │ │ ├── __init__.py │ │ ├── a02_maze_dfs.py │ │ └── a01_maze_bfs.py ├── c08_backtrack │ ├── __init__.py │ ├── preorder_traversal_i_compact.py │ ├── backtrack.py │ ├── preorder_traversal_ii_compact.py │ ├── search_node.py │ ├── preorder_traversal_iii_compact.py │ ├── permutations_i.py │ ├── permutations_ii.py │ ├── subset_sum_i.py │ ├── subset_sum_i_naive.py │ ├── subset_sum_ii.py │ ├── n_queens.py │ └── preorder_traversal_iii_template.py ├── c05_search │ ├── __init__.py │ ├── two_sum.py │ ├── linear_search.py │ ├── binary_search_edge.py │ ├── hashing_search.py │ ├── binary_search.py │ └── binary_search_insertion.py ├── c12_leetcode │ ├── p000 │ │ ├── __init__.py │ │ ├── a70_climbing_stairs.py │ │ ├── a05_longest_palindromic_substring.py │ │ ├── a20_valid_parentheses.py │ │ ├── a19_remove_nth_node_from_end_of_list.py │ │ ├── a86_partition_list.py │ │ ├── a23_merge_k_sorted_lists.py │ │ └── a21_merge_two_sorted_lists.py │ ├── p100 │ │ ├── __init__.py │ │ ├── a160_intersection_of_two_linked_lists.py │ │ ├── a167_two_sum_ii_input_array_is_sorted.py │ │ ├── a142_linked_list_cycle_ii.py │ │ ├── a141_linked_list_cycle.py │ │ └── a155_min_stack.py │ ├── p1000 │ │ └── __init__.py │ ├── p1100 │ │ └── __init__.py │ ├── p1200 │ │ └── __init__.py │ ├── p1300 │ │ └── __init__.py │ ├── p1400 │ │ └── __init__.py │ ├── p1500 │ │ └── __init__.py │ ├── p1600 │ │ └── __init__.py │ ├── p1700 │ │ └── __init__.py │ ├── p1800 │ │ └── __init__.py │ ├── p1900 │ │ └── __init__.py │ ├── p200 │ │ ├── __init__.py │ │ ├── a283_move_zeroes.py │ │ ├── a206_reverse_linked_list.py │ │ ├── a215_kth_largest_element_in_an_array.py │ │ └── a232_implement_queue_using_stacks.py │ ├── p2000 │ │ └── __init__.py │ ├── p2100 │ │ └── __init__.py │ ├── p2200 │ │ └── __init__.py │ ├── p300 │ │ ├── __init__.py │ │ └── a392_is_subsequence.py │ ├── p400 │ │ ├── __init__.py │ │ └── a496_next_greater_element.py │ ├── p500 │ │ └── __init__.py │ ├── p600 │ │ ├── __init__.py │ │ └── a682_baseball_game.py │ ├── p700 │ │ └── __init__.py │ ├── p800 │ │ ├── __init__.py │ │ ├── a876_middle_of_the_linked_list.py │ │ └── a844_backspace_string_compare.py │ ├── p900 │ │ └── __init__.py │ └── __init__.py ├── c03_graph │ ├── __init__.py │ ├── graph_dfs.py │ ├── graph_bfs.py │ └── graph_adjacency_list.py ├── c01_data_structure │ ├── hashing │ │ ├── __init__.py │ │ ├── built_in_hash.py │ │ ├── hash_map.py │ │ ├── simple_hash.py │ │ └── array_hash_map.py │ ├── queue │ │ ├── __init__.py │ │ ├── queue.py │ │ ├── deque.py │ │ ├── linked_queue.py │ │ ├── linkedlist_queue.py │ │ └── array_queue.py │ ├── stack │ │ ├── __init__.py │ │ ├── stack.py │ │ ├── stack_linked_list.py │ │ ├── stack_array.py │ │ ├── array_stack.py │ │ └── linkedlist_stack.py │ ├── linkedlist │ │ ├── __init__.py │ │ ├── lru_cache.py │ │ ├── linked_list_double.py │ │ ├── linked_list_single_cycle.py │ │ ├── linked_list_double_cycle.py │ │ ├── linked_list_single.py │ │ └── palindromic_number.py │ └── __init__.py ├── c06_string │ └── __init__.py ├── c09_dynamic │ ├── __init__.py │ ├── m02_fibonacci.py │ ├── climbing_stairs_dfs.py │ ├── climbing_stairs_constraint_dp.py │ ├── climbing_stairs_dfs_mem.py │ ├── climbing_stairs_dp.py │ ├── climbing_stairs_backtrack.py │ ├── min_cost_climbing_stairs_dp.py │ ├── m04_elevator.py │ ├── m01_cut_steel.py │ ├── unbounded_knapsack.py │ ├── coin_change_ii.py │ ├── m06_bag.py │ ├── coin_change.py │ ├── m05_subsequence.py │ ├── m03_matrix_chain.py │ ├── min_path_sum.py │ └── knapsack.py ├── c04_sort │ ├── __init__.py │ ├── base │ │ ├── __init__.py │ │ ├── sortutil.py │ │ └── template.py │ ├── selection_sort.py │ ├── insertion_sort.py │ ├── m01_insert_sort.py │ ├── m00_bubble_sort.py │ ├── m02_select_sort.py │ ├── m10_find_minmax.py │ ├── bucket_sort.py │ ├── m09_bucket_sort.py │ ├── m13_imin_list.py │ ├── heap_sort.py │ ├── m08_radix_sort.py │ ├── m07_count_sort.py │ ├── m11_imin_select.py │ ├── bubble_sort.py │ ├── merge_sort.py │ ├── counting_sort.py │ ├── radix_sort.py │ ├── m03_merge_sort.py │ ├── m06_heap_sort.py │ ├── m12_imin_select2.py │ ├── m05_quick_sort.py │ └── m04_merge_insert_sort.py ├── __init__.py ├── c11_advanced │ └── __init__.py ├── models │ ├── vertex.py │ ├── __init__.py │ ├── list_node.py │ ├── tree_node.py │ └── print_util.py ├── build.py └── test_all.py └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.pyc 3 | /venv/ 4 | site/ 5 | -------------------------------------------------------------------------------- /algorithms/c07_divide/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 分治算法 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c10_greedy/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 贪心算法 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c14_sample/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """一些常见算法示例 3 | """ 4 | -------------------------------------------------------------------------------- /algorithms/c02_tree/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 树结构、堆结构。 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c13_ai/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c13_ai/basic/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | AI基础算法集合 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c05_search/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 二分查找、调表、散列表、哈希算法 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p100/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1000/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1100/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1200/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1300/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1400/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1500/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1600/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1700/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1800/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p1900/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p200/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p2000/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p2100/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p2200/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p300/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p400/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p500/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p600/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p700/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p800/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p900/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c03_graph/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """图算法 3 | Some of description... 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/hashing/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/queue/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | description... 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c06_string/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """字符串相关算法 3 | Some of description... 4 | """ 5 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | 动态规划 5 | """ 6 | -------------------------------------------------------------------------------- /algorithms/c04_sort/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: 各类排序算法 5 | """ 6 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | LeeCode算法题库 5 | """ 6 | -------------------------------------------------------------------------------- /algorithms/c04_sort/base/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: sample 5 | """ 6 | -------------------------------------------------------------------------------- /algorithms/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | 《算法导论》第3版,使用python3语言实现书中经典的算法示例 5 | """ 6 | -------------------------------------------------------------------------------- /algorithms/c11_advanced/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 高级数据结构和算法: 4 | 拓扑排序、最短路径、位图、概率统计、向量空间、B+树。 5 | 搜索、索引、并行计算 6 | """ 7 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/stack/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 栈数据结构 4 | 5 | LeetCode题目: 6 | 20,155,224,232,496,682,844 7 | """ 8 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/linkedlist/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 链表数据结构 4 | 5 | 练习题LeetCode对应编号:206,141,21,19,876 6 | 7 | * 单链表反转 8 | * 链表中环的检测 9 | * 两个有序的链表合并 10 | * 删除链表倒数第 n 个结点 11 | * 求链表的中间结点 12 | """ 13 | 14 | 15 | class ListNode: 16 | def __init__(self, val=0, next=None): 17 | self.val = val 18 | self.next = next 19 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/m02_fibonacci.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: 动态规划O(n)时间内实现fibonacci数列 5 | Desc : 6 | """ 7 | 8 | 9 | def dynamic_fibo(n): 10 | r = [1, 1] 11 | for i in range(2, n): 12 | r.append(r[i - 2] + r[i - 1]) 13 | return r 14 | 15 | 16 | if __name__ == '__main__': 17 | print(dynamic_fibo(10)) 18 | -------------------------------------------------------------------------------- /algorithms/c04_sort/base/sortutil.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 将重复的方法抽取出来 4 | """ 5 | 6 | 7 | def insert_sort(seq_): 8 | """插入排序""" 9 | for j in range(1, len(seq_)): 10 | key = seq_[j] 11 | # insert arrays[j] into the sorted seq[0...j-1] 12 | i = j - 1 13 | while i >= 0 and key < seq_[i]: 14 | seq_[i + 1] = seq_[i] # element move forward 15 | i -= 1 16 | seq_[i + 1] = key # at last, put key to right place 17 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m10_code_funny.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 编程之美的几个小算法 4 | """ 5 | Topic: sample 6 | Desc : 编程之美的几个小算法 7 | """ 8 | __author__ = 'Xiong Neng' 9 | 10 | 11 | def list_chess(): 12 | """ 13 | 打印连个将/帅的所有合法的位置, 14 | 用1..9标明第一个将9个位置,1..9标明第二个帅 15 | """ 16 | for i in range(1, 10): 17 | print([(i, k) for k in range(1, 10) if abs(k - i) % 3 != 0]) 18 | 19 | 20 | if __name__ == '__main__': 21 | list_chess() 22 | -------------------------------------------------------------------------------- /algorithms/models/vertex.py: -------------------------------------------------------------------------------- 1 | # File: vertex.py 2 | # Created Time: 2023-02-23 3 | # 4 | 5 | class Vertex: 6 | """顶点类""" 7 | 8 | def __init__(self, val: int): 9 | self.val = val 10 | 11 | 12 | def vals_to_vets(vals: list[int]) -> list["Vertex"]: 13 | """输入值列表 vals ,返回顶点列表 vets""" 14 | return [Vertex(val) for val in vals] 15 | 16 | 17 | def vets_to_vals(vets: list["Vertex"]) -> list[int]: 18 | """输入顶点列表 vets ,返回值列表 vals""" 19 | return [vet.val for vet in vets] 20 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m12_right_shift.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 数组循环右移 4 | """ 5 | Topic: sample 6 | Desc : 数组循环右移 7 | """ 8 | __author__ = 'Xiong Neng' 9 | 10 | 11 | def right_shift(seq, k): 12 | n = len(seq) 13 | k %= n 14 | seq[0:n - k] = seq[n - k - 1::-1] 15 | seq[n - k: n] = seq[:n - k - 1:-1] 16 | seq[:] = seq[::-1] 17 | 18 | 19 | if __name__ == '__main__': 20 | s = list('abcd1234') 21 | right_shift(s, 4) 22 | print(s) 23 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m06_hornerpoly.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # Horner多项式求值算法 4 | """ 5 | Topic: sample 6 | Desc : Horner多项式求值算法 7 | P(x) = Σ(k=0,n)a(k)x^k = a0 + x(a1 + x(a2+ ... + x(an-1 + x*an)...)) 8 | """ 9 | __author__ = 'Xiong Neng' 10 | 11 | 12 | def horner_poly(coefficient_arr, x): 13 | res = 0 14 | for i in range(len(coefficient_arr))[-1::-1]: 15 | res = coefficient_arr[i] + x * res 16 | return res 17 | 18 | 19 | if __name__ == '__main__': 20 | print(horner_poly((1, 2, 3), 2)) 21 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m04_binary_add.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 两个N位的二进制数相加 4 | """ 5 | Topic: sample 6 | Desc : 两个N位的二进制数相加 7 | """ 8 | __author__ = 'Xiong Neng' 9 | 10 | 11 | def biAdd(a, b): 12 | res = [] 13 | m = 0 14 | r = list(range(0, len(a))) 15 | r.reverse() 16 | for i in r: 17 | m, n = divmod(a[i] + b[i] + m, 2) 18 | res.insert(0, n) 19 | res.insert(0, m) 20 | return res 21 | 22 | 23 | if __name__ == '__main__': 24 | print(biAdd([0, 1, 0, 1, 1, 0], [1, 1, 0, 1, 1, 0])) 25 | -------------------------------------------------------------------------------- /algorithms/c10_greedy/m01_activity.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 活动选择问题。 4 | 活动序列a[1..n],开始时间序列s[1..n],结束时间序列f[1..n]。 5 | 每个a[i]活动时间为。并且满足按照结束时间升序排列 6 | """ 7 | 8 | 9 | def greedy_activity_selector(s, f): 10 | n = len(s) 11 | ans = [0] # 初始选择第一个活动,必定在其中,这里保存最终活动下标即可。 12 | for m in range(1, n): 13 | if s[m] >= f[ans[-1]]: 14 | ans.append(m) 15 | return ans 16 | 17 | 18 | if __name__ == '__main__': 19 | s = [1, 3, 0, 5, 3, 5, 6, 8, 8, 2, 12] 20 | f = [4, 5, 6, 7, 9, 9, 10, 11, 12, 14, 16] 21 | print(greedy_activity_selector(s, f)) 22 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m02_triangle_str.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 三角打印字符串 4 | """ 5 | Topic: sample 6 | Desc : 三角打印字符串 7 | """ 8 | __author__ = 'Xiong Neng' 9 | 10 | 11 | # 三角打印 12 | def triangleDisplay(mystr): 13 | # mystr = unicode(mystr, 'utf-8') 14 | mystr += ' ' 15 | result = [] 16 | le = len(mystr) 17 | for i in range(1, le): 18 | result.append(mystr[-i: -1]) 19 | for i in range(le): 20 | result.append(mystr[i: -1]) 21 | return result 22 | 23 | 24 | for each in triangleDisplay(u"我和我的小伙伴们都惊呆了"): 25 | print(each) 26 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/climbing_stairs_dfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: climbing_stairs_dfs.py 3 | Created Time: 2023-06-30 4 | """ 5 | 6 | 7 | def dfs(i: int) -> int: 8 | """搜索""" 9 | # 已知 dp[1] 和 dp[2] ,返回之 10 | if i == 1 or i == 2: 11 | return i 12 | # dp[i] = dp[i-1] + dp[i-2] 13 | count = dfs(i - 1) + dfs(i - 2) 14 | return count 15 | 16 | 17 | def climbing_stairs_dfs(n: int) -> int: 18 | """爬楼梯:搜索""" 19 | return dfs(n) 20 | 21 | 22 | """Driver Code""" 23 | if __name__ == "__main__": 24 | n = 9 25 | 26 | res = climbing_stairs_dfs(n) 27 | print(f"爬 {n} 阶楼梯共有 {res} 种方案") 28 | -------------------------------------------------------------------------------- /algorithms/build.py: -------------------------------------------------------------------------------- 1 | import glob 2 | import py_compile as pyc 3 | 4 | if __name__ == "__main__": 5 | # find source code files 6 | src_paths = sorted(glob.glob("codes/python/**/*.py")) 7 | num_src = len(src_paths) 8 | num_src_error = 0 9 | 10 | # compile python code 11 | for src_path in src_paths: 12 | try: 13 | pyc.compile(src_path, doraise=True) 14 | except pyc.PyCompileError as e: 15 | num_src_error += 1 16 | print(e) 17 | 18 | print(f"===== Build Complete =====") 19 | print(f"Total: {num_src}") 20 | print(f"Error: {num_src_error}") 21 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 线性表数据结构:数组、链表、队列、栈。 4 | """ 5 | 6 | 7 | class Node: 8 | """ 9 | 单指针节点 10 | """ 11 | 12 | def __init__(self, data_, next_=None): 13 | self.data = data_ 14 | self.next = next_ 15 | 16 | def __str__(self): 17 | return str(self.data) 18 | 19 | 20 | class NodeDouble: 21 | """ 22 | 双指针节点 23 | """ 24 | 25 | def __init__(self, data_, next_=None, pre_=None): 26 | self.data = data_ 27 | self.next = next_ 28 | self.pre = pre_ 29 | 30 | def __str__(self): 31 | return str(self.data) 32 | -------------------------------------------------------------------------------- /algorithms/models/__init__.py: -------------------------------------------------------------------------------- 1 | # Follow the PEP 585 – Type Hinting Generics In Standard Collections 2 | # https://peps.python.org/pep-0585/ 3 | from __future__ import annotations 4 | 5 | # Import common libs here to simplify the code by `from module import *` 6 | from .list_node import ( 7 | ListNode, 8 | list_to_linked_list, 9 | linked_list_to_list, 10 | get_list_node, 11 | ) 12 | from .tree_node import TreeNode, list_to_tree, tree_to_list 13 | from .vertex import Vertex, vals_to_vets, vets_to_vals 14 | from .print_util import ( 15 | print_matrix, 16 | print_linked_list, 17 | print_tree, 18 | print_dict, 19 | print_heap, 20 | ) 21 | -------------------------------------------------------------------------------- /algorithms/c04_sort/selection_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: selection_sort.py 3 | Created Time: 2023-05-22 4 | """ 5 | 6 | 7 | def selection_sort(nums: list[int]): 8 | """选择排序""" 9 | n = len(nums) 10 | # 外循环:未排序区间为 [i, n-1] 11 | for i in range(n - 1): 12 | # 内循环:找到未排序区间内的最小元素 13 | k = i 14 | for j in range(i + 1, n): 15 | if nums[j] < nums[k]: 16 | k = j # 记录最小元素的索引 17 | # 将该最小元素与未排序区间的首个元素交换 18 | nums[i], nums[k] = nums[k], nums[i] 19 | 20 | 21 | """Driver Code""" 22 | if __name__ == "__main__": 23 | nums = [4, 1, 3, 1, 5, 2] 24 | selection_sort(nums) 25 | print("选择排序完成后 nums =", nums) 26 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m15_bracket_match.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 括号匹配 4 | """ 5 | Topic: 输入字符串,判断是否是合法的括号组合 6 | Desc : 7 | 大括号{},中括号[],小括号()的合法匹配 8 | 比如{[()()]}合法,但是[{()(})]不合法 9 | """ 10 | 11 | __author__ = 'Xiong Neng' 12 | 13 | 14 | def match(arr): 15 | left = ['{', '[', '('] 16 | mmap = {'}': '{', ']': '[', ')': '('} 17 | stack = [] 18 | for c in arr: 19 | if c in left: 20 | stack.append(c) 21 | else: 22 | if mmap[c] != stack.pop(): return False 23 | return True 24 | 25 | 26 | if __name__ == '__main__': 27 | print(match('[[()()]]')) 28 | print(match('[[()(])]')) 29 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m13_sin_cpu.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # CPU正弦曲线 4 | """ 5 | Topic: sample 6 | Desc : CPU正弦曲线 7 | """ 8 | import itertools 9 | import math 10 | import sys 11 | import time 12 | 13 | __author__ = 'Xiong Neng' 14 | 15 | time_period = float(sys.argv[1]) if len(sys.argv) > 1 else 60 # seconds 16 | time_slice = float(sys.argv[2]) if len(sys.argv) > 2 else 0.04 # seconds 17 | 18 | N = int(time_period / time_slice) 19 | for i in itertools.cycle(range(N)): 20 | busy_time = time_slice / 2 * (math.sin(2 * math.pi * i / N) + 1) 21 | t = time.clock() + busy_time 22 | while t > time.clock(): 23 | pass 24 | time.sleep(time_slice - busy_time) 25 | -------------------------------------------------------------------------------- /algorithms/c04_sort/insertion_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: insertion_sort.py 3 | Created Time: 2022-11-25 4 | Author: timi (xisunyy@163.com) 5 | """ 6 | 7 | 8 | def insertion_sort(nums: list[int]): 9 | """插入排序""" 10 | # 外循环:已排序区间为 [0, i-1] 11 | for i in range(1, len(nums)): 12 | base = nums[i] 13 | j = i - 1 14 | # 内循环:将 base 插入到已排序区间 [0, i-1] 中的正确位置 15 | while j >= 0 and nums[j] > base: 16 | nums[j + 1] = nums[j] # 将 nums[j] 向右移动一位 17 | j -= 1 18 | nums[j + 1] = base # 将 base 赋值到正确位置 19 | 20 | 21 | """Driver Code""" 22 | if __name__ == "__main__": 23 | nums = [4, 1, 3, 1, 5, 2] 24 | insertion_sort(nums) 25 | print("插入排序完成后 nums =", nums) 26 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/a70_climbing_stairs.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 70. 爬楼梯 4 | 假设你正在爬楼梯。需要 n阶你才能到达楼顶。 5 | 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 6 | 注意:给定 n 是一个正整数。 7 | """ 8 | 9 | 10 | class Solution: 11 | def climb_stairs(self, n: int) -> int: 12 | if n == 1: 13 | return 1 14 | if n == 2: 15 | return 2 16 | a, b = 1, 2 17 | for i in range(3, n + 1): 18 | a, b = b, a + b 19 | 20 | return b 21 | 22 | 23 | if __name__ == '__main__': 24 | import sys 25 | 26 | while True: 27 | line = sys.stdin.readline().strip() 28 | if line == '': 29 | break 30 | print(Solution().climb_stairs(int(line))) 31 | -------------------------------------------------------------------------------- /algorithms/c10_greedy/max_capacity.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: max_capacity.py 3 | Created Time: 2023-07-21 4 | """ 5 | 6 | 7 | def max_capacity(ht: list[int]) -> int: 8 | """最大容量:贪心""" 9 | # 初始化 i, j 分列数组两端 10 | i, j = 0, len(ht) - 1 11 | # 初始最大容量为 0 12 | res = 0 13 | # 循环贪心选择,直至两板相遇 14 | while i < j: 15 | # 更新最大容量 16 | cap = min(ht[i], ht[j]) * (j - i) 17 | res = max(res, cap) 18 | # 向内移动短板 19 | if ht[i] < ht[j]: 20 | i += 1 21 | else: 22 | j -= 1 23 | return res 24 | 25 | 26 | """Driver Code""" 27 | if __name__ == "__main__": 28 | ht = [3, 8, 5, 2, 7, 7, 3, 4] 29 | 30 | # 贪心算法 31 | res = max_capacity(ht) 32 | print(f"最大容量为 {res}") 33 | -------------------------------------------------------------------------------- /algorithms/c10_greedy/max_product_cutting.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: max_product_cutting.py 3 | Created Time: 2023-07-21 4 | """ 5 | 6 | import math 7 | 8 | 9 | def max_product_cutting(n: int) -> int: 10 | """最大切分乘积:贪心""" 11 | # 当 n <= 3 时,必须切分出一个 1 12 | if n <= 3: 13 | return 1 * (n - 1) 14 | # 贪心地切分出 3 ,a 为 3 的个数,b 为余数 15 | a, b = n // 3, n % 3 16 | if b == 1: 17 | # 当余数为 1 时,将一对 1 * 3 转化为 2 * 2 18 | return int(math.pow(3, a - 1)) * 2 * 2 19 | if b == 2: 20 | # 当余数为 2 时,不做处理 21 | return int(math.pow(3, a)) * 2 22 | # 当余数为 0 时,不做处理 23 | return int(math.pow(3, a)) 24 | 25 | 26 | """Driver Code""" 27 | if __name__ == "__main__": 28 | n = 58 29 | 30 | # 贪心算法 31 | res = max_product_cutting(n) 32 | print(f"最大切分乘积为 {res}") 33 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/climbing_stairs_constraint_dp.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: climbing_stairs_constraint_dp.py 3 | Created Time: 2023-06-30 4 | """ 5 | 6 | 7 | def climbing_stairs_constraint_dp(n: int) -> int: 8 | """带约束爬楼梯:动态规划""" 9 | if n == 1 or n == 2: 10 | return 1 11 | # 初始化 dp 表,用于存储子问题的解 12 | dp = [[0] * 3 for _ in range(n + 1)] 13 | # 初始状态:预设最小子问题的解 14 | dp[1][1], dp[1][2] = 1, 0 15 | dp[2][1], dp[2][2] = 0, 1 16 | # 状态转移:从较小子问题逐步求解较大子问题 17 | for i in range(3, n + 1): 18 | dp[i][1] = dp[i - 1][2] 19 | dp[i][2] = dp[i - 2][1] + dp[i - 2][2] 20 | return dp[n][1] + dp[n][2] 21 | 22 | 23 | """Driver Code""" 24 | if __name__ == "__main__": 25 | n = 9 26 | 27 | res = climbing_stairs_constraint_dp(n) 28 | print(f"爬 {n} 阶楼梯共有 {res} 种方案") 29 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p200/a283_move_zeroes.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 283. 移动零 4 | 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 5 | 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 6 | 输入: nums = [0,1,0,3,12] 7 | 输出: [1,3,12,0,0] 8 | 输入: nums = [0] 9 | 输出: [0] 10 | 11 | 解题思路: 12 | 快慢指针,慢指针保留所有非0值,快指针遍历完数组。最后将慢指针后面的值全部赋值为0即可 13 | """ 14 | from typing import List, NoReturn 15 | 16 | 17 | class Solution: 18 | def moveZeroes(self, nums: List[int]) -> NoReturn: 19 | """ 20 | Do not return anything, modify nums in-place instead. 21 | """ 22 | slow, fast = 0, 0 23 | while fast < len(nums): 24 | if nums[fast] != 0: 25 | nums[slow] = nums[fast] 26 | slow += 1 27 | fast += 1 28 | while slow < len(nums): 29 | nums[slow] = 0 30 | slow += 1 31 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m01_insert_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """插入排序 3 | 我们将数组中的数据分为两个区间,已排序区间和未排序区间。初始已排序区间只有一个元素,就是数组的第一个元素。 4 | 插入算法的核心思想是取未排序区间中的元素,在已排序区间中找到合适的插入位置将其插入,并保证已排序区间数据一直有序。 5 | 重复这个过程,直到未排序区间中元素为空,算法结束。 6 | 7 | 由于其内层循环非常紧凑,对于小规模的输入,插入排序是一种非常快的原址排序算法。 8 | 注:如果输入数组中仅有常数个元素需要在排序过程中存储在数组外,则称这种排序算法是原址的。 9 | 10 | 冒泡排序的数据交换要比插入排序的数据移动要复杂,冒泡排序需要3个赋值操作,而插入排序只需要1个。 11 | 所以,虽然冒泡排序和插入排序在时间复杂度上是一样的,都是 O(n2),但是如果我们希望把性能优化做到极致, 12 | 那肯定首选插入排序。 13 | 14 | 算法复杂度:N^2 15 | 稳定排序:重复元素排序完后仍然保持原来的相对位置。 16 | """ 17 | from algorithms.c04_sort.base.sortutil import insert_sort 18 | from algorithms.c04_sort.base.template import SortTemplate 19 | 20 | 21 | class InsertSort(SortTemplate): 22 | 23 | def sort(self): 24 | insert_sort(self.seq) 25 | 26 | 27 | if __name__ == '__main__': 28 | select_sort = InsertSort([4, 2, 5, 1, 6, 3]) 29 | select_sort.main() 30 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/climbing_stairs_dfs_mem.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: climbing_stairs_dfs_mem.py 3 | Created Time: 2023-06-30 4 | """ 5 | 6 | 7 | def dfs(i: int, mem: list[int]) -> int: 8 | """记忆化搜索""" 9 | # 已知 dp[1] 和 dp[2] ,返回之 10 | if i == 1 or i == 2: 11 | return i 12 | # 若存在记录 dp[i] ,则直接返回之 13 | if mem[i] != -1: 14 | return mem[i] 15 | # dp[i] = dp[i-1] + dp[i-2] 16 | count = dfs(i - 1, mem) + dfs(i - 2, mem) 17 | # 记录 dp[i] 18 | mem[i] = count 19 | return count 20 | 21 | 22 | def climbing_stairs_dfs_mem(n: int) -> int: 23 | """爬楼梯:记忆化搜索""" 24 | # mem[i] 记录爬到第 i 阶的方案总数,-1 代表无记录 25 | mem = [-1] * (n + 1) 26 | return dfs(n, mem) 27 | 28 | 29 | """Driver Code""" 30 | if __name__ == "__main__": 31 | n = 9 32 | 33 | res = climbing_stairs_dfs_mem(n) 34 | print(f"爬 {n} 阶楼梯共有 {res} 种方案") 35 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/stack/stack.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: stack.py 3 | Created Time: 2022-11-29 4 | Author: Peng Chen (pengchzn@gmail.com) 5 | """ 6 | 7 | """Driver Code""" 8 | if __name__ == "__main__": 9 | # 初始化栈 10 | # Python 没有内置的栈类,可以把 list 当作栈来使用 11 | stack: list[int] = [] 12 | 13 | # 元素入栈 14 | stack.append(1) 15 | stack.append(3) 16 | stack.append(2) 17 | stack.append(5) 18 | stack.append(4) 19 | print("栈 stack =", stack) 20 | 21 | # 访问栈顶元素 22 | peek: int = stack[-1] 23 | print("栈顶元素 peek =", peek) 24 | 25 | # 元素出栈 26 | pop: int = stack.pop() 27 | print("出栈元素 pop =", pop) 28 | print("出栈后 stack =", stack) 29 | 30 | # 获取栈的长度 31 | size: int = len(stack) 32 | print("栈的长度 size =", size) 33 | 34 | # 判断是否为空 35 | is_empty: bool = len(stack) == 0 36 | print("栈是否为空 =", is_empty) 37 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m00_bubble_sort.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 冒泡排序 4 | 当某次冒泡操作已经没有数据交换时,说明已经达到完全有序,不用再继续执行后续的冒泡操作。 5 | 6 | 算法复杂度:N^2 7 | 稳定排序:重复元素排序完后仍然保持原来的相对位置。 8 | """ 9 | from algorithms.c04_sort.base.template import SortTemplate 10 | 11 | 12 | class BubbleSort(SortTemplate): 13 | 14 | def sort(self): 15 | le = len(self.seq) 16 | for i in range(le): 17 | # 提前退出排序的标志,本次循环是否有数据交换 18 | has_exchange = False 19 | for j in range(0, le - i - 1): 20 | if self.seq[j] > self.seq[j + 1]: 21 | self.seq[j], self.seq[j + 1] = self.seq[j + 1], self.seq[j] 22 | has_exchange = True # 表示有数据交换 23 | if not has_exchange: 24 | break 25 | 26 | 27 | if __name__ == '__main__': 28 | bubble_sort = BubbleSort([4, 2, 5, 1, 6, 3]) 29 | bubble_sort.main() 30 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/preorder_traversal_i_compact.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: preorder_traversal_i_compact.py 3 | Created Time: 2023-04-15 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import TreeNode, print_tree, list_to_tree 11 | 12 | 13 | def pre_order(root: TreeNode): 14 | """前序遍历:例题一""" 15 | if root is None: 16 | return 17 | if root.val == 7: 18 | # 记录解 19 | res.append(root) 20 | pre_order(root.left) 21 | pre_order(root.right) 22 | 23 | 24 | """Driver Code""" 25 | if __name__ == "__main__": 26 | root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) 27 | print("\n初始化二叉树") 28 | print_tree(root) 29 | 30 | # 前序遍历 31 | res = list[TreeNode]() 32 | pre_order(root) 33 | 34 | print("\n输出所有值为 7 的节点") 35 | print([node.val for node in res]) 36 | -------------------------------------------------------------------------------- /algorithms/c04_sort/base/template.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: 排序模板 5 | """ 6 | 7 | 8 | class SortTemplate: 9 | 10 | def __init__(self, seq): 11 | self.seq = seq 12 | 13 | def sort(self): 14 | """ 15 | 排序方法 16 | """ 17 | pass 18 | 19 | def less(self, val1, val2): 20 | """ 21 | 对比两个元素,如果从小到大则返回True 22 | """ 23 | return val1 <= val2 24 | 25 | def show(self, vals): 26 | for val in vals: 27 | print("{}".format(val), end=' ') 28 | 29 | def is_sorted(self, vals): 30 | for i in range(1, len(vals)): 31 | if self.less(vals[i], vals[i - 1]): 32 | return False 33 | return True 34 | 35 | def main(self): 36 | self.sort() 37 | assert self.is_sorted(self.seq) 38 | self.show(self.seq) 39 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m02_select_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """选择排序 4 | 1. 找到数组中最小的元素,将其与第一个元素交换位置(如果第一个元素最小,就啥也不做)。 5 | 2. 在剩下的元素中找到最小元素,将其与数组第二个元素交换位置。 6 | 3. 如此反复,直到剩下元素为1个,整个数组排序完。 7 | 8 | 不稳定排序,使用场景少。 9 | 复杂度:O(N^2),大约需要N^2/2次比较和N次交换。 10 | """ 11 | from algorithms.c04_sort.base.template import SortTemplate 12 | 13 | 14 | class SelectSort(SortTemplate): 15 | 16 | def sort(self): 17 | le = len(self.seq) 18 | for i in range(le - 1): 19 | min_index = i 20 | for j in range(i, le): 21 | if self.seq[min_index] > self.seq[j]: 22 | min_index = j 23 | if i != min_index: 24 | self.seq[i], self.seq[min_index] = self.seq[min_index], self.seq[i] 25 | 26 | 27 | if __name__ == '__main__': 28 | select_sort = SelectSort([4, 2, 5, 1, 6, 3]) 29 | select_sort.main() 30 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m03_duplicate_words.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: 找重名的单词 5 | Desc : 6 | """ 7 | 8 | 9 | def find_count(filename): 10 | count_map = {} 11 | with open(filename, encoding='utf-8') as f: 12 | for line in f: 13 | if line in count_map: 14 | count_map[line] += 1 15 | else: 16 | count_map[line] = 1 17 | return {k: v for k, v in count_map.items() if v > 1} 18 | 19 | 20 | if __name__ == '__main__': 21 | # filename = sys.argv[1] 22 | # print(find_count(filename)) 23 | 24 | arr = [3, 5, 200, 304, 22, 34, 5, 12, 99, 567] 25 | max_num = 1000000 # 比这些数字里面最大的数大即可 26 | init_arr = [0 for i in range(max_num)] # 初始化数组 27 | for n in arr: 28 | init_arr[n] += 1 29 | if init_arr[n] > 1: 30 | print('找到重复的了,{}'.format(n)) 31 | break 32 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m10_find_minmax.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 在数组中同时找出最小和最大的 4 | """ 5 | Topic: sample 6 | Desc : 在数组中同时找出最小和最大的 7 | 算法描述:如果n是奇数,最小和最大初始化为第一个元素, 8 | 如果是偶数,先对前两个元素比较,决定最小和最大初值。 9 | """ 10 | __author__ = 'Xiong Neng' 11 | 12 | 13 | def minMax(A): 14 | n = len(A) 15 | if n % 2 == 0: 16 | lastMin, lastMax = (A[0], A[1]) if A[0] < A[1] else (A[1], A[0]) 17 | else: 18 | lastMin = lastMax = A[0] 19 | for i in range(0, (n + 1) // 2 - 1): 20 | tmp1 = A[2 * i + 1] 21 | tmp2 = A[2 * i + 2] 22 | tmpMin, tmpMax = (tmp1, tmp2) if tmp1 < tmp2 else (tmp2, tmp1) 23 | lastMin = lastMin if lastMin < tmpMin else tmpMin 24 | lastMax = lastMax if lastMax > tmpMax else tmpMax 25 | return lastMin, lastMax 26 | 27 | 28 | if __name__ == '__main__': 29 | print(minMax([4, 23, 65, 22, 12, 4, 1, 1, 256, 34, 27])) 30 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/queue/queue.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: queue.py 3 | Created Time: 2022-11-29 4 | Author: Peng Chen (pengchzn@gmail.com) 5 | """ 6 | 7 | from collections import deque 8 | 9 | """Driver Code""" 10 | if __name__ == "__main__": 11 | # 初始化队列 12 | # 在 Python 中,我们一般将双向队列类 deque 看作队列使用 13 | # 虽然 queue.Queue() 是纯正的队列类,但不太好用 14 | que: deque[int] = deque() 15 | 16 | # 元素入队 17 | que.append(1) 18 | que.append(3) 19 | que.append(2) 20 | que.append(5) 21 | que.append(4) 22 | print("队列 que =", que) 23 | 24 | # 访问队首元素 25 | front: int = que[0] 26 | print("队首元素 front =", front) 27 | 28 | # 元素出队 29 | pop: int = que.popleft() 30 | print("出队元素 pop =", pop) 31 | print("出队后 que =", que) 32 | 33 | # 获取队列的长度 34 | size: int = len(que) 35 | print("队列长度 size =", size) 36 | 37 | # 判断队列是否为空 38 | is_empty: bool = len(que) == 0 39 | print("队列是否为空 =", is_empty) 40 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p300/a392_is_subsequence.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 392. 判断子序列 4 | 给定字符串 s 和 t ,判断 s 是否为 t 的子序列。 5 | 6 | 字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。 7 | (例如,"ace"是"abcde"的一个子序列,而"aec"不是)。 8 | 9 | 进阶: 10 | 如果有大量输入的 S,称作 S1, S2, ... , Sk 其中 k >= 10亿,你需要依次检查它们是否为 T 的子序列。 11 | 在这种情况下,你会怎样改变代码? 12 | 13 | 算法思路: 14 | 通过双指针遍历两个序列,两个相等时,左指针向前一步,直到左指针跑完左边字符串s即可。 15 | python中可通过生成器方式简化算法的写法。 16 | 17 | 输入:s = "abc", t = "ahbgdc" 18 | 输出:true 19 | 20 | 输入:s = "axc", t = "ahbgdc" 21 | 输出:false 22 | """ 23 | 24 | 25 | class Solution: 26 | def isSubsequence(self, s: str, t: str) -> bool: 27 | t_iter = iter(t) # 这里需要单独提取出来,否则for中每次都会生成新的迭代器。 28 | return all(i in t_iter for i in s) 29 | 30 | 31 | if __name__ == '__main__': 32 | print(Solution().isSubsequence('abc', 'ahbgdc')) 33 | print(Solution().isSubsequence('axc', 'ahbgdc')) 34 | print(Solution().isSubsequence('acb', 'ahbgdc')) 35 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/hashing/built_in_hash.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: built_in_hash.py 3 | Created Time: 2023-06-15 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import ListNode 11 | 12 | """Driver Code""" 13 | if __name__ == "__main__": 14 | num = 3 15 | hash_num = hash(num) 16 | print(f"整数 {num} 的哈希值为 {hash_num}") 17 | 18 | bol = True 19 | hash_bol = hash(bol) 20 | print(f"布尔量 {bol} 的哈希值为 {hash_bol}") 21 | 22 | dec = 3.14159 23 | hash_dec = hash(dec) 24 | print(f"小数 {dec} 的哈希值为 {hash_dec}") 25 | 26 | str = "Hello 算法" 27 | hash_str = hash(str) 28 | print(f"字符串 {str} 的哈希值为 {hash_str}") 29 | 30 | tup = (12836, "小哈") 31 | hash_tup = hash(tup) 32 | print(f"元组 {tup} 的哈希值为 {hash(hash_tup)}") 33 | 34 | obj = ListNode(0) 35 | hash_obj = hash(obj) 36 | print(f"节点对象 {obj} 的哈希值为 {hash_obj}") 37 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/backtrack.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 回溯法框架代码 4 | """ 5 | 6 | 7 | def backtrack(state, choices, res): 8 | # 判断是否为解 9 | if is_solution(state): 10 | # 记录解 11 | record_solution(state, res) 12 | # 不再继续搜索 13 | return 14 | # 遍历所有选择 15 | for choice in choices: 16 | # 剪枝 17 | if is_valid(state, choice): 18 | # 尝试:做出选择,更新状态 19 | make_choice(state, choice) 20 | # 使用新的状态继续寻找 21 | backtrack(state, choices, res) 22 | # 回退:撤销选择,恢复到之前的状态 23 | undo_choice(state, choice) 24 | 25 | 26 | def is_solution(state) -> bool: 27 | return True 28 | 29 | 30 | def record_solution(state, res): 31 | pass 32 | 33 | 34 | def is_valid(state, choice) -> bool: 35 | return True 36 | 37 | 38 | def make_choice(state, choice): 39 | pass 40 | 41 | 42 | def undo_choice(state, choice): 43 | pass 44 | -------------------------------------------------------------------------------- /algorithms/c02_tree/top_k.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: top_k.py 3 | Created Time: 2023-06-10 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import print_heap 11 | 12 | import heapq 13 | 14 | 15 | def top_k_heap(nums: list[int], k: int) -> list[int]: 16 | """基于堆查找数组中最大的 k 个元素""" 17 | # 初始化小顶堆 18 | heap = [] 19 | # 将数组的前 k 个元素入堆 20 | for i in range(k): 21 | heapq.heappush(heap, nums[i]) 22 | # 从第 k+1 个元素开始,保持堆的长度为 k 23 | for i in range(k, len(nums)): 24 | # 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆 25 | if nums[i] > heap[0]: 26 | heapq.heappop(heap) 27 | heapq.heappush(heap, nums[i]) 28 | return heap 29 | 30 | 31 | """Driver Code""" 32 | if __name__ == "__main__": 33 | nums = [1, 7, 6, 3, 2] 34 | k = 3 35 | 36 | res = top_k_heap(nums, k) 37 | print(f"最大的 {k} 个元素为") 38 | print_heap(res) 39 | -------------------------------------------------------------------------------- /algorithms/c04_sort/bucket_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: bucket_sort.py 3 | Created Time: 2023-03-30 4 | """ 5 | 6 | 7 | def bucket_sort(nums: list[float]): 8 | """桶排序""" 9 | # 初始化 k = n/2 个桶,预期向每个桶分配 2 个元素 10 | k = len(nums) // 2 11 | buckets = [[] for _ in range(k)] 12 | # 1. 将数组元素分配到各个桶中 13 | for num in nums: 14 | # 输入数据范围 [0, 1),使用 num * k 映射到索引范围 [0, k-1] 15 | i = int(num * k) 16 | # 将 num 添加进桶 i 17 | buckets[i].append(num) 18 | # 2. 对各个桶执行排序 19 | for bucket in buckets: 20 | # 使用内置排序函数,也可以替换成其他排序算法 21 | bucket.sort() 22 | # 3. 遍历桶合并结果 23 | i = 0 24 | for bucket in buckets: 25 | for num in bucket: 26 | nums[i] = num 27 | i += 1 28 | 29 | 30 | if __name__ == "__main__": 31 | # 设输入数据为浮点数,范围为 [0, 1) 32 | nums = [0.49, 0.96, 0.82, 0.09, 0.57, 0.43, 0.91, 0.75, 0.15, 0.37] 33 | bucket_sort(nums) 34 | print("桶排序完成后 nums =", nums) 35 | -------------------------------------------------------------------------------- /algorithms/c02_tree/binary_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: binary_tree.py 3 | Created Time: 2022-12-20 4 | Author: a16su (lpluls001@gmail.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import TreeNode, print_tree 12 | 13 | """Driver Code""" 14 | if __name__ == "__main__": 15 | # 初始化二叉树 16 | # 初始化节点 17 | n1 = TreeNode(val=1) 18 | n2 = TreeNode(val=2) 19 | n3 = TreeNode(val=3) 20 | n4 = TreeNode(val=4) 21 | n5 = TreeNode(val=5) 22 | # 构建引用指向(即指针) 23 | n1.left = n2 24 | n1.right = n3 25 | n2.left = n4 26 | n2.right = n5 27 | print("\n初始化二叉树\n") 28 | print_tree(n1) 29 | 30 | # 插入与删除节点 31 | P = TreeNode(0) 32 | # 在 n1 -> n2 中间插入节点 P 33 | n1.left = P 34 | P.left = n2 35 | print("\n插入节点 P 后\n") 36 | print_tree(n1) 37 | # 删除节点 38 | n1.left = n2 39 | print("\n删除节点 P 后\n") 40 | print_tree(n1) 41 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m16_circle_queue.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Josephus排列 5 | Topic: n个人围成一圈,从某个指定人开始,沿着环将遇到的每第m个人移出去。 6 | 每个人移出去后,继续沿着环将剩下的人按同样规则移出去 7 | 默认队列第一个编号为1,以此类推。。。 8 | """ 9 | 10 | 11 | def circle_out(n, m): 12 | # 初始化数组,1表示在队列中,0表示已经出了队列 13 | queue_status = [1 for n in range(0, n)] 14 | result = [] # 出队序列 15 | out_count = 0 # 出队人数 16 | pass_num = 0 # 每次小循环经过的人数 17 | index = 0 # 循环下标 18 | while out_count < n: 19 | while True: 20 | if queue_status[index] == 1: 21 | pass_num += 1 22 | if pass_num >= m: 23 | break 24 | index = (index + 1) % n 25 | # 出队 26 | queue_status[index] = 0 27 | out_count += 1 28 | result.append(index + 1) 29 | pass_num = 0 30 | print(result) 31 | return result 32 | 33 | 34 | if __name__ == '__main__': 35 | circle_out(7, 3) 36 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m09_bucket_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 桶排序 4 | """ 5 | Topic: sample 6 | Desc : 桶排序 7 | 桶排序假设数据服从均匀分布,平均情况下它的代价为O(n) 8 | 桶排序假定输入是由一个随机过程产生,该过程将元素均匀、独立分布在[0,1)区间上 9 | 将[0,1)区间划分成n个相同大小的子区间,称为桶。然后将n个输入数分别放入桶中 10 | 然后循环n个桶,对每个桶排序,采用插入排序算法 11 | """ 12 | from math import floor 13 | 14 | from algorithms.c04_sort.base.sortutil import insert_sort 15 | 16 | 17 | def bucketSort(A): 18 | n = len(A) 19 | B = [[] for i in range(n)] 20 | for i in range(0, n): 21 | ind = int(floor(n * A[i])) 22 | B[ind].append(A[i]) 23 | for i in range(0, n): 24 | insert_sort(B[i]) 25 | res = [] 26 | for i in range(0, n): 27 | res.extend(B[i]) 28 | A[:] = res[:] 29 | 30 | 31 | if __name__ == '__main__': 32 | AA = [9, 15, 17, 10, 16, 3, 14, 12, 1, 4] 33 | BB = [i / 20.0 for i in AA] 34 | print(BB) 35 | bucketSort(BB) 36 | print(BB) 37 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/climbing_stairs_dp.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: climbing_stairs_dp.py 3 | Created Time: 2023-06-30 4 | """ 5 | 6 | 7 | def climbing_stairs_dp(n: int) -> int: 8 | """爬楼梯:动态规划""" 9 | if n == 1 or n == 2: 10 | return n 11 | # 初始化 dp 表,用于存储子问题的解 12 | dp = [0] * (n + 1) 13 | # 初始状态:预设最小子问题的解 14 | dp[1], dp[2] = 1, 2 15 | # 状态转移:从较小子问题逐步求解较大子问题 16 | for i in range(3, n + 1): 17 | dp[i] = dp[i - 1] + dp[i - 2] 18 | return dp[n] 19 | 20 | 21 | def climbing_stairs_dp_comp(n: int) -> int: 22 | """爬楼梯:空间优化后的动态规划""" 23 | if n == 1 or n == 2: 24 | return n 25 | a, b = 1, 2 26 | for _ in range(3, n + 1): 27 | a, b = b, a + b 28 | return b 29 | 30 | 31 | """Driver Code""" 32 | if __name__ == "__main__": 33 | n = 9 34 | 35 | res = climbing_stairs_dp(n) 36 | print(f"爬 {n} 阶楼梯共有 {res} 种方案") 37 | 38 | res = climbing_stairs_dp_comp(n) 39 | print(f"爬 {n} 阶楼梯共有 {res} 种方案") 40 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/climbing_stairs_backtrack.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: climbing_stairs_backtrack.py 3 | Created Time: 2023-06-30 4 | """ 5 | 6 | 7 | def backtrack(choices: list[int], state: int, n: int, res: list[int]) -> int: 8 | """回溯""" 9 | # 当爬到第 n 阶时,方案数量加 1 10 | if state == n: 11 | res[0] += 1 12 | # 遍历所有选择 13 | for choice in choices: 14 | # 剪枝:不允许越过第 n 阶 15 | if state + choice > n: 16 | break 17 | # 尝试:做出选择,更新状态 18 | backtrack(choices, state + choice, n, res) 19 | # 回退 20 | 21 | 22 | def climbing_stairs_backtrack(n: int) -> int: 23 | """爬楼梯:回溯""" 24 | choices = [1, 2] # 可选择向上爬 1 或 2 阶 25 | state = 0 # 从第 0 阶开始爬 26 | res = [0] # 使用 res[0] 记录方案数量 27 | backtrack(choices, state, n, res) 28 | return res[0] 29 | 30 | 31 | """Driver Code""" 32 | if __name__ == "__main__": 33 | n = 9 34 | 35 | res = climbing_stairs_backtrack(n) 36 | print(f"爬 {n} 阶楼梯共有 {res} 种方案") 37 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p100/a160_intersection_of_two_linked_lists.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 160. 相交链表 4 | 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 5 | 输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3 6 | 输出:Intersected at '8' 7 | 输入:intersectVal = 2, listA = [1,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1 8 | 输出:Intersected at '2' 9 | 10 | 解题思路: 11 | 我们可以让 p1 遍历完链表 A 之后开始遍历链表 B,让 p2 遍历完链表 B 之后开始遍历链表 A, 12 | 这样相当于「逻辑上」两条链表接在了一起。 13 | 如果这样进行拼接,就可以让 p1 和 p2 同时进入公共部分,也就是同时到达相交节点 c1 14 | """ 15 | from typing import Optional 16 | 17 | from algorithms.c01_data_structure.linkedlist import ListNode 18 | 19 | 20 | class Solution: 21 | def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]: 22 | p1 = headA 23 | p2 = headB 24 | while p1 != p2: 25 | p1 = p1.next if p1 else headB 26 | p2 = p2.next if p2 else headA 27 | return p1 28 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/preorder_traversal_ii_compact.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: preorder_traversal_ii_compact.py 3 | Created Time: 2023-04-15 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import TreeNode, print_tree, list_to_tree 11 | 12 | 13 | def pre_order(root: TreeNode): 14 | """前序遍历:例题二""" 15 | if root is None: 16 | return 17 | # 尝试 18 | path.append(root) 19 | if root.val == 7: 20 | # 记录解 21 | res.append(list(path)) 22 | pre_order(root.left) 23 | pre_order(root.right) 24 | # 回退 25 | path.pop() 26 | 27 | 28 | """Driver Code""" 29 | if __name__ == "__main__": 30 | root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) 31 | print("\n初始化二叉树") 32 | print_tree(root) 33 | 34 | # 前序遍历 35 | path = list[TreeNode]() 36 | res = list[list[TreeNode]]() 37 | pre_order(root) 38 | 39 | print("\n输出所有根节点到节点 7 的路径") 40 | for path in res: 41 | print([node.val for node in path]) 42 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/search_node.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 在二叉树中搜索所有值为的节点,请返回根节点到这些节点的路径。 4 | 使用回溯框架代码实现 5 | """ 6 | 7 | 8 | def backtrack(state: list[int], choices: list[int], res: list[list[int]]): 9 | # 判断是否为解 10 | if is_solution(state): 11 | # 记录解 12 | record_solution(state, res) 13 | # 不再继续搜索 14 | return 15 | # 遍历所有选择 16 | for choice in choices: 17 | # 剪枝 18 | if is_valid(state, choice): 19 | # 尝试:做出选择,更新状态 20 | make_choice(state, choice) 21 | # 使用新的状态继续寻找 22 | backtrack(state, choices, res) 23 | # 回退:撤销选择,恢复到之前的状态 24 | undo_choice(state, choice) 25 | 26 | 27 | def is_solution(state: list[int]) -> bool: 28 | return state[-1] == 7 29 | 30 | 31 | def record_solution(state, res): 32 | pass 33 | 34 | 35 | def is_valid(state, choice) -> bool: 36 | return True 37 | 38 | 39 | def make_choice(state, choice): 40 | pass 41 | 42 | 43 | def undo_choice(state, choice): 44 | pass 45 | -------------------------------------------------------------------------------- /algorithms/c07_divide/binary_search_recur.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: binary_search_recur.py 3 | Created Time: 2023-07-17 4 | Author: krahets (krahets@163.com) 5 | """ 6 | 7 | 8 | def dfs(nums: list[int], target: int, i: int, j: int) -> int: 9 | """二分查找:问题 f(i, j)""" 10 | # 若区间为空,代表无目标元素,则返回 -1 11 | if i > j: 12 | return -1 13 | # 计算中点索引 m 14 | m = (i + j) // 2 15 | if nums[m] < target: 16 | # 递归子问题 f(m+1, j) 17 | return dfs(nums, target, m + 1, j) 18 | elif nums[m] > target: 19 | # 递归子问题 f(i, m-1) 20 | return dfs(nums, target, i, m - 1) 21 | else: 22 | # 找到目标元素,返回其索引 23 | return m 24 | 25 | 26 | def binary_search(nums: list[int], target: int) -> int: 27 | """二分查找""" 28 | n = len(nums) 29 | # 求解问题 f(0, n-1) 30 | return dfs(nums, target, 0, n - 1) 31 | 32 | 33 | """Driver Code""" 34 | if __name__ == "__main__": 35 | target = 6 36 | nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] 37 | 38 | # 二分查找(双闭区间) 39 | index: int = binary_search(nums, target) 40 | print("目标元素 6 的索引 = ", index) 41 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/preorder_traversal_iii_compact.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: preorder_traversal_iii_compact.py 3 | Created Time: 2023-04-15 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import TreeNode, print_tree, list_to_tree 11 | 12 | 13 | def pre_order(root: TreeNode): 14 | """前序遍历:例题三""" 15 | # 剪枝 16 | if root is None or root.val == 3: 17 | return 18 | # 尝试 19 | path.append(root) 20 | if root.val == 7: 21 | # 记录解 22 | res.append(list(path)) 23 | pre_order(root.left) 24 | pre_order(root.right) 25 | # 回退 26 | path.pop() 27 | 28 | 29 | """Driver Code""" 30 | if __name__ == "__main__": 31 | root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) 32 | print("\n初始化二叉树") 33 | print_tree(root) 34 | 35 | # 前序遍历 36 | path = list[TreeNode]() 37 | res = list[list[TreeNode]]() 38 | pre_order(root) 39 | 40 | print("\n输出所有根节点到节点 7 的路径,路径中不包含值为 3 的节点") 41 | for path in res: 42 | print([node.val for node in path]) 43 | -------------------------------------------------------------------------------- /algorithms/test_all.py: -------------------------------------------------------------------------------- 1 | import os 2 | import glob 3 | import subprocess 4 | 5 | env = os.environ.copy() 6 | env["PYTHONIOENCODING"] = "utf-8" 7 | 8 | if __name__ == "__main__": 9 | # find source code files 10 | src_paths = sorted(glob.glob("chapter*/*.py")) 11 | errors = [] 12 | 13 | # run python code 14 | for src_path in src_paths: 15 | process = subprocess.Popen( 16 | ["python", src_path], 17 | stdout=subprocess.PIPE, 18 | stderr=subprocess.PIPE, 19 | text=True, 20 | env=env, 21 | encoding='utf-8' 22 | ) 23 | # Wait for the process to complete, and get the output and error messages 24 | stdout, stderr = process.communicate() 25 | # Check the exit status 26 | exit_status = process.returncode 27 | if exit_status != 0: 28 | errors.append(stderr) 29 | 30 | print(f"Tested {len(src_paths)} files") 31 | print(f"Found exception in {len(errors)} files") 32 | if len(errors) > 0: 33 | raise RuntimeError("\n\n".join(errors)) 34 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p100/a167_two_sum_ii_input_array_is_sorted.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 167. 两数之和 II - 输入有序数组 4 | 给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 , 5 | 请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] , 6 | 则 1 <= index1 < index2 <= numbers.length 。 7 | 以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。 8 | 你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。 9 | 你所设计的解决方案必须只使用常量级的额外空间。 10 | 11 | 示例 1: 12 | 输入:numbers = [2,7,11,15], target = 9 13 | 输出:[1,2] 14 | 解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。 15 | """ 16 | from typing import List, Optional 17 | 18 | 19 | class Solution: 20 | def twoSum(self, numbers: List[int], target: int) -> Optional[List[int]]: 21 | left, right = 0, len(numbers) - 1 22 | while left < right: 23 | sum_ = numbers[left] + numbers[right] 24 | if sum_ == target: 25 | return [left + 1, right + 1] 26 | elif sum_ < target: 27 | left += 1 28 | else: 29 | right -= 1 30 | return [-1, -1] 31 | -------------------------------------------------------------------------------- /algorithms/models/list_node.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: list_node.py 3 | Created Time: 2021-12-11 4 | """ 5 | 6 | 7 | class ListNode: 8 | """Definition for a singly-linked list node""" 9 | 10 | def __init__(self, val: int): 11 | self.val: int = val # 节点值 12 | self.next: ListNode | None = None # 后继节点引用 13 | 14 | 15 | def list_to_linked_list(arr: list[int]) -> ListNode | None: 16 | """Generate a linked list with a list""" 17 | dum = head = ListNode(0) 18 | for a in arr: 19 | node = ListNode(a) 20 | head.next = node 21 | head = head.next 22 | return dum.next 23 | 24 | 25 | def linked_list_to_list(head: ListNode | None) -> list[int]: 26 | """Serialize a linked list into an array""" 27 | arr: list[int] = [] 28 | while head: 29 | arr.append(head.val) 30 | head = head.next 31 | return arr 32 | 33 | 34 | def get_list_node(head: ListNode | None, val: int) -> ListNode | None: 35 | """Get a list node with specific value from a linked list""" 36 | while head and head.val != val: 37 | head = head.next 38 | return head 39 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/queue/deque.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: deque.py 3 | Created Time: 2022-11-29 4 | Author: Peng Chen (pengchzn@gmail.com) 5 | """ 6 | 7 | from collections import deque 8 | 9 | """Driver Code""" 10 | if __name__ == "__main__": 11 | # 初始化双向队列 12 | deq: deque[int] = deque() 13 | 14 | # 元素入队 15 | deq.append(2) # 添加至队尾 16 | deq.append(5) 17 | deq.append(4) 18 | deq.appendleft(3) # 添加至队首 19 | deq.appendleft(1) 20 | print("双向队列 deque =", deq) 21 | 22 | # 访问元素 23 | front: int = deq[0] # 队首元素 24 | print("队首元素 front =", front) 25 | rear: int = deq[-1] # 队尾元素 26 | print("队尾元素 rear =", rear) 27 | 28 | # 元素出队 29 | pop_front: int = deq.popleft() # 队首元素出队 30 | print("队首出队元素 pop_front =", pop_front) 31 | print("队首出队后 deque =", deq) 32 | pop_rear: int = deq.pop() # 队尾元素出队 33 | print("队尾出队元素 pop_rear =", pop_rear) 34 | print("队尾出队后 deque =", deq) 35 | 36 | # 获取双向队列的长度 37 | size: int = len(deq) 38 | print("双向队列长度 size =", size) 39 | 40 | # 判断双向队列是否为空 41 | is_empty: bool = len(deq) == 0 42 | print("双向队列是否为空 =", is_empty) 43 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m09_max_subarr3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 寻找最大子数组(非递归的线性时间算法) 4 | """ 5 | Topic: sample 6 | Desc : 寻找最大子数组(非递归的线性时间算法) 7 | 从数组A的左边界开始,从左至右记录目前已经找到了的最大子数组 8 | 若已知A[0..j]的最大子数组为A[m..n],基于如下性质扩展到A[0..j+1]: 9 | A[0..j+1]的最大子数组要么就是A[0..j]的最大子数组,要么是某个子数组 10 | A[i..j+1](0<=i<=j+1)。这样可以在线性时间内找到这个子数组 11 | """ 12 | __author__ = 'Xiong Neng' 13 | 14 | 15 | def maxSubArr(seq): 16 | maxSubTuple = (0, 0, seq[0]) # 最大子数组先初始化为数组第一个元素 17 | for i in range(1, len(seq)): 18 | tmpMax = float('-Inf') 19 | tmpSum = 0 20 | for j in range(i, maxSubTuple[1], -1): 21 | tmpSum += seq[j] 22 | if tmpSum > tmpMax: 23 | tmpMax = tmpSum 24 | tmpLow = j 25 | r1 = (tmpLow, i, tmpMax) 26 | r2 = (maxSubTuple[0], i, maxSubTuple[2] + tmpSum) 27 | maxSubTuple = max([maxSubTuple, r1, r2], key=lambda k: k[2]) 28 | return maxSubTuple 29 | 30 | 31 | if __name__ == '__main__': 32 | print(maxSubArr([13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7])) 33 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/a05_longest_palindromic_substring.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 5. 最长回文子串 4 | 给你一个字符串 s,找到 s 中最长的回文子串。 5 | 如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。 6 | 7 | 示例 1: 8 | 输入:s = "babad" 9 | 输出:"bab" 10 | 解释:"aba" 同样是符合题意的答案。 11 | """ 12 | 13 | 14 | class Solution: 15 | def longestPalindrome(self, s: str) -> str: 16 | res = '' 17 | for i in range(len(s)): 18 | s1 = self._max_palindrome(s, i, i) 19 | s2 = self._max_palindrome(s, i, i + 1) 20 | res = res if len(res) > len(s1) else s1 21 | res = res if len(res) > len(s2) else s2 22 | return res 23 | 24 | def _max_palindrome(self, s: str, l: int, r: int) -> str: 25 | step = 0 26 | max_index = len(s) - 1 27 | while True: 28 | left = l - step 29 | right = r + step 30 | if left < 0 or right > max_index: 31 | break 32 | if s[left] != s[right]: 33 | break 34 | step += 1 35 | return s[left + 1:right] 36 | 37 | 38 | if __name__ == '__main__': 39 | print(Solution().longestPalindrome("babad")) 40 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/stack/stack_linked_list.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """自己实现的一个简单栈,内部使用链表方式,同时将这个栈设计成一个可迭代对象。 3 | 可迭代的对象一定不能是自身的迭代器。也就是说,可迭代的对象必须实现`__iter__`方法,但不能实现 `__next__` 方法。 4 | 另一方面,迭代器应该一直可以迭代。迭代器的 `__iter__` 方法应该返回自身。 5 | 一般可使用生成器函数实现更符合python风格的可迭代对象。 6 | """ 7 | from algorithms.c01_data_structure import Node 8 | 9 | 10 | class LinkedStack: 11 | def __init__(self): 12 | self.first = None # top of stack 13 | self.n = 0 # size of the stack 14 | 15 | def is_empty(self): 16 | return self.first is None 17 | 18 | def size(self): 19 | return self.n 20 | 21 | def push(self, item): 22 | self.first = Node(item, self.first) 23 | self.n += 1 24 | 25 | def pop(self): 26 | if self.is_empty(): 27 | return None 28 | result = self.first.data 29 | self.first = self.first.next 30 | self.n -= 1 31 | return result 32 | 33 | def peek(self): 34 | if self.is_empty(): 35 | return None 36 | return self.first.data 37 | 38 | def __iter__(self): 39 | while self.n > 0: 40 | yield self.pop() 41 | -------------------------------------------------------------------------------- /algorithms/c05_search/two_sum.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: two_sum.py 3 | Created Time: 2022-11-25 4 | """ 5 | 6 | 7 | def two_sum_brute_force(nums: list[int], target: int) -> list[int]: 8 | """方法一:暴力枚举""" 9 | # 两层循环,时间复杂度 O(n^2) 10 | for i in range(len(nums) - 1): 11 | for j in range(i + 1, len(nums)): 12 | if nums[i] + nums[j] == target: 13 | return [i, j] 14 | return [] 15 | 16 | 17 | def two_sum_hash_table(nums: list[int], target: int) -> list[int]: 18 | """方法二:辅助哈希表""" 19 | # 辅助哈希表,空间复杂度 O(n) 20 | dic = {} 21 | # 单层循环,时间复杂度 O(n) 22 | for i in range(len(nums)): 23 | if target - nums[i] in dic: 24 | return [dic[target - nums[i]], i] 25 | dic[nums[i]] = i 26 | return [] 27 | 28 | 29 | """Driver Code""" 30 | if __name__ == "__main__": 31 | # ======= Test Case ======= 32 | nums = [2, 7, 11, 15] 33 | target = 13 34 | 35 | # ====== Driver Code ====== 36 | # 方法一 37 | res: list[int] = two_sum_brute_force(nums, target) 38 | print("方法一 res =", res) 39 | # 方法二 40 | res: list[int] = two_sum_hash_table(nums, target) 41 | print("方法二 res =", res) 42 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m13_imin_list.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 前i个最小数 4 | """ 5 | Topic: sample 6 | Desc : 前i个最小数 7 | 先通过找到第i小的数,然后将这个数作为pivot去划分这个数组, 8 | 左边 + 这个pivot即是解 9 | """ 10 | from algorithms.c04_sort.m12_imin_select2 import iminSelect2 11 | 12 | 13 | def iminList(A, i): 14 | seq = A[:] # 不改变A 15 | imin = iminSelect2(seq, i) 16 | print('imin=%d' % imin) 17 | pivotIndex = __midPartition(seq, 0, len(seq) - 1, imin) 18 | return seq[0: pivotIndex + 1] 19 | 20 | 21 | def __midPartition(A, p, r, midNum): 22 | """分解子数组: 指定pivot的版本""" 23 | midIndex = p 24 | for ii in range(p, r + 1): 25 | if A[ii] == midNum: 26 | midIndex = ii 27 | break 28 | A[midIndex], A[r] = A[r], A[midIndex] # 还是将这个pivot放到最后 29 | x = A[r] 30 | i = p - 1 31 | for j in range(p, r): 32 | if A[j] <= x: 33 | i += 1 34 | A[i], A[j] = A[j], A[i] 35 | A[i + 1], A[r] = A[r], A[i + 1] 36 | return i + 1 37 | 38 | 39 | if __name__ == '__main__': 40 | print(iminList([4, 23, 65, 3, 22, 3, 34, 3, 67, 3, 12, 3, 7, 1, 1, 256, 3, 34, 27], 10)) 41 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p100/a142_linked_list_cycle_ii.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 142. 环形链表 II 4 | 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 5 | 6 | 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环, 7 | 评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。 8 | 注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 9 | 10 | 解题思路: 11 | 判断链表是否包含环属于经典问题了,解决方案也是用快慢指针: 12 | 每当慢指针 slow 前进一步,快指针 fast 就前进两步。 13 | 如果 fast 最终遇到空指针,说明链表中没有环;如果 fast 最终和 slow 相遇,那肯定是 fast 超过了 slow 一圈, 14 | 说明链表中含有环。 15 | 当快慢指针相遇时,让其中任一个指针指向头节点,然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置。 16 | """ 17 | from typing import Optional 18 | 19 | from algorithms.c01_data_structure.linkedlist import ListNode 20 | 21 | 22 | class Solution: 23 | def detectCycle(self, head: Optional[ListNode]) -> Optional[ListNode]: 24 | slow, fast = head, head 25 | while fast and fast.next: 26 | slow = slow.next 27 | fast = fast.next.next 28 | if fast == slow: 29 | break 30 | if not fast or not fast.next: 31 | return None 32 | slow = head 33 | while slow != fast: 34 | fast = fast.next 35 | slow = slow.next 36 | return slow 37 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/permutations_i.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: permutations_i.py 3 | Created Time: 2023-04-15 4 | """ 5 | 6 | 7 | def backtrack( 8 | state: list[int], choices: list[int], selected: list[bool], res: list[list[int]] 9 | ): 10 | """回溯算法:全排列 I""" 11 | # 当状态长度等于元素数量时,记录解 12 | if len(state) == len(choices): 13 | res.append(list(state)) 14 | return 15 | # 遍历所有选择 16 | for i, choice in enumerate(choices): 17 | # 剪枝:不允许重复选择元素 18 | if not selected[i]: 19 | # 尝试:做出选择,更新状态 20 | selected[i] = True 21 | state.append(choice) 22 | # 进行下一轮选择 23 | backtrack(state, choices, selected, res) 24 | # 回退:撤销选择,恢复到之前的状态 25 | selected[i] = False 26 | state.pop() 27 | 28 | 29 | def permutations_i(nums: list[int]) -> list[list[int]]: 30 | """全排列 I""" 31 | res = [] 32 | backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) 33 | return res 34 | 35 | 36 | """Driver Code""" 37 | if __name__ == "__main__": 38 | nums = [1, 2, 3] 39 | 40 | res = permutations_i(nums) 41 | 42 | print(f"输入数组 nums = {nums}") 43 | print(f"所有排列 res = {res}") 44 | -------------------------------------------------------------------------------- /algorithms/c02_tree/binary_tree_bfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: binary_tree_bfs.py 3 | Created Time: 2022-12-20 4 | Author: a16su (lpluls001@gmail.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import TreeNode, list_to_tree, print_tree 12 | from collections import deque 13 | 14 | 15 | def level_order(root: TreeNode | None) -> list[int]: 16 | """层序遍历""" 17 | # 初始化队列,加入根节点 18 | queue: deque[TreeNode] = deque() 19 | queue.append(root) 20 | # 初始化一个列表,用于保存遍历序列 21 | res = [] 22 | while queue: 23 | node: TreeNode = queue.popleft() # 队列出队 24 | res.append(node.val) # 保存节点值 25 | if node.left is not None: 26 | queue.append(node.left) # 左子节点入队 27 | if node.right is not None: 28 | queue.append(node.right) # 右子节点入队 29 | return res 30 | 31 | 32 | """Driver Code""" 33 | if __name__ == "__main__": 34 | # 初始化二叉树 35 | # 这里借助了一个从数组直接生成二叉树的函数 36 | root: TreeNode = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) 37 | print("\n初始化二叉树\n") 38 | print_tree(root) 39 | 40 | # 层序遍历 41 | res: list[int] = level_order(root) 42 | print("\n层序遍历的节点打印序列 = ", res) 43 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/min_cost_climbing_stairs_dp.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: min_cost_climbing_stairs_dp.py 3 | Created Time: 2023-06-30 4 | """ 5 | 6 | 7 | def min_cost_climbing_stairs_dp(cost: list[int]) -> int: 8 | """爬楼梯最小代价:动态规划""" 9 | n = len(cost) - 1 10 | if n == 1 or n == 2: 11 | return cost[n] 12 | # 初始化 dp 表,用于存储子问题的解 13 | dp = [0] * (n + 1) 14 | # 初始状态:预设最小子问题的解 15 | dp[1], dp[2] = cost[1], cost[2] 16 | # 状态转移:从较小子问题逐步求解较大子问题 17 | for i in range(3, n + 1): 18 | dp[i] = min(dp[i - 1], dp[i - 2]) + cost[i] 19 | return dp[n] 20 | 21 | 22 | def min_cost_climbing_stairs_dp_comp(cost: list[int]) -> int: 23 | """爬楼梯最小代价:空间优化后的动态规划""" 24 | n = len(cost) - 1 25 | if n == 1 or n == 2: 26 | return cost[n] 27 | a, b = cost[1], cost[2] 28 | for i in range(3, n + 1): 29 | a, b = b, min(a, b) + cost[i] 30 | return b 31 | 32 | 33 | """Driver Code""" 34 | if __name__ == "__main__": 35 | cost = [0, 1, 10, 1, 1, 1, 10, 1, 1, 10, 1] 36 | print(f"输入楼梯的代价列表为 {cost}") 37 | 38 | res = min_cost_climbing_stairs_dp(cost) 39 | print(f"爬完楼梯的最低代价为 {res}") 40 | 41 | res = min_cost_climbing_stairs_dp_comp(cost) 42 | print(f"爬完楼梯的最低代价为 {res}") 43 | -------------------------------------------------------------------------------- /algorithms/c10_greedy/fractional_knapsack.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: fractional_knapsack.py 3 | Created Time: 2023-07-19 4 | """ 5 | 6 | 7 | class Item: 8 | """物品""" 9 | 10 | def __init__(self, w: int, v: int): 11 | self.w = w # 物品重量 12 | self.v = v # 物品价值 13 | 14 | 15 | def fractional_knapsack(wgt: list[int], val: list[int], cap: int) -> int: 16 | """分数背包:贪心""" 17 | # 创建物品列表,包含两个属性:重量、价值 18 | items = [Item(w, v) for w, v in zip(wgt, val)] 19 | # 按照单位价值 item.v / item.w 从高到低进行排序 20 | items.sort(key=lambda item: item.v / item.w, reverse=True) 21 | # 循环贪心选择 22 | res = 0 23 | for item in items: 24 | if item.w <= cap: 25 | # 若剩余容量充足,则将当前物品整个装进背包 26 | res += item.v 27 | cap -= item.w 28 | else: 29 | # 若剩余容量不足,则将当前物品的一部分装进背包 30 | res += (item.v / item.w) * cap 31 | # 已无剩余容量,因此跳出循环 32 | break 33 | return res 34 | 35 | 36 | """Driver Code""" 37 | if __name__ == "__main__": 38 | wgt = [10, 20, 30, 40, 50] 39 | val = [50, 120, 150, 210, 240] 40 | cap = 50 41 | n = len(wgt) 42 | 43 | # 贪心算法 44 | res = fractional_knapsack(wgt, val, cap) 45 | print(f"不超过背包容量的最大物品价值为 {res}") 46 | -------------------------------------------------------------------------------- /algorithms/c04_sort/heap_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: heap_sort.py 3 | Created Time: 2023-05-24 4 | """ 5 | 6 | 7 | def sift_down(nums: list[int], n: int, i: int): 8 | """堆的长度为 n ,从节点 i 开始,从顶至底堆化""" 9 | while True: 10 | # 判断节点 i, l, r 中值最大的节点,记为 ma 11 | l = 2 * i + 1 12 | r = 2 * i + 2 13 | ma = i 14 | if l < n and nums[l] > nums[ma]: 15 | ma = l 16 | if r < n and nums[r] > nums[ma]: 17 | ma = r 18 | # 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出 19 | if ma == i: 20 | break 21 | # 交换两节点 22 | nums[i], nums[ma] = nums[ma], nums[i] 23 | # 循环向下堆化 24 | i = ma 25 | 26 | 27 | def heap_sort(nums: list[int]): 28 | """堆排序""" 29 | # 建堆操作:堆化除叶节点以外的其他所有节点 30 | for i in range(len(nums) // 2 - 1, -1, -1): 31 | sift_down(nums, len(nums), i) 32 | # 从堆中提取最大元素,循环 n-1 轮 33 | for i in range(len(nums) - 1, 0, -1): 34 | # 交换根节点与最右叶节点(即交换首元素与尾元素) 35 | nums[0], nums[i] = nums[i], nums[0] 36 | # 以根节点为起点,从顶至底进行堆化 37 | sift_down(nums, i, 0) 38 | 39 | 40 | """Driver Code""" 41 | if __name__ == "__main__": 42 | nums = [4, 1, 3, 1, 5, 2] 43 | heap_sort(nums) 44 | print("堆排序完成后 nums =", nums) 45 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m08_radix_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 基数排序 4 | """ 5 | Topic: sample 6 | Desc : 基数排序 7 | 有时需要对记录的几个关键字分别排序,比如用三个关键字年、月、日对日期排序。 8 | 可以用基数排序,用一种稳定排序算法(比如计数排序)对这些信息进行三次排序: 9 | 优先级从低到高,权重从低到高。 10 | """ 11 | 12 | 13 | def radixSort(A, digit, base): 14 | """ 15 | digit: 代表排序数组的最大位数 16 | base: 代表进制 17 | """ 18 | for di in range(1, digit + 1): 19 | B = [0] * len(A) # 最终输出的排序数组 20 | C = [0] * base # 临时存储数组 21 | for i in range(0, len(A)): 22 | # split the specified digit from the element 23 | tmpSplitDigit = A[i] // pow(10, di - 1) - (A[i] // pow(10, di)) * 10 24 | C[tmpSplitDigit] += 1 # C[i]现在代表数组A中元素等于i的个数 25 | for i in range(1, base): 26 | C[i] += C[i - 1] # C[i]现在代表数组A中元素小于等于i的个数 27 | for j in range(len(A) - 1, -1, -1): 28 | tmpSplitDigit = A[j] // pow(10, di - 1) - (A[j] // pow(10, di)) * 10 29 | B[C[tmpSplitDigit] - 1] = A[j] 30 | C[tmpSplitDigit] -= 1 # 防止数组A有重复的数,占据了相同的位置 31 | A[:] = B[:] 32 | 33 | 34 | if __name__ == '__main__': 35 | A = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] 36 | radixSort(A, 2, 10) 37 | print(A) 38 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m07_count_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 计数排序 4 | """ 5 | Topic: sample 6 | Desc : 计数排序 7 | 实际工作中,如果k=O(n),那么选择计数排序,线性时间。 8 | 比如排序数组[1, 10*2, 10*3, .... 10*100],n = 100, k=10n=O(n), 9 | 那么可以用这个计数排序 10 | 计数排序是稳定的:原数组中相同元素在输出数组中的次序是一样的 11 | """ 12 | 13 | 14 | def countSort(A, k, offset=0): 15 | """ 16 | A: 待排序数组 17 | k: 数组A区间-offset后的最大值 18 | offset: 有时候A在一个区间内[a,b],这时候,可以设置offset为a 19 | """ 20 | if offset > 0: 21 | A[:] = [p - offset for p in A] 22 | B = [0] * len(A) # 最终输出的排序数组 23 | C = [0] * k # 临时存储数组 24 | for i in range(0, len(A)): 25 | C[A[i]] += 1 # C[i]现在代表数组A中元素等于i的个数 26 | for i in range(1, k): 27 | C[i] += C[i - 1] # C[i]现在代表数组A中元素小于等于i的个数 28 | for j in range(len(A) - 1, -1, -1): 29 | B[C[A[j]] - 1] = A[j] 30 | C[A[j]] -= 1 # 防止数组A有重复的数,占据了相同的位置 31 | A[:] = B[:] 32 | if offset > 0: 33 | A[:] = [p + offset for p in A] 34 | 35 | 36 | if __name__ == '__main__': 37 | A = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] 38 | countSort(A, 20) 39 | print(A) 40 | A = [9, 7, 8, 10, 16, 3, 14, 2, 1, 4] 41 | B = [100 + p for p in A] 42 | countSort(B, 30, 96) 43 | print(B) 44 | -------------------------------------------------------------------------------- /algorithms/c07_divide/hanota.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: hanota.py 3 | Created Time: 2023-07-16 4 | """ 5 | 6 | 7 | def move(src: list[int], tar: list[int]): 8 | """移动一个圆盘""" 9 | # 从 src 顶部拿出一个圆盘 10 | pan = src.pop() 11 | # 将圆盘放入 tar 顶部 12 | tar.append(pan) 13 | 14 | 15 | def dfs(i: int, src: list[int], buf: list[int], tar: list[int]): 16 | """求解汉诺塔:问题 f(i)""" 17 | # 若 src 只剩下一个圆盘,则直接将其移到 tar 18 | if i == 1: 19 | move(src, tar) 20 | return 21 | # 子问题 f(i-1) :将 src 顶部 i-1 个圆盘借助 tar 移到 buf 22 | dfs(i - 1, src, tar, buf) 23 | # 子问题 f(1) :将 src 剩余一个圆盘移到 tar 24 | move(src, tar) 25 | # 子问题 f(i-1) :将 buf 顶部 i-1 个圆盘借助 src 移到 tar 26 | dfs(i - 1, buf, src, tar) 27 | 28 | 29 | def solve_hanota(A: list[int], B: list[int], C: list[int]): 30 | """求解汉诺塔""" 31 | n = len(A) 32 | # 将 A 顶部 n 个圆盘借助 B 移到 C 33 | dfs(n, A, B, C) 34 | 35 | 36 | """Driver Code""" 37 | if __name__ == "__main__": 38 | # 列表尾部是柱子顶部 39 | A = [5, 4, 3, 2, 1] 40 | B = [] 41 | C = [] 42 | print("初始状态下:") 43 | print(f"A = {A}") 44 | print(f"B = {B}") 45 | print(f"C = {C}") 46 | 47 | solve_hanota(A, B, C) 48 | 49 | print("圆盘移动完成后:") 50 | print(f"A = {A}") 51 | print(f"B = {B}") 52 | print(f"C = {C}") 53 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/hashing/hash_map.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: hash_map.py 3 | Created Time: 2022-12-14 4 | Author: msk397 (machangxinq@gmail.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import print_dict 12 | 13 | """Driver Code""" 14 | if __name__ == "__main__": 15 | # 初始化哈希表 16 | hmap = dict[int, str]() 17 | 18 | # 添加操作 19 | # 在哈希表中添加键值对 (key, value) 20 | hmap[12836] = "小哈" 21 | hmap[15937] = "小啰" 22 | hmap[16750] = "小算" 23 | hmap[13276] = "小法" 24 | hmap[10583] = "小鸭" 25 | print("\n添加完成后,哈希表为\nKey -> Value") 26 | print_dict(hmap) 27 | 28 | # 查询操作 29 | # 向哈希表输入键 key ,得到值 value 30 | name: str = hmap[15937] 31 | print("\n输入学号 15937 ,查询到姓名 " + name) 32 | 33 | # 删除操作 34 | # 在哈希表中删除键值对 (key, value) 35 | hmap.pop(10583) 36 | print("\n删除 10583 后,哈希表为\nKey -> Value") 37 | print_dict(hmap) 38 | 39 | # 遍历哈希表 40 | print("\n遍历键值对 Key->Value") 41 | for key, value in hmap.items(): 42 | print(key, "->", value) 43 | 44 | print("\n单独遍历键 Key") 45 | for key in hmap.keys(): 46 | print(key) 47 | 48 | print("\n单独遍历值 Value") 49 | for val in hmap.values(): 50 | print(val) 51 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/hashing/simple_hash.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: simple_hash.py 3 | Created Time: 2023-06-15 4 | """ 5 | 6 | 7 | def add_hash(key: str) -> int: 8 | """加法哈希""" 9 | hash = 0 10 | modulus = 1000000007 11 | for c in key: 12 | hash += ord(c) 13 | return hash % modulus 14 | 15 | 16 | def mul_hash(key: str) -> int: 17 | """乘法哈希""" 18 | hash = 0 19 | modulus = 1000000007 20 | for c in key: 21 | hash = 31 * hash + ord(c) 22 | return hash % modulus 23 | 24 | 25 | def xor_hash(key: str) -> int: 26 | """异或哈希""" 27 | hash = 0 28 | modulus = 1000000007 29 | for c in key: 30 | hash ^= ord(c) 31 | return hash % modulus 32 | 33 | 34 | def rot_hash(key: str) -> int: 35 | """旋转哈希""" 36 | hash = 0 37 | modulus = 1000000007 38 | for c in key: 39 | hash = (hash << 4) ^ (hash >> 28) ^ ord(c) 40 | return hash % modulus 41 | 42 | 43 | """Driver Code""" 44 | if __name__ == "__main__": 45 | key = "Hello 算法" 46 | 47 | hash = add_hash(key) 48 | print(f"加法哈希值为 {hash}") 49 | 50 | hash = mul_hash(key) 51 | print(f"乘法哈希值为 {hash}") 52 | 53 | hash = xor_hash(key) 54 | print(f"异或哈希值为 {hash}") 55 | 56 | hash = rot_hash(key) 57 | print(f"旋转哈希值为 {hash}") 58 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m11_imin_select.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 顺序统计量的选择算法 4 | """ 5 | Topic: sample 6 | Desc : 顺序统计量的选择算法 7 | pivot加入了随机特性,算法的期望运行时间是O(n) 8 | """ 9 | from random import randint 10 | 11 | __author__ = 'Xiong Neng' 12 | 13 | 14 | def iminSelect(A, i): 15 | """ 16 | 返回数组A中第i小的元素 17 | """ 18 | return __iminSelect(A, 0, len(A) - 1, i) 19 | 20 | 21 | def __iminSelect(A, p, r, i): 22 | """ 23 | 返回数组A[p..r]中第i小的元素 24 | """ 25 | if p == r: 26 | return A[p] 27 | q = __randPartition(A, p, r) 28 | k = q - p + 1 29 | if i == k: 30 | return A[q] 31 | elif i < k: 32 | return __iminSelect(A, p, q - 1, i) 33 | else: 34 | return __iminSelect(A, q + 1, r, i - k) 35 | 36 | 37 | def __randPartition(A, p, r): 38 | """分解子数组: 随机化版本""" 39 | rinx = randint(p, r) # 随机的pivot 40 | A[rinx], A[r] = A[r], A[rinx] # 还是将这个pivot放到最后 41 | x = A[r] 42 | i = p - 1 43 | for j in range(p, r): 44 | if A[j] <= x: 45 | i += 1 46 | A[i], A[j] = A[j], A[i] 47 | A[i + 1], A[r] = A[r], A[i + 1] 48 | return i + 1 49 | 50 | 51 | if __name__ == '__main__': 52 | print(iminSelect([4, 23, 65, 22, 12, 3, 7, 1, 256, 34, 27], 3)) 53 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p100/a141_linked_list_cycle.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 141. 环形链表 4 | 给你一个链表的头节点 head ,判断链表中是否有环。 5 | 6 | 如果链表中有某个节点,可以通过连续跟踪 next指针再次到达,则链表中存在环。 7 | 不能直接通过判定next是否为null来决定是否有环,因为如有环则是一个无限循环。 8 | 9 | 通过快慢指针法,慢指针走一步,快指针走两步。 10 | 如果快指针率先走到尽头结束,无环。 11 | 否则快指针率先进环,慢指针则在后面进环。快指针一定会追上满指针,一旦追上了,循环结束。 12 | """ 13 | # Definition for singly-linked list. 14 | from typing import Optional 15 | 16 | 17 | class ListNode: 18 | def __init__(self, x): 19 | self.val = x 20 | self.next = None 21 | 22 | 23 | class Solution: 24 | def hasCycle(self, head: Optional[ListNode]) -> bool: 25 | if not head or not head.next or not head.next.next: 26 | return False 27 | slow = head.next 28 | fast = head.next.next 29 | while slow != fast: 30 | if not fast.next or not fast.next.next: 31 | return False 32 | slow = slow.next 33 | fast = fast.next.next 34 | return True 35 | 36 | 37 | if __name__ == '__main__': 38 | a = ListNode(1) 39 | b = ListNode(2) 40 | c = ListNode(3) 41 | d = ListNode(4) 42 | f = ListNode(5) 43 | a.next = b 44 | b.next = c 45 | c.next = d 46 | d.next = f 47 | f.next = b 48 | print(Solution().hasCycle(a)) 49 | -------------------------------------------------------------------------------- /algorithms/c05_search/linear_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: linear_search.py 3 | Created Time: 2022-11-26 4 | Author: timi (xisunyy@163.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import ListNode, list_to_linked_list 12 | 13 | 14 | def linear_search_array(nums: list[int], target: int) -> int: 15 | """线性查找(数组)""" 16 | # 遍历数组 17 | for i in range(len(nums)): 18 | if nums[i] == target: # 找到目标元素,返回其索引 19 | return i 20 | return -1 # 未找到目标元素,返回 -1 21 | 22 | 23 | def linear_search_linkedlist(head: ListNode, target: int) -> ListNode | None: 24 | """线性查找(链表)""" 25 | # 遍历链表 26 | while head: 27 | if head.val == target: # 找到目标节点,返回之 28 | return head 29 | head = head.next 30 | return None # 未找到目标节点,返回 None 31 | 32 | 33 | """Driver Code""" 34 | if __name__ == "__main__": 35 | target = 3 36 | 37 | # 在数组中执行线性查找 38 | nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] 39 | index: int = linear_search_array(nums, target) 40 | print("目标元素 3 的索引 =", index) 41 | 42 | # 在链表中执行线性查找 43 | head: ListNode = list_to_linked_list(nums) 44 | node: ListNode | None = linear_search_linkedlist(head, target) 45 | print("目标节点值 3 的对应节点对象为", node) 46 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m05_nine_number.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: 从1到9,组成3个三位数,要求每个数字只用一次, 5 | 要求结果第二个数是第一个数的两倍,第三个数是第一个数的三倍。求所有的组合 6 | 7 | 算法思想: 8 | 使用三层循环先计算第一个数each1,要求三个位不一样 9 | 然后先计算符合倍数关系的第二个数each2=2*each1,第三个数each3=3*each1, 10 | 然后判断each1、each2和each3这三个数是否每个位都各不相同, 11 | 这个将它们拆成单个字符然后放入集合中,如果集合个数=9就符合条件 12 | """ 13 | 14 | 15 | def nine_number(): 16 | nine = [1, 2, 3, 4, 5, 6, 7, 8, 9] 17 | result = [] 18 | for i in nine: 19 | each1 = 0 20 | each1 += i * 100 21 | for j in nine: 22 | if j == i: continue 23 | each1 += j * 10 24 | for k in nine: 25 | if k == j or k == i: continue 26 | each1 += k 27 | each2 = 2 * each1 28 | each3 = 3 * each1 29 | if each2 > 999 or each3 > 999: continue 30 | all_num = [] 31 | all_num.extend(list(str(each1))) 32 | all_num.extend(list(str(each2))) 33 | all_num.extend(list(str(each3))) 34 | num_set = set(all_num) 35 | if len(num_set) == 9: 36 | result.append((each1, each2, each3)) 37 | return result 38 | 39 | 40 | if __name__ == '__main__': 41 | print(nine_number()) 42 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/queue/linked_queue.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """基于链表实现的一个简单队列 3 | 队尾插入元素,队头取元素。就跟在菜市场排队买菜原理是一样的 4 | """ 5 | from algorithms.c01_data_structure import Node 6 | 7 | 8 | class LinkedQueue: 9 | def __init__(self): 10 | self.first = None # beginning of queue 11 | self.last = None # end of queue 12 | self.n = 0 # number of elements on queue 13 | 14 | def is_empty(self): 15 | return self.first is None 16 | 17 | def size(self): 18 | return self.n 19 | 20 | def enqueue(self, item): 21 | old_last = self.last 22 | self.last = Node(item) # 将新插入的节点变成队尾元素 23 | if self.is_empty(): 24 | self.first = self.last 25 | else: 26 | old_last.next = self.last # 同时将原来的队尾元素的next指向新的队尾元素 27 | 28 | def dequeue(self): 29 | if self.is_empty(): 30 | raise LookupError('Queue underflow') 31 | item = self.first.data 32 | self.first = self.first.next 33 | self.n -= 1 34 | if self.is_empty(): 35 | # 如果队列现在为空了,说明之前队列中只有一个元素了。 36 | # 这时候需要把last赋空,防止一直引用着对象,导致无法内存回收 37 | self.last = None 38 | return item 39 | 40 | def __iter__(self): 41 | while self.n > 0: 42 | yield self.dequeue() 43 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/a20_valid_parentheses.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 20. 有效的括号 4 | 5 | 给定一个只包括 '(',')','{','}','[',']' 的字符串s ,判断字符串是否有效。 6 | 7 | 有效字符串需满足: 8 | 9 | 左括号必须用相同类型的右括号闭合。 10 | 左括号必须以正确的顺序闭合。 11 | 12 | 示例 2: 13 | 输入:s = "()[]{}" 14 | 输出:true 15 | 16 | 示例 4: 17 | 输入:s = "([)]" 18 | 输出:false 19 | 20 | 例 5: 21 | 输入:s = "{[]}" 22 | 输出:true 23 | 24 | 算法思路:栈的最简单的应用。左括号入栈,右括号出栈+对比匹配。不匹配则False,最后栈空则True 25 | """ 26 | from algorithms.c01_data_structure.stack.stack_linked_list import LinkedStack 27 | 28 | 29 | class Solution: 30 | def isValid(self, s: str) -> bool: 31 | p_map = {'{': '}', '[': ']', '(': ')'} 32 | stack = LinkedStack() 33 | for ch in s: 34 | if ch in p_map: 35 | stack.push(ch) 36 | else: 37 | stack_item = stack.pop() 38 | if stack_item not in p_map or ch != p_map[stack_item]: 39 | return False 40 | return stack.is_empty() 41 | 42 | 43 | if __name__ == '__main__': 44 | # print(Solution().isValid(']]]')) 45 | print(Solution().isValid(']')) 46 | # s = input('input something: ') 47 | # print(s) 48 | # while True: 49 | # s = input() 50 | # if not s: 51 | # break 52 | # print(Solution().isValid(s)) 53 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/permutations_ii.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: permutations_ii.py 3 | Created Time: 2023-04-15 4 | """ 5 | 6 | 7 | def backtrack(state: list[int], choices: list[int], selected: list[bool], res: list[list[int]]): 8 | """回溯算法:全排列 II""" 9 | # 当状态长度等于元素数量时,记录解 10 | if len(state) == len(choices): 11 | res.append(list(state)) 12 | return 13 | # 遍历所有选择 14 | duplicated = set[int]() 15 | for i, choice in enumerate(choices): 16 | # 剪枝:不允许重复选择元素 且 不允许重复选择相等元素 17 | if not selected[i] and choice not in duplicated: 18 | # 尝试:做出选择,更新状态 19 | duplicated.add(choice) # 记录选择过的元素值 20 | selected[i] = True 21 | state.append(choice) 22 | # 进行下一轮选择 23 | backtrack(state, choices, selected, res) 24 | # 回退:撤销选择,恢复到之前的状态 25 | selected[i] = False 26 | state.pop() 27 | 28 | 29 | def permutations_ii(nums: list[int]) -> list[list[int]]: 30 | """全排列 II""" 31 | res = [] 32 | backtrack(state=[], choices=nums, selected=[False] * len(nums), res=res) 33 | return res 34 | 35 | 36 | """Driver Code""" 37 | if __name__ == "__main__": 38 | nums = [1, 2, 2] 39 | 40 | res = permutations_ii(nums) 41 | 42 | print(f"输入数组 nums = {nums}") 43 | print(f"所有排列 res = {res}") 44 | -------------------------------------------------------------------------------- /algorithms/c04_sort/bubble_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: bubble_sort.py 3 | Created Time: 2022-11-25 4 | Author: timi (xisunyy@163.com) 5 | """ 6 | 7 | 8 | def bubble_sort(nums: list[int]): 9 | """冒泡排序""" 10 | n = len(nums) 11 | # 外循环:未排序区间为 [0, i] 12 | for i in range(n - 1, 0, -1): 13 | # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 14 | for j in range(i): 15 | if nums[j] > nums[j + 1]: 16 | # 交换 nums[j] 与 nums[j + 1] 17 | nums[j], nums[j + 1] = nums[j + 1], nums[j] 18 | 19 | 20 | def bubble_sort_with_flag(nums: list[int]): 21 | """冒泡排序(标志优化)""" 22 | n = len(nums) 23 | # 外循环:未排序区间为 [0, i] 24 | for i in range(n - 1, 0, -1): 25 | flag = False # 初始化标志位 26 | # 内循环:将未排序区间 [0, i] 中的最大元素交换至该区间的最右端 27 | for j in range(i): 28 | if nums[j] > nums[j + 1]: 29 | # 交换 nums[j] 与 nums[j + 1] 30 | nums[j], nums[j + 1] = nums[j + 1], nums[j] 31 | flag = True # 记录交换元素 32 | if not flag: 33 | break # 此轮冒泡未交换任何元素,直接跳出 34 | 35 | 36 | """Driver Code""" 37 | if __name__ == "__main__": 38 | nums = [4, 1, 3, 1, 5, 2] 39 | bubble_sort(nums) 40 | print("冒泡排序完成后 nums =", nums) 41 | 42 | nums1 = [4, 1, 3, 1, 5, 2] 43 | bubble_sort_with_flag(nums1) 44 | print("冒泡排序完成后 nums =", nums1) 45 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/m04_elevator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 动态规划:电梯调度算法 4 | """ 5 | Topic: sample 6 | Desc : 电梯调度算法 7 | 电梯停在哪一层楼,能够保证这次乘坐电梯的所有乘客爬楼梯的层数之和最少 8 | 这个属于动态规划问题 9 | 动态规划。假设电梯停在第x层,已知目的楼层 10 | 在x层以下的有N1人, 11 | 在x层的有N2人, 12 | 在x层以上的有N3人。 13 | 此时总花费为sum。 14 | 则往上走一层的话,总花费变为sum + N2 + N1 - N3。 15 | 那么初始状态电梯停在第一层,向上进行状态的变迁,开始时N2 + N1 - N3 < 0。 16 | sum越来越小,直到某一层N2 + N1 >= N3,就没有必要在往上走了。 17 | 这时已求出最合适的楼层了 18 | """ 19 | __author__ = 'Xiong Neng' 20 | 21 | 22 | def elevatorSchedule(seq): 23 | """ 24 | seq: 去往每层的人数, 下标代表楼层号, 很明显0和1层都是0 25 | """ 26 | N1 = N2 = 0 # 到当前层以下的有N1人, 到当前层的有N1人 27 | N3 = 0 # 到当前层以上的有N3人 28 | nMinFloors = 0 # 所有乘客要爬的楼层最小总和 29 | nTargetFloor = 1 # 达到最小值时候的楼层 30 | for i in range(2, len(seq)): 31 | N3 += seq[i] 32 | nMinFloors += seq[i] * (i - 1) 33 | for i in range(2, len(seq)): 34 | if N1 + N2 < N3: 35 | nTargetFloor = i 36 | nMinFloors += (N1 + N2 - N3) 37 | N1 += N2 38 | N2 = seq[i] 39 | N3 -= seq[i] 40 | else: 41 | break 42 | return nTargetFloor, nMinFloors 43 | 44 | 45 | if __name__ == '__main__': 46 | s = [0, 0, 2, 4, 5, 7, 2, 1] 47 | print(elevatorSchedule(s)) 48 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/linkedlist/lru_cache.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Question:怎样用单链表实现LRU缓存淘汰算法? 4 | 5 | 我们维护一个单链表,越靠近链表尾部的结点是越早之前访问的。当有一个新的数据被访问时, 6 | 我们从链表头开始顺序遍历链表。 7 | 8 | 1. 如果此数据之前已经被缓存在链表中了,我们遍历得到这个数据对应的结点,并将其从原来的位置删除, 9 | 然后再插入到链表的头部。 10 | 2. 如果此数据没有在缓存链表中,又可以分为两种情况: 11 | 2.1 如果此时缓存未满,则将此结点直接插入到链表的头部; 12 | 2.2 如果此时缓存已满,则链表尾结点删除,将新的数据结点插入链表的头部。 13 | """ 14 | from algorithms.c01_data_structure import Node 15 | from algorithms.c01_data_structure.linkedlist.linked_list_single import LinkedListSingle 16 | 17 | 18 | def lru(list_single_, data): 19 | find_node = list_single_.search(data) 20 | if find_node: 21 | list_single_.remove(find_node) 22 | list_single_.insert(list_single_.head, find_node) 23 | else: 24 | if not list_single_.is_full(): 25 | list_single_.insert(list_single_.head, new_node=Node(data)) 26 | else: 27 | tail = list_single_.get_tail() 28 | new_node = Node(data) 29 | list_single_.remove(tail) 30 | list_single_.insert(list_single_.head, new_node) 31 | list_single_.print() 32 | 33 | 34 | if __name__ == '__main__': 35 | list_single = LinkedListSingle(3) 36 | lru(list_single, 2) 37 | lru(list_single, 3) 38 | lru(list_single, 2) 39 | lru(list_single, 1) 40 | lru(list_single, 5) 41 | lru(list_single, 6) 42 | -------------------------------------------------------------------------------- /algorithms/c10_greedy/coin_change_greedy.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: coin_change_greedy.py 3 | Created Time: 2023-07-18 4 | """ 5 | 6 | 7 | def coin_change_greedy(coins: list[int], amt: int) -> int: 8 | """零钱兑换:贪心""" 9 | # 假设 coins 列表有序 10 | i = len(coins) - 1 11 | count = 0 12 | # 循环进行贪心选择,直到无剩余金额 13 | while amt > 0: 14 | # 找到小于且最接近剩余金额的硬币 15 | while i > 0 and coins[i] > amt: 16 | i -= 1 17 | # 选择 coins[i] 18 | amt -= coins[i] 19 | count += 1 20 | # 若未找到可行方案,则返回 -1 21 | return count if amt == 0 else -1 22 | 23 | 24 | """Driver Code""" 25 | if __name__ == "__main__": 26 | # 贪心:能够保证找到全局最优解 27 | coins = [1, 5, 10, 20, 50, 100] 28 | amt = 186 29 | res = coin_change_greedy(coins, amt) 30 | print(f"\ncoins = {coins}, amt = {amt}") 31 | print(f"凑到 {amt} 所需的最少硬币数量为 {res}") 32 | 33 | # 贪心:无法保证找到全局最优解 34 | coins = [1, 20, 50] 35 | amt = 60 36 | res = coin_change_greedy(coins, amt) 37 | print(f"\ncoins = {coins}, amt = {amt}") 38 | print(f"凑到 {amt} 所需的最少硬币数量为 {res}") 39 | print(f"实际上需要的最少数量为 3 ,即 20 + 20 + 20") 40 | 41 | # 贪心:无法保证找到全局最优解 42 | coins = [1, 49, 50] 43 | amt = 98 44 | res = coin_change_greedy(coins, amt) 45 | print(f"\ncoins = {coins}, amt = {amt}") 46 | print(f"凑到 {amt} 所需的最少硬币数量为 {res}") 47 | print(f"实际上需要的最少数量为 2 ,即 49 + 49") 48 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/m01_cut_steel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: 动态规划,切割钢管,使得收益最大化 5 | Desc : 6 | 给定一段长度为n的钢条和一个价格表p[i](i=1,2,3,4...n), 7 | 求切割方案,使得销售收益r[n]最大。 8 | 注意,如果长度为n的钢条价格p[n]足够大,最优解可能是完全不需要切割。 9 | """ 10 | __author__ = 'Xiong Neng' 11 | 12 | 13 | def bottom_up_cut_rod(p, n): 14 | """自底向上版本的动态规划,自底向上时间复杂性函数通常具有更小的系数 15 | 自底向上版本采用子问题的自然顺序,若i list[list[int]]: 31 | """求解子集和 I""" 32 | state = [] # 状态(子集) 33 | nums.sort() # 对 nums 进行排序 34 | start = 0 # 遍历起始点 35 | res = [] # 结果列表(子集列表) 36 | backtrack(state, target, nums, start, res) 37 | return res 38 | 39 | 40 | """Driver Code""" 41 | if __name__ == "__main__": 42 | nums = [3, 4, 5] 43 | target = 9 44 | res = subset_sum_i(nums, target) 45 | 46 | print(f"输入数组 nums = {nums}, target = {target}") 47 | print(f"所有和等于 {target} 的子集 res = {res}") 48 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/subset_sum_i_naive.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: subset_sum_i_naive.py 3 | Created Time: 2023-06-17 4 | """ 5 | 6 | 7 | def backtrack( 8 | state: list[int], 9 | target: int, 10 | total: int, 11 | choices: list[int], 12 | res: list[list[int]], 13 | ): 14 | """回溯算法:子集和 I""" 15 | # 子集和等于 target 时,记录解 16 | if total == target: 17 | res.append(list(state)) 18 | return 19 | # 遍历所有选择 20 | for i in range(len(choices)): 21 | # 剪枝:若子集和超过 target ,则跳过该选择 22 | if total + choices[i] > target: 23 | continue 24 | # 尝试:做出选择,更新元素和 total 25 | state.append(choices[i]) 26 | # 进行下一轮选择 27 | backtrack(state, target, total + choices[i], choices, res) 28 | # 回退:撤销选择,恢复到之前的状态 29 | state.pop() 30 | 31 | 32 | def subset_sum_i_naive(nums: list[int], target: int) -> list[list[int]]: 33 | """求解子集和 I(包含重复子集)""" 34 | state = [] # 状态(子集) 35 | total = 0 # 子集和 36 | res = [] # 结果列表(子集列表) 37 | backtrack(state, target, total, nums, res) 38 | return res 39 | 40 | 41 | """Driver Code""" 42 | if __name__ == "__main__": 43 | nums = [3, 4, 5] 44 | target = 9 45 | res = subset_sum_i_naive(nums, target) 46 | 47 | print(f"输入数组 nums = {nums}, target = {target}") 48 | print(f"所有和等于 {target} 的子集 res = {res}") 49 | print(f"请注意,该方法输出的结果包含重复集合") 50 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m11_rand_permute.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 随机排列数组算法 4 | """ 5 | Topic: sample 6 | Desc : 随机排列数组算法 7 | 第一种算法: 8 | 对给定的数组A,我们对每个A[i]生成一个随机的优先级 9 | 然后根据这个优先级对A进行排序,这个排序需要O(nlg(n))复杂度 10 | 第二种算法: 11 | 原址排列给定数组,在O(n)时间内完成,在进行第i次迭代时, 12 | A[i]从A[i]至A[n]中随机选取。之后A[i]就再也不变了。 13 | """ 14 | from random import randint, shuffle 15 | 16 | __author__ = 'Xiong Neng' 17 | 18 | 19 | def randPermuteBySort(seq): 20 | """ 21 | 对给定的数组A,我们对每个A[i]生成一个随机的优先级 22 | 然后根据这个优先级对A进行排序,这个排序需要O(nlg(n))复杂度 23 | """ 24 | maxR = pow(len(seq), 3) 25 | p = [] 26 | for i in range(0, len(seq)): 27 | p.append(randint(1, maxR)) 28 | seq[:] = [m for (m, n) in sorted(zip(seq, p), key=lambda k: k[1])] 29 | 30 | 31 | def randPermuteBySwap(seq): 32 | """ 33 | 原址排列给定数组,在O(n)时间内完成,在进行第i次迭代时, 34 | A[i]从A[i]至A[n]中随机选取。之后A[i]就再也不变了。 35 | """ 36 | le = len(seq) 37 | for i in range(0, le): 38 | swapIndex = randint(i, le - 1) 39 | seq[i], seq[swapIndex] = seq[swapIndex], seq[i] 40 | 41 | 42 | if __name__ == '__main__': 43 | se = [4, 5, 12, 44, 56, 6] 44 | randPermuteBySort(se) 45 | print(se) 46 | se = [4, 5, 12, 44, 56, 6] 47 | randPermuteBySwap(se) 48 | print(se) 49 | se = [4, 5, 12, 44, 56, 6] 50 | shuffle(se) # python中的函数 51 | print(se) 52 | -------------------------------------------------------------------------------- /algorithms/c13_ai/basic/a02_maze_dfs.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 迷宫搜索:深度优先算法,通过栈来实现 4 | """ 5 | 6 | from algorithms.c01_data_structure.stack.stack_linked_list import LinkedStack 7 | 8 | 9 | class Point: 10 | def __init__(self, row, column): 11 | self.row = row 12 | self.column = column 13 | self.parent = None 14 | 15 | def __eq__(self, other): 16 | return self.row == other.row and self.column == other.column 17 | 18 | def __str__(self): 19 | return f'({self.row},{self.column})' 20 | 21 | 22 | def print_path(point): 23 | """打印路径""" 24 | stack = LinkedStack() 25 | while point is not None: 26 | stack.push(stack) 27 | point = point.parent 28 | for point in stack: 29 | print(point, sep=',') 30 | 31 | 32 | def run_dfs(maze, root_point, visited_points): 33 | s = LinkedStack() 34 | s.push(root_point) 35 | while not s.is_empty(): 36 | current_point = s.pop() 37 | if current_point not in visited_points: 38 | visited_points.add(current_point) 39 | if current_point == maze.goal: 40 | print_path(current_point) 41 | return current_point 42 | # 构造当前节点的东南西北节点作为邻居节点 43 | neighbors = [] 44 | for neighbor in neighbors: 45 | neighbor.parent = current_point 46 | s.push(neighbor) 47 | print('find no path') 48 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/stack/stack_array.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 基于数组实现的栈结构 4 | """ 5 | from algorithms.c01_data_structure import Node 6 | 7 | 8 | class ArrayStack: 9 | def __init__(self, size): 10 | self.items = [None] * size # 初始数组大小 11 | self.size = size # size of the stack 12 | self.num = 0 # 元素的个数 13 | 14 | def push(self, item): 15 | """ 16 | 入栈操作 17 | :param item: 入栈数据 18 | :return: 是否入栈成功 19 | """ 20 | # 数组空间不够了,直接返回false,入栈失败。 21 | if self.num == self.size: 22 | return False 23 | # 将item放到下标为num的位置,并且num加一 24 | self.items[self.num] = Node(item) 25 | self.num += 1 26 | return True 27 | 28 | def pop(self): 29 | if self.num == 0: 30 | return None 31 | result = self.items[self.num - 1].data 32 | self.items[self.num - 1] = None 33 | self.num -= 1 34 | return result 35 | 36 | def peek(self): 37 | if self.num == 0: 38 | return None 39 | return self.items[0].data 40 | 41 | def __iter__(self): 42 | while self.num > 0: 43 | yield self.pop() 44 | 45 | 46 | if __name__ == '__main__': 47 | s = ArrayStack(3) 48 | s.push(1) 49 | s.push(2) 50 | s.push(3) 51 | print("-----------------") 52 | print(s.pop()) 53 | print(s.pop()) 54 | print(s.pop()) 55 | print("=================") 56 | -------------------------------------------------------------------------------- /algorithms/c05_search/binary_search_edge.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: binary_search_edge.py 3 | Created Time: 2023-08-04 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from binary_search_insertion import binary_search_insertion 11 | 12 | 13 | def binary_search_left_edge(nums: list[int], target: int) -> int: 14 | """二分查找最左一个 target""" 15 | # 等价于查找 target 的插入点 16 | i = binary_search_insertion(nums, target) 17 | # 未找到 target ,返回 -1 18 | if i == len(nums) or nums[i] != target: 19 | return -1 20 | # 找到 target ,返回索引 i 21 | return i 22 | 23 | 24 | def binary_search_right_edge(nums: list[int], target: int) -> int: 25 | """二分查找最右一个 target""" 26 | # 转化为查找最左一个 target + 1 27 | i = binary_search_insertion(nums, target + 1) 28 | # j 指向最右一个 target ,i 指向首个大于 target 的元素 29 | j = i - 1 30 | # 未找到 target ,返回 -1 31 | if j == -1 or nums[j] != target: 32 | return -1 33 | # 找到 target ,返回索引 j 34 | return j 35 | 36 | 37 | """Driver Code""" 38 | if __name__ == "__main__": 39 | # 包含重复元素的数组 40 | nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] 41 | print(f"\n数组 nums = {nums}") 42 | 43 | # 二分查找左边界和右边界 44 | for target in [6, 7]: 45 | index = binary_search_left_edge(nums, target) 46 | print(f"最左一个元素 {target} 的索引为 {index}") 47 | index = binary_search_right_edge(nums, target) 48 | print(f"最右一个元素 {target} 的索引为 {index}") 49 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p100/a155_min_stack.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 155. 最小栈 4 | 设计一个支持 push、pop、top 操作,并能在常数时间内检索到最小元素的栈。 5 | 6 | 实现 MinStack 类: 7 | 8 | MinStack() 初始化堆栈对象。 9 | void push(int val) 将元素val推入堆栈。 10 | void pop() 删除堆栈顶部的元素。 11 | int top() 获取堆栈顶部的元素。 12 | int getMin() 获取堆栈中的最小元素。 13 | 14 | 算法思路: 15 | 通过空间换时间的方法。将每次入栈的节点包装成一个对象,存储两个信息,一个是节点值,一个是当前栈的最小值。 16 | 这样获取栈顶元素就同时能获取到这两个值了。 17 | """ 18 | from algorithms.c01_data_structure import Node 19 | 20 | 21 | class MinStack: 22 | 23 | def __init__(self): 24 | self.first = None # top of stack 25 | 26 | def push(self, val: int) -> None: 27 | current_min = self.getMin() 28 | min_val = min(val, current_min) if current_min is not None else val 29 | self.first = Node((val, min_val), self.first) 30 | 31 | def pop(self) -> None: 32 | if self.first: 33 | self.first = self.first.next 34 | 35 | def top(self) -> int: 36 | if not self.first: 37 | return None 38 | return self.first.data[0] 39 | 40 | def getMin(self) -> int: 41 | if not self.first: 42 | return None 43 | return self.first.data[1] 44 | 45 | 46 | if __name__ == '__main__': 47 | pass 48 | # Your MinStack object will be instantiated and called as such: 49 | obj = MinStack() 50 | obj.push(0) 51 | obj.push(1) 52 | obj.push(0) 53 | print(obj.getMin()) 54 | obj.pop() 55 | print(obj.getMin()) 56 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p200/a206_reverse_linked_list.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 206. 反转链表 4 | 给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。 5 | 输入:head = [1,2,3,4,5] 6 | 输出:[5,4,3,2,1] 7 | 8 | 提示: 9 | 链表中节点的数目范围是 [0, 5000] 10 | -5000 <= Node.val <= 5000 11 | 12 | 进阶:链表可以选用迭代或递归方式完成反转。你能否用两种方法解决这道题? 13 | 14 | 使用贪吃蛇算法,将原始链表比喻成糖葫芦,贪吃蛇一口一个,把糖葫芦吃完为止。 15 | """ 16 | 17 | 18 | # Definition for singly-linked list. 19 | class ListNode: 20 | def __init__(self, val=0, next=None): 21 | self.val = val 22 | self.next = next 23 | 24 | def __str__(self): 25 | return str(self.val) 26 | 27 | 28 | class Solution: 29 | def reverseList(self, head: ListNode) -> ListNode: 30 | if not head: 31 | return None 32 | food = head 33 | snake = None 34 | 35 | while food: 36 | apple = food # 取下糖葫芦第一个苹果 37 | food = food.next # 糖葫芦减掉一个 38 | apple.next = snake # 吞掉一个苹果 39 | snake = apple # 调整蛇的头部指向这个苹果 40 | return snake 41 | 42 | 43 | if __name__ == '__main__': 44 | # 本地运行需要以下输入输出的补充 45 | # list_input = list(input('input list: ').split()) 46 | 47 | head = ListNode(1) 48 | head.next = ListNode(2) 49 | head.next.next = ListNode(3) 50 | head.next.next.next = ListNode(4) 51 | head.next.next.next.next = ListNode(5) 52 | 53 | # solve 54 | solution = Solution() 55 | res = solution.reverseList(head) 56 | print() 57 | # output 58 | -------------------------------------------------------------------------------- /algorithms/c07_divide/build_tree.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: build_tree.py 3 | Created Time: 2023-07-15 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import TreeNode, print_tree 11 | 12 | 13 | def dfs( 14 | preorder: list[int], 15 | inorder_map: dict[int, int], 16 | i: int, 17 | l: int, 18 | r: int, 19 | ) -> TreeNode | None: 20 | """构建二叉树:分治""" 21 | # 子树区间为空时终止 22 | if r - l < 0: 23 | return None 24 | # 初始化根节点 25 | root = TreeNode(preorder[i]) 26 | # 查询 m ,从而划分左右子树 27 | m = inorder_map[preorder[i]] 28 | # 子问题:构建左子树 29 | root.left = dfs(preorder, inorder_map, i + 1, l, m - 1) 30 | # 子问题:构建右子树 31 | root.right = dfs(preorder, inorder_map, i + 1 + m - l, m + 1, r) 32 | # 返回根节点 33 | return root 34 | 35 | 36 | def build_tree(preorder: list[int], inorder: list[int]) -> TreeNode | None: 37 | """构建二叉树""" 38 | # 初始化哈希表,存储 inorder 元素到索引的映射 39 | inorder_map = {val: i for i, val in enumerate(inorder)} 40 | root = dfs(preorder, inorder_map, 0, 0, len(inorder) - 1) 41 | return root 42 | 43 | 44 | """Driver Code""" 45 | if __name__ == "__main__": 46 | preorder = [3, 9, 2, 1, 7] 47 | inorder = [9, 3, 1, 2, 7] 48 | print(f"前序遍历 = {preorder}") 49 | print(f"中序遍历 = {inorder}") 50 | 51 | root = build_tree(preorder, inorder) 52 | print("构建的二叉树为:") 53 | print_tree(root) 54 | -------------------------------------------------------------------------------- /algorithms/c05_search/hashing_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: hashing_search.py 3 | Created Time: 2022-11-26 4 | Author: timi (xisunyy@163.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import ListNode, list_to_linked_list 12 | 13 | 14 | def hashing_search_array(hmap: dict[int, int], target: int) -> int: 15 | """哈希查找(数组)""" 16 | # 哈希表的 key: 目标元素,value: 索引 17 | # 若哈希表中无此 key ,返回 -1 18 | return hmap.get(target, -1) 19 | 20 | 21 | def hashing_search_linkedlist( 22 | hmap: dict[int, ListNode], target: int 23 | ) -> ListNode | None: 24 | """哈希查找(链表)""" 25 | # 哈希表的 key: 目标元素,value: 节点对象 26 | # 若哈希表中无此 key ,返回 None 27 | return hmap.get(target, None) 28 | 29 | 30 | """Driver Code""" 31 | if __name__ == "__main__": 32 | target = 3 33 | 34 | # 哈希查找(数组) 35 | nums = [1, 5, 3, 2, 4, 7, 5, 9, 10, 8] 36 | # 初始化哈希表 37 | map0 = dict[int, int]() 38 | for i in range(len(nums)): 39 | map0[nums[i]] = i # key: 元素,value: 索引 40 | index: int = hashing_search_array(map0, target) 41 | print("目标元素 3 的索引 =", index) 42 | 43 | # 哈希查找(链表) 44 | head: ListNode = list_to_linked_list(nums) 45 | # 初始化哈希表 46 | map1 = dict[int, ListNode]() 47 | while head: 48 | map1[head.val] = head # key: 节点值,value: 节点 49 | head = head.next 50 | node: ListNode = hashing_search_linkedlist(map1, target) 51 | print("目标节点值 3 的对应节点对象为", node) 52 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/a19_remove_nth_node_from_end_of_list.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 19. 删除单链表的倒数第N个结点 4 | 5 | 给你一个单链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。 6 | 7 | 两轮循环: 8 | 1、第一轮循环获取链表的节点总数size。 9 | 2、第二轮循环删除第size-n位置上的节点。 10 | """ 11 | 12 | 13 | # Definition for singly-linked list. 14 | class ListNode: 15 | def __init__(self, val=0, next=None): 16 | self.val = val 17 | self.next = next 18 | 19 | def __str__(self): 20 | return str(self.val) 21 | 22 | 23 | class Solution: 24 | def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: 25 | if not head: 26 | return head 27 | size = 0 28 | temp = head 29 | while temp: 30 | size += 1 31 | temp = temp.next 32 | if n > size: 33 | return head 34 | 35 | pre = None 36 | remove_item = head 37 | for _ in range(size - n): 38 | pre = remove_item 39 | remove_item = remove_item.next 40 | if pre: 41 | pre.next = remove_item.next 42 | else: 43 | head = remove_item.next 44 | return head 45 | 46 | 47 | if __name__ == '__main__': 48 | # 本地运行需要以下输入输出的补充 49 | head = ListNode(1) 50 | head.next = ListNode(2) 51 | head.next.next = ListNode(3) 52 | head.next.next.next = ListNode(4) 53 | head.next.next.next.next = ListNode(5) 54 | 55 | # solve 56 | solution = Solution() 57 | res = solution.removeNthFromEnd(head, 5) 58 | print() 59 | # output 60 | -------------------------------------------------------------------------------- /algorithms/c04_sort/merge_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: merge_sort.py 3 | Created Time: 2022-11-25 4 | """ 5 | 6 | 7 | def merge(nums: list[int], left: int, mid: int, right: int): 8 | """合并左子数组和右子数组""" 9 | # 左子数组区间 [left, mid], 右子数组区间 [mid+1, right] 10 | # 创建一个临时数组 tmp ,用于存放合并后的结果 11 | tmp = [0] * (right - left + 1) 12 | # 初始化左子数组和右子数组的起始索引 13 | i, j, k = left, mid + 1, 0 14 | # 当左右子数组都还有元素时,比较并将较小的元素复制到临时数组中 15 | while i <= mid and j <= right: 16 | if nums[i] <= nums[j]: 17 | tmp[k] = nums[i] 18 | i += 1 19 | else: 20 | tmp[k] = nums[j] 21 | j += 1 22 | k += 1 23 | # 将左子数组和右子数组的剩余元素复制到临时数组中 24 | while i <= mid: 25 | tmp[k] = nums[i] 26 | i += 1 27 | k += 1 28 | while j <= right: 29 | tmp[k] = nums[j] 30 | j += 1 31 | k += 1 32 | # 将临时数组 tmp 中的元素复制回原数组 nums 的对应区间 33 | for k in range(0, len(tmp)): 34 | nums[left + k] = tmp[k] 35 | 36 | 37 | def merge_sort(nums: list[int], left: int, right: int): 38 | """归并排序""" 39 | # 终止条件 40 | if left >= right: 41 | return # 当子数组长度为 1 时终止递归 42 | # 划分阶段 43 | mid = (left + right) // 2 # 计算中点 44 | merge_sort(nums, left, mid) # 递归左子数组 45 | merge_sort(nums, mid + 1, right) # 递归右子数组 46 | # 合并阶段 47 | merge(nums, left, mid, right) 48 | 49 | 50 | """Driver Code""" 51 | if __name__ == "__main__": 52 | nums = [7, 3, 2, 6, 0, 1, 5, 4] 53 | merge_sort(nums, 0, len(nums) - 1) 54 | print("归并排序完成后 nums =", nums) 55 | -------------------------------------------------------------------------------- /algorithms/c13_ai/basic/a01_maze_bfs.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 迷宫搜索:广度优先算法,通过队列来实现 4 | """ 5 | 6 | from algorithms.c01_data_structure.queue.linked_queue import LinkedQueue 7 | from algorithms.c01_data_structure.stack.stack_linked_list import LinkedStack 8 | 9 | 10 | class Point: 11 | def __init__(self, row, column): 12 | self.row = row 13 | self.column = column 14 | self.parent = None 15 | 16 | def __eq__(self, other): 17 | return self.row == other.row and self.column == other.column 18 | 19 | def __str__(self): 20 | return f'({self.row},{self.column})' 21 | 22 | 23 | def print_path(point): 24 | """打印路径""" 25 | stack = LinkedStack() 26 | while point is not None: 27 | stack.push(stack) 28 | point = point.parent 29 | for point in stack: 30 | print(point, sep=',') 31 | 32 | 33 | def run_bfs(maze, current_point, visited_points): 34 | q = LinkedQueue() 35 | q.enqueue(current_point) 36 | visited_points.add(current_point) 37 | while not q.is_empty(): 38 | current_point = q.dequeue() 39 | # 当前节点的东南西北节点作为邻居节点 40 | neighbors = [] 41 | for neighbor in neighbors: 42 | if neighbor not in visited_points: 43 | visited_points.add(neighbor) 44 | neighbor.parent = current_point 45 | q.enqueue(neighbor) 46 | if neighbor == maze.goal: 47 | print_path(neighbor) 48 | return neighbor 49 | print('find no path') 50 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/linkedlist/linked_list_double.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 双向链表数据结构 4 | """ 5 | from algorithms.c01_data_structure import NodeDouble 6 | 7 | 8 | class LinkedListDouble: 9 | """ 10 | 双向链表,带哨兵 11 | """ 12 | 13 | def __init__(self): 14 | self.head = NodeDouble(None) 15 | 16 | def insert_node(self, node, new_node): 17 | """ 18 | :param node:插入点节点 19 | :param new_node:新节点 20 | :return: 21 | """ 22 | new_node.next = node.next 23 | new_node.pre = node 24 | node.next = new_node 25 | 26 | def remove_node(self, node): 27 | """ 28 | :param node:待删除节点 29 | :return: 30 | """ 31 | if not node.pre: # 空表 32 | return 33 | node.pre.next = node.next 34 | if node.next: # 如果node有下一个节点 35 | node.next.pre = node.pre 36 | 37 | def search(self, data): 38 | """ 39 | 直接比较原始数据 40 | :param data: 待查询数据 41 | :return: 查询到的节点 42 | """ 43 | target = self.head.next 44 | while target and target.data != data: 45 | target = target.next 46 | return target 47 | 48 | def search_equal(self, data, equal): 49 | """ 50 | 通过传入equal比较函数来进行相等判断 51 | :param data: 待查询数据 52 | :param equal: 相等函数 53 | :return: 查询到的节点 54 | """ 55 | target = self.head.next 56 | while target and equal(target.data, data): 57 | target = target.next 58 | return target 59 | -------------------------------------------------------------------------------- /algorithms/c03_graph/graph_dfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: graph_dfs.py 3 | Created Time: 2023-02-23 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import Vertex, vets_to_vals, vals_to_vets 11 | from graph_adjacency_list import GraphAdjList 12 | 13 | 14 | def dfs(graph: GraphAdjList, visited: set[Vertex], res: list[Vertex], vet: Vertex): 15 | """深度优先遍历 DFS 辅助函数""" 16 | res.append(vet) # 记录访问顶点 17 | visited.add(vet) # 标记该顶点已被访问 18 | # 遍历该顶点的所有邻接顶点 19 | for adjVet in graph.adj_list[vet]: 20 | if adjVet in visited: 21 | continue # 跳过已被访问过的顶点 22 | # 递归访问邻接顶点 23 | dfs(graph, visited, res, adjVet) 24 | 25 | 26 | def graph_dfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: 27 | """深度优先遍历 DFS""" 28 | # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 29 | # 顶点遍历序列 30 | res = [] 31 | # 哈希表,用于记录已被访问过的顶点 32 | visited = set[Vertex]() 33 | dfs(graph, visited, res, start_vet) 34 | return res 35 | 36 | 37 | """Driver Code""" 38 | if __name__ == "__main__": 39 | # 初始化无向图 40 | v = vals_to_vets([0, 1, 2, 3, 4, 5, 6]) 41 | edges = [ 42 | [v[0], v[1]], 43 | [v[0], v[3]], 44 | [v[1], v[2]], 45 | [v[2], v[5]], 46 | [v[4], v[5]], 47 | [v[5], v[6]], 48 | ] 49 | graph = GraphAdjList(edges) 50 | print("\n初始化后,图为") 51 | graph.print() 52 | 53 | # 深度优先遍历 DFS 54 | res = graph_dfs(graph, v[0]) 55 | print("\n深度优先遍历(DFS)顶点序列为") 56 | print(vets_to_vals(res)) 57 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/subset_sum_ii.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: subset_sum_ii.py 3 | Created Time: 2023-06-17 4 | """ 5 | 6 | 7 | def backtrack( 8 | state: list[int], target: int, choices: list[int], start: int, res: list[list[int]] 9 | ): 10 | """回溯算法:子集和 II""" 11 | # 子集和等于 target 时,记录解 12 | if target == 0: 13 | res.append(list(state)) 14 | return 15 | # 遍历所有选择 16 | # 剪枝二:从 start 开始遍历,避免生成重复子集 17 | # 剪枝三:从 start 开始遍历,避免重复选择同一元素 18 | for i in range(start, len(choices)): 19 | # 剪枝一:若子集和超过 target ,则直接结束循环 20 | # 这是因为数组已排序,后边元素更大,子集和一定超过 target 21 | if target - choices[i] < 0: 22 | break 23 | # 剪枝四:如果该元素与左边元素相等,说明该搜索分支重复,直接跳过 24 | if i > start and choices[i] == choices[i - 1]: 25 | continue 26 | # 尝试:做出选择,更新 target, start 27 | state.append(choices[i]) 28 | # 进行下一轮选择 29 | backtrack(state, target - choices[i], choices, i + 1, res) 30 | # 回退:撤销选择,恢复到之前的状态 31 | state.pop() 32 | 33 | 34 | def subset_sum_ii(nums: list[int], target: int) -> list[list[int]]: 35 | """求解子集和 II""" 36 | state = [] # 状态(子集) 37 | nums.sort() # 对 nums 进行排序 38 | start = 0 # 遍历起始点 39 | res = [] # 结果列表(子集列表) 40 | backtrack(state, target, nums, start, res) 41 | return res 42 | 43 | 44 | """Driver Code""" 45 | if __name__ == "__main__": 46 | nums = [4, 4, 5] 47 | target = 9 48 | res = subset_sum_ii(nums, target) 49 | 50 | print(f"输入数组 nums = {nums}, target = {target}") 51 | print(f"所有和等于 {target} 的子集 res = {res}") 52 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/linkedlist/linked_list_single_cycle.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 单向循环链表数据结构 4 | """ 5 | from algorithms.c01_data_structure import Node 6 | 7 | 8 | class LinkedListSingleCycle: 9 | """ 10 | 单向循环链表,带哨兵 11 | """ 12 | 13 | def __init__(self): 14 | self.head = Node(None) 15 | self.head.next = self.head 16 | 17 | def insert_node(self, node, new_node): 18 | """ 19 | :param node:插入点节点 20 | :param new_node:新节点 21 | :return: 22 | """ 23 | new_node.next = node.next 24 | node.next = new_node 25 | 26 | def remove_node(self, node): 27 | """ 28 | :param node:待删除节点 29 | :return: 30 | """ 31 | pre_node = self.head 32 | while pre_node.next != node: 33 | pre_node = pre_node.next 34 | pre_node.next = node.next 35 | 36 | def search(self, data): 37 | """ 38 | 直接比较原始数据 39 | :param data: 待查询数据 40 | :return: 查询到的节点 41 | """ 42 | target = self.head.next 43 | while target != self.head and target.data != data: 44 | target = target.next 45 | return target 46 | 47 | def search_equal(self, data, equal): 48 | """ 49 | 通过传入equal比较函数来进行相等判断 50 | :param data: 待查询数据 51 | :param equal: 相等函数 52 | :return: 查询到的节点 53 | """ 54 | target = self.head.next 55 | while target != self.head and equal(target.data, data): 56 | target = target.next 57 | return target 58 | -------------------------------------------------------------------------------- /algorithms/c02_tree/binary_tree_dfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: binary_tree_dfs.py 3 | Created Time: 2022-12-20 4 | Author: a16su (lpluls001@gmail.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import TreeNode, list_to_tree, print_tree 12 | 13 | 14 | def pre_order(root: TreeNode | None): 15 | """前序遍历""" 16 | if root is None: 17 | return 18 | # 访问优先级:根节点 -> 左子树 -> 右子树 19 | res.append(root.val) 20 | pre_order(root=root.left) 21 | pre_order(root=root.right) 22 | 23 | 24 | def in_order(root: TreeNode | None): 25 | """中序遍历""" 26 | if root is None: 27 | return 28 | # 访问优先级:左子树 -> 根节点 -> 右子树 29 | in_order(root=root.left) 30 | res.append(root.val) 31 | in_order(root=root.right) 32 | 33 | 34 | def post_order(root: TreeNode | None): 35 | """后序遍历""" 36 | if root is None: 37 | return 38 | # 访问优先级:左子树 -> 右子树 -> 根节点 39 | post_order(root=root.left) 40 | post_order(root=root.right) 41 | res.append(root.val) 42 | 43 | 44 | """Driver Code""" 45 | if __name__ == "__main__": 46 | # 初始化二叉树 47 | # 这里借助了一个从数组直接生成二叉树的函数 48 | root = list_to_tree(arr=[1, 2, 3, 4, 5, 6, 7]) 49 | print("\n初始化二叉树\n") 50 | print_tree(root) 51 | 52 | # 前序遍历 53 | res = [] 54 | pre_order(root) 55 | print("\n前序遍历的节点打印序列 = ", res) 56 | 57 | # 中序遍历 58 | res.clear() 59 | in_order(root) 60 | print("\n中序遍历的节点打印序列 = ", res) 61 | 62 | # 后序遍历 63 | res.clear() 64 | post_order(root) 65 | print("\n后序遍历的节点打印序列 = ", res) 66 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/unbounded_knapsack.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: unbounded_knapsack.py 3 | Created Time: 2023-07-10 4 | """ 5 | 6 | 7 | def unbounded_knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: 8 | """完全背包:动态规划""" 9 | n = len(wgt) 10 | # 初始化 dp 表 11 | dp = [[0] * (cap + 1) for _ in range(n + 1)] 12 | # 状态转移 13 | for i in range(1, n + 1): 14 | for c in range(1, cap + 1): 15 | if wgt[i - 1] > c: 16 | # 若超过背包容量,则不选物品 i 17 | dp[i][c] = dp[i - 1][c] 18 | else: 19 | # 不选和选物品 i 这两种方案的较大值 20 | dp[i][c] = max(dp[i - 1][c], dp[i][c - wgt[i - 1]] + val[i - 1]) 21 | return dp[n][cap] 22 | 23 | 24 | def unbounded_knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: 25 | """完全背包:空间优化后的动态规划""" 26 | n = len(wgt) 27 | # 初始化 dp 表 28 | dp = [0] * (cap + 1) 29 | # 状态转移 30 | for i in range(1, n + 1): 31 | # 正序遍历 32 | for c in range(1, cap + 1): 33 | if wgt[i - 1] > c: 34 | # 若超过背包容量,则不选物品 i 35 | dp[c] = dp[c] 36 | else: 37 | # 不选和选物品 i 这两种方案的较大值 38 | dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) 39 | return dp[cap] 40 | 41 | 42 | """Driver Code""" 43 | if __name__ == "__main__": 44 | wgt = [1, 2, 3] 45 | val = [5, 11, 15] 46 | cap = 4 47 | 48 | # 动态规划 49 | res = unbounded_knapsack_dp(wgt, val, cap) 50 | print(f"不超过背包容量的最大物品价值为 {res}") 51 | 52 | # 空间优化后的动态规划 53 | res = unbounded_knapsack_dp_comp(wgt, val, cap) 54 | print(f"不超过背包容量的最大物品价值为 {res}") 55 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/linkedlist/linked_list_double_cycle.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 双向循环链表数据结构 4 | """ 5 | from algorithms.c01_data_structure import NodeDouble 6 | 7 | 8 | class LinkedListDouble: 9 | """ 10 | 双向循环链表,带哨兵 11 | """ 12 | 13 | def __init__(self): 14 | head = NodeDouble(None) 15 | head.pre = head.next = head # 将pre和next都指向自己 16 | self.head = head 17 | 18 | def insert_node(self, node, new_node): 19 | """ 20 | :param node:插入点节点 21 | :param new_node:新节点 22 | :return: 23 | """ 24 | new_node.next = node.next 25 | new_node.pre = node 26 | node.next = new_node 27 | 28 | def remove_node(self, node): 29 | """ 30 | :param node:待删除节点 31 | :return: 32 | """ 33 | if node == self.head: # 不能删除头节点 34 | return 35 | node.pre.next = node.next 36 | node.next.pre = node.pre 37 | 38 | def search(self, data): 39 | """ 40 | 直接比较原始数据 41 | :param data: 待查询数据 42 | :return: 查询到的节点 43 | """ 44 | target = self.head.next 45 | while target != self.head and target.data != data: 46 | target = target.next 47 | return target 48 | 49 | def search_equal(self, data, equal): 50 | """ 51 | 通过传入equal比较函数来进行相等判断 52 | :param data: 待查询数据 53 | :param equal: 相等函数 54 | :return: 查询到的节点 55 | """ 56 | target = self.head.next 57 | while target != self.head and equal(target.data, data): 58 | target = target.next 59 | return target 60 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/coin_change_ii.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: coin_change_ii.py 3 | Created Time: 2023-07-10 4 | """ 5 | 6 | 7 | def coin_change_ii_dp(coins: list[int], amt: int) -> int: 8 | """零钱兑换 II:动态规划""" 9 | n = len(coins) 10 | # 初始化 dp 表 11 | dp = [[0] * (amt + 1) for _ in range(n + 1)] 12 | # 初始化首列 13 | for i in range(n + 1): 14 | dp[i][0] = 1 15 | # 状态转移 16 | for i in range(1, n + 1): 17 | for a in range(1, amt + 1): 18 | if coins[i - 1] > a: 19 | # 若超过背包容量,则不选硬币 i 20 | dp[i][a] = dp[i - 1][a] 21 | else: 22 | # 不选和选硬币 i 这两种方案之和 23 | dp[i][a] = dp[i - 1][a] + dp[i][a - coins[i - 1]] 24 | return dp[n][amt] 25 | 26 | 27 | def coin_change_ii_dp_comp(coins: list[int], amt: int) -> int: 28 | """零钱兑换 II:空间优化后的动态规划""" 29 | n = len(coins) 30 | # 初始化 dp 表 31 | dp = [0] * (amt + 1) 32 | dp[0] = 1 33 | # 状态转移 34 | for i in range(1, n + 1): 35 | # 正序遍历 36 | for a in range(1, amt + 1): 37 | if coins[i - 1] > a: 38 | # 若超过背包容量,则不选硬币 i 39 | dp[a] = dp[a] 40 | else: 41 | # 不选和选硬币 i 这两种方案之和 42 | dp[a] = dp[a] + dp[a - coins[i - 1]] 43 | return dp[amt] 44 | 45 | 46 | """Driver Code""" 47 | if __name__ == "__main__": 48 | coins = [1, 2, 5] 49 | amt = 5 50 | n = len(coins) 51 | 52 | # 动态规划 53 | res = coin_change_ii_dp(coins, amt) 54 | print(f"凑出目标金额的硬币组合数量为 {res}") 55 | 56 | # 空间优化后的动态规划 57 | res = coin_change_ii_dp_comp(coins, amt) 58 | print(f"凑出目标金额的硬币组合数量为 {res}") 59 | -------------------------------------------------------------------------------- /algorithms/c05_search/binary_search.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: binary_search.py 3 | Created Time: 2022-11-26 4 | Author: timi (xisunyy@163.com) 5 | """ 6 | 7 | 8 | def binary_search(nums: list[int], target: int) -> int: 9 | """二分查找(双闭区间)""" 10 | # 初始化双闭区间 [0, n-1] ,即 i, j 分别指向数组首元素、尾元素 11 | i, j = 0, len(nums) - 1 12 | # 循环,当搜索区间为空时跳出(当 i > j 时为空) 13 | while i <= j: 14 | # 理论上 Python 的数字可以无限大(取决于内存大小),无须考虑大数越界问题 15 | m = (i + j) // 2 # 计算中点索引 m 16 | if nums[m] < target: 17 | i = m + 1 # 此情况说明 target 在区间 [m+1, j] 中 18 | elif nums[m] > target: 19 | j = m - 1 # 此情况说明 target 在区间 [i, m-1] 中 20 | else: 21 | return m # 找到目标元素,返回其索引 22 | return -1 # 未找到目标元素,返回 -1 23 | 24 | 25 | def binary_search_lcro(nums: list[int], target: int) -> int: 26 | """二分查找(左闭右开)""" 27 | # 初始化左闭右开 [0, n) ,即 i, j 分别指向数组首元素、尾元素+1 28 | i, j = 0, len(nums) 29 | # 循环,当搜索区间为空时跳出(当 i = j 时为空) 30 | while i < j: 31 | m = (i + j) // 2 # 计算中点索引 m 32 | if nums[m] < target: 33 | i = m + 1 # 此情况说明 target 在区间 [m+1, j) 中 34 | elif nums[m] > target: 35 | j = m # 此情况说明 target 在区间 [i, m) 中 36 | else: 37 | return m # 找到目标元素,返回其索引 38 | return -1 # 未找到目标元素,返回 -1 39 | 40 | 41 | """Driver Code""" 42 | if __name__ == "__main__": 43 | target = 6 44 | nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] 45 | 46 | # 二分查找(双闭区间) 47 | index: int = binary_search(nums, target) 48 | print("目标元素 6 的索引 = ", index) 49 | 50 | # 二分查找(左闭右开) 51 | index: int = binary_search_lcro(nums, target) 52 | print("目标元素 6 的索引 = ", index) 53 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/m06_bag.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 动态规划解决0-1背包问题 4 | 有n个商品a[0..n],价值v[0..n]美元,重量w[0..n]磅。一个背包最多能承重top磅。 5 | 怎样装商品能获得最大价值。 6 | """ 7 | 8 | 9 | def bag_choice(v, w, top): 10 | n = len(v) - 1 11 | # 先初始化最优解值二维数组 12 | # vals[i][w]表示:对于前i个物品,当前背包的容量为w时,这种情况下可以装下的最大价值 13 | vals = [[0 for _ in range(top + 1)] for _ in range(n + 1)] 14 | # 用来保存每个最优方案三维数组,ans[i][j]表示前i个物品上限为j时候的最优方案 15 | ans = [[[0 for _ in range(i + 1)] for _ in range(top + 1)] for i in range(n + 1)] 16 | for i in range(1, n + 1): # 物品选择从1到n 17 | for j in range(1, top + 1): # 重量上限从1到top 18 | if j - w[i] < 0: 19 | # 这时候只能选择不选i 20 | vals[i][j] = vals[i - 1][j] 21 | # 方案为前i-1最优方案+不选择i 22 | ans[i][j] = ans[i - 1][j] + [0] 23 | else: 24 | # 不选择i的时候,则最优解就是前i-1个物品上限为j的最优解 25 | unchoose_val = vals[i - 1][j] 26 | # 选择i的时候,则最优解就是前i-1个物品上限为j-w[i]最优解+v[i]的和 27 | choose_val = vals[i - 1][j - w[i]] + v[i] 28 | if unchoose_val > choose_val: 29 | vals[i][j] = unchoose_val 30 | # 方案为前i-1最优方案+不选择i 31 | ans[i][j] = ans[i - 1][j] + [0] 32 | else: 33 | vals[i][j] = choose_val 34 | # 方案为前i-1最优方案+选择i 35 | ans[i][j] = ans[i - 1][j - w[i]] + [1] 36 | return vals[n][top], ans[n][top] 37 | 38 | 39 | if __name__ == '__main__': 40 | w = [0, 1, 2, 12, 1, 4] # 重量数组 41 | v = [0, 1, 2, 4, 2, 10] # 价值数组 42 | top = 7 # 背包重量上限 43 | print(bag_choice(v, w, top)) 44 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/a86_partition_list.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。 4 | 5 | 你应当 保留 两个分区中每个节点的初始相对位置。 6 | 7 | 输入:head = [1,4,3,2,5,2], x = 3 8 | 输出:[1,2,2,4,3,5] 9 | 10 | 输入:head = [2,1], x = 2 11 | 输出:[1,2] 12 | """ 13 | from typing import Optional 14 | 15 | from algorithms.c01_data_structure.linkedlist import ListNode 16 | 17 | 18 | class Solution: 19 | def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]: 20 | p1 = None # 小于x的链表 21 | p1_head = None 22 | p2 = None # 大于等于x的链表 23 | p2_head = None 24 | p = head # 循环原链表 25 | while p: 26 | if p.val < x: 27 | if not p1: 28 | p1 = p 29 | p1_head = p 30 | else: 31 | p1.next = p 32 | p1 = p1.next 33 | else: 34 | if not p2: 35 | p2 = p 36 | p2_head = p 37 | else: 38 | p2.next = p 39 | p2 = p2.next 40 | temp = p.next 41 | p.next = None 42 | p = temp 43 | if p1: 44 | p1.next = p2_head 45 | return p1_head if p1_head else p2_head 46 | 47 | 48 | if __name__ == '__main__': 49 | # [1,4,3,2,5,2] 50 | head = ListNode(2) 51 | head = ListNode(5, head) 52 | head = ListNode(2, head) 53 | head = ListNode(3, head) 54 | head = ListNode(4, head) 55 | head = ListNode(1, head) 56 | head = Solution().partition(head, 3) 57 | while head: 58 | print(head.val, end=' ') 59 | head = head.next 60 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/a23_merge_k_sorted_lists.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 23. 合并 K 个升序链表 4 | 5 | 给你一个链表数组,每个链表都已经按升序排列。 6 | 请你将所有链表合并到一个升序链表中,返回合并后的链表。 7 | 8 | 输入:lists = [[1,4,5],[1,3,4],[2,6]] 9 | 输出:[1,1,2,3,4,4,5,6] 10 | """ 11 | from typing import List, Optional 12 | 13 | from algorithms.c01_data_structure.linkedlist import ListNode 14 | from queue import PriorityQueue 15 | 16 | 17 | class Solution: 18 | def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: 19 | if not lists: 20 | return None 21 | p = None 22 | p_head = None 23 | pq = PriorityQueue() 24 | for list_idx, each in enumerate(lists): 25 | if each: 26 | pq.put((each.val, list_idx, each)) 27 | while not pq.empty(): 28 | node = pq.get() 29 | if p: 30 | p.next = node[2] 31 | p = p.next 32 | else: 33 | p = node[2] 34 | p_head = node[2] 35 | if node[2].next: 36 | pq.put((node[2].next.val, node[1], node[2].next)) 37 | return p_head 38 | 39 | 40 | if __name__ == '__main__': 41 | """ 42 | 输入:lists = [[1,4,5],[1,3,4],[2,6]] 43 | 输出:[1,1,2,3,4,4,5,6] 44 | """ 45 | node1 = ListNode(5) 46 | node1 = ListNode(4, node1) 47 | node1 = ListNode(1, node1) 48 | node2 = ListNode(4) 49 | node2 = ListNode(3, node2) 50 | node2 = ListNode(1, node2) 51 | node3 = ListNode(6) 52 | node3 = ListNode(2, node3) 53 | lists = [node1, node2, node3] 54 | head = Solution().mergeKLists(lists) 55 | while head: 56 | print(head.val, end=' ') 57 | head = head.next 58 | ... 59 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/coin_change.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: coin_change.py 3 | Created Time: 2023-07-10 4 | """ 5 | 6 | 7 | def coin_change_dp(coins: list[int], amt: int) -> int: 8 | """零钱兑换:动态规划""" 9 | n = len(coins) 10 | MAX = amt + 1 11 | # 初始化 dp 表 12 | dp = [[0] * (amt + 1) for _ in range(n + 1)] 13 | # 状态转移:首行首列 14 | for a in range(1, amt + 1): 15 | dp[0][a] = MAX 16 | # 状态转移:其余行列 17 | for i in range(1, n + 1): 18 | for a in range(1, amt + 1): 19 | if coins[i - 1] > a: 20 | # 若超过背包容量,则不选硬币 i 21 | dp[i][a] = dp[i - 1][a] 22 | else: 23 | # 不选和选硬币 i 这两种方案的较小值 24 | dp[i][a] = min(dp[i - 1][a], dp[i][a - coins[i - 1]] + 1) 25 | return dp[n][amt] if dp[n][amt] != MAX else -1 26 | 27 | 28 | def coin_change_dp_comp(coins: list[int], amt: int) -> int: 29 | """零钱兑换:空间优化后的动态规划""" 30 | n = len(coins) 31 | MAX = amt + 1 32 | # 初始化 dp 表 33 | dp = [MAX] * (amt + 1) 34 | dp[0] = 0 35 | # 状态转移 36 | for i in range(1, n + 1): 37 | # 正序遍历 38 | for a in range(1, amt + 1): 39 | if coins[i - 1] > a: 40 | # 若超过背包容量,则不选硬币 i 41 | dp[a] = dp[a] 42 | else: 43 | # 不选和选硬币 i 这两种方案的较小值 44 | dp[a] = min(dp[a], dp[a - coins[i - 1]] + 1) 45 | return dp[amt] if dp[amt] != MAX else -1 46 | 47 | 48 | """Driver Code""" 49 | if __name__ == "__main__": 50 | coins = [1, 2, 5] 51 | amt = 4 52 | 53 | # 动态规划 54 | res = coin_change_dp(coins, amt) 55 | print(f"凑到目标金额所需的最少硬币数量为 {res}") 56 | 57 | # 空间优化后的动态规划 58 | res = coin_change_dp_comp(coins, amt) 59 | print(f"凑到目标金额所需的最少硬币数量为 {res}") 60 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/stack/array_stack.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: array_stack.py 3 | Created Time: 2022-11-29 4 | Author: Peng Chen (pengchzn@gmail.com) 5 | """ 6 | 7 | 8 | class ArrayStack: 9 | """基于数组实现的栈""" 10 | 11 | def __init__(self): 12 | """构造方法""" 13 | self._stack: list[int] = [] 14 | 15 | def size(self) -> int: 16 | """获取栈的长度""" 17 | return len(self._stack) 18 | 19 | def is_empty(self) -> bool: 20 | """判断栈是否为空""" 21 | return self._stack == [] 22 | 23 | def push(self, item: int): 24 | """入栈""" 25 | self._stack.append(item) 26 | 27 | def pop(self) -> int: 28 | """出栈""" 29 | if self.is_empty(): 30 | raise IndexError("栈为空") 31 | return self._stack.pop() 32 | 33 | def peek(self) -> int: 34 | """访问栈顶元素""" 35 | if self.is_empty(): 36 | raise IndexError("栈为空") 37 | return self._stack[-1] 38 | 39 | def to_list(self) -> list[int]: 40 | """返回列表用于打印""" 41 | return self._stack 42 | 43 | 44 | """Driver Code""" 45 | if __name__ == "__main__": 46 | # 初始化栈 47 | stack = ArrayStack() 48 | 49 | # 元素入栈 50 | stack.push(1) 51 | stack.push(3) 52 | stack.push(2) 53 | stack.push(5) 54 | stack.push(4) 55 | print("栈 stack =", stack.to_list()) 56 | 57 | # 访问栈顶元素 58 | peek: int = stack.peek() 59 | print("栈顶元素 peek =", peek) 60 | 61 | # 元素出栈 62 | pop: int = stack.pop() 63 | print("出栈元素 pop =", pop) 64 | print("出栈后 stack =", stack.to_list()) 65 | 66 | # 获取栈的长度 67 | size: int = stack.size() 68 | print("栈的长度 size =", size) 69 | 70 | # 判断是否为空 71 | is_empty: bool = stack.is_empty() 72 | print("栈是否为空 =", is_empty) 73 | -------------------------------------------------------------------------------- /algorithms/c05_search/binary_search_insertion.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: binary_search_insertion.py 3 | Created Time: 2023-08-04 4 | """ 5 | 6 | 7 | def binary_search_insertion_simple(nums: list[int], target: int) -> int: 8 | """二分查找插入点(无重复元素)""" 9 | i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] 10 | while i <= j: 11 | m = (i + j) // 2 # 计算中点索引 m 12 | if nums[m] < target: 13 | i = m + 1 # target 在区间 [m+1, j] 中 14 | elif nums[m] > target: 15 | j = m - 1 # target 在区间 [i, m-1] 中 16 | else: 17 | return m # 找到 target ,返回插入点 m 18 | # 未找到 target ,返回插入点 i 19 | return i 20 | 21 | 22 | def binary_search_insertion(nums: list[int], target: int) -> int: 23 | """二分查找插入点(存在重复元素)""" 24 | i, j = 0, len(nums) - 1 # 初始化双闭区间 [0, n-1] 25 | while i <= j: 26 | m = (i + j) // 2 # 计算中点索引 m 27 | if nums[m] < target: 28 | i = m + 1 # target 在区间 [m+1, j] 中 29 | elif nums[m] > target: 30 | j = m - 1 # target 在区间 [i, m-1] 中 31 | else: 32 | j = m - 1 # 首个小于 target 的元素在区间 [i, m-1] 中 33 | # 返回插入点 i 34 | return i 35 | 36 | 37 | """Driver Code""" 38 | if __name__ == "__main__": 39 | # 无重复元素的数组 40 | nums = [1, 3, 6, 8, 12, 15, 23, 26, 31, 35] 41 | print(f"\n数组 nums = {nums}") 42 | # 二分查找插入点 43 | for target in [6, 9]: 44 | index = binary_search_insertion_simple(nums, target) 45 | print(f"元素 {target} 的插入点的索引为 {index}") 46 | 47 | # 包含重复元素的数组 48 | nums = [1, 3, 6, 6, 6, 6, 6, 10, 12, 15] 49 | print(f"\n数组 nums = {nums}") 50 | # 二分查找插入点 51 | for target in [2, 6, 20]: 52 | index = binary_search_insertion(nums, target) 53 | print(f"元素 {target} 的插入点的索引为 {index}") 54 | -------------------------------------------------------------------------------- /algorithms/c04_sort/counting_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: counting_sort.py 3 | Created Time: 2023-03-21 4 | """ 5 | 6 | 7 | def counting_sort_naive(nums: list[int]): 8 | """计数排序""" 9 | # 简单实现,无法用于排序对象 10 | # 1. 统计数组最大元素 m 11 | m = 0 12 | for num in nums: 13 | m = max(m, num) 14 | # 2. 统计各数字的出现次数 15 | # counter[num] 代表 num 的出现次数 16 | counter = [0] * (m + 1) 17 | for num in nums: 18 | counter[num] += 1 19 | # 3. 遍历 counter ,将各元素填入原数组 nums 20 | i = 0 21 | for num in range(m + 1): 22 | for _ in range(counter[num]): 23 | nums[i] = num 24 | i += 1 25 | 26 | 27 | def counting_sort(nums: list[int]): 28 | """计数排序""" 29 | # 完整实现,可排序对象,并且是稳定排序 30 | # 1. 统计数组最大元素 m 31 | m = max(nums) 32 | # 2. 统计各数字的出现次数 33 | # counter[num] 代表 num 的出现次数 34 | counter = [0] * (m + 1) 35 | for num in nums: 36 | counter[num] += 1 37 | # 3. 求 counter 的前缀和,将“出现次数”转换为“尾索引” 38 | # 即 counter[num]-1 是 num 在 res 中最后一次出现的索引 39 | for i in range(m): 40 | counter[i + 1] += counter[i] 41 | # 4. 倒序遍历 nums ,将各元素填入结果数组 res 42 | # 初始化数组 res 用于记录结果 43 | n = len(nums) 44 | res = [0] * n 45 | for i in range(n - 1, -1, -1): 46 | num = nums[i] 47 | res[counter[num] - 1] = num # 将 num 放置到对应索引处 48 | counter[num] -= 1 # 令前缀和自减 1 ,得到下次放置 num 的索引 49 | # 使用结果数组 res 覆盖原数组 nums 50 | for i in range(n): 51 | nums[i] = res[i] 52 | 53 | 54 | """Driver Code""" 55 | if __name__ == "__main__": 56 | nums = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] 57 | 58 | counting_sort_naive(nums) 59 | print(f"计数排序(无法排序对象)完成后 nums = {nums}") 60 | 61 | nums1 = [1, 0, 1, 2, 0, 4, 0, 2, 2, 4] 62 | counting_sort(nums1) 63 | print(f"计数排序完成后 nums1 = {nums1}") 64 | -------------------------------------------------------------------------------- /algorithms/c03_graph/graph_bfs.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: graph_bfs.py 3 | Created Time: 2023-02-23 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import Vertex, vals_to_vets, vets_to_vals 11 | from collections import deque 12 | from graph_adjacency_list import GraphAdjList 13 | 14 | 15 | def graph_bfs(graph: GraphAdjList, start_vet: Vertex) -> list[Vertex]: 16 | """广度优先遍历 BFS""" 17 | # 使用邻接表来表示图,以便获取指定顶点的所有邻接顶点 18 | # 顶点遍历序列 19 | res = [] 20 | # 哈希表,用于记录已被访问过的顶点 21 | visited = set[Vertex]([start_vet]) 22 | # 队列用于实现 BFS 23 | que = deque[Vertex]([start_vet]) 24 | # 以顶点 vet 为起点,循环直至访问完所有顶点 25 | while len(que) > 0: 26 | vet = que.popleft() # 队首顶点出队 27 | res.append(vet) # 记录访问顶点 28 | # 遍历该顶点的所有邻接顶点 29 | for adj_vet in graph.adj_list[vet]: 30 | if adj_vet in visited: 31 | continue # 跳过已被访问过的顶点 32 | que.append(adj_vet) # 只入队未访问的顶点 33 | visited.add(adj_vet) # 标记该顶点已被访问 34 | # 返回顶点遍历序列 35 | return res 36 | 37 | 38 | """Driver Code""" 39 | if __name__ == "__main__": 40 | # 初始化无向图 41 | v = vals_to_vets([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) 42 | edges = [ 43 | [v[0], v[1]], 44 | [v[0], v[3]], 45 | [v[1], v[2]], 46 | [v[1], v[4]], 47 | [v[2], v[5]], 48 | [v[3], v[4]], 49 | [v[3], v[6]], 50 | [v[4], v[5]], 51 | [v[4], v[7]], 52 | [v[5], v[8]], 53 | [v[6], v[7]], 54 | [v[7], v[8]], 55 | ] 56 | graph = GraphAdjList(edges) 57 | print("\n初始化后,图为") 58 | graph.print() 59 | 60 | # 广度优先遍历 BFS 61 | res = graph_bfs(graph, v[0]) 62 | print("\n广度优先遍历(BFS)顶点序列为") 63 | print(vets_to_vals(res)) 64 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/m05_subsequence.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 最长公共子序列 4 | # author: XiongNeng 5 | """ 6 | 一个子序列代表,将一个序列中去掉若干元素后得到的序列,可以间隔。 7 | 公共子序列就是,序列A和序列B的公共子序列 8 | 最长公共子序列就是,公共子序列里面长度最长的。 9 | 10 | 思路: 11 | 接受两个序列作为输入 12 | 将两个序列按照下标变成矩阵或者是二维数组c(m+1 * n+1) 13 | 按行主次序(row-major-order)计算表项,即首先计算C的第一行,然后是第二行。。 14 | 另外还维护一个二维数组b(m * n),b[i,j]指向的表项对应计算c[i,j]时选择的子问题最优解 15 | c[m-1][n-1]保存了X和Y的LCS的长度 16 | """ 17 | 18 | 19 | def lcs(arr1, arr2): 20 | m = len(arr1) 21 | n = len(arr2) 22 | c = [[0 for kk in range(n + 1)] for kk in range(m + 1)] 23 | b = [[-1 for kk in range(n)] for kk in range(m)] 24 | for i in range(1, m + 1): 25 | for j in range(1, n + 1): 26 | if arr1[i - 1] == arr2[j - 1]: 27 | c[i][j] = c[i - 1][j - 1] + 1 28 | b[i - 1][j - 1] = '↖' # 代表此元素放入LCS 29 | elif c[i - 1][j] >= c[i][j - 1]: 30 | c[i][j] = c[i - 1][j] 31 | b[i - 1][j - 1] = '↑' # 行减1,往上 32 | else: 33 | c[i][j] = c[i][j - 1] 34 | b[i - 1][j - 1] = '←' # 列减1,往左 35 | rlcs = [] 36 | get_lcs_arr(b, arr1, len(arr1) - 1, len(arr2) - 1, rlcs) 37 | print('LCS长度为:%d' % c[m][n]) 38 | print('一个最优解:%s' % str(rlcs)) 39 | return c[m][n], rlcs 40 | 41 | 42 | def get_lcs_arr(b, X, i, j, arr): 43 | if i < 0 or j < 0: 44 | return 45 | if b[i][j] == '↖': 46 | get_lcs_arr(b, X, i - 1, j - 1, arr) 47 | arr.append(X[i]) 48 | elif b[i][j] == '↑': 49 | get_lcs_arr(b, X, i - 1, j, arr) 50 | else: 51 | get_lcs_arr(b, X, i, j - 1, arr) 52 | 53 | 54 | if __name__ == '__main__': 55 | x = ['A', 'B', 'D', 'A', 'C', 'K'] 56 | y = ['B', 'D', 'D', 'E', 'C', 'K', 'M'] 57 | lcs(x, y) 58 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p800/a876_middle_of_the_linked_list.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 876. 链表的中间结点 4 | 给定一个头结点为 head 的非空单链表,返回链表的中间结点。 5 | 6 | 如果有两个中间结点,则返回第二个中间结点。 7 | 8 | 示例 1: 9 | 输入:[1,2,3,4,5] 10 | 输出:此列表中的结点 3 (序列化形式:[3,4,5]) 11 | 返回的结点值为 3 。 (测评系统对该结点序列化表述是 [3,4,5])。 12 | 注意,我们返回了一个 ListNode 类型的对象 ans,这样: 13 | ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL. 14 | 15 | 示例 2: 16 | 输入:[1,2,3,4,5,6] 17 | 输出:此列表中的结点 4 (序列化形式:[4,5,6]) 18 | 由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。 19 | 20 | 快慢指针经典案例。 21 | """ 22 | 23 | 24 | # Definition for singly-linked list. 25 | class ListNode: 26 | def __init__(self, val=0, next=None): 27 | self.val = val 28 | self.next = next 29 | 30 | def __str__(self): 31 | return str(self.val) 32 | 33 | def __iter__(self): 34 | node = self 35 | while node: 36 | yield node.val 37 | node = node.next 38 | 39 | 40 | class Solution: 41 | def middleNode(self, head: ListNode) -> ListNode: 42 | if not head: 43 | return head 44 | slow = fast = head 45 | while fast.next and fast.next.next: 46 | slow = slow.next 47 | fast = fast.next.next 48 | if fast.next: # 偶数情况 49 | return slow.next 50 | else: 51 | return slow 52 | 53 | 54 | if __name__ == '__main__': 55 | # input 56 | # list_input = list(input('input list: ').split()) 57 | head = ListNode(1) 58 | head.next = ListNode(2) 59 | head.next.next = ListNode(3) 60 | head.next.next.next = ListNode(4) 61 | head.next.next.next.next = ListNode(5) 62 | head.next.next.next.next.next = ListNode(6) 63 | # solve 64 | solution = Solution() 65 | res = solution.middleNode(head) 66 | # output 67 | print(list(res)) 68 | -------------------------------------------------------------------------------- /algorithms/c04_sort/radix_sort.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: radix_sort.py 3 | Created Time: 2023-03-26 4 | """ 5 | 6 | 7 | def digit(num: int, exp: int) -> int: 8 | """获取元素 num 的第 k 位,其中 exp = 10^(k-1)""" 9 | # 传入 exp 而非 k 可以避免在此重复执行昂贵的次方计算 10 | return (num // exp) % 10 11 | 12 | 13 | def counting_sort_digit(nums: list[int], exp: int): 14 | """计数排序(根据 nums 第 k 位排序)""" 15 | # 十进制的位范围为 0~9 ,因此需要长度为 10 的桶 16 | counter = [0] * 10 17 | n = len(nums) 18 | # 统计 0~9 各数字的出现次数 19 | for i in range(n): 20 | d = digit(nums[i], exp) # 获取 nums[i] 第 k 位,记为 d 21 | counter[d] += 1 # 统计数字 d 的出现次数 22 | # 求前缀和,将“出现个数”转换为“数组索引” 23 | for i in range(1, 10): 24 | counter[i] += counter[i - 1] 25 | # 倒序遍历,根据桶内统计结果,将各元素填入 res 26 | res = [0] * n 27 | for i in range(n - 1, -1, -1): 28 | d = digit(nums[i], exp) 29 | j = counter[d] - 1 # 获取 d 在数组中的索引 j 30 | res[j] = nums[i] # 将当前元素填入索引 j 31 | counter[d] -= 1 # 将 d 的数量减 1 32 | # 使用结果覆盖原数组 nums 33 | for i in range(n): 34 | nums[i] = res[i] 35 | 36 | 37 | def radix_sort(nums: list[int]): 38 | """基数排序""" 39 | # 获取数组的最大元素,用于判断最大位数 40 | m = max(nums) 41 | # 按照从低位到高位的顺序遍历 42 | exp = 1 43 | while exp <= m: 44 | # 对数组元素的第 k 位执行计数排序 45 | # k = 1 -> exp = 1 46 | # k = 2 -> exp = 10 47 | # 即 exp = 10^(k-1) 48 | counting_sort_digit(nums, exp) 49 | exp *= 10 50 | 51 | 52 | """Driver Code""" 53 | if __name__ == "__main__": 54 | # 基数排序 55 | nums = [ 56 | 10546151, 57 | 35663510, 58 | 42865989, 59 | 34862445, 60 | 81883077, 61 | 88906420, 62 | 72429244, 63 | 30524779, 64 | 82060337, 65 | 63832996, 66 | ] 67 | radix_sort(nums) 68 | print("基数排序完成后 nums =", nums) 69 | -------------------------------------------------------------------------------- /algorithms/c02_tree/heap.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: heap.py 3 | Created Time: 2023-02-23 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import print_heap 11 | 12 | import heapq 13 | 14 | 15 | def test_push(heap: list, val: int, flag: int = 1): 16 | heapq.heappush(heap, flag * val) # 元素入堆 17 | print(f"\n元素 {val} 入堆后") 18 | print_heap([flag * val for val in heap]) 19 | 20 | 21 | def test_pop(heap: list, flag: int = 1): 22 | val = flag * heapq.heappop(heap) # 堆顶元素出堆 23 | print(f"\n堆顶元素 {val} 出堆后") 24 | print_heap([flag * val for val in heap]) 25 | 26 | 27 | """Driver Code""" 28 | if __name__ == "__main__": 29 | # 初始化小顶堆 30 | min_heap, flag = [], 1 31 | # 初始化大顶堆 32 | max_heap, flag = [], -1 33 | 34 | print("\n以下测试样例为大顶堆") 35 | # Python 的 heapq 模块默认实现小顶堆 36 | # 考虑将“元素取负”后再入堆,这样就可以将大小关系颠倒,从而实现大顶堆 37 | # 在本示例中,flag = 1 时对应小顶堆,flag = -1 时对应大顶堆 38 | 39 | # 元素入堆 40 | test_push(max_heap, 1, flag) 41 | test_push(max_heap, 3, flag) 42 | test_push(max_heap, 2, flag) 43 | test_push(max_heap, 5, flag) 44 | test_push(max_heap, 4, flag) 45 | 46 | # 获取堆顶元素 47 | peek: int = flag * max_heap[0] 48 | print(f"\n堆顶元素为 {peek}") 49 | 50 | # 堆顶元素出堆 51 | test_pop(max_heap, flag) 52 | test_pop(max_heap, flag) 53 | test_pop(max_heap, flag) 54 | test_pop(max_heap, flag) 55 | test_pop(max_heap, flag) 56 | 57 | # 获取堆大小 58 | size: int = len(max_heap) 59 | print(f"\n堆元素数量为 {size}") 60 | 61 | # 判断堆是否为空 62 | is_empty: bool = not max_heap 63 | print(f"\n堆是否为空 {is_empty}") 64 | 65 | # 输入列表并建堆 66 | # 时间复杂度为 O(n) ,而非 O(nlogn) 67 | min_heap = [1, 3, 2, 5, 4] 68 | heapq.heapify(min_heap) 69 | print("\n输入列表并建立小顶堆后") 70 | print_heap(min_heap) 71 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/n_queens.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: n_queens.py 3 | Created Time: 2023-04-26 4 | """ 5 | 6 | 7 | def backtrack( 8 | row: int, 9 | n: int, 10 | state: list[list[str]], 11 | res: list[list[list[str]]], 12 | cols: list[bool], 13 | diags1: list[bool], 14 | diags2: list[bool], 15 | ): 16 | """回溯算法:N 皇后""" 17 | # 当放置完所有行时,记录解 18 | if row == n: 19 | res.append([list(row) for row in state]) 20 | return 21 | # 遍历所有列 22 | for col in range(n): 23 | # 计算该格子对应的主对角线和副对角线 24 | diag1 = row - col + n - 1 25 | diag2 = row + col 26 | # 剪枝:不允许该格子所在列、主对角线、副对角线存在皇后 27 | if not cols[col] and not diags1[diag1] and not diags2[diag2]: 28 | # 尝试:将皇后放置在该格子 29 | state[row][col] = "Q" 30 | cols[col] = diags1[diag1] = diags2[diag2] = True 31 | # 放置下一行 32 | backtrack(row + 1, n, state, res, cols, diags1, diags2) 33 | # 回退:将该格子恢复为空位 34 | state[row][col] = "#" 35 | cols[col] = diags1[diag1] = diags2[diag2] = False 36 | 37 | 38 | def n_queens(n: int) -> list[list[list[str]]]: 39 | """求解 N 皇后""" 40 | # 初始化 n*n 大小的棋盘,其中 'Q' 代表皇后,'#' 代表空位 41 | state = [["#" for _ in range(n)] for _ in range(n)] 42 | cols = [False] * n # 记录列是否有皇后 43 | diags1 = [False] * (2 * n - 1) # 记录主对角线是否有皇后 44 | diags2 = [False] * (2 * n - 1) # 记录副对角线是否有皇后 45 | res = [] 46 | backtrack(0, n, state, res, cols, diags1, diags2) 47 | 48 | return res 49 | 50 | 51 | """Driver Code""" 52 | if __name__ == "__main__": 53 | n = 4 54 | res = n_queens(n) 55 | 56 | print(f"输入棋盘长宽为 {n}") 57 | print(f"皇后放置方案共有 {len(res)} 种") 58 | for state in res: 59 | print("--------------------") 60 | for row in state: 61 | print(row) 62 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p000/a21_merge_two_sorted_lists.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 21. 合并两个有序链表 4 | 5 | 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 6 | 7 | 可使用类似贪吃蛇的算法。当前指针就像一条贪吃蛇一样,将两个队列节点一个个吃掉。 8 | 任何时刻,都有一个队列是蛇,一个队列是食物。但是贪吃蛇一次只能吃一边的一个节点, 9 | 当它吃掉一个节点时,该节点所在的队列成为蛇的身体,另外一个队列成为食物。最后食物吃完循环结束。 10 | """ 11 | 12 | # Definition for singly-linked list. 13 | from typing import Optional 14 | 15 | 16 | class ListNode: 17 | def __init__(self, val=0, next=None): 18 | self.val = val 19 | self.next = next 20 | 21 | def __str__(self): 22 | return self.val 23 | 24 | 25 | class Solution: 26 | def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: 27 | if not list1: 28 | return list2 29 | if not list2: 30 | return list1 31 | result = list1 if list1.val <= list2.val else list2 32 | snake = result # 贪吃蛇指针 33 | food = list2 if list1.val <= list2.val else list1 # 要吞并的食物,跟蛇身取反 34 | while food: # 只要食物还在就一直吃下去 35 | if not snake.next: # 贪吃蛇自己到头了,就一口吃掉剩余所有食物,循环结束。 36 | snake.next = food 37 | break 38 | if snake.next.val <= food.val: 39 | snake = snake.next # 自己的身体的下一部分比较小,就先吃自己。 40 | else: 41 | temp_food = snake.next 42 | snake.next = food # 食物比较小,先吃食物,让食物变成蛇的一部分 43 | food = temp_food # 将原来剩余的蛇身变成食物。这样完成了蛇身和食物的交换。 44 | return result 45 | 46 | 47 | if __name__ == '__main__': 48 | a = ListNode(1) 49 | b = ListNode(2) 50 | c = ListNode(4) 51 | a.next = b 52 | b.next = c 53 | 54 | aa = ListNode(1) 55 | bb = ListNode(3) 56 | cc = ListNode(4) 57 | aa.next = bb 58 | bb.next = cc 59 | 60 | r = Solution().mergeTwoLists(a, aa) 61 | print(r) 62 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m03_merge_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """归并排序(分治法) 3 | 归并排序算法完全遵循分治模式,操作如下: 4 | 分解: 分解待排序的n个元素序列成各具n/2个元素的两个子序列 5 | 解决: 使用归并排序递归的排序两个子序列 6 | 合并: 合并两个已排序的子序列以产生已排序的答案 7 | 8 | 复杂度:N*lg(N) 9 | 稳定排序:重复元素排序完后仍然保持原来的相对位置。 10 | 11 | 它有一个致命的“弱点”,那就是归并排序不是原地排序算法。 12 | """ 13 | from algorithms.c04_sort.base.template import SortTemplate 14 | 15 | 16 | class MergeSort(SortTemplate): 17 | 18 | def sort(self): 19 | self.merge_sort_range(0, len(self.seq) - 1) 20 | 21 | def merge_sort_range(self, start, end): 22 | """ 23 | 归并排序一个序列的子序列 24 | start: 子序列的start下标 25 | end: 子序列的end下标 26 | """ 27 | if start < end: # 如果start >= end就终止递归调用 28 | middle = (start + end) // 2 29 | self.merge_sort_range(start, middle) # 排好左边的一半 30 | self.merge_sort_range(middle + 1, end) # 再排好右边的一半 31 | self.merge_ordered_seq(start, middle, end) # 最后合并排序结果 32 | 33 | def merge_ordered_seq(self, left, middle, right): 34 | """ 35 | seq: 待排序序列 36 | left <= middle <= right 37 | 子数组seq[left..middle]和seq[middle+1..right]都是排好序的 38 | 该排序的时间复杂度为O(n) 39 | """ 40 | temp_seq = [] 41 | i = left 42 | j = middle + 1 43 | while i <= middle and j <= right: 44 | if self.seq[i] <= self.seq[j]: 45 | temp_seq.append(self.seq[i]) 46 | i += 1 47 | else: 48 | temp_seq.append(self.seq[j]) 49 | j += 1 50 | if i <= middle: 51 | temp_seq.extend(self.seq[i:middle + 1]) 52 | else: 53 | temp_seq.extend(self.seq[j:right + 1]) 54 | self.seq[left:right + 1] = temp_seq[:] 55 | 56 | 57 | if __name__ == '__main__': 58 | merge_sort = MergeSort([4, 2, 5, 1, 6, 3]) 59 | merge_sort.main() 60 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p200/a215_kth_largest_element_in_an_array.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 215. 数组中的第K个最大元素 4 | 5 | 给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。 6 | 请注意,你需要找的是数组排序后的第k个最大的元素,而不是第k个不同的元素。 7 | 8 | 输入: [3,2,1,5,6,4] 和 k = 2 9 | 输出: 5 10 | 11 | 输入: [3,2,3,1,2,4,5,5,6] 和 k = 4 12 | 输出: 4 13 | 14 | 算法思路: 15 | 采用快排的分治思想。我们选择数组区间 A[0...n-1]的最后一个元素 A[n-1]作为 pivot, 16 | 对数组 A[0...n-1]原地分区,左边大右边小。分区完成后数组就分成了三部分,A[0...p-1]、A[p]、A[p+1...n-1]。 17 | 如果 p+1=K,那 A[p]就是要求解的元素; 18 | 如果 p+1K,说明第 K 大元素出现在左边 A[0...p-1]区间。 20 | 我们再按照上面的思路递归地在 A[p+1...n-1] 或者A[0...p-1]区间内存查找。 21 | """ 22 | from typing import List 23 | 24 | 25 | class Solution: 26 | def findKthLargest(self, nums: List[int], k: int) -> int: 27 | return self.recursive_choose_pivot(nums, 0, len(nums) - 1, k) 28 | 29 | def recursive_choose_pivot(self, nums: List[int], start, end, k: int) -> int: 30 | while start <= end: 31 | pivot = self.choose_pivot_by_last(nums, start, end) 32 | if pivot + 1 == k: 33 | return nums[pivot] 34 | if pivot + 1 < k: 35 | return self.recursive_choose_pivot(nums, pivot + 1, end, k) 36 | else: 37 | return self.recursive_choose_pivot(nums, start, pivot - 1, k) 38 | 39 | def choose_pivot_by_last(self, nums: List[int], start, end) -> int: 40 | pivot_value = nums[end] # 将最后一个元素定为pivot 41 | i = start - 1 # 以退为进,初始值设置为start-1 42 | for j in range(start, end): 43 | if nums[j] >= pivot_value: # 将大的数放左边 44 | i += 1 45 | nums[i], nums[j] = nums[j], nums[i] 46 | nums[i + 1], nums[end] = nums[end], nums[i + 1] 47 | return i + 1 48 | 49 | 50 | if __name__ == '__main__': 51 | res = Solution().findKthLargest([3, 2, 1, 5, 6, 4], 2) 52 | assert res == 5 53 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m07_max_subarr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 寻找最大子数组(分治法) 4 | """ 5 | Topic: sample 6 | Desc : 寻找最大子数组 7 | 寻找数组A的和最大的非空连续的子数组 8 | 假定寻找子数组A[low..high]的最大子数组,使用分治法将A划分为两个规模尽量相等的子数组 9 | A[low..middle]和A[middle+1..high],那么A[low..high]的任何连续子数组必须是下来三种之一: 10 | 1, 完全位于A[low..middle]中,即low<=i<=j<=middle 11 | 2, 完全位于A[middle+1..high]中,即middle leftSum: 42 | leftSum = sumTemp 43 | maxLeft = i 44 | rightSum = float('-Inf') 45 | sumTemp = 0 46 | for j in range(mid + 1, high + 1): 47 | sumTemp += seq[j] 48 | if sumTemp > rightSum: 49 | rightSum = sumTemp 50 | maxRight = j 51 | return maxLeft, maxRight, leftSum + rightSum 52 | 53 | 54 | if __name__ == '__main__': 55 | print(maxSubArr([13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7])) 56 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m06_heap_sort.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """使用二叉堆实现堆排序最优雅代码 3 | 原理:如果左右子树都已经是最大堆了,则通过下沉当前根节点操作即可重新构造为一个最大堆。 4 | """ 5 | from algorithms.c04_sort.base.template import SortTemplate 6 | 7 | 8 | class HeapSort(SortTemplate): 9 | 10 | def __init__(self, seq): 11 | super().__init__(seq) 12 | self._size = len(seq) 13 | 14 | def sort(self): 15 | """最简的堆排序算法""" 16 | # 第一步:首先通过下沉操作构造最大二叉堆 17 | k = self._size // 2 # 从中间开始往前循环 18 | while k >= 1: 19 | self._sink(k, self._size) 20 | k -= 1 21 | 22 | # 第二步:通过每次将堆顶元素跟后面元素交换,堆大小减少1。 23 | # 然后再下沉堆顶元素重新构造堆,直到堆大小为1 24 | k = self._size 25 | while k > 1: 26 | self._exchange(1, k) 27 | k -= 1 28 | self._sink(1, k) 29 | 30 | def _sink(self, index, n): 31 | """向下沉下去,把老大的位置叫出来,谁更牛逼谁做老大""" 32 | while 2 * index <= n: 33 | j = 2 * index 34 | if j < n and self._less(j, j + 1): 35 | j += 1 # 如果有两个下属,把最牛逼的下属拿出来做对比 36 | if not self._less(index, j): 37 | break # 如果比最牛逼的那个下属还要厉害,说明这个老大位置没问题了 38 | self._exchange(index, j) # 如果没有下属厉害,就自己乖乖把位置让出来,跟他交换一下 39 | index = j # 现在index的值修改成新的位置,继续向下做对比,直到找到自己合适的位置 40 | 41 | def _less(self, i, j): 42 | """ 43 | 比较两个元素大小,如果左边小于等于右边,返回True 44 | 注意,这里索引都减1,用来支持索引值从1开始的序列 45 | """ 46 | if self.seq[i - 1] is None or self.seq[j - 1] is None: 47 | raise IndexError('index error') 48 | return self.seq[i - 1] < self.seq[j - 1] 49 | 50 | def _exchange(self, i, j): 51 | """交换两个元素,这里索引也都减1""" 52 | self.seq[i - 1], self.seq[j - 1] = self.seq[j - 1], self.seq[i - 1] 53 | 54 | 55 | if __name__ == '__main__': 56 | heap_sort = HeapSort([9, 7, 8, 10, 16, 3, 14, 2, 1, 4]) 57 | heap_sort.main() 58 | -------------------------------------------------------------------------------- /algorithms/c08_backtrack/preorder_traversal_iii_template.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: preorder_traversal_iii_template.py 3 | Created Time: 2023-04-15 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import TreeNode, print_tree, list_to_tree 11 | 12 | 13 | def is_solution(state: list[TreeNode]) -> bool: 14 | """判断当前状态是否为解""" 15 | return state and state[-1].val == 7 16 | 17 | 18 | def record_solution(state: list[TreeNode], res: list[list[TreeNode]]): 19 | """记录解""" 20 | res.append(list(state)) 21 | 22 | 23 | def is_valid(state: list[TreeNode], choice: TreeNode) -> bool: 24 | """判断在当前状态下,该选择是否合法""" 25 | return choice is not None and choice.val != 3 26 | 27 | 28 | def make_choice(state: list[TreeNode], choice: TreeNode): 29 | """更新状态""" 30 | state.append(choice) 31 | 32 | 33 | def undo_choice(state: list[TreeNode], choice: TreeNode): 34 | """恢复状态""" 35 | state.pop() 36 | 37 | 38 | def backtrack(state: list[TreeNode], choices: list[TreeNode], res: list[list[TreeNode]]): 39 | """回溯算法:例题三""" 40 | # 检查是否为解 41 | if is_solution(state): 42 | # 记录解 43 | record_solution(state, res) 44 | # 遍历所有选择 45 | for choice in choices: 46 | # 剪枝:检查选择是否合法 47 | if is_valid(state, choice): 48 | # 尝试:做出选择,更新状态 49 | make_choice(state, choice) 50 | # 进行下一轮选择 51 | backtrack(state, [choice.left, choice.right], res) 52 | # 回退:撤销选择,恢复到之前的状态 53 | undo_choice(state, choice) 54 | 55 | 56 | """Driver Code""" 57 | if __name__ == "__main__": 58 | root = list_to_tree([1, 7, 3, 4, 5, 6, 7]) 59 | print("\n初始化二叉树") 60 | print_tree(root) 61 | 62 | # 回溯算法 63 | res = [] 64 | backtrack(state=[], choices=[root], res=res) 65 | 66 | print("\n输出所有根节点到节点 7 的路径,要求路径中不包含值为 3 的节点") 67 | for path in res: 68 | print([node.val for node in path]) 69 | -------------------------------------------------------------------------------- /algorithms/models/tree_node.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: tree_node.py 3 | Created Time: 2021-12-11 4 | """ 5 | 6 | from collections import deque 7 | 8 | 9 | class TreeNode: 10 | """二叉树节点类""" 11 | 12 | def __init__(self, val: int = 0): 13 | self.val: int = val # 节点值 14 | self.height: int = 0 # 节点高度 15 | self.left: TreeNode | None = None # 左子节点引用 16 | self.right: TreeNode | None = None # 右子节点引用 17 | 18 | # 序列化编码规则请参考: 19 | # https://www.hello-algo.com/chapter_tree/array_representation_of_tree/ 20 | # 二叉树的数组表示: 21 | # [1, 2, 3, 4, None, 6, 7, 8, 9, None, None, 12, None, None, 15] 22 | # 二叉树的链表表示: 23 | # /——— 15 24 | # /——— 7 25 | # /——— 3 26 | # | \——— 6 27 | # | \——— 12 28 | # ——— 1 29 | # \——— 2 30 | # | /——— 9 31 | # \——— 4 32 | # \——— 8 33 | 34 | 35 | def list_to_tree_dfs(arr: list[int], i: int) -> TreeNode | None: 36 | """将列表反序列化为二叉树:递归""" 37 | # 如果索引超出数组长度,或者对应的元素为 None ,则返回 None 38 | if i < 0 or i >= len(arr) or arr[i] is None: 39 | return None 40 | # 构建当前节点 41 | root = TreeNode(arr[i]) 42 | # 递归构建左右子树 43 | root.left = list_to_tree_dfs(arr, 2 * i + 1) 44 | root.right = list_to_tree_dfs(arr, 2 * i + 2) 45 | return root 46 | 47 | 48 | def list_to_tree(arr: list[int]) -> TreeNode | None: 49 | """将列表反序列化为二叉树""" 50 | return list_to_tree_dfs(arr, 0) 51 | 52 | 53 | def tree_to_list_dfs(root: TreeNode, i: int, res: list[int]) -> list[int]: 54 | """将二叉树序列化为列表:递归""" 55 | if root is None: 56 | return 57 | if i >= len(res): 58 | res += [None] * (i - len(res) + 1) 59 | res[i] = root.val 60 | tree_to_list_dfs(root.left, 2 * i + 1, res) 61 | tree_to_list_dfs(root.right, 2 * i + 2, res) 62 | 63 | 64 | def tree_to_list(root: TreeNode | None) -> list[int]: 65 | """将二叉树序列化为列表""" 66 | res = [] 67 | tree_to_list_dfs(root, 0, res) 68 | return res 69 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/m03_matrix_chain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | """ 4 | Topic: 动态规划:最优矩阵链乘法括号化算法 5 | Desc : 6 | 对于矩阵A[1]A[2]...A[n]相乘,由于满足结合律,求一个最优括号算法使得计算的次数最少 7 | 对于矩阵A[i]*A[j]的计算次数为:p[i-1]*p[i]*p[j] => row(左)*column(左)*column(右) 8 | 算法思路: 9 | 给定一个序列p:,对每个Ai 10 | 定义一个n*n二维数组m,其中m[i,j]=min(i<= k < j){m[i,k]+m[k+1,j]+p[i-1]*p[k]*p[j]} 11 | 再定义一个n-1*n-1二维数组s,其中s[i,j]表示m[i,j]时候的括号分割点,利用s可以获得最后的括号序列 12 | """ 13 | 14 | 15 | def matrix_order(p): 16 | """:param p: 矩阵规模序列,A[i]行列分别为p[i-1],p[i]""" 17 | INF = float('inf') # 无穷大 18 | n = len(p) - 1 # 矩阵长度 19 | vals = [[0 for _ in range(n)] for _ in range(n)] # 保存子问题A[i]...A[j]最优值 20 | ans = [[-1 for _ in range(n)] for _ in range(n)] # 保存子问题A[i]...A[j]最优值时候的括号分割点 21 | for chain_len in range(2, n + 1): # chain_len表示每次循环计算链的长度2..n 22 | for i in range(0, n - chain_len + 1): 23 | j = i + chain_len - 1 24 | vals[i][j] = INF # 上面两层循环则是对m方阵的右上三角(除对角线)进行某个赋值MAX 25 | for k in range(i, j): # 然后对每个计算最小值 26 | # 此时m[i][k]和m[k + 1][j]一定已经有值了。why??? 27 | # 因为对于某个i,比j小的肯定赋值过 28 | # 对于某个j,比i大的肯定也赋值过 29 | # 上面循环方向示意图可以画下,是从右上三角,斜右下右下的循环。 30 | q = vals[i][k] + vals[k + 1][j] + p[i] * p[k + 1] * p[j + 1] 31 | if q < vals[i][j]: 32 | vals[i][j] = q 33 | ans[i][j] = k 34 | for mm in vals: 35 | print(mm) 36 | for ss in ans: 37 | print(ss) 38 | print_optimal(ans, 0, n - 1) 39 | return vals, ans 40 | 41 | 42 | def print_optimal(s, i, j): 43 | """根据保存的括号位置表打印出最后的括号最优解""" 44 | if i == j: 45 | print('A', end='') 46 | else: 47 | print('(', end='') 48 | print_optimal(s, i, s[i][j]) 49 | print_optimal(s, s[i][j] + 1, j) 50 | print(')', end='') 51 | 52 | 53 | if __name__ == '__main__': 54 | p = [30, 35, 15, 5, 10, 20, 25] 55 | matrix_order(p) 56 | -------------------------------------------------------------------------------- /algorithms/models/print_util.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: print_util.py 3 | Created Time: 2021-12-11 4 | """ 5 | 6 | from .tree_node import TreeNode, list_to_tree 7 | from .list_node import ListNode, linked_list_to_list 8 | 9 | 10 | def print_matrix(mat: list[list[int]]): 11 | """Print a matrix""" 12 | s = [] 13 | for arr in mat: 14 | s.append(" " + str(arr)) 15 | print("[\n" + ",\n".join(s) + "\n]") 16 | 17 | 18 | def print_linked_list(head: ListNode | None): 19 | """Print a linked list""" 20 | arr: list[int] = linked_list_to_list(head) 21 | print(" -> ".join([str(a) for a in arr])) 22 | 23 | 24 | class Trunk: 25 | def __init__(self, prev, string: str | None = None): 26 | self.prev = prev 27 | self.str = string 28 | 29 | 30 | def show_trunks(p: Trunk | None): 31 | if p is None: 32 | return 33 | show_trunks(p.prev) 34 | print(p.str, end="") 35 | 36 | 37 | def print_tree(root: TreeNode | None, prev: Trunk | None = None, is_right: bool = False): 38 | """ 39 | Print a binary tree 40 | This tree printer is borrowed from TECHIE DELIGHT 41 | https://www.techiedelight.com/c-program-print-binary-tree/ 42 | """ 43 | if root is None: 44 | return 45 | 46 | prev_str = " " 47 | trunk = Trunk(prev, prev_str) 48 | print_tree(root.right, trunk, True) 49 | 50 | if prev is None: 51 | trunk.str = "———" 52 | elif is_right: 53 | trunk.str = "/———" 54 | prev_str = " |" 55 | else: 56 | trunk.str = "\———" 57 | prev.str = prev_str 58 | 59 | show_trunks(trunk) 60 | print(" " + str(root.val)) 61 | if prev: 62 | prev.str = prev_str 63 | trunk.str = " |" 64 | print_tree(root.left, trunk, False) 65 | 66 | 67 | def print_dict(hmap: dict): 68 | """Print a dict""" 69 | for key, value in hmap.items(): 70 | print(key, "->", value) 71 | 72 | 73 | def print_heap(heap: list[int]): 74 | """Print a heap both in array and tree representations""" 75 | print("堆的数组表示:", heap) 76 | print("堆的树状表示:") 77 | root: TreeNode | None = list_to_tree(heap) 78 | print_tree(root) 79 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m08_max_subarr2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 寻找最大子数组(对小数组采用暴力算法) 4 | """ 5 | Topic: sample 6 | Desc : 寻找最大子数组(对小数组采用暴力算法) 7 | 理论跟归并排序中对小数组采用插入排序一样,有个阀值,直接写结论: 8 | 最后结论: k < lg(n)的时候,使用暴力算法 9 | """ 10 | from math import log 11 | 12 | __author__ = 'Xiong Neng' 13 | 14 | 15 | def maxSubArr(seq): 16 | return __findMaxSubArr(seq, 0, len(seq) - 1, log(len(seq), 2)) 17 | 18 | 19 | def __findMaxSubArr(seq, low, high, threshold): 20 | if high - low + 1 < threshold: 21 | return __violentSubArr(seq, low, high) 22 | elif low == high: 23 | return low, high, seq[low] 24 | else: 25 | mid = (low + high) // 2 26 | l = lefLow, leftHigh, leftSum = __findMaxSubArr(seq, low, mid, threshold) 27 | r = rightLow, rightHigh, right_sum = __findMaxSubArr(seq, mid + 1, high, threshold) 28 | c = crossLow, crossHigh, crossSum = __maxCrossingSubArr(seq, low, mid, high) 29 | return max([l, r, c], key=lambda k: k[2]) # 这个太cool了 30 | 31 | 32 | def __violentSubArr(seq, low, high): 33 | maxSum = float('-Inf') 34 | for i in range(low, high + 1): 35 | eachSum = 0 36 | for j in range(i, high + 1): 37 | eachSum += seq[j] 38 | if eachSum > maxSum: 39 | low, high = i, j 40 | maxSum = eachSum 41 | return low, high, maxSum 42 | 43 | 44 | def __maxCrossingSubArr(seq, low, mid, high): 45 | """ 46 | 寻找seq[low..high]跨越了中点mid的最大子数组 47 | 总循环次数为high-low+1,线性的 48 | """ 49 | leftSum = float('-Inf') 50 | sumTemp = 0 51 | for i in range(mid, low - 1, -1): 52 | sumTemp += seq[i] 53 | if sumTemp > leftSum: 54 | leftSum = sumTemp 55 | maxLeft = i 56 | rightSum = float('-Inf') 57 | sumTemp = 0 58 | for j in range(mid + 1, high + 1): 59 | sumTemp += seq[j] 60 | if sumTemp > rightSum: 61 | rightSum = sumTemp 62 | maxRight = j 63 | return maxLeft, maxRight, leftSum + rightSum 64 | 65 | 66 | if __name__ == '__main__': 67 | print(maxSubArr([13, -3, -25, 20, -3, -16, -23, 18, 20, -7, 12, -5, -22, 15, -4, 7])) 68 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m12_imin_select2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 顺序统计量的选择算法(最坏情况下O(n)) 4 | """ 5 | Topic: sample 6 | Desc : 顺序统计量的选择算法(最坏情况下O(n)) 7 | 利用中位数的中位数作为pivot划分数组 8 | """ 9 | from algorithms.c04_sort.base.sortutil import insert_sort 10 | 11 | 12 | def iminSelect2(A, i): 13 | """ 14 | 返回数组A中第i小的元素,返回结果也就是A从小到大排序后第i个元素 15 | """ 16 | return __iminSelect2(A, 0, len(A) - 1, i) 17 | 18 | 19 | def __iminSelect2(A, p, r, nn): 20 | """ 21 | 返回数组A[p..r]中第i小的元素 22 | 利用中位数的中位数作为pivot划分数组 23 | """ 24 | if p == r: 25 | return A[p] 26 | midOfMid = __selectMidOfMid(A[p: r + 1]) 27 | midIndex = p 28 | for i in range(p, r + 1): 29 | if A[i] == midOfMid: 30 | midIndex = i 31 | break 32 | q = __midPartition(A, p, r, midIndex) 33 | k = q - p + 1 34 | if nn == k: 35 | return A[q] 36 | elif nn < k: 37 | return __iminSelect2(A, p, q - 1, nn) 38 | else: 39 | return __iminSelect2(A, q + 1, r, nn - k) 40 | 41 | 42 | def __selectMidOfMid(seq): 43 | """获取中位数的中位数算法""" 44 | while len(seq) > 1: 45 | grpNum, lastNum = divmod(len(seq), 5) # 分组,每组5个 46 | midArr = [] # 每组的中位数列表 47 | for i in range(0, grpNum): 48 | eachGroup = seq[i * 5: (i + 1) * 5] 49 | insert_sort(eachGroup) 50 | midArr.append(eachGroup[2]) 51 | if lastNum > 0: 52 | lastGroup = seq[grpNum * 5: grpNum * 5 + lastNum] 53 | insert_sort(lastGroup) 54 | midArr.append(lastGroup[(lastNum - 1) // 2]) 55 | seq = midArr 56 | return seq[0] 57 | 58 | 59 | def __midPartition(A, p, r, midIndex): 60 | """分解子数组: 中位数作为pivot的版本""" 61 | A[midIndex], A[r] = A[r], A[midIndex] # 还是将这个pivot放到最后 62 | x = A[r] 63 | i = p - 1 64 | for j in range(p, r): 65 | if A[j] <= x: 66 | i += 1 67 | A[i], A[j] = A[j], A[i] 68 | A[i + 1], A[r] = A[r], A[i + 1] 69 | return i + 1 70 | 71 | 72 | if __name__ == '__main__': 73 | bb = [4, 23, 65, 3, 22, 3, 34, 3, 67, 3, 12, 3, 7, 1, 1, 256, 3, 34, 27] 74 | print(sorted(bb)) 75 | print(iminSelect2(bb, 10)) 76 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m14_bestsinger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 歌曲投票 4 | """ 5 | Topic: 找出7号歌曲包揽5项大奖的投票组合 6 | Desc : 7 | 云宏歌词比赛投票,总共166人参与投票 8 | 每人从1-13编号的歌曲中选5首不同的歌,依次放入1-5号大奖箱中, 9 | 最后分别将每个奖箱中得票数最多的那个号码提取出来。 10 | 请找出一个组合使得7号歌曲包揽5项大奖的投票组合 11 | """ 12 | 13 | __author__ = 'Xiong Neng' 14 | 15 | 16 | def lucky_seven(rows=166, cols=5, choices=13, lucky=7, start=1): 17 | """ 18 | 找出一个7号包揽5项大奖的投票组合 19 | rows: 行数,也就是投票人数 20 | cols:列数,也就是每人允许投几票 21 | choices:被投票的歌曲的数量,从1-choices编号 22 | lucky:幸运数字,终止这首歌包揽5项大奖 23 | start:列填充循环时候的起始数 24 | """ 25 | # 先初始化一个二维输入数组 26 | votes = [[None] * cols for x in range(rows)] 27 | # 第一步用7斜线填充 28 | for i in range(rows): 29 | votes[i][i % cols] = lucky 30 | # 第二步,填充每一列 31 | for col in range(cols): 32 | nextnum = start 33 | for row in range(rows): 34 | if votes[row][col] is None: 35 | while nextnum in votes[row]: 36 | nextnum += 1 37 | if nextnum > choices: 38 | nextnum -= choices 39 | if nextnum > choices: 40 | nextnum -= choices 41 | votes[row][col] = nextnum 42 | nextnum += 1 43 | return votes 44 | 45 | 46 | def analyse_votes(votes): 47 | rows = len(votes) 48 | cols = len(votes[0]) 49 | result = [] 50 | for col in range(cols): 51 | counts = {} 52 | for row in range(rows): 53 | num = votes[row][col] 54 | if num in counts: 55 | counts[num] += 1 56 | else: 57 | counts[num] = 1 58 | sort_row = sorted(counts.items(), key=lambda x: x[1], reverse=True) 59 | result.append(sort_row[0]) 60 | counts.clear() 61 | 62 | print('The Champion'.center(50, '*')) 63 | for r in range(len(result)): 64 | print('column %2d: ' % (r + 1,), result[r]) 65 | 66 | print('Last votes: '.center(50, '*')) 67 | for r in votes: 68 | print(r) 69 | 70 | 71 | def main(): 72 | votes = lucky_seven() 73 | analyse_votes(votes) 74 | 75 | 76 | if __name__ == '__main__': 77 | main() 78 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/stack/linkedlist_stack.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: linkedlist_stack.py 3 | Created Time: 2022-11-29 4 | Author: Peng Chen (pengchzn@gmail.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import ListNode 12 | 13 | 14 | class LinkedListStack: 15 | """基于链表实现的栈""" 16 | 17 | def __init__(self): 18 | """构造方法""" 19 | self._peek: ListNode | None = None 20 | self._size: int = 0 21 | 22 | def size(self) -> int: 23 | """获取栈的长度""" 24 | return self._size 25 | 26 | def is_empty(self) -> bool: 27 | """判断栈是否为空""" 28 | return not self._peek 29 | 30 | def push(self, val: int): 31 | """入栈""" 32 | node = ListNode(val) 33 | node.next = self._peek 34 | self._peek = node 35 | self._size += 1 36 | 37 | def pop(self) -> int: 38 | """出栈""" 39 | num = self.peek() 40 | self._peek = self._peek.next 41 | self._size -= 1 42 | return num 43 | 44 | def peek(self) -> int: 45 | """访问栈顶元素""" 46 | if self.is_empty(): 47 | raise IndexError("栈为空") 48 | return self._peek.val 49 | 50 | def to_list(self) -> list[int]: 51 | """转化为列表用于打印""" 52 | arr = [] 53 | node = self._peek 54 | while node: 55 | arr.append(node.val) 56 | node = node.next 57 | arr.reverse() 58 | return arr 59 | 60 | 61 | """Driver Code""" 62 | if __name__ == "__main__": 63 | # 初始化栈 64 | stack = LinkedListStack() 65 | 66 | # 元素入栈 67 | stack.push(1) 68 | stack.push(3) 69 | stack.push(2) 70 | stack.push(5) 71 | stack.push(4) 72 | print("栈 stack =", stack.to_list()) 73 | 74 | # 访问栈顶元素 75 | peek: int = stack.peek() 76 | print("栈顶元素 peek =", peek) 77 | 78 | # 元素出栈 79 | pop: int = stack.pop() 80 | print("出栈元素 pop =", pop) 81 | print("出栈后 stack =", stack.to_list()) 82 | 83 | # 获取栈的长度 84 | size: int = stack.size() 85 | print("栈的长度 size =", size) 86 | 87 | # 判断是否为空 88 | is_empty: bool = stack.is_empty() 89 | print("栈是否为空 =", is_empty) 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 飞污熊算法笔记 - python3实现 2 | 3 | 算法参考书籍列表,建议按照顺序阅读: 4 | 5 | * 《Hello算法》 6 | * 《算法图解》 7 | * 《啊哈算法》 8 | * 《算法导论》 9 | * 《labuladong的算法小抄》 10 | 11 | ## 章节说明 12 | 13 | 1. 【第一章】基础数据结构:数组、链表、队列、栈、哈希表。 14 | 2. 【第二章】树数据结构:二叉树、红黑树、递归树、堆。 15 | 3. 【第三章】图数据结构 16 | 4. 【第四章】各类排序算法:(冒泡、插入、选择)、(快排、归并)、(桶、计数、基数)。 17 | 5. 【第五章】各类搜索算法:二分查找、调表、散列表、哈希算法 18 | 6. 【第六章】字符串匹配算法 19 | 7. 【第七章】分治算法 20 | 8. 【第八章】回溯算法 21 | 9. 【第九章】动态规划算法 22 | 10. 【第十章】贪心算法 23 | 11. 【第十一章】高级数据结构和算法 24 | 12. 【第十三章】LeetCode刷题 25 | 13. 【第十二章】AI算法 26 | 14. 【第十四章】算法小抄 27 | 28 | ## 作者的话 29 | 30 | 书中经典的算法示例使用python3语言实现。 31 | 32 | 整个工程分为三个部分。第一部分是阅读经典算法书籍后自己的总结,作为基础算法部分。 33 | 第二部分是自己在LeetCode上面的刷题总结,第三部分是人工智能AI算法示例。 34 | 35 | 从2013年就开始写这个系列,写到动态规划后就停了,那段时间实在是太懒了。 36 | 今年2020年开始决定继续把之前的捡起来,重新更新这个系列,做事情得有始有终,希望能把这个系列坚持写完。 37 | 38 | 有任何问题都可以联系我: 39 | 40 | * Email: yidao620@gmail.com 41 | * Blog: https://www.xncoding.com/ 42 | * GitHub: https://github.com/yidao620c 43 | 44 | **欢迎关注我的个人公众号“飞污熊”,我会定期分享一些自己的Python学习笔记和心得。** 45 | 46 | ![公众号](https://github.com/yidao620c/python3-cookbook/raw/master/exts/wuxiong.jpg) 47 | 48 | ## How to Contribute 49 | 50 | You are welcome to contribute to the project as follow 51 | 52 | * add/edit wiki 53 | * report/fix issue 54 | * code review 55 | * commit new feature 56 | * add testcase 57 | 58 | Meanwhile, you'd better follow the rules below 59 | 60 | * It's *NOT* recommended to submit a pull request directly to `master` branch. `develop` branch is more appropriate 61 | * Follow common Python coding conventions 62 | * Add the following [license](#license) in each source file 63 | 64 | ## License 65 | 66 | (The Apache License) 67 | 68 | Copyright (c) 2013-2020 [Xiong Neng](https://www.xncoding.com/) and other contributors 69 | 70 | Licensed under the Apache License, Version 2.0 (the "License"); 71 | you may not use this file except in compliance with the License. You may obtain a copy of the License at 72 | 73 | http://www.apache.org/licenses/LICENSE-2.0 74 | 75 | Unless required by applicable law or agreed to in writing, 76 | software distributed under the License is distributed on an "AS IS" BASIS, 77 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 78 | See the License for the specific language governing permissions and limitations under the License. 79 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/linkedlist/linked_list_single.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 单向链表数据结构 4 | """ 5 | from algorithms.c01_data_structure import Node 6 | 7 | 8 | class LinkedListSingle: 9 | """ 10 | 单向链表,带哨兵 11 | """ 12 | 13 | def __init__(self, limit): 14 | self.head = Node(None) 15 | self.size = 1 16 | self.limit = limit + 1 # 预留一个给哨兵头节点 17 | 18 | def insert(self, node, new_node): 19 | """ 20 | :param node: 插入点节点 21 | :param new_node: 新节点 22 | :return: 23 | """ 24 | if self.size >= self.limit: 25 | raise IndexError('list is full') 26 | new_node.next = node.next 27 | node.next = new_node 28 | self.size += 1 29 | 30 | def is_full(self): 31 | return self.size >= self.limit 32 | 33 | def is_empty(self): 34 | return self.size == 1 35 | 36 | def remove(self, node): 37 | """ 38 | :param node: 待删除节点 39 | :return: 40 | """ 41 | if self.is_empty(): 42 | return 43 | pre_node = self.head 44 | while pre_node.next != node: 45 | pre_node = pre_node.next 46 | pre_node.next = node.next 47 | self.size -= 1 48 | 49 | def get_tail(self): 50 | target = self.head 51 | while target.next: 52 | target = target.next 53 | return target 54 | 55 | def search(self, data): 56 | """ 57 | 直接比较原始数据 58 | :param data: 待查询数据 59 | :return: 查询到的节点 60 | """ 61 | target = self.head.next 62 | while target and target.data != data: 63 | target = target.next 64 | return target 65 | 66 | def search_equal(self, data, equal): 67 | """ 68 | 通过传入equal比较函数来进行相等判断 69 | :param data: 待查询数据 70 | :param equal: 相等函数 71 | :return: 查询到的节点 72 | """ 73 | target = self.head.next 74 | while target and equal(target.data, data): 75 | target = target.next 76 | return target 77 | 78 | def __iter__(self): 79 | node = self.head.next 80 | while node: 81 | yield node.data 82 | node = node.next 83 | 84 | def print(self): 85 | print(list(x for x in self)) 86 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m05_quick_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | 快速排序 4 | 采用分治法思想: 5 | 分解: 将数组A[p..r]划分成两个(也可能是空)的子数组A[p..q-1]和A[q+1..r], 6 | 使得左边数组中的元素都小于A[p],而右边数组元素都大于A[p] 7 | 解决: 通过递归调用快速排序,对子数组A[p..q-1]和A[q+1..r]进行排序 8 | 合并: 原址排序,不需要合并,数组已经排好序了 9 | 10 | 快速排序的优点: 11 | 最坏情况下时间复杂度为O(n^2),但是期望时间是O(nlg(n)), 12 | 而且O(n*lg(n))隐含常数因子非常的小,而且还是原址排序, 13 | 所以实际中使用最多的排序算法就是快速排序 14 | 15 | 复杂度为: O(n*lgn) 16 | 快速排序的缺点是它是一个不稳定排序算法。如果要保持原有的相同元素顺序,则不能选择快排。 17 | """ 18 | from random import randint 19 | 20 | from algorithms.c04_sort.base.template import SortTemplate 21 | 22 | 23 | class QuickSort(SortTemplate): 24 | 25 | def sort(self): 26 | # self._quick_sub_sort_recursive(seq, 0, len(seq) - 1) 27 | self._quick_sub_sort_tail(0, len(self.seq) - 1) 28 | 29 | def _quick_sub_sort_tail(self, start, end): 30 | """循环版本,模拟尾递归,可以大大减少递归栈深度,而且时间复杂度不变""" 31 | while start < end: 32 | pivot = self._rand_partition(start, end) 33 | if pivot - start < end - pivot: 34 | self._quick_sub_sort_tail(start, pivot - 1) 35 | start = pivot + 1 # 巧妙的通过改变start来实现右边数组的递归 36 | else: 37 | self._quick_sub_sort_tail(pivot + 1, end) 38 | end = pivot - 1 39 | 40 | def _rand_partition(self, start, end): 41 | """分解子数组:随机化版本""" 42 | pivot = randint(start, end) # 随机的pivot 43 | # 还是将这个pivot放到最后 44 | self.seq[pivot], self.seq[end] = self.seq[end], self.seq[pivot] 45 | pivot_value = self.seq[end] 46 | i = start - 1 # 以退为进,先初始化为start-1 47 | for j in range(start, end): 48 | if self.seq[j] <= pivot_value: 49 | i += 1 50 | # 只需要确保所有不大于标杆的元素被蛇吞掉即可。 51 | self.seq[i], self.seq[j] = self.seq[j], self.seq[i] 52 | self.seq[i + 1], self.seq[end] = self.seq[end], self.seq[i + 1] 53 | return i + 1 54 | 55 | def _quick_sub_sort_recursive(self, start, end): 56 | """递归版本的""" 57 | if start < end: 58 | q = self._rand_partition(start, end) 59 | self._quick_sub_sort_recursive(start, q - 1) 60 | self._quick_sub_sort_recursive(q + 1, end) 61 | 62 | 63 | if __name__ == '__main__': 64 | quick_sort = QuickSort([9, 7, 8, 10, 16, 3, 14, 2, 1, 4]) 65 | quick_sort.main() 66 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p200/a232_implement_queue_using_stacks.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 232. 用栈实现队列 4 | 请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty) 5 | 6 | 实现 MyQueue 类: 7 | void push(int x) 将元素 x 推到队列的末尾 8 | int pop() 从队列的开头移除并返回元素 9 | int peek() 返回队列开头的元素 10 | boolean empty() 如果队列为空,返回 true ;否则,返回 false 11 | 12 | 说明: 13 | 你只能使用标准的栈操作,也就是只有push to top, peek/pop from top, size,和is empty作是合法的。 14 | 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。 15 | 16 | 算法思路: 17 | 两个栈,左边栈用于最终存放数据,右边栈只是临时来交换数据用。每次push新数据的时候, 18 | 先将左边栈所有的数据一个个压到右边栈,然后将新数据压到左边栈底,再将右边栈一个个压到左边栈。 19 | """ 20 | 21 | 22 | class ListNode: 23 | def __init__(self, val_, next_=None): 24 | self.val = val_ 25 | self.next = next_ 26 | 27 | def __str__(self): 28 | return str(self.val) 29 | 30 | 31 | class MyStack: 32 | def __init__(self): 33 | self.first = None 34 | 35 | def push(self, val): 36 | self.first = ListNode(val, self.first) 37 | 38 | def pop(self): 39 | if not self.first: 40 | return None 41 | res = self.first.val 42 | self.first = self.first.next 43 | return res 44 | 45 | def empty(self): 46 | return self.first is None 47 | 48 | 49 | class MyQueue: 50 | 51 | def __init__(self): 52 | self.left_stack = MyStack() 53 | self.right_stack = MyStack() 54 | 55 | def push(self, x: int) -> None: 56 | # 先将左栈全部压入右栈 57 | while not self.left_stack.empty(): 58 | self.right_stack.push(self.left_stack.pop()) 59 | # 然后将新数据压入左栈底 60 | self.left_stack.push(x) 61 | # 最后将右栈全部压入左栈 62 | while not self.right_stack.empty(): 63 | self.left_stack.push(self.right_stack.pop()) 64 | 65 | def pop(self) -> int: 66 | if self.left_stack.empty(): 67 | return None 68 | return self.left_stack.pop() 69 | 70 | def peek(self) -> int: 71 | if self.left_stack.empty(): 72 | return None 73 | return self.left_stack.first.val 74 | 75 | def empty(self) -> bool: 76 | return self.left_stack.empty() 77 | 78 | 79 | if __name__ == '__main__': 80 | # Your MyQueue object will be instantiated and called as such: 81 | obj = MyQueue() 82 | obj.push(1) 83 | obj.push(2) 84 | obj.push(3) 85 | param_2 = obj.pop() 86 | param_3 = obj.peek() 87 | param_4 = obj.empty() 88 | print(param_2, param_3, param_4) 89 | -------------------------------------------------------------------------------- /algorithms/c14_sample/m01_math.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- encoding: utf-8 -*- 3 | # 最大公约数,公倍数,素因子分解,闰年判断,找零钱,斐波那契数列 4 | """ 5 | 基本的初等数学类 6 | 最大公约数,公倍数,素因子分解,闰年判断,找零钱,斐波那契数列 7 | """ 8 | __author__ = 'Xiong Neng' 9 | 10 | 11 | # 闰年判断 12 | def isLeapYear(year): 13 | return (not year % 4 and year % 100) or (not year % 400) 14 | 15 | 16 | # 找零钱 17 | def mod(money): 18 | cent = int(money * 100) 19 | all_cent = {25: 0, 10: 0, 5: 0, 1: 0} 20 | for k in all_cent: 21 | all_cent[k], cent = divmod(cent, k) 22 | return all_cent 23 | 24 | 25 | # 最大公约数,辗转相除法 26 | def maxCommonDivisor(m, n): 27 | while True: 28 | remainder = m % n 29 | if not remainder: 30 | return n 31 | else: 32 | m, n = n, remainder 33 | 34 | 35 | # 最小公倍数 36 | def minCommonMultiple(m, n): 37 | return m * n / maxCommonDivisor(m, n) 38 | 39 | 40 | # 素数的判断 41 | def isprime(n): 42 | result = True 43 | for i in range(n / 2, 1, -1): 44 | if n % i == 0: 45 | result = False 46 | break 47 | return result 48 | 49 | 50 | # 获取n的所有因子 51 | def getfactors(n): 52 | result = [n] 53 | for i in range(n / 2, 0, -1): 54 | if n % i == 0: 55 | result.append(i) 56 | return result 57 | 58 | 59 | # 素因子分解 60 | def decompose(n): 61 | all_factors = getfactors(n) 62 | all_factors.remove(1) 63 | all_factors.remove(n) 64 | prime_factors = [x for x in all_factors if isprime(x)] 65 | prime_factors.sort(reverse=True) 66 | result = [] 67 | remainder = n 68 | for f in prime_factors: 69 | while remainder >= f: 70 | qut, rem = divmod(remainder, f) 71 | if rem != 0: 72 | break 73 | else: 74 | remainder = qut 75 | result.append(f) 76 | return result 77 | 78 | 79 | # 获取前N个斐波那契数列 80 | def fibonacci(n): 81 | result = [] 82 | if n == 1: 83 | result.append(1) 84 | elif n >= 2: 85 | result.append(1) 86 | result.append(1) 87 | for i in range(2, n): 88 | result.append(result[-1] + result[-2]) 89 | return result 90 | 91 | 92 | def main(): 93 | print(fibonacci(8)) 94 | mod(0.78) 95 | print(maxCommonDivisor(24, 36)) 96 | print(minCommonMultiple(24, 36)) 97 | 98 | 99 | if __name__ == '__main__': 100 | main() 101 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/queue/linkedlist_queue.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: linkedlist_queue.py 3 | Created Time: 2022-12-01 4 | Author: Peng Chen (pengchzn@gmail.com) 5 | """ 6 | 7 | import sys 8 | from pathlib import Path 9 | 10 | sys.path.append(str(Path(__file__).parent.parent)) 11 | from algorithms.models import ListNode 12 | 13 | 14 | class LinkedListQueue: 15 | """基于链表实现的队列""" 16 | 17 | def __init__(self): 18 | """构造方法""" 19 | self._front: ListNode | None = None # 头节点 front 20 | self._rear: ListNode | None = None # 尾节点 rear 21 | self._size: int = 0 22 | 23 | def size(self) -> int: 24 | """获取队列的长度""" 25 | return self._size 26 | 27 | def is_empty(self) -> bool: 28 | """判断队列是否为空""" 29 | return not self._front 30 | 31 | def push(self, num: int): 32 | """入队""" 33 | # 尾节点后添加 num 34 | node = ListNode(num) 35 | # 如果队列为空,则令头、尾节点都指向该节点 36 | if self._front is None: 37 | self._front = node 38 | self._rear = node 39 | # 如果队列不为空,则将该节点添加到尾节点后 40 | else: 41 | self._rear.next = node 42 | self._rear = node 43 | self._size += 1 44 | 45 | def pop(self) -> int: 46 | """出队""" 47 | num = self.peek() 48 | # 删除头节点 49 | self._front = self._front.next 50 | self._size -= 1 51 | return num 52 | 53 | def peek(self) -> int: 54 | """访问队首元素""" 55 | if self.is_empty(): 56 | raise IndexError("队列为空") 57 | return self._front.val 58 | 59 | def to_list(self) -> list[int]: 60 | """转化为列表用于打印""" 61 | queue = [] 62 | temp = self._front 63 | while temp: 64 | queue.append(temp.val) 65 | temp = temp.next 66 | return queue 67 | 68 | 69 | """Driver Code""" 70 | if __name__ == "__main__": 71 | # 初始化队列 72 | queue = LinkedListQueue() 73 | 74 | # 元素入队 75 | queue.push(1) 76 | queue.push(3) 77 | queue.push(2) 78 | queue.push(5) 79 | queue.push(4) 80 | print("队列 queue =", queue.to_list()) 81 | 82 | # 访问队首元素 83 | peek: int = queue.peek() 84 | print("队首元素 front =", peek) 85 | 86 | # 元素出队 87 | pop_front: int = queue.pop() 88 | print("出队元素 pop =", pop_front) 89 | print("出队后 queue =", queue.to_list()) 90 | 91 | # 获取队列的长度 92 | size: int = queue.size() 93 | print("队列长度 size =", size) 94 | 95 | # 判断队列是否为空 96 | is_empty: bool = queue.is_empty() 97 | print("队列是否为空 =", is_empty) 98 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/linkedlist/palindromic_number.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | Question:如果字符串是通过单链表来存储的,那该如何来判断是一个回文串? 4 | 5 | 快慢指针法实现过程: 6 | 由于回文串最重要的就是对称,那么最重要的问题就是找到那个中心,用快指针每步两格走,当他到达链表末端的时候, 7 | 慢指针刚好到达中心,慢指针在过来的这趟路上还做了一件事,他把走过的节点反向了,在中心点再开辟一个新的指针用于往回走, 8 | 而慢指针继续向前,当慢指针扫完整个链表,就可以判断这是回文串,否则就提前退出。 9 | 10 | 总的来说时间复杂度按慢指针遍历一遍来算是O(n),空间复杂度因为只开辟了3个额外的辅助,所以是o(1) 11 | 12 | 1.1 奇数情况,中点位置不需要矫正 13 | 1.2 偶数情况,使用偶数定位中点策略,要确定是返回上中位数或下中位数 14 | 1.2.1 如果是返回上中位数,后半部分串头取next <<=这里使用这张上中位数数 15 | 1.2.2 如果是返回下中位数,后半部分串头既是当前节点位置,但前半部分串尾要删除掉当前节点 16 | """ 17 | from algorithms.c01_data_structure import Node 18 | from algorithms.c01_data_structure.linkedlist.linked_list_single import LinkedListSingle 19 | 20 | 21 | def palindromic(list_single_): 22 | if list_single_.is_empty() or list_single_.size == 2: 23 | return True 24 | if list_single_.size == 3: 25 | return list_single_.head.next.data == list_single_.head.next.next.data 26 | 27 | slow = list_single_.head.next.next # slow指向第2个节点 28 | slow_next = slow.next # 保存下一步的slow后驱节点 29 | fast = slow.next # fast指向第3个节点 30 | slow.next = list_single_.head.next # 第一个slow指针反向 31 | list_single_.head.next.next = None # 第一个节点下一个指针初始化为None 32 | 33 | while fast.next and fast.next.next: # 还能继续往前跑 34 | fast = fast.next.next # 快指针先往后面走2步 35 | slow_pre = slow # 临时保存slow 36 | slow = slow_next # slow往后走1步 37 | slow_next = slow.next # 保存slow下一步的后驱节点 38 | slow.next = slow_pre # 将slow节点的next反转 39 | if not fast.next: # 奇数情况 40 | # 左边指针从slow.next开始,右边指针从slow_next开始 41 | slow = slow.next 42 | if not slow: 43 | return True 44 | while slow: 45 | if slow.data != slow_next.data: 46 | return False 47 | slow = slow.next 48 | slow_next = slow_next.next 49 | else: # 偶数情况 50 | # 左边指针从slow开始,右边指针从slow_next开始 51 | while slow: 52 | if slow.data != slow_next.data: 53 | return False 54 | slow = slow.next 55 | slow_next = slow_next.next 56 | return True 57 | 58 | 59 | if __name__ == '__main__': 60 | list_single = LinkedListSingle(30) 61 | list_single.insert(list_single.head, Node('d')) 62 | list_single.insert(list_single.head, Node('a')) 63 | list_single.insert(list_single.head, Node('b')) 64 | list_single.insert(list_single.head, Node('c')) 65 | list_single.insert(list_single.head, Node('b')) 66 | list_single.insert(list_single.head, Node('a')) 67 | list_single.insert(list_single.head, Node('d')) 68 | list_single.print() 69 | print(palindromic(list_single)) 70 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/queue/array_queue.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: array_queue.py 3 | Created Time: 2022-12-01 4 | Author: Peng Chen (pengchzn@gmail.com) 5 | """ 6 | 7 | 8 | class ArrayQueue: 9 | """基于环形数组实现的队列""" 10 | 11 | def __init__(self, size: int): 12 | """构造方法""" 13 | self._nums: list[int] = [0] * size # 用于存储队列元素的数组 14 | self._front: int = 0 # 队首指针,指向队首元素 15 | self._size: int = 0 # 队列长度 16 | 17 | def capacity(self) -> int: 18 | """获取队列的容量""" 19 | return len(self._nums) 20 | 21 | def size(self) -> int: 22 | """获取队列的长度""" 23 | return self._size 24 | 25 | def is_empty(self) -> bool: 26 | """判断队列是否为空""" 27 | return self._size == 0 28 | 29 | def push(self, num: int): 30 | """入队""" 31 | if self._size == self.capacity(): 32 | raise IndexError("队列已满") 33 | # 计算尾指针,指向队尾索引 + 1 34 | # 通过取余操作,实现 rear 越过数组尾部后回到头部 35 | rear: int = (self._front + self._size) % self.capacity() 36 | # 将 num 添加至队尾 37 | self._nums[rear] = num 38 | self._size += 1 39 | 40 | def pop(self) -> int: 41 | """出队""" 42 | num: int = self.peek() 43 | # 队首指针向后移动一位,若越过尾部则返回到数组头部 44 | self._front = (self._front + 1) % self.capacity() 45 | self._size -= 1 46 | return num 47 | 48 | def peek(self) -> int: 49 | """访问队首元素""" 50 | if self.is_empty(): 51 | raise IndexError("队列为空") 52 | return self._nums[self._front] 53 | 54 | def to_list(self) -> list[int]: 55 | """返回列表用于打印""" 56 | res = [0] * self.size() 57 | j: int = self._front 58 | for i in range(self.size()): 59 | res[i] = self._nums[(j % self.capacity())] 60 | j += 1 61 | return res 62 | 63 | 64 | """Driver Code""" 65 | if __name__ == "__main__": 66 | # 初始化队列 67 | queue = ArrayQueue(10) 68 | 69 | # 元素入队 70 | queue.push(1) 71 | queue.push(3) 72 | queue.push(2) 73 | queue.push(5) 74 | queue.push(4) 75 | print("队列 queue =", queue.to_list()) 76 | 77 | # 访问队首元素 78 | peek: int = queue.peek() 79 | print("队首元素 peek =", peek) 80 | 81 | # 元素出队 82 | pop: int = queue.pop() 83 | print("出队元素 pop =", pop) 84 | print("出队后 queue =", queue.to_list()) 85 | 86 | # 获取队列的长度 87 | size: int = queue.size() 88 | print("队列长度 size =", size) 89 | 90 | # 判断队列是否为空 91 | is_empty: bool = queue.is_empty() 92 | print("队列是否为空 =", is_empty) 93 | 94 | # 测试环形数组 95 | for i in range(10): 96 | queue.push(i) 97 | queue.pop() 98 | print("第", i, "轮入队 + 出队后 queue = ", queue.to_list()) 99 | -------------------------------------------------------------------------------- /algorithms/c04_sort/m04_merge_insert_sort.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """归并排序中对小数组采用插入排序 3 | 纯归并排序的复杂度为: O(nlgn),而纯插入排序的时间复杂度为:O(n^2)。数据量很大的时候采用归并排序 4 | 但是在n较小的时候插入排序可能运行的会更快点。因此在归并排序中当子问题变得足够小时, 5 | 采用插入排序来使得递归的叶子变粗可以加快排序速度。那么这个足够小到底怎么去衡量呢? 请看下面: 6 | 这么几个我不证明了,比较简单: 7 | A,插入排序最坏情况下可以在O(nk)时间内排序每个长度为k的n/k个子列表 8 | B,在最坏情况下可在O(nlg(n/k))的时间内合并这些子表 9 | C,修订后的算法的最坏情况运行时间复杂度是O(nk + nlg(n/k)) 10 | 那么,O(nk+nlg(n/k))=O(nlgn).只能最大是k=O(lgn).等式左边中第一项是高阶项。 11 | k如果大于lgn,则比归并排序复杂度大了。左边可以写成nk+nlgn-nlgk,k等于lgn时, 12 | 就是2nlgn-nlglgn.忽略恒定系数,则与归并排序是一样的。 13 | 14 | 最后结论:k < lg(n)的时候,使用插入排序 15 | 16 | 复杂度为: O(nlgn) 17 | 稳定排序:重复元素排序完后仍然保持原来的相对位置。 18 | 19 | 它有一个致命的“弱点”,那就是归并排序不是原地排序算法。 20 | """ 21 | from math import log 22 | 23 | from algorithms.c04_sort.base.sortutil import insert_sort 24 | from algorithms.c04_sort.base.template import SortTemplate 25 | 26 | 27 | class MergeAndInsertSort(SortTemplate): 28 | 29 | def sort(self): 30 | self.merge_insert_sort_range(0, len(self.seq) - 1, log(len(self.seq), 2)) 31 | 32 | def merge_insert_sort_range(self, start, end, threshold): 33 | """ 34 | 归并排序一个序列的子序列 35 | start: 子序列的start下标 36 | end: 子序列的end下标 37 | threshold: 待排序长度低于这个值,就采用插入排序 38 | """ 39 | if end - start + 1 < threshold: 40 | temp_seq = self.seq[start: end + 1] 41 | insert_sort(temp_seq) # 小数组使用插入排序 42 | self.seq[start: end + 1] = temp_seq[:] 43 | elif start < end: # 如果start >= end就终止递归调用 44 | middle = (start + end) // 2 45 | self.merge_insert_sort_range(start, middle, threshold) # 排好左边的一半 46 | self.merge_insert_sort_range(middle + 1, end, threshold) # 再排好右边的一半 47 | self.merge_insert_sort_seq(start, middle, end) # 最后合并排序结果 48 | 49 | def merge_insert_sort_seq(self, left, middle, right): 50 | """ 51 | seq: 待排序序列 52 | left <= middle <= right 53 | 子数组seq[left..middle]和seq[middle+1..right]都是排好序的 54 | 该排序的时间复杂度为O(n) 55 | """ 56 | temp_seq = [] 57 | i = left 58 | j = middle + 1 59 | while i <= middle and j <= right: 60 | if self.seq[i] <= self.seq[j]: 61 | temp_seq.append(self.seq[i]) 62 | i += 1 63 | else: 64 | temp_seq.append(self.seq[j]) 65 | j += 1 66 | if i <= middle: 67 | temp_seq.extend(self.seq[i:middle + 1]) 68 | else: 69 | temp_seq.extend(self.seq[j:right + 1]) 70 | self.seq[left:right + 1] = temp_seq[:] 71 | 72 | 73 | if __name__ == '__main__': 74 | merge_and_insert_sort = MergeAndInsertSort([4, 2, 5, 1, 6, 3, 7, 9, 8]) 75 | merge_and_insert_sort.main() 76 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p800/a844_backspace_string_compare.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 844. 比较含退格的字符串 4 | 给定s和t两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。 5 | 6 | 注意:如果对空文本输入退格字符,文本继续为空。 7 | 8 | 输入:s = "ab#c", t = "ad#c" 9 | 输出:true 10 | 解释:s 和 t 都会变成 "ac"。 11 | 12 | 输入:s = "ab##", t = "c#d#" 13 | 输出:true 14 | 解释:s 和 t 都会变成 ""。 15 | 16 | 输入:s = "a#c", t = "b" 17 | 输出:false 18 | 解释:s 会变成 "c",但 t 仍然是 "b"。 19 | 20 | 算法思路: 21 | 用两个栈遍历两个字符串,遇到#号就从栈弹出一个元素。最后再依次对比两个栈的每个元素。 22 | """ 23 | 24 | 25 | class LinkedNode: 26 | def __init__(self, val_, next_=None): 27 | self.val = val_ 28 | self.next = next_ 29 | 30 | def __str__(self): 31 | return str(self.val) 32 | 33 | 34 | class LinkedStack: 35 | def __init__(self): 36 | self.first = None 37 | 38 | def push(self, val): 39 | self.first = LinkedNode(val, self.first) 40 | 41 | def pop(self): 42 | if not self.first: 43 | return None 44 | res = self.first.val 45 | self.first = self.first.next 46 | return res 47 | 48 | def peek(self): 49 | if not self.first: 50 | return None 51 | return self.first.val 52 | 53 | def empty(self): 54 | return self.first is None 55 | 56 | def __iter__(self): 57 | while self.first: 58 | yield self.first.val 59 | self.first = self.first.next 60 | 61 | 62 | class Solution: 63 | def __init__(self): 64 | self.left_stack = LinkedStack() 65 | self.right_stack = LinkedStack() 66 | 67 | def backspaceCompare(self, s: str, t: str) -> bool: 68 | for left_val in s: 69 | if left_val == '#': 70 | if not self.left_stack.empty(): 71 | self.left_stack.pop() 72 | else: 73 | self.left_stack.push(left_val) 74 | for right_val in t: 75 | if right_val == '#': 76 | if not self.right_stack.empty(): 77 | self.right_stack.pop() 78 | else: 79 | self.right_stack.push(right_val) 80 | while not self.left_stack.empty() and not self.right_stack.empty(): 81 | if self.left_stack.pop() != self.right_stack.pop(): 82 | return False 83 | if not self.left_stack.empty() and self.right_stack.empty(): 84 | return False 85 | if self.left_stack.empty() and not self.right_stack.empty(): 86 | return False 87 | return True 88 | 89 | 90 | if __name__ == '__main__': 91 | print(Solution().backspaceCompare("ab##", "c#d#")) 92 | print(Solution().backspaceCompare("ab#c", "ad#c")) 93 | print(Solution().backspaceCompare("a#c", "b")) 94 | print(Solution().backspaceCompare("y#fo##f", "y#f#o##f")) 95 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p400/a496_next_greater_element.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 496. 下一个更大元素 I 4 | nums1中数字x的下一个更大元素是指x在nums2中对应位置右侧的第一个比x大的元素。 5 | 6 | 给你两个没有重复元素的数组nums1和nums2,下标从0开始计数,其中nums1是nums2的子集。 7 | 8 | 对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j , 9 | 并且在nums2确定nums2[j]的下一个更大元素。如果不存在下一个更大元素,那么本次查询的答案是-1 。 10 | 11 | 返回一个长度为nums1.length的数组 ans 作为答案,满足ans[i]是如上所述的下一个更大元素。 12 | 13 | 输入:nums1 = [4,1,2], nums2 = [1,3,4,2]. 14 | 输出:[-1,3,-1] 15 | 解释:nums1 中每个值的下一个更大元素如下所述: 16 | - 4 ,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 17 | - 1 ,nums2 = [1,3,4,2]。下一个更大元素是 3 。 18 | - 2 ,nums2 = [1,3,4,2]。不存在下一个更大元素,所以答案是 -1 。 19 | 20 | 输入:nums1 = [2,4], nums2 = [1,2,3,4]. 21 | 输出:[3,-1] 22 | 解释:nums1 中每个值的下一个更大元素如下所述: 23 | - 2 ,nums2 = [1,2,3,4]。下一个更大元素是 3 。 24 | - 4 ,nums2 = [1,2,3,4]。不存在下一个更大元素,所以答案是 -1。 25 | 26 | 进阶:你可以设计一个时间复杂度为 O(nums1.length + nums2.length) 的解决方案吗? 27 | 28 | 算法思路: 29 | 这个问题可以这样抽象思考:把数组的元素想象成并列站立的人,元素大小想象成人的身高。这些人面对你站成一列, 30 | 如何求元素「2」的 Next Greater Number 呢?很简单,如果能够看到元素「2」, 31 | 那么他后面可见的第一个人就是「2」的 Next Greater Number,因为比「2」小的元素身高不够,都被「2」挡住了, 32 | 第一个露出来的就是答案。 33 | 34 | 单调栈解决问题的模板。for 循环要从后往前扫描元素,因为我们借助的是栈的结构,倒着入栈, 35 | 其实是正着出栈。while 循环是把两个“高个”元素之间的元素排除,因为他们的存在没有意义,前面挡着个“更高”的元素, 36 | 所以他们不可能被作为后续进来的元素的 Next Great Number 了。 37 | 38 | 这个算法的时间复杂度不是那么直观,如果你看到 for 循环嵌套 while 循环,可能认为这个算法的复杂度也是 O(n^2), 39 | 但是实际上这个算法的复杂度只有 O(n)。 40 | 41 | 分析它的时间复杂度,要从整体来看:总共有 n 个元素,每个元素都被 push 入栈了一次,而最多会被 pop 一次, 42 | 没有任何冗余操作。所以总的计算规模是和元素规模 n 成正比的,也就是 O(n) 的复杂度。 43 | 44 | 先用单调栈找到nums2每个元素对应的下个最大元素,然后装入Map中。后面对于num1的求值直接从Map中获取。 45 | """ 46 | from typing import List 47 | 48 | 49 | class LinkedNode: 50 | def __init__(self, val_, next_=None): 51 | self.val = val_ 52 | self.next = next_ 53 | 54 | def __str__(self): 55 | return str(self.val) 56 | 57 | 58 | class LinkedStack: 59 | def __init__(self): 60 | self.first = None 61 | 62 | def push(self, val): 63 | self.first = LinkedNode(val, self.first) 64 | 65 | def pop(self): 66 | if not self.first: 67 | return None 68 | res = self.first.val 69 | self.first = self.first.next 70 | return res 71 | 72 | def peek(self): 73 | if not self.first: 74 | return None 75 | return self.first.val 76 | 77 | def empty(self): 78 | return self.first is None 79 | 80 | 81 | class Solution: 82 | def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]: 83 | nums2_size = len(nums2) 84 | nums2_map = {} # 存放num2的单调栈 85 | count = 0 86 | stack = LinkedStack() # 存放高个元素的栈 87 | for item in nums2[::-1]: 88 | while not stack.empty() and nums2[nums2_size - 1 - count] > stack.peek(): 89 | stack.pop() # 把中间小的数剔除掉 90 | nums2_map[item] = -1 if stack.empty() else stack.peek() 91 | stack.push(item) # 然后再把这个数入栈,接受后面的身高判定 92 | count += 1 93 | return [nums2_map[i] for i in nums1] 94 | 95 | 96 | if __name__ == '__main__': 97 | ans = Solution().nextGreaterElement([4, 1, 2], [1, 3, 4, 2]) 98 | print(ans) 99 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/min_path_sum.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: min_path_sum.py 3 | Created Time: 2023-07-04 4 | """ 5 | 6 | from math import inf 7 | 8 | 9 | def min_path_sum_dfs(grid: list[list[int]], i: int, j: int) -> int: 10 | """最小路径和:暴力搜索""" 11 | # 若为左上角单元格,则终止搜索 12 | if i == 0 and j == 0: 13 | return grid[0][0] 14 | # 若行列索引越界,则返回 +∞ 代价 15 | if i < 0 or j < 0: 16 | return inf 17 | # 计算从左上角到 (i-1, j) 和 (i, j-1) 的最小路径代价 18 | up = min_path_sum_dfs(grid, i - 1, j) 19 | left = min_path_sum_dfs(grid, i, j - 1) 20 | # 返回从左上角到 (i, j) 的最小路径代价 21 | return min(left, up) + grid[i][j] 22 | 23 | 24 | def min_path_sum_dfs_mem( 25 | grid: list[list[int]], mem: list[list[int]], i: int, j: int 26 | ) -> int: 27 | """最小路径和:记忆化搜索""" 28 | # 若为左上角单元格,则终止搜索 29 | if i == 0 and j == 0: 30 | return grid[0][0] 31 | # 若行列索引越界,则返回 +∞ 代价 32 | if i < 0 or j < 0: 33 | return inf 34 | # 若已有记录,则直接返回 35 | if mem[i][j] != -1: 36 | return mem[i][j] 37 | # 左边和上边单元格的最小路径代价 38 | up = min_path_sum_dfs_mem(grid, mem, i - 1, j) 39 | left = min_path_sum_dfs_mem(grid, mem, i, j - 1) 40 | # 记录并返回左上角到 (i, j) 的最小路径代价 41 | mem[i][j] = min(left, up) + grid[i][j] 42 | return mem[i][j] 43 | 44 | 45 | def min_path_sum_dp(grid: list[list[int]]) -> int: 46 | """最小路径和:动态规划""" 47 | n, m = len(grid), len(grid[0]) 48 | # 初始化 dp 表 49 | dp = [[0] * m for _ in range(n)] 50 | dp[0][0] = grid[0][0] 51 | # 状态转移:首行 52 | for j in range(1, m): 53 | dp[0][j] = dp[0][j - 1] + grid[0][j] 54 | # 状态转移:首列 55 | for i in range(1, n): 56 | dp[i][0] = dp[i - 1][0] + grid[i][0] 57 | # 状态转移:其余行列 58 | for i in range(1, n): 59 | for j in range(1, m): 60 | dp[i][j] = min(dp[i][j - 1], dp[i - 1][j]) + grid[i][j] 61 | return dp[n - 1][m - 1] 62 | 63 | 64 | def min_path_sum_dp_comp(grid: list[list[int]]) -> int: 65 | """最小路径和:空间优化后的动态规划""" 66 | n, m = len(grid), len(grid[0]) 67 | # 初始化 dp 表 68 | dp = [0] * m 69 | # 状态转移:首行 70 | dp[0] = grid[0][0] 71 | for j in range(1, m): 72 | dp[j] = dp[j - 1] + grid[0][j] 73 | # 状态转移:其余行 74 | for i in range(1, n): 75 | # 状态转移:首列 76 | dp[0] = dp[0] + grid[i][0] 77 | # 状态转移:其余列 78 | for j in range(1, m): 79 | dp[j] = min(dp[j - 1], dp[j]) + grid[i][j] 80 | return dp[m - 1] 81 | 82 | 83 | """Driver Code""" 84 | if __name__ == "__main__": 85 | grid = [[1, 3, 1, 5], [2, 2, 4, 2], [5, 3, 2, 1], [4, 3, 5, 2]] 86 | n, m = len(grid), len(grid[0]) 87 | 88 | # 暴力搜索 89 | res = min_path_sum_dfs(grid, n - 1, m - 1) 90 | print(f"从左上角到右下角的做小路径和为 {res}") 91 | 92 | # 记忆化搜索 93 | mem = [[-1] * m for _ in range(n)] 94 | res = min_path_sum_dfs_mem(grid, mem, n - 1, m - 1) 95 | print(f"从左上角到右下角的做小路径和为 {res}") 96 | 97 | # 动态规划 98 | res = min_path_sum_dp(grid) 99 | print(f"从左上角到右下角的做小路径和为 {res}") 100 | 101 | # 空间优化后的动态规划 102 | res = min_path_sum_dp_comp(grid) 103 | print(f"从左上角到右下角的做小路径和为 {res}") 104 | -------------------------------------------------------------------------------- /algorithms/c09_dynamic/knapsack.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: knapsack.py 3 | Created Time: 2023-07-03 4 | """ 5 | 6 | 7 | def knapsack_dfs(wgt: list[int], val: list[int], i: int, c: int) -> int: 8 | """0-1 背包:暴力搜索""" 9 | # 若已选完所有物品或背包无容量,则返回价值 0 10 | if i == 0 or c == 0: 11 | return 0 12 | # 若超过背包容量,则只能不放入背包 13 | if wgt[i - 1] > c: 14 | return knapsack_dfs(wgt, val, i - 1, c) 15 | # 计算不放入和放入物品 i 的最大价值 16 | no = knapsack_dfs(wgt, val, i - 1, c) 17 | yes = knapsack_dfs(wgt, val, i - 1, c - wgt[i - 1]) + val[i - 1] 18 | # 返回两种方案中价值更大的那一个 19 | return max(no, yes) 20 | 21 | 22 | def knapsack_dfs_mem( 23 | wgt: list[int], val: list[int], mem: list[list[int]], i: int, c: int 24 | ) -> int: 25 | """0-1 背包:记忆化搜索""" 26 | # 若已选完所有物品或背包无容量,则返回价值 0 27 | if i == 0 or c == 0: 28 | return 0 29 | # 若已有记录,则直接返回 30 | if mem[i][c] != -1: 31 | return mem[i][c] 32 | # 若超过背包容量,则只能不放入背包 33 | if wgt[i - 1] > c: 34 | return knapsack_dfs_mem(wgt, val, mem, i - 1, c) 35 | # 计算不放入和放入物品 i 的最大价值 36 | no = knapsack_dfs_mem(wgt, val, mem, i - 1, c) 37 | yes = knapsack_dfs_mem(wgt, val, mem, i - 1, c - wgt[i - 1]) + val[i - 1] 38 | # 记录并返回两种方案中价值更大的那一个 39 | mem[i][c] = max(no, yes) 40 | return mem[i][c] 41 | 42 | 43 | def knapsack_dp(wgt: list[int], val: list[int], cap: int) -> int: 44 | """0-1 背包:动态规划""" 45 | n = len(wgt) 46 | # 初始化 dp 表 47 | dp = [[0] * (cap + 1) for _ in range(n + 1)] 48 | # 状态转移 49 | for i in range(1, n + 1): 50 | for c in range(1, cap + 1): 51 | if wgt[i - 1] > c: 52 | # 若超过背包容量,则不选物品 i 53 | dp[i][c] = dp[i - 1][c] 54 | else: 55 | # 不选和选物品 i 这两种方案的较大值 56 | dp[i][c] = max(dp[i - 1][c], dp[i - 1][c - wgt[i - 1]] + val[i - 1]) 57 | return dp[n][cap] 58 | 59 | 60 | def knapsack_dp_comp(wgt: list[int], val: list[int], cap: int) -> int: 61 | """0-1 背包:空间优化后的动态规划""" 62 | n = len(wgt) 63 | # 初始化 dp 表 64 | dp = [0] * (cap + 1) 65 | # 状态转移 66 | for i in range(1, n + 1): 67 | # 倒序遍历 68 | for c in range(cap, 0, -1): 69 | if wgt[i - 1] > c: 70 | # 若超过背包容量,则不选物品 i 71 | dp[c] = dp[c] 72 | else: 73 | # 不选和选物品 i 这两种方案的较大值 74 | dp[c] = max(dp[c], dp[c - wgt[i - 1]] + val[i - 1]) 75 | return dp[cap] 76 | 77 | 78 | """Driver Code""" 79 | if __name__ == "__main__": 80 | wgt = [10, 20, 30, 40, 50] 81 | val = [50, 120, 150, 210, 240] 82 | cap = 50 83 | n = len(wgt) 84 | 85 | # 暴力搜索 86 | res = knapsack_dfs(wgt, val, n, cap) 87 | print(f"不超过背包容量的最大物品价值为 {res}") 88 | 89 | # 记忆化搜索 90 | mem = [[-1] * (cap + 1) for _ in range(n + 1)] 91 | res = knapsack_dfs_mem(wgt, val, mem, n, cap) 92 | print(f"不超过背包容量的最大物品价值为 {res}") 93 | 94 | # 动态规划 95 | res = knapsack_dp(wgt, val, cap) 96 | print(f"不超过背包容量的最大物品价值为 {res}") 97 | 98 | # 空间优化后的动态规划 99 | res = knapsack_dp_comp(wgt, val, cap) 100 | print(f"不超过背包容量的最大物品价值为 {res}") 101 | -------------------------------------------------------------------------------- /algorithms/c01_data_structure/hashing/array_hash_map.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: array_hash_map.py 3 | Created Time: 2022-12-14 4 | Author: msk397 (machangxinq@gmail.com) 5 | """ 6 | 7 | 8 | class Pair: 9 | """键值对""" 10 | 11 | def __init__(self, key: int, val: str): 12 | self.key = key 13 | self.val = val 14 | 15 | 16 | class ArrayHashMap: 17 | """基于数组简易实现的哈希表""" 18 | 19 | def __init__(self): 20 | """构造方法""" 21 | # 初始化数组,包含 100 个桶 22 | self.buckets: list[Pair | None] = [None] * 100 23 | 24 | def hash_func(self, key: int) -> int: 25 | """哈希函数""" 26 | index = key % 100 27 | return index 28 | 29 | def get(self, key: int) -> str: 30 | """查询操作""" 31 | index: int = self.hash_func(key) 32 | pair: Pair = self.buckets[index] 33 | if pair is None: 34 | return None 35 | return pair.val 36 | 37 | def put(self, key: int, val: str): 38 | """添加操作""" 39 | pair = Pair(key, val) 40 | index: int = self.hash_func(key) 41 | self.buckets[index] = pair 42 | 43 | def remove(self, key: int): 44 | """删除操作""" 45 | index: int = self.hash_func(key) 46 | # 置为 None ,代表删除 47 | self.buckets[index] = None 48 | 49 | def entry_set(self) -> list[Pair]: 50 | """获取所有键值对""" 51 | result: list[Pair] = [] 52 | for pair in self.buckets: 53 | if pair is not None: 54 | result.append(pair) 55 | return result 56 | 57 | def key_set(self) -> list[int]: 58 | """获取所有键""" 59 | result = [] 60 | for pair in self.buckets: 61 | if pair is not None: 62 | result.append(pair.key) 63 | return result 64 | 65 | def value_set(self) -> list[str]: 66 | """获取所有值""" 67 | result = [] 68 | for pair in self.buckets: 69 | if pair is not None: 70 | result.append(pair.val) 71 | return result 72 | 73 | def print(self): 74 | """打印哈希表""" 75 | for pair in self.buckets: 76 | if pair is not None: 77 | print(pair.key, "->", pair.val) 78 | 79 | 80 | """Driver Code""" 81 | if __name__ == "__main__": 82 | # 初始化哈希表 83 | hmap = ArrayHashMap() 84 | 85 | # 添加操作 86 | # 在哈希表中添加键值对 (key, value) 87 | hmap.put(12836, "小哈") 88 | hmap.put(15937, "小啰") 89 | hmap.put(16750, "小算") 90 | hmap.put(13276, "小法") 91 | hmap.put(10583, "小鸭") 92 | print("\n添加完成后,哈希表为\nKey -> Value") 93 | hmap.print() 94 | 95 | # 查询操作 96 | # 向哈希表输入键 key ,得到值 value 97 | name = hmap.get(15937) 98 | print("\n输入学号 15937 ,查询到姓名 " + name) 99 | 100 | # 删除操作 101 | # 在哈希表中删除键值对 (key, value) 102 | hmap.remove(10583) 103 | print("\n删除 10583 后,哈希表为\nKey -> Value") 104 | hmap.print() 105 | 106 | # 遍历哈希表 107 | print("\n遍历键值对 Key->Value") 108 | for pair in hmap.entry_set(): 109 | print(pair.key, "->", pair.val) 110 | 111 | print("\n单独遍历键 Key") 112 | for key in hmap.key_set(): 113 | print(key) 114 | 115 | print("\n单独遍历值 Value") 116 | for val in hmap.value_set(): 117 | print(val) 118 | -------------------------------------------------------------------------------- /algorithms/c12_leetcode/p600/a682_baseball_game.py: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | """ 3 | 682. 棒球比赛 4 | 你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。 5 | 6 | 比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i 项操作,ops 遵循下述规则: 7 | 8 | 整数 x - 表示本回合新获得分数 x 9 | "+" - 表示本回合新获得的得分是前两次得分的总和。题目数据保证记录此操作时前面总是存在两个有效的分数。 10 | "D" - 表示本回合新获得的得分是前一次得分的两倍。题目数据保证记录此操作时前面总是存在一个有效的分数。 11 | "C" - 表示前一次得分无效,将其从记录中移除。题目数据保证记录此操作时前面总是存在一个有效的分数。 12 | 请你返回记录中所有得分的总和。 13 | 14 | 输入:ops = ["5","2","C","D","+"] 15 | 输出:30 16 | 解释: 17 | "5" - 记录加 5 ,记录现在是 [5] 18 | "2" - 记录加 2 ,记录现在是 [5, 2] 19 | "C" - 使前一次得分的记录无效并将其移除,记录现在是 [5]. 20 | "D" - 记录加 2 * 5 = 10 ,记录现在是 [5, 10]. 21 | "+" - 记录加 5 + 10 = 15 ,记录现在是 [5, 10, 15]. 22 | 所有得分的总和 5 + 10 + 15 = 30 23 | 24 | 输入:ops = ["5","-2","4","C","D","9","+","+"] 25 | 输出:27 26 | 解释: 27 | "5" - 记录加 5 ,记录现在是 [5] 28 | "-2" - 记录加 -2 ,记录现在是 [5, -2] 29 | "4" - 记录加 4 ,记录现在是 [5, -2, 4] 30 | "C" - 使前一次得分的记录无效并将其移除,记录现在是 [5, -2] 31 | "D" - 记录加 2 * -2 = -4 ,记录现在是 [5, -2, -4] 32 | "9" - 记录加 9 ,记录现在是 [5, -2, -4, 9] 33 | "+" - 记录加 -4 + 9 = 5 ,记录现在是 [5, -2, -4, 9, 5] 34 | "+" - 记录加 9 + 5 = 14 ,记录现在是 [5, -2, -4, 9, 5, 14] 35 | 所有得分的总和 5 + -2 + -4 + 9 + 5 + 14 = 27 36 | 37 | 算法思路: 38 | 跟加减乘除思路类似,这里的C、D、+代表操作符,优先级一样。 39 | 准备两个栈,左栈作为操作数,右栈作为操作符。遇到数字压入左栈,遇到操作符根据规则进行计算即可。 40 | """ 41 | from typing import List 42 | 43 | 44 | class LinkedNode: 45 | def __init__(self, val_, next_=None): 46 | self.val = val_ 47 | self.next = next_ 48 | 49 | def __str__(self): 50 | return str(self.val) 51 | 52 | 53 | class LinkedStack: 54 | def __init__(self): 55 | self.first = None 56 | 57 | def push(self, val): 58 | self.first = LinkedNode(val, self.first) 59 | 60 | def pop(self): 61 | if not self.first: 62 | return None 63 | res = self.first.val 64 | self.first = self.first.next 65 | return res 66 | 67 | def peek(self): 68 | if not self.first: 69 | return None 70 | return self.first.val 71 | 72 | def empty(self): 73 | return self.first is None 74 | 75 | def __iter__(self): 76 | while self.first: 77 | yield self.first.val 78 | self.first = self.first.next 79 | 80 | 81 | class Solution: 82 | 83 | def __init__(self): 84 | self.left_stack = LinkedStack() 85 | self.right_stack = LinkedStack() 86 | 87 | def calPoints(self, ops: List[str]) -> int: 88 | for op in ops: 89 | if self.is_number(op): 90 | self.left_stack.push(int(op)) 91 | else: 92 | if op == 'C': 93 | self.left_stack.pop() 94 | elif op == 'D': 95 | self.left_stack.push(self.left_stack.peek() * 2) 96 | elif op == '+': 97 | self.left_stack.push(self.left_stack.peek() + self.left_stack.first.next.val) 98 | return sum(self.left_stack) 99 | 100 | def is_number(self, str_): 101 | try: 102 | int(str_) 103 | return True 104 | except ValueError: 105 | return False 106 | 107 | 108 | if __name__ == '__main__': 109 | print(Solution().calPoints(["5", "2", "C", "D", "+"])) 110 | -------------------------------------------------------------------------------- /algorithms/c03_graph/graph_adjacency_list.py: -------------------------------------------------------------------------------- 1 | """ 2 | File: graph_adjacency_list.py 3 | Created Time: 2023-02-23 4 | """ 5 | 6 | import sys 7 | from pathlib import Path 8 | 9 | sys.path.append(str(Path(__file__).parent.parent)) 10 | from algorithms.models import Vertex, vals_to_vets 11 | 12 | 13 | class GraphAdjList: 14 | """基于邻接表实现的无向图类""" 15 | 16 | def __init__(self, edges: list[list[Vertex]]): 17 | """构造方法""" 18 | # 邻接表,key: 顶点,value:该顶点的所有邻接顶点 19 | self.adj_list = dict[Vertex, list[Vertex]]() 20 | # 添加所有顶点和边 21 | for edge in edges: 22 | self.add_vertex(edge[0]) 23 | self.add_vertex(edge[1]) 24 | self.add_edge(edge[0], edge[1]) 25 | 26 | def size(self) -> int: 27 | """获取顶点数量""" 28 | return len(self.adj_list) 29 | 30 | def add_edge(self, vet1: Vertex, vet2: Vertex): 31 | """添加边""" 32 | if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: 33 | raise ValueError() 34 | # 添加边 vet1 - vet2 35 | self.adj_list[vet1].append(vet2) 36 | self.adj_list[vet2].append(vet1) 37 | 38 | def remove_edge(self, vet1: Vertex, vet2: Vertex): 39 | """删除边""" 40 | if vet1 not in self.adj_list or vet2 not in self.adj_list or vet1 == vet2: 41 | raise ValueError() 42 | # 删除边 vet1 - vet2 43 | self.adj_list[vet1].remove(vet2) 44 | self.adj_list[vet2].remove(vet1) 45 | 46 | def add_vertex(self, vet: Vertex): 47 | """添加顶点""" 48 | if vet in self.adj_list: 49 | return 50 | # 在邻接表中添加一个新链表 51 | self.adj_list[vet] = [] 52 | 53 | def remove_vertex(self, vet: Vertex): 54 | """删除顶点""" 55 | if vet not in self.adj_list: 56 | raise ValueError() 57 | # 在邻接表中删除顶点 vet 对应的链表 58 | self.adj_list.pop(vet) 59 | # 遍历其他顶点的链表,删除所有包含 vet 的边 60 | for vertex in self.adj_list: 61 | if vet in self.adj_list[vertex]: 62 | self.adj_list[vertex].remove(vet) 63 | 64 | def print(self): 65 | """打印邻接表""" 66 | print("邻接表 =") 67 | for vertex in self.adj_list: 68 | tmp = [v.val for v in self.adj_list[vertex]] 69 | print(f"{vertex.val}: {tmp},") 70 | 71 | 72 | """Driver Code""" 73 | if __name__ == "__main__": 74 | # 初始化无向图 75 | v = vals_to_vets([1, 3, 2, 5, 4]) 76 | edges = [ 77 | [v[0], v[1]], 78 | [v[0], v[3]], 79 | [v[1], v[2]], 80 | [v[2], v[3]], 81 | [v[2], v[4]], 82 | [v[3], v[4]], 83 | ] 84 | graph = GraphAdjList(edges) 85 | print("\n初始化后,图为") 86 | graph.print() 87 | 88 | # 添加边 89 | # 顶点 1, 2 即 v[0], v[2] 90 | graph.add_edge(v[0], v[2]) 91 | print("\n添加边 1-2 后,图为") 92 | graph.print() 93 | 94 | # 删除边 95 | # 顶点 1, 3 即 v[0], v[1] 96 | graph.remove_edge(v[0], v[1]) 97 | print("\n删除边 1-3 后,图为") 98 | graph.print() 99 | 100 | # 添加顶点 101 | v5 = Vertex(6) 102 | graph.add_vertex(v5) 103 | print("\n添加顶点 6 后,图为") 104 | graph.print() 105 | 106 | # 删除顶点 107 | # 顶点 3 即 v[1] 108 | graph.remove_vertex(v[1]) 109 | print("\n删除顶点 3 后,图为") 110 | graph.print() 111 | --------------------------------------------------------------------------------