From daf25d5e643563a8d40685314c326ac073c0405d Mon Sep 17 00:00:00 2001 From: Yudong Jin Date: Fri, 25 Nov 2022 20:12:20 +0800 Subject: [PATCH] Add python codes and for the chapter of computational complexity. Update Java codes. Update Contributors. --- README.md | 2 +- .../leetcode_two_sum.go | 3 + .../leetcode_two_sum.java | 11 +- .../space_complexity_types.java | 4 +- .../time_complexity_types.java | 21 +- .../chapter_array_and_linkedlist/array.py | 1 + .../linked_list.py | 5 +- .../chapter_array_and_linkedlist/list.py | 5 +- .../chapter_array_and_linkedlist/my_list.py | 5 +- .../leetcode_two_sum.py | 31 +++ .../space_complexity_types.py | 68 +++++++ .../time_complexity_types.py | 130 +++++++++++++ .../worst_best_time_complexity.py | 24 +++ .../space_complexity.md | 92 +++++++-- .../space_time_tradeoff.md | 67 ++++--- .../time_complexity.md | 182 +++++++++++++++--- docs/chapter_preface/index.md | 2 +- 17 files changed, 571 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 68e3440ef..1a2c886b7 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ ## To-Dos -- [ ] [代码翻译](https://github.com/krahets/hello-algo/issues/15)(JavaScript, TypeScript, C, C#, ... 请求大佬帮助) +- [x] [代码翻译](https://github.com/krahets/hello-algo/issues/15)(JavaScript, TypeScript, C, C#, ... 请求大佬帮助) - [ ] 数据结构:散列表、堆(优先队列)、图 - [ ] 算法:搜索与回溯、选择 / 堆排序、动态规划、贪心、分治 diff --git a/codes/go/chapter_computational_complexity/leetcode_two_sum.go b/codes/go/chapter_computational_complexity/leetcode_two_sum.go index c3c34f99f..6712d97ec 100644 --- a/codes/go/chapter_computational_complexity/leetcode_two_sum.go +++ b/codes/go/chapter_computational_complexity/leetcode_two_sum.go @@ -7,6 +7,7 @@ package chapter_computational_complexity // twoSumBruteForce func twoSumBruteForce(nums []int, target int) []int { size := len(nums) + // 两层循环,时间复杂度 O(n^2) for i := 0; i < size-1; i++ { for j := i + 1; i < size; j++ { if nums[i]+nums[j] == target { @@ -19,7 +20,9 @@ func twoSumBruteForce(nums []int, target int) []int { // twoSumHashTable func twoSumHashTable(nums []int, target int) []int { + // 辅助哈希表,空间复杂度 O(n) hashTable := map[int]int{} + // 单层循环,时间复杂度 O(n) for idx, val := range nums { if preIdx, ok := hashTable[target-val]; ok { return []int{preIdx, idx} diff --git a/codes/java/chapter_computational_complexity/leetcode_two_sum.java b/codes/java/chapter_computational_complexity/leetcode_two_sum.java index 328e1c294..a05c014ae 100644 --- a/codes/java/chapter_computational_complexity/leetcode_two_sum.java +++ b/codes/java/chapter_computational_complexity/leetcode_two_sum.java @@ -8,9 +8,10 @@ package chapter_computational_complexity; import java.util.*; -class solution_brute_force { +class SolutionBruteForce { public int[] twoSum(int[] nums, int target) { int size = nums.length; + // 两层循环,时间复杂度 O(n^2) for (int i = 0; i < size - 1; i++) { for (int j = i + 1; j < size; j++) { if (nums[i] + nums[j] == target) @@ -21,10 +22,12 @@ class solution_brute_force { } } -class solution_hash_map { +class SolutionHashMap { public int[] twoSum(int[] nums, int target) { int size = nums.length; + // 辅助哈希表,空间复杂度 O(n) Map dic = new HashMap<>(); + // 单层循环,时间复杂度 O(n) for (int i = 0; i < size; i++) { if (dic.containsKey(target - nums[i])) { return new int[] { dic.get(target - nums[i]), i }; @@ -43,11 +46,11 @@ public class leetcode_two_sum { // ====== Driver Code ====== // 方法一 - solution_brute_force slt1 = new solution_brute_force(); + SolutionBruteForce slt1 = new SolutionBruteForce(); int[] res = slt1.twoSum(nums, target); System.out.println(Arrays.toString(res)); // 方法二 - solution_hash_map slt2 = new solution_hash_map(); + SolutionHashMap slt2 = new SolutionHashMap(); res = slt2.twoSum(nums, target); System.out.println(Arrays.toString(res)); } diff --git a/codes/java/chapter_computational_complexity/space_complexity_types.java b/codes/java/chapter_computational_complexity/space_complexity_types.java index 182dc47ef..0a8e1bded 100644 --- a/codes/java/chapter_computational_complexity/space_complexity_types.java +++ b/codes/java/chapter_computational_complexity/space_complexity_types.java @@ -100,7 +100,7 @@ public class space_complexity_types { quadratic(n); quadraticRecur(n); // 指数阶 - TreeNode tree = buildTree(n); - PrintUtil.printTree(tree); + TreeNode root = buildTree(n); + PrintUtil.printTree(root); } } diff --git a/codes/java/chapter_computational_complexity/time_complexity_types.java b/codes/java/chapter_computational_complexity/time_complexity_types.java index 2237ed977..27dc8caf1 100644 --- a/codes/java/chapter_computational_complexity/time_complexity_types.java +++ b/codes/java/chapter_computational_complexity/time_complexity_types.java @@ -29,7 +29,6 @@ public class time_complexity_types { int count = 0; // 循环次数与数组长度成正比 for (int num : nums) { - // System.out.println(num); count++; } return count; @@ -38,6 +37,7 @@ public class time_complexity_types { /* 平方阶 */ static int quadratic(int n) { int count = 0; + // 循环次数与数组长度成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; @@ -47,18 +47,22 @@ public class time_complexity_types { } /* 平方阶(冒泡排序) */ - static void bubbleSort(int[] nums) { - int n = nums.length; - for (int i = 0; i < n - 1; i++) { - for (int j = 0; j < n - 1 - i; j++) { + static int bubbleSort(int[] nums) { + int count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.length - 1; i > 0; i--) { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 和 nums[j + 1] + // 交换 nums[j] 与 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; + count += 3; // 元素交换包含 3 个单元操作 } } } + return count; } /* 指数阶(循环实现) */ @@ -135,6 +139,11 @@ public class time_complexity_types { count = quadratic(n); System.out.println("平方阶的计算操作数量 = " + count); + int[] nums = new int[n]; + for (int i = 0; i < n; i++) + nums[i] = n - i; // [n,n-1,...,2,1] + count = bubbleSort(nums); + System.out.println("平方阶(冒泡排序)的计算操作数量 = " + count); count = exponential(n); System.out.println("指数阶(循环实现)的计算操作数量 = " + count); diff --git a/codes/python/chapter_array_and_linkedlist/array.py b/codes/python/chapter_array_and_linkedlist/array.py index f505aa43e..826c3bd9e 100644 --- a/codes/python/chapter_array_and_linkedlist/array.py +++ b/codes/python/chapter_array_and_linkedlist/array.py @@ -57,6 +57,7 @@ def find(nums, target): return i return -1 + """ Driver Code """ if __name__ == "__main__": """ 初始化数组 """ diff --git a/codes/python/chapter_array_and_linkedlist/linked_list.py b/codes/python/chapter_array_and_linkedlist/linked_list.py index 1f5aaca32..d176d8dd2 100644 --- a/codes/python/chapter_array_and_linkedlist/linked_list.py +++ b/codes/python/chapter_array_and_linkedlist/linked_list.py @@ -41,9 +41,8 @@ def find(head, target): index += 1 return -1 -""" -Driver Code -""" + +""" Driver Code """ if __name__ == "__main__": """ 初始化链表 """ # 初始化各个结点 diff --git a/codes/python/chapter_array_and_linkedlist/list.py b/codes/python/chapter_array_and_linkedlist/list.py index 8f457654f..4cd9b0312 100644 --- a/codes/python/chapter_array_and_linkedlist/list.py +++ b/codes/python/chapter_array_and_linkedlist/list.py @@ -8,9 +8,8 @@ import sys, os.path as osp sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * -""" -Driver Code -""" + +""" Driver Code """ if __name__ == "__main__": """ 初始化列表 """ list = [1, 3, 2, 5, 4] diff --git a/codes/python/chapter_array_and_linkedlist/my_list.py b/codes/python/chapter_array_and_linkedlist/my_list.py index e5202ed59..54a75b338 100644 --- a/codes/python/chapter_array_and_linkedlist/my_list.py +++ b/codes/python/chapter_array_and_linkedlist/my_list.py @@ -8,7 +8,6 @@ import sys, os.path as osp sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * - """ 列表类简易实现 """ class MyList: """ 构造函数 """ @@ -71,9 +70,7 @@ class MyList: return self._nums[:self._size] -""" -Driver Code -""" +""" Driver Code """ if __name__ == "__main__": """ 初始化列表 """ list = MyList() diff --git a/codes/python/chapter_computational_complexity/leetcode_two_sum.py b/codes/python/chapter_computational_complexity/leetcode_two_sum.py index f235ddb8e..61b7b5c2e 100644 --- a/codes/python/chapter_computational_complexity/leetcode_two_sum.py +++ b/codes/python/chapter_computational_complexity/leetcode_two_sum.py @@ -8,3 +8,34 @@ import sys, os.path as osp sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * +class SolutionBruteForce: + def twoSum(self, nums: List[int], target: int) -> List[int]: + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return i, j + return [] + +class SolutionHashMap: + def twoSum(self, nums: List[int], target: int) -> List[int]: + dic = {} + for i in range(len(nums)): + if target - nums[i] in dic: + return dic[target - nums[i]], i + dic[nums[i]] = i + return [] + + +""" Driver Code """ +if __name__ == '__main__': + # ======= Test Case ======= + nums = [ 2,7,11,15 ]; + target = 9; + + # ====== Driver Code ====== + # 方法一 + res = SolutionBruteForce().twoSum(nums, target); + print(res) + # 方法二 + res = SolutionHashMap().twoSum(nums, target); + print(res) diff --git a/codes/python/chapter_computational_complexity/space_complexity_types.py b/codes/python/chapter_computational_complexity/space_complexity_types.py index 8905c5b20..e8568a2cf 100644 --- a/codes/python/chapter_computational_complexity/space_complexity_types.py +++ b/codes/python/chapter_computational_complexity/space_complexity_types.py @@ -8,3 +8,71 @@ import sys, os.path as osp sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * +""" 函数 """ +def function(): + # do something + return 0 + +""" 常数阶 """ +def constant(n): + # 常量、变量、对象占用 O(1) 空间 + a = 0 + nums = [0] * 10000 + node = ListNode(0) + # 循环中的变量占用 O(1) 空间 + for _ in range(n): + c = 0 + # 循环中的函数占用 O(1) 空间 + for _ in range(n): + function() + +""" 线性阶 """ +def linear(n): + # 长度为 n 的列表占用 O(n) 空间 + nums = [0] * n + # 长度为 n 的哈希表占用 O(n) 空间 + mapp = {} + for i in range(n): + mapp[i] = str(i) + +""" 线性阶(递归实现) """ +def linearRecur(n): + print("递归 n = ", n) + if n == 1: return + linearRecur(n - 1) + +""" 平方阶 """ +def quadratic(n): + # 二维列表占用 O(n^2) 空间 + num_matrix = [[0] * n for _ in range(n)] + +""" 平方阶(递归实现) """ +def quadratic_recur(n): + if n <= 0: return 0 + nums = [0] * n + print("递归 n = {} 中的 nums 长度 = {}".format(n, len(nums))) + return quadratic_recur(n - 1) + +""" 指数阶(建立满二叉树) """ +def build_tree(n): + if n == 0: return None + root = TreeNode(0) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root + + +""" Driver Code """ +if __name__ == "__main__": + n = 5 + # 常数阶 + constant(n) + # 线性阶 + linear(n) + linearRecur(n) + # 平方阶 + quadratic(n) + quadratic_recur(n) + # 指数阶 + root = build_tree(n) + print_tree(root) diff --git a/codes/python/chapter_computational_complexity/time_complexity_types.py b/codes/python/chapter_computational_complexity/time_complexity_types.py index e60ae4cfd..c56ef7b04 100644 --- a/codes/python/chapter_computational_complexity/time_complexity_types.py +++ b/codes/python/chapter_computational_complexity/time_complexity_types.py @@ -8,3 +8,133 @@ import sys, os.path as osp sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * +""" 常数阶 """ +def constant(n): + count = 0 + size = 100000 + for _ in range(size): + count += 1 + return count + +""" 线性阶 """ +def linear(n): + count = 0 + for _ in range(n): + count += 1 + return count + +""" 线性阶(遍历数组)""" +def array_traversal(nums): + count = 0 + # 循环次数与数组长度成正比 + for num in nums: + count += 1 + return count + +""" 平方阶 """ +def quadratic(n): + count = 0 + # 循环次数与数组长度成平方关系 + for i in range(n): + for j in range(n): + count += 1 + return count + +""" 平方阶(冒泡排序)""" +def bubble_sort(nums): + count = 0 # 计数器 + # 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for i in range(len(nums) - 1, 0, -1): + # 内循环:冒泡操作 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交换 nums[j] 与 nums[j + 1] + tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 元素交换包含 3 个单元操作 + return count + +""" 指数阶(循环实现)""" +def exponential(n): + count, base = 0, 1 + # cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for _ in range(n): + for _ in range(base): + count += 1 + base *= 2 + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count + +""" 指数阶(递归实现)""" +def exp_recur(n): + if n == 1: return 1 + return exp_recur(n - 1) + exp_recur(n - 1) + 1 + +""" 对数阶(循环实现)""" +def logarithmic(n): + count = 0 + while n > 1: + n = n / 2 + count += 1 + return count + +""" 对数阶(递归实现)""" +def log_recur(n): + if n <= 1: return 0 + return log_recur(n / 2) + 1 + +""" 线性对数阶 """ +def linear_log_recur(n): + if n <= 1: return 1 + count = linear_log_recur(n // 2) + \ + linear_log_recur(n // 2) + for _ in range(n): + count += 1 + return count + +""" 阶乘阶(递归实现)""" +def factorial_recur(n): + if n == 0: return 1 + count = 0 + # 从 1 个分裂出 n 个 + for _ in range(n): + count += factorial_recur(n - 1) + return count + + +""" Driver Code """ +if __name__ == "__main__": + # 可以修改 n 运行,体会一下各种复杂度的操作数量变化趋势 + n = 8 + print("输入数据大小 n =", n) + + count = constant(n) + print("常数阶的计算操作数量 =", count) + + count = linear(n) + print("线性阶的计算操作数量 =", count) + count = array_traversal([0] * n) + print("线性阶(遍历数组)的计算操作数量 =", count) + + count = quadratic(n) + print("平方阶的计算操作数量 =", count) + nums = [i for i in range(n, 0, -1)] # [n,n-1,...,2,1] + count = bubble_sort(nums) + print("平方阶(冒泡排序)的计算操作数量 =", count) + + count = exponential(n) + print("指数阶(循环实现)的计算操作数量 =", count) + count = exp_recur(n) + print("指数阶(递归实现)的计算操作数量 =", count) + + count = logarithmic(n) + print("对数阶(循环实现)的计算操作数量 =", count) + count = log_recur(n) + print("对数阶(递归实现)的计算操作数量 =", count) + + count = linear_log_recur(n) + print("线性对数阶(递归实现)的计算操作数量 =", count) + + count = factorial_recur(n) + print("阶乘阶(递归实现)的计算操作数量 =", count) diff --git a/codes/python/chapter_computational_complexity/worst_best_time_complexity.py b/codes/python/chapter_computational_complexity/worst_best_time_complexity.py index 7a4b246d8..7b052a3e0 100644 --- a/codes/python/chapter_computational_complexity/worst_best_time_complexity.py +++ b/codes/python/chapter_computational_complexity/worst_best_time_complexity.py @@ -8,3 +8,27 @@ import sys, os.path as osp sys.path.append(osp.dirname(osp.dirname(osp.abspath(__file__)))) from include import * +""" 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """ +def random_numbers(n): + # 生成数组 nums =: 1, 2, 3, ..., n + nums = [i for i in range(1, n + 1)] + # 随机打乱数组元素 + random.shuffle(nums) + return nums + +""" 查找数组 nums 中数字 1 所在索引 """ +def find_one(nums): + for i in range(len(nums)): + if nums[i] == 1: + return i + return -1 + + +""" Driver Code """ +if __name__ == "__main__": + for i in range(10): + n = 100 + nums = random_numbers(n) + index = find_one(nums) + print("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) + print("数字 1 的索引为", index) diff --git a/docs/chapter_computational_complexity/space_complexity.md b/docs/chapter_computational_complexity/space_complexity.md index b7856b65d..c1b3a984b 100644 --- a/docs/chapter_computational_complexity/space_complexity.md +++ b/docs/chapter_computational_complexity/space_complexity.md @@ -62,7 +62,23 @@ comments: true === "Python" ```python title="" - + """ 类 """ + class Node: + def __init__(self, x): + self.val = x # 结点值 + self.next = None # 指向下一结点的指针(引用) + + """ 函数(或称方法) """ + def function(): + # do something... + return 0 + + def algorithm(n): # 输入数据 + a = 0 # 暂存数据(常量) + b = 0 # 暂存数据(变量) + node = Node(0) # 暂存数据(对象) + c = function() # 栈帧空间(调用函数) + return a + b + c # 输出数据 ``` ## 推算方法 @@ -94,7 +110,11 @@ comments: true === "Python" ```python title="" - + def algorithm(n): + a = 0 # O(1) + b = [0] * 10000 # O(1) + if n > 10: + nums = [0] * n # O(n) ``` **在递归函数中,需要注意统计栈帧空间。** 例如函数 `loop()`,在循环中调用了 $n$ 次 `function()` ,每轮中的 `function()` 都返回并释放了栈帧空间,因此空间复杂度仍为 $O(1)$ 。而递归函数 `recur()` 在运行中会同时存在 $n$ 个未返回的 `recur()` ,从而使用 $O(n)$ 的栈帧空间。 @@ -106,13 +126,13 @@ comments: true // do something return 0; } - /* 循环 */ + /* 循环 O(1) */ void loop(int n) { for (int i = 0; i < n; i++) { function(); } } - /* 递归 */ + /* 递归 O(n) */ void recur(int n) { if (n == 1) return; return recur(n - 1); @@ -128,7 +148,19 @@ comments: true === "Python" ```python title="" - + def function(): + # do something + return 0 + + """ 循环 O(1) """ + def loop(n): + for _ in range(n): + function() + + """ 递归 O(n) """ + def recur(n): + if n == 1: return + return recur(n - 1) ``` ## 常见类型 @@ -186,7 +218,18 @@ $$ === "Python" ```python title="space_complexity_types.py" - + """ 常数阶 """ + def constant(n): + # 常量、变量、对象占用 O(1) 空间 + a = 0 + nums = [0] * 10000 + node = ListNode(0) + # 循环中的变量占用 O(1) 空间 + for _ in range(n): + c = 0 + # 循环中的函数占用 O(1) 空间 + for _ in range(n): + function() ``` ### 线性阶 $O(n)$ @@ -222,7 +265,14 @@ $$ === "Python" ```python title="space_complexity_types.py" - + """ 线性阶 """ + def linear(n): + # 长度为 n 的列表占用 O(n) 空间 + nums = [0] * n + # 长度为 n 的哈希表占用 O(n) 空间 + mapp = {} + for i in range(n): + mapp[i] = str(i) ``` 以下递归函数会同时存在 $n$ 个未返回的 `algorithm()` 函数,使用 $O(n)$ 大小的栈帧空间。 @@ -247,7 +297,11 @@ $$ === "Python" ```python title="space_complexity_types.py" - + """ 线性阶(递归实现) """ + def linearRecur(n): + print("递归 n = ", n) + if n == 1: return + linearRecur(n - 1) ``` ![space_complexity_recursive_linear](space_complexity.assets/space_complexity_recursive_linear.png) @@ -286,7 +340,10 @@ $$ === "Python" ```python title="space_complexity_types.py" - + """ 平方阶 """ + def quadratic(n): + # 二维列表占用 O(n^2) 空间 + num_matrix = [[0] * n for _ in range(n)] ``` 在以下递归函数中,同时存在 $n$ 个未返回的 `algorihtm()` ,并且每个函数中都初始化了一个数组,长度分别为 $n, n-1, n-2, ..., 2, 1$ ,平均长度为 $\frac{n}{2}$ ,因此总体使用 $O(n^2)$ 空间。 @@ -297,8 +354,8 @@ $$ /* 平方阶(递归实现) */ int quadraticRecur(int n) { if (n <= 0) return 0; + // 数组 nums 长度为 n, n-1, ..., 2, 1 int[] nums = new int[n]; - System.out.println("递归 n = " + n + " 中的 nums 长度 = " + nums.length); return quadraticRecur(n - 1); } ``` @@ -312,7 +369,12 @@ $$ === "Python" ```python title="space_complexity_types.py" - + """ 平方阶(递归实现) """ + def quadratic_recur(n): + if n <= 0: return 0 + # 数组 nums 长度为 n, n-1, ..., 2, 1 + nums = [0] * n + return quadratic_recur(n - 1) ``` ![space_complexity_recursive_quadratic](space_complexity.assets/space_complexity_recursive_quadratic.png) @@ -345,7 +407,13 @@ $$ === "Python" ```python title="space_complexity_types.py" - + """ 指数阶(建立满二叉树) """ + def build_tree(n): + if n == 0: return None + root = TreeNode(0) + root.left = build_tree(n - 1) + root.right = build_tree(n - 1) + return root ``` ![space_complexity_exponential](space_complexity.assets/space_complexity_exponential.png) diff --git a/docs/chapter_computational_complexity/space_time_tradeoff.md b/docs/chapter_computational_complexity/space_time_tradeoff.md index 0f82f6c43..07975a5bb 100644 --- a/docs/chapter_computational_complexity/space_time_tradeoff.md +++ b/docs/chapter_computational_complexity/space_time_tradeoff.md @@ -17,16 +17,18 @@ === "Java" ```java title="" title="leetcode_two_sum.java" - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 外层 * 内层循环,时间复杂度为 O(n) - for (int i = 0; i < size - 1; i++) { - for (int j = i + 1; j < size; j++) { - if (nums[i] + nums[j] == target) - return new int[] { i, j }; + class SolutionBruteForce { + public int[] twoSum(int[] nums, int target) { + int size = nums.length; + // 两层循环,时间复杂度 O(n^2) + for (int i = 0; i < size - 1; i++) { + for (int j = i + 1; j < size; j++) { + if (nums[i] + nums[j] == target) + return new int[] { i, j }; + } } + return new int[0]; } - return new int[0]; } ``` @@ -39,14 +41,22 @@ === "Python" ```python title="leetcode_two_sum.py" - + class SolutionBruteForce: + def twoSum(self, nums: List[int], target: int) -> List[int]: + # 两层循环,时间复杂度 O(n^2) + for i in range(len(nums) - 1): + for j in range(i + 1, len(nums)): + if nums[i] + nums[j] == target: + return i, j + return [] ``` === "Go" ```go title="leetcode_two_sum.go" - func twoSum(nums []int, target int) []int { + func twoSumBruteForce(nums []int, target int) []int { size := len(nums) + // 两层循环,时间复杂度 O(n^2) for i := 0; i < size-1; i++ { for j := i + 1; i < size; j++ { if nums[i]+nums[j] == target { @@ -58,8 +68,6 @@ } ``` - - ### 方法二:辅助哈希表 时间复杂度 $O(N)$ ,空间复杂度 $O(N)$ ,属于「空间换时间」。 @@ -69,18 +77,20 @@ === "Java" ```java title="" title="leetcode_two_sum.java" - public int[] twoSum(int[] nums, int target) { - int size = nums.length; - // 辅助哈希表,空间复杂度 O(n) - Map dic = new HashMap<>(); - // 单层循环,时间复杂度 O(n) - for (int i = 0; i < size; i++) { - if (dic.containsKey(target - nums[i])) { - return new int[] { dic.get(target - nums[i]), i }; + class SolutionHashMap { + public int[] twoSum(int[] nums, int target) { + int size = nums.length; + // 辅助哈希表,空间复杂度 O(n) + Map dic = new HashMap<>(); + // 单层循环,时间复杂度 O(n) + for (int i = 0; i < size; i++) { + if (dic.containsKey(target - nums[i])) { + return new int[] { dic.get(target - nums[i]), i }; + } + dic.put(nums[i], i); } - dic.put(nums[i], i); + return new int[0]; } - return new int[0]; } ``` @@ -93,14 +103,25 @@ === "Python" ```python title="leetcode_two_sum.py" - + class SolutionHashMap: + def twoSum(self, nums: List[int], target: int) -> List[int]: + # 辅助哈希表,空间复杂度 O(n) + dic = {} + # 单层循环,时间复杂度 O(n) + for i in range(len(nums)): + if target - nums[i] in dic: + return dic[target - nums[i]], i + dic[nums[i]] = i + return [] ``` === "Go" ```go title="leetcode_two_sum.go" func twoSumHashTable(nums []int, target int) []int { + // 辅助哈希表,空间复杂度 O(n) hashTable := map[int]int{} + // 单层循环,时间复杂度 O(n) for idx, val := range nums { if preIdx, ok := hashTable[target-val]; ok { return []int{preIdx, idx} diff --git a/docs/chapter_computational_complexity/time_complexity.md b/docs/chapter_computational_complexity/time_complexity.md index 7742aec5f..a25cabf71 100644 --- a/docs/chapter_computational_complexity/time_complexity.md +++ b/docs/chapter_computational_complexity/time_complexity.md @@ -42,7 +42,14 @@ $$ === "Python" ```python title="" - + # 在某运行平台下 + def algorithm(n): + a = 2 # 1 ns + a = a + 1 # 1 ns + a = a * 2 # 10 ns + # 循环 n 次 + for _ in range(n): # 1 ns + print(0) # 5 ns ``` 但实际上, **统计算法的运行时间既不合理也不现实。** 首先,我们不希望预估时间和运行平台绑定,毕竟算法需要跑在各式各样的平台之上。其次,我们很难获知每一种操作的运行时间,这为预估过程带来了极大的难度。 @@ -87,7 +94,17 @@ $$ === "Python" ```python title="" - + # 算法 A 时间复杂度:常数阶 + def algorithm_A(n): + print(0) + # 算法 B 时间复杂度:线性阶 + def algorithm_B(n): + for _ in range(n): + print(0) + # 算法 C 时间复杂度:常数阶 + def algorithm_C(n): + for _ in range(1000000): + print(0) ``` ![time_complexity_first_example](time_complexity.assets/time_complexity_first_example.png) @@ -105,9 +122,11 @@ $$ ## 函数渐进上界 设算法「计算操作数量」为 $T(n)$ ,其是一个关于输入数据大小 $n$ 的函数。例如,以下算法的操作数量为 + $$ T(n) = 3 + 2n $$ + === "Java" ```java title="" @@ -131,14 +150,21 @@ $$ === "Python" ```python title="" - + def algorithm(n): + a = 1 # +1 + a = a + 1 # +1 + a = a * 2 # +1 + # 循环 n 次 + for i in range(n): # +1 + print(0) # +1 + } ``` $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得时间复杂度是线性阶。 我们将线性阶的时间复杂度记为 $O(n)$ ,这个数学符号被称为「大 $O$ 记号 Big-$O$ Notation」,代表函数 $T(n)$ 的「渐进上界 asymptotic upper bound」。 -我们要推算时间复杂度,本质上是在计算「操作数量函数 $T(n)$ 」的渐进上界。下面我们先来看看函数渐进上界的数学定义。 +我们要推算时间复杂度,本质上是在计算「操作数量函数 $T(n)$ 」的渐进上界。下面我们先来看看函数渐进上界的数学定义。 !!! abstract "函数渐进上界" @@ -174,6 +200,7 @@ $T(n)$ 是个一次函数,说明时间增长趋势是线性的,因此易得 3. **循环嵌套时使用乘法。** 总操作数量等于外层循环和内层循环操作数量之积,每一层循环依然可以分别套用上述 `1.` 和 `2.` 技巧。 根据以下示例,使用上述技巧前、后的统计结果分别为 + $$ \begin{aligned} T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{完整统计 (-.-|||)} \newline @@ -181,6 +208,7 @@ T(n) & = 2n(n + 1) + (5n + 1) + 2 & \text{完整统计 (-.-|||)} \newline T(n) & = n^2 + n & \text{偷懒统计 (o.O)} \end{aligned} $$ + 最终,两者都能推出相同的时间复杂度结果,即 $O(n^2)$ 。 === "Java" @@ -211,7 +239,16 @@ $$ === "Python" ```python title="" - + def algorithm(n): + a = 1 # +0(技巧 1) + a = a + n # +0(技巧 1) + # +n(技巧 2) + for i in range(5 * n + 1): + print(0) + # +n*n(技巧 3) + for i in range(2 * n): + for j in range(n + 1): + print(0) ``` ### 2. 判断渐进上界 @@ -279,7 +316,13 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 常数阶 """ + def constant(n): + count = 0 + size = 100000 + for _ in range(size): + count += 1 + return count ``` ### 线性阶 $O(n)$ @@ -307,7 +350,12 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 线性阶 """ + def linear(n): + count = 0 + for _ in range(n): + count += 1 + return count ``` 「遍历数组」和「遍历链表」等操作,时间复杂度都为 $O(n)$ ,其中 $n$ 为数组或链表的长度。 @@ -339,7 +387,13 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 线性阶(遍历数组)""" + def array_traversal(nums): + count = 0 + # 循环次数与数组长度成正比 + for num in nums: + count += 1 + return count ``` ### 平方阶 $O(n^2)$ @@ -352,6 +406,7 @@ $$ /* 平方阶 */ int quadratic(int n) { int count = 0; + // 循环次数与数组长度成平方关系 for (int i = 0; i < n; i++) { for (int j = 0; j < n; j++) { count++; @@ -370,7 +425,14 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 平方阶 """ + def quadratic(n): + count = 0 + # 循环次数与数组长度成平方关系 + for i in range(n): + for j in range(n): + count += 1 + return count ``` ![time_complexity_constant_linear_quadratic](time_complexity.assets/time_complexity_constant_linear_quadratic.png) @@ -387,18 +449,22 @@ $$ ```java title="" title="time_complexity_types.java" /* 平方阶(冒泡排序) */ - void bubbleSort(int[] nums) { - int n = nums.length; - for (int i = 0; i < n - 1; i++) { - for (int j = 0; j < n - 1 - i; j++) { + int bubbleSort(int[] nums) { + int count = 0; // 计数器 + // 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for (int i = nums.length - 1; i > 0; i--) { + // 内循环:冒泡操作 + for (int j = 0; j < i; j++) { if (nums[j] > nums[j + 1]) { - // 交换 nums[j] 和 nums[j + 1] + // 交换 nums[j] 与 nums[j + 1] int tmp = nums[j]; nums[j] = nums[j + 1]; nums[j + 1] = tmp; + count += 3; // 元素交换包含 3 个单元操作 } } } + return count; } ``` @@ -411,7 +477,20 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 平方阶(冒泡排序)""" + def bubble_sort(nums): + count = 0 # 计数器 + # 外循环:待排序元素数量为 n-1, n-2, ..., 1 + for i in range(len(nums) - 1, 0, -1): + # 内循环:冒泡操作 + for j in range(i): + if nums[j] > nums[j + 1]: + # 交换 nums[j] 与 nums[j + 1] + tmp = nums[j] + nums[j] = nums[j + 1] + nums[j + 1] = tmp + count += 3 # 元素交换包含 3 个单元操作 + return count ``` ### 指数阶 $O(2^n)$ @@ -425,7 +504,7 @@ $$ === "Java" ```java title="" title="time_complexity_types.java" - /* 指数阶(遍历实现) */ + /* 指数阶(循环实现) */ int exponential(int n) { int count = 0, base = 1; // cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) @@ -449,7 +528,16 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 指数阶(循环实现)""" + def exponential(n): + count, base = 0, 1 + # cell 每轮一分为二,形成数列 1, 2, 4, 8, ..., 2^(n-1) + for _ in range(n): + for _ in range(base): + count += 1 + base *= 2 + # count = 1 + 2 + 4 + 8 + .. + 2^(n-1) = 2^n - 1 + return count ``` ![time_complexity_exponential](time_complexity.assets/time_complexity_exponential.png) @@ -477,7 +565,10 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 指数阶(递归实现)""" + def exp_recur(n): + if n == 1: return 1 + return exp_recur(n - 1) + exp_recur(n - 1) + 1 ``` ### 对数阶 $O(\log n)$ @@ -511,7 +602,13 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 对数阶(循环实现)""" + def logarithmic(n): + count = 0 + while n > 1: + n = n / 2 + count += 1 + return count ``` ![time_complexity_logarithmic](time_complexity.assets/time_complexity_logarithmic.png) @@ -539,7 +636,10 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 对数阶(递归实现)""" + def log_recur(n): + if n <= 1: return 0 + return log_recur(n / 2) + 1 ``` ### 线性对数阶 $O(n \log n)$ @@ -572,7 +672,14 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 线性对数阶 """ + def linear_log_recur(n): + if n <= 1: return 1 + count = linear_log_recur(n // 2) + \ + linear_log_recur(n // 2) + for _ in range(n): + count += 1 + return count ``` ![time_complexity_logarithmic_linear](time_complexity.assets/time_complexity_logarithmic_linear.png) @@ -613,7 +720,14 @@ $$ === "Python" ```python title="time_complexity_types.py" - + """ 阶乘阶(递归实现)""" + def factorial_recur(n): + if n == 0: return 1 + count = 0 + # 从 1 个分裂出 n 个 + for _ in range(n): + count += factorial_recur(n - 1) + return count ``` ![time_complexity_factorial](time_complexity.assets/time_complexity_factorial.png) @@ -681,7 +795,29 @@ $$ === "Python" ```python title="worst_best_time_complexity.py" - + """ 生成一个数组,元素为: 1, 2, ..., n ,顺序被打乱 """ + def random_numbers(n): + # 生成数组 nums =: 1, 2, 3, ..., n + nums = [i for i in range(1, n + 1)] + # 随机打乱数组元素 + random.shuffle(nums) + return nums + + """ 查找数组 nums 中数字 1 所在索引 """ + def find_one(nums): + for i in range(len(nums)): + if nums[i] == 1: + return i + return -1 + + """ Driver Code """ + if __name__ == "__main__": + for i in range(10): + n = 100 + nums = random_numbers(n) + index = find_one(nums) + print("\n数组 [ 1, 2, ..., n ] 被打乱后 =", nums) + print("数字 1 的索引为", index) ``` !!! tip diff --git a/docs/chapter_preface/index.md b/docs/chapter_preface/index.md index 7c7a9beae..597649e72 100644 --- a/docs/chapter_preface/index.md +++ b/docs/chapter_preface/index.md @@ -111,7 +111,7 @@ comments: true ## 致谢 -感谢本开源书的每一位撰稿人,是他们的无私奉献让这本书变得更好,他们的 GitHub ID(按首次提交时间排序)为:krahets, *(等待下一位创作者)* +感谢本开源书的每一位撰稿人,是他们的无私奉献让这本书变得更好,他们的 GitHub ID(按首次提交时间排序)为:krahets, Reanon. 本书的成书过程中,我获得了许多人的帮助,包括但不限于: