diff --git "a/1\350\264\252\345\277\203 \345\217\266\345\200\274\347\232\204\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221.png" "b/1\350\264\252\345\277\203 \345\217\266\345\200\274\347\232\204\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221.png" new file mode 100644 index 0000000000000000000000000000000000000000..4c7dcd6378df952474cb08dbf07abbe5546608b5 Binary files /dev/null and "b/1\350\264\252\345\277\203 \345\217\266\345\200\274\347\232\204\346\234\200\345\260\217\347\224\237\346\210\220\346\240\221.png" differ diff --git "a/2.\350\264\252\345\277\203\357\274\214\344\270\211\350\247\222\345\275\242\346\234\200\345\244\247\345\221\250\351\225\277.png" "b/2.\350\264\252\345\277\203\357\274\214\344\270\211\350\247\222\345\275\242\346\234\200\345\244\247\345\221\250\351\225\277.png" new file mode 100644 index 0000000000000000000000000000000000000000..04169b2a7f7593449ddc7ba88050fb494239bdb6 Binary files /dev/null and "b/2.\350\264\252\345\277\203\357\274\214\344\270\211\350\247\222\345\275\242\346\234\200\345\244\247\345\221\250\351\225\277.png" differ diff --git "a/3\345\233\236\346\272\257N\347\232\207\345\220\216.png" "b/3\345\233\236\346\272\257N\347\232\207\345\220\216.png" new file mode 100644 index 0000000000000000000000000000000000000000..70ba38e6f879d89e9865dce7566522eb72feec23 Binary files /dev/null and "b/3\345\233\236\346\272\257N\347\232\207\345\220\216.png" differ diff --git a/alg.md b/alg.md index 271e21d2fd04625453e368924186838dfafd6463..97cec80b834d4fad19ee667d9f8bdf0c2dc87599 100644 --- a/alg.md +++ b/alg.md @@ -1,154 +1,300 @@ # 解题报告 -## 1.插入后的最大值 +## 1.叶值的最小代价生成树(1130贪心) ### 题目描述 -给你一个非常大的整数 `n` 和一个整数数字 `x` ,大整数 `n` 用一个字符串表示。`n` 中每一位数字和数字`x` 都处于闭区间` [1, 9]` 中,且 `n` 可能表示一个 负数 。 +给你一个正整数数组 arr,考虑所有满足以下条件的二叉树: -你打算通过在`n`的十进制表示的任意位置插入`x`来**最大化** `n` 的 **数值** 。但 **不能** 在负号的左边插入` x` 。 +每个节点都有 0 个或是 2 个子节点。 +数组 arr 中的值与树的中序遍历中每个叶节点的值一一对应。 +每个非叶节点的值等于其左子树和右子树中叶节点的最大值的乘积。 +在所有这样的二叉树中,返回每个非叶节点的值的最小可能总和。这个和的值是一个 32 位整数。 -- 例如,如果 `n = 73` 且 `x = 6` ,那么最佳方案是将 `6` 插入 `7` 和 `3` 之间,使 `n = 763` 。 -- 如果 `n = -55` 且 `x = 2` ,那么最佳方案是将 `2` 插在第一个 `5` 之前,使 `n = -255` 。 +如果一个节点有 0 个子节点,那么该节点为叶节点。 -返回插入操作后,用字符串表示的 `n` 的最大值。 +来源:力扣(LeetCode)1130 ### 思路分析 -由于插入操作不会改变 `n` 的符号,因此如果 `n` 为负数,那么插入后的最大值对应的**绝对值**最小;如果`n`为正数,那么插入后的最大值对应的**绝对值**最大。我们首先通过 *n* 的第一个字符是否为 ‘-‘ 确定它为负数还是正数,进而判断我们需要找到的绝对值应当最大还是最小。 +该算法采用了贪心算法的思想,每次选择左右子节点中较小的那个值,保证了当前节点的值尽量小,从而使得所有非叶子节点的值的和最小。具体实现时,使用单调栈来维护当前节点的左子树,每次遍历到一个新的数时,将栈顶元素和当前数作为左右子节点,计算它们的乘积并加入结果中,然后将当前数压入栈中。如果当前数比栈顶元素小,则将栈顶元素和之后的节点组成新的左子树,直到栈顶元素比当前数大为止。最后,将栈中剩余的节点两两组成左右子节点,计算它们的乘积并加入结果中。 + +其中mctFromLeafValues函数的作用是对于一个非空的整数数组arr,构建一颗二叉树,使得每个叶子节点的值为arr中的一个元素,并且每个非叶子节点的值为其左右子节点中较大值的乘积,求得所有可能的二叉树中,所有非叶子节点的值的和的最小值。 +函数采用单调栈的方法来处理,具体来说,维护一个单调递减的栈stack,将arr中的元素从左往右加入栈中。对于每个新元素num,如果栈顶元素stack[-1]比num小,就将栈顶元素取出,作为左右子节点中较小值,num作为较大值,计算它们的乘积,并加入到结果res中。这个过程相当于构建一颗二叉树中的一个子树,其中栈顶元素为左子节点,num为右子节点。因为我们要使得非叶子节点的值最小,所以应该让左右子节点的值尽量接近,这也是为什么要取较小值作为左子节点的原因。 -1. 如果 `n` 是负数,那么若 `n` 中存在比 `x` 小的位,那么这些位之前的插入位置中的第一个对应的插入后绝对值一定是最大的;若`n`中存在比 x 大的位,那么这些位之前的插入位置中的第一个对应的插入后绝对值一定是最小的。我们需要寻找 `n` 中第一个比`x` 大的位置。如果存在,则返回对应的插入后字符串;如果不存在,则返回 `x` 插入 `n` 结尾对应的字符串。 -2. 如果 `n` 是正数,类似地,我们需要寻找 `n` 中第一个比 `x` 小的位置。如果存在,则返回对应的插入后字符串;如果不存在,则返回 `x` 插入 `n` 结尾对应的字符串。 ### 代码实现 ```python +from random import randint +def generate_data(): + n = random.randint(2, 100) + arr = [random.randint(1, 100) for _ in range(n)] + return (arr, ) +from typing import List class Solution: - def maxValue(self, n: str, x: int) -> str: - l = len(n) - if n[0] == '-': - for i in range(1, l): - if int(n[i]) > x: - return n[:i] + str(x) + n[i:] - return n + str(x) - else: - for i in range(l): - if int(n[i]) < x: - return n[:i] + str(x) + n[i:] - return n + str(x) + def mctFromLeafValues(self, arr: List[int]) -> int: + res = 0 + stack = [float('inf')] + for num in arr: + while stack[-1] <= num: + mid = stack.pop() + res += mid * min(stack[-1], num) + stack.append(num) + while len(stack) > 2: + res += stack.pop() * stack[-1] + return res ``` +## 数据测试 + +为了测试算法的鲁棒性,我们需要生成一些随机的数据进行测试。生成数据的思路如下: + +1. 极端情况:输入为空数组,期望输出为0;输入只有一个元素,期望输出为0; + +2. 特殊情况:输入数组中所有元素都相等,期望输出为元素个数乘以该元素的值;输入数组按照升序或降序排列,期望输出为升序排列时的结果; + +3. 随机情况:随机生成不同长度的输入数组,每个元素随机取值,期望输出的结果可以通过暴力枚举得出,检查算法输出是否与暴力算法输出一致。 + +4. 边界情况:输入数组的长度为2,期望输出为两个元素的乘积;输入数组的长度为3,期望输出为两个元素的乘积加上第三个元素的值; + +5. 各种组合情况:输入数组中既有奇数,又有偶数;输入数组中有正数和负数等。 + +6. 我们可以通过构造一些特定的测试用例来测试该算法的正确性,例如: + + 输入[1,2,3,4,5],期望输出 33,此时最优的构建方法是: + + ​ 15 + ​ / \ + 10 5 + / \ + 6 4 + / \ + 2 3 + + 输入[3,2,5,4,1],期望输出 45,此时最优的构建方法是: + + ​ 27 + ​ / \ + 15 12 + / \ + 6 9 + / \ + 3 2 + + 输入[5,4,3,2,1],期望输出 40,此时最优的构建方法是: + + ​ 26 + ​ / \ + 12 14 + / \ + 4 8 + / \ + 2 2 + + 通过以上测试用例,我们可以验证该算法的正确性。 + + ## 测试示例 + + ​ 下面是一个测试代码的例子: + + ```python + def test(): + solution = Solution() + for i in range(10): + arr, = generate_data() + res = solution.mctFromLeafValues(arr) + print("Test case ", i + 1, ":\narr = ", arr, "\nres = ", res, "\n") + if __name__ == "__main__": + test() + ``` + + + + ![](C:\Users\XUZIY\Desktop\叶值代价生成树.png) + ### 复杂度分析 -- 时间复杂度:***O(l)***,其中 `l` 为 `n` 的长度。遍历 `n` 寻找插入位置的最坏时间复杂度为 ***O(l)***,将数字插入字符串的最坏时间复杂度为 ***O(l)***。 -- 空间复杂度:***O(1)***。 +- 该算法需要遍历整个输入数组,因此时间复杂度为O(n)。 +- 空间复杂度:每个数最多会被压入和弹出一次栈,因此空间复杂度也为O(n)。 -## 2.全排列 +## 2.三角形的最大周长(976贪心) ### 题目描述 -给定一个不含重复数字的数组 `nums` ,返回其 *所有可能的全排列* 。你可以 **按任意顺序** 返回答案。 +给定由一些正数(代表长度)组成的数组 nums ,返回 由其中三个长度组成的、面积不为零的三角形的最大周长 。如果不能形成任何面积不为零的三角形,返回 0。 ### 思路分析 -这个问题可以看作有 `n` 个排列成一行的空格,我们需要从左往右依此填入题目给定的 `n` 个数,每个数只能使用一次。那么很直接的可以想到一种穷举的算法,即从左往右每一个位置都依此尝试填入一个数,看能不能填完这 `n` 个空格,在程序中我们可以用「回溯法」来模拟这个过程。 - -我们定义递归函数 `backtrack(first,output)` 表示从左往右填到第 `first` 个位置,当前排列为`output`。 那么整个递归函数分为两个情况: - -- 如果 `first=n`,说明我们已经填完了 `n` 个位置(注意下标从 0 开始),找到了一个可行的解,我们将 `output` 放入答案数组中,递归结束。 -- 如果 `first int: + A.sort(reverse=True) + for i in range(len(A)-2): + if A[i] int: - size = len(nums) - if size == 0: - return 0 - return self.__max_sub_array(nums, 0, size - 1) - - def __max_sub_array(self, nums, left, right): - if left == right: - return nums[left] - mid = (left + right) >> 1 - return max(self.__max_sub_array(nums, left, mid), - self.__max_sub_array(nums, mid + 1, right), - self.__max_cross_array(nums, left, mid, right)) - - def __max_cross_array(self, nums, left, mid, right): - # 一定包含 nums[mid] 元素的最大连续子数组的和, - # 思路是看看左边"扩散到底",得到一个最大数,右边"扩散到底"得到一个最大数 - # 然后再加上中间数 - left_sum_max = 0 - start_left = mid - 1 - s1 = 0 - while start_left >= left: - s1 += nums[start_left] - left_sum_max = max(left_sum_max, s1) - start_left -= 1 - - right_sum_max = 0 - start_right = mid + 1 - s2 = 0 - while start_right <= right: - s2 += nums[start_right] - right_sum_max = max(right_sum_max, s2) - start_right += 1 - return left_sum_max + nums[mid] + right_sum_max + def solveNQueens(self, n: int) -> List[List[str]]: + # 初始化棋盘,全部填充为 . + board = [['.' for _ in range(n)] for _ in range(n)] + res = [] + # 已占用的列、正斜线、反斜线 + cols, diag1, diag2 = set(), set(), set() + def dfs(row): + # 如果已经遍历到最后一行,则将当前摆放情况加入结果集 + if row == n: + res.append(["".join(row) for row in board]) + return + # 遍历每一列 + for col in range(n): + # 判断当前位置是否合法 + if col not in cols and row + col not in diag1 and row - col not in diag2: + # 如果当前位置合法,则将皇后放置在该位置,并更新已占用的列、正斜线、反斜线 + board[row][col] = 'Q' + cols.add(col) + diag1.add(row + col) + diag2.add(row - col) + # 递归到下一行 + dfs(row + 1) + # 回溯,将皇后从该位置移除,并恢复已占用的列、正斜线、反斜线 + board[row][col] = '.' + cols.remove(col) + diag1.remove(row + col) + diag2.remove(row - col) + dfs(0) + return res +def generate_data(): + n = random.randint(1, 10) + return n +``` + +## 数据测试 + +数据生成函数 `generate_data()` 的思路很简单,只需要随机生成一个整数 `n`,表示棋盘的大小,即皇后的数量。该函数返回值即为 `n`。在测试时,会多次调用该函数,每次生成一个不同的 `n`。 + +## 测试示例 + +​ 下面是一个测试代码的例子: + +```python +def test_data(n): + s = Solution() + res = s.solveNQueens(n) + expected = math.factorial(n) if n <= 10 else None + if len(res) == expected: + print("test case passed: n = %d, num of solutions = %d" % (n, len(res))) + else: + print("test case failed: n = %d, num of solutions = %d, expected = %s" % (n, len(res), expected)) +if __name__ == "__main__": + for i in range(10): + n = generate_data() + print("testing case #%d: n = %d" % (i+1, n)) + test_data(n) ``` +![](C:\Users\XUZIY\Desktop\回溯N皇后.png) + ### 复杂度分析 -- 时间复杂度:***O(NlogN)***,这里递归的深度是对数级别的,每一层需要遍历一遍数组(或者数组的一半、四分之一); -- 空间复杂度:***O(logN)***,需要常数个变量用于选取最大值,需要使用的空间取决于递归栈的深度。 +- 时间复杂度:***O(n^n)***,算法的时间复杂度主要取决于在回溯过程中遍历的次数。对于每一行,最多需要遍历 $n$ 列,因此总的遍历次数为***O(n^n)*** ,其中 n 表示棋盘的大小,即皇后的数量。 +- 空间复杂度:***O(n^n)***,需要常数个变量用于选取最大值,需要使用的空间取决于递归栈的深度。此外,对于每个合法的摆放位置,需要将结果保存在结果集中,因此空间复杂度也为***O(n^n)***。总的时间复杂度和空间复杂度都非常高,限制了算法的使用场景。 + +## 心得体会 + +在使用 Jupyter Notebook 写完这些道题目后,我对 Jupyter Notebook 的使用更加熟练了。Jupyter Notebook 是一个非常好用的交互式开发工具,可以轻松地将代码、文本、图片、公式等内容集成在一起,方便编写和展示。在编写算法代码时,我可以通过 Markdown 单元格编写算法思路分析和时间复杂度分析,使得代码更加易于理解。同时,我还可以在代码单元格中使用代码高亮、代码补全、代码调试等功能,提高了编写代码的效率。 +此外,我还使用 Jupyter Notebook 编写了数据生成函数和测试函数,并将它们封装成了一个完整的 Jupyter Notebook 工程文件块,方便测试和演示。这种方式比直接在终端中执行代码更加直观,可以看到每个测试用例的结果,方便进行调试和优化。 +总的来说,Jupyter Notebook 是一个非常好用的工具,特别适合编写和展示算法代码。它具有交互性强、可视化好、易于编写和展示等特点,可以提高编程效率和代码可读性。 + +## 实验环境 + +win10 + +python3.11 +jupyter notebook +git. \ No newline at end of file diff --git a/alg1.py b/alg1.py index 726c54d385677e901f17574d263e9b72835b51cc..c2e979693982875d14ae28886451bf8a42ba7315 100644 --- a/alg1.py +++ b/alg1.py @@ -1,12 +1,13 @@ -def maxValue(n: str, x: int): - l = len(n) - if n[0] == '-': - for i in range(1, l): - if int(n[i]) > x: - return n[:i] + str(x) + n[i:] - return n + str(x) - else: - for i in range(l): - if int(n[i]) < x: - return n[:i] + str(x) + n[i:] - return n + str(x) \ No newline at end of file +from typing import List +class Solution: + def mctFromLeafValues(self, arr: List[int]) -> int: + res = 0 + stack = [float('inf')] + for num in arr: + while stack[-1] <= num: + mid = stack.pop() + res += mid * min(stack[-1], num) + stack.append(num) + while len(stack) > 2: + res += stack.pop() * stack[-1] + return res \ No newline at end of file diff --git a/alg2.py b/alg2.py index 8db3ebd05bace8312b5b5687f81cd12ace7c8445..5d90626948e5b8c37187c67b5907756ee65f0cd3 100644 --- a/alg2.py +++ b/alg2.py @@ -1,21 +1,8 @@ -def permute(nums): - """ - :type nums: List[int] - :rtype: List[List[int]] - """ - def backtrack(first = 0): - # 所有数都填完了 - if first == n: - res.append(nums[:]) - for i in range(first, n): - # 动态维护数组 - nums[first], nums[i] = nums[i], nums[first] - # 继续递归填下一个数 - backtrack(first + 1) - # 撤销操作 - nums[first], nums[i] = nums[i], nums[first] - - n = len(nums) - res = [] - backtrack() - return res \ No newline at end of file +from typing import List +class Solution: + def largestPerimeter(self, A: List[int]) -> int: + A.sort(reverse=True) + for i in range(len(A)-2): + if A[i]> 1 - return max(__max_sub_array(nums, left, mid), - __max_sub_array(nums, mid + 1, right), - __max_cross_array(nums, left, mid, right)) - -def __max_cross_array(nums, left, mid, right): - # 一定包含 nums[mid] 元素的最大连续子数组的和, - # 思路是看看左边"扩散到底",得到一个最大数,右边"扩散到底"得到一个最大数 - # 然后再加上中间数 - left_sum_max = 0 - start_left = mid - 1 - s1 = 0 - while start_left >= left: - s1 += nums[start_left] - left_sum_max = max(left_sum_max, s1) - start_left -= 1 - - right_sum_max = 0 - start_right = mid + 1 - s2 = 0 - while start_right <= right: - s2 += nums[start_right] - right_sum_max = max(right_sum_max, s2) - start_right += 1 - return left_sum_max + nums[mid] + right_sum_max \ No newline at end of file +import random +import math +from typing import List +class Solution: + def solveNQueens(self, n: int) -> List[List[str]]: + # 初始化棋盘,全部填充为 . + board = [['.' for _ in range(n)] for _ in range(n)] + res = [] + # 已占用的列、正斜线、反斜线 + cols, diag1, diag2 = set(), set(), set() + def dfs(row): + # 如果已经遍历到最后一行,则将当前摆放情况加入结果集 + if row == n: + res.append(["".join(row) for row in board]) + return + # 遍历每一列 + for col in range(n): + # 判断当前位置是否合法 + if col not in cols and row + col not in diag1 and row - col not in diag2: + # 如果当前位置合法,则将皇后放置在该位置,并更新已占用的列、正斜线、反斜线 + board[row][col] = 'Q' + cols.add(col) + diag1.add(row + col) + diag2.add(row - col) + # 递归到下一行 + dfs(row + 1) + # 回溯,将皇后从该位置移除,并恢复已占用的列、正斜线、反斜线 + board[row][col] = '.' + cols.remove(col) + diag1.remove(row + col) + diag2.remove(row - col) + dfs(0) + return res \ No newline at end of file diff --git a/gen1.py b/gen1.py index fe97e8b7a8dc8126a072a0a67e79ea7c86117eed..d747c7c8047d9cf05ac0b8e7ebd02b757f06eed1 100644 --- a/gen1.py +++ b/gen1.py @@ -1,2 +1,9 @@ -n = "123" -x = 6 \ No newline at end of file +# 数据生成函数 +import random +def generate_data(): + n = random.randint(2, 100) + arr = [random.randint(1, 100) for _ in range(n)] + return (arr, ) + + + diff --git a/gen2.py b/gen2.py index 2fb3f8c70410a4664128dc1063593c7f106a5224..d9ba82f3ef7281c513ba338ce041a2eb0767b990 100644 --- a/gen2.py +++ b/gen2.py @@ -1 +1,7 @@ -nums = [1, 2, 3] \ No newline at end of file +import random +def generate_data(): + n = random.randint(3, 100) + A = [random.randint(1, 10**6) for _ in range(n)] + return (A, ) + + diff --git a/gen3.py b/gen3.py index 9033ff8dfeae798806e4da1fa37a9f314b537770..7b87fc08ef1926cb19244886314291a92b1787ec 100644 --- a/gen3.py +++ b/gen3.py @@ -1 +1,6 @@ -nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4] \ No newline at end of file +def generate_data(): + n = random.randint(1, 10) + return n + + + diff --git a/test1.py b/test1.py index 90eca9d6d68b8e5593140797342d2930006f6d6b..e14a46961611324e2cc0c8855de4e4ce2d9e50fa 100644 --- a/test1.py +++ b/test1.py @@ -1,7 +1,11 @@ -import gen1 -import alg1 -n = gen1.n -x = gen1.x -print(n, x) -a = alg1.maxValue(n, x) -print(a) \ No newline at end of file +import gen +import alg + +# 数据测试函数 +def test(): + solution = Solution() + for i in range(10): + arr, = generate_data() + res = solution.mctFromLeafValues(arr) + print("Test case ", i + 1, ":\narr = ", arr, "\nres = ", res, "\n") +test() \ No newline at end of file diff --git a/test2.py b/test2.py index 9c532ef7b00fae78fcf62a235445c0a1f75763e4..8cef9bd7d6bb74f23477379536875f2bfeb7008d 100644 --- a/test2.py +++ b/test2.py @@ -1,7 +1,11 @@ -import gen2 -import alg2 - -n = gen2.nums -print(n) -a = alg2.permute(n) -print(a) \ No newline at end of file +import gen +import alg +# 数据测试函数 +def test(): + solution = Solution() + for i in range(10): + A, = generate_data() + res = solution.largestPerimeter(A) + print("Test case ", i + 1, ":\nA = ", A, "\nres = ", res, "\n") +if __name__ == "__main__": + test() \ No newline at end of file diff --git a/test3.py b/test3.py index 952040626b6492de598bf397983aed6372c95896..a1e7293616c3f8424dda0469352d5a0f9d26829e 100644 --- a/test3.py +++ b/test3.py @@ -1,7 +1,16 @@ -import gen3 -import alg3 +import gen +import alg -n = gen3.nums -print(n) -a = alg3.maxSubArray(n) -print(a) \ No newline at end of file +def test_data(n): + s = Solution() + res = s.solveNQueens(n) + expected = math.factorial(n) if n <= 10 else None + if len(res) == expected: + print("test case passed: n = %d, num of solutions = %d" % (n, len(res))) + else: + print("test case failed: n = %d, num of solutions = %d, expected = %s" % (n, len(res), expected)) +if __name__ == "__main__": + for i in range(10): + n = generate_data() + print("testing case #%d: n = %d" % (i+1, n)) + test_data(n) \ No newline at end of file diff --git "a/\345\261\217\345\271\225\346\210\252\345\233\276 2023-05-21 173336.png" "b/\345\261\217\345\271\225\346\210\252\345\233\276 2023-05-21 173336.png" deleted file mode 100644 index 4b444153e686d4ee00ca89f518cf7f958726fd6f..0000000000000000000000000000000000000000 Binary files "a/\345\261\217\345\271\225\346\210\252\345\233\276 2023-05-21 173336.png" and /dev/null differ