diff --git a/introduction/golang.md b/1.introduction/golang.md similarity index 100% rename from introduction/golang.md rename to 1.introduction/golang.md diff --git a/introduction/python.md b/1.introduction/python.md similarity index 100% rename from introduction/python.md rename to 1.introduction/python.md diff --git a/introduction/quickstart.md b/1.introduction/quickstart.md similarity index 100% rename from introduction/quickstart.md rename to 1.introduction/quickstart.md diff --git a/data_structure/binary_tree.md b/2.data_structure/1.binary_tree.md similarity index 65% rename from data_structure/binary_tree.md rename to 2.data_structure/1.binary_tree.md index 90bf1d18..77d82a68 100644 --- a/data_structure/binary_tree.md +++ b/2.data_structure/1.binary_tree.md @@ -4,11 +4,11 @@ ### 二叉树遍历 -**前序遍历**:**先访问根节点**,再前序遍历左子树,再前序遍历右子树 -**中序遍历**:先中序遍历左子树,**再访问根节点**,再中序遍历右子树 -**后序遍历**:先后序遍历左子树,再后序遍历右子树,**再访问根节点** +- **前序遍历**:**先访问根节点**,再前序遍历左子树,再前序遍历右子树 +- **中序遍历**:先中序遍历左子树,**再访问根节点**,再中序遍历右子树 +- **后序遍历**:先后序遍历左子树,再后序遍历右子树,**再访问根节点** -注意点 +注意点: - 以根访问顺序决定是什么遍历 - 左子树都是优先右子树 @@ -45,7 +45,10 @@ def postorder_rec(root): #### [前序非递归](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/) -- 本质上是图的DFS的一个特例,因此可以用栈来实现 +- 本质上是图的DFS的一个特例,因此可以用栈来实现。前中后序遍历都是DFS的一种。 +- 理解的时候,左右指的不是左右节点,而是左右子树。 +- 递归的实现方法都可以使用栈来实现。递归在编程语言中的实现本身也是基于栈的“函数调用栈”。 +- 参考:[关于二叉树,你该了解这些!| 二叉树理论基础一网打尽,二叉树的种类、二叉树的存储方式、二叉树节点定义、二叉树的遍历顺序](https://www.bilibili.com/video/BV1Hy4y1t7ij) ```Python class Solution: @@ -105,8 +108,7 @@ class Solution: else: last_visit = s.pop() postorder.append(last_visit.val) - - + return postorder ``` @@ -114,7 +116,7 @@ class Solution: - 核心就是:根节点必须在右节点弹出之后,再弹出 -DFS 深度搜索-从下向上(分治法) +#### DFS 深度搜索-从下向上(分治法) ```Python class Solution: @@ -131,7 +133,47 @@ class Solution: 注意点: -> DFS 深度搜索(从上到下) 和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并 +- DFS 深度搜索(从上到下)和分治法区别:前者一般将最终结果通过指针参数传入,后者一般递归返回结果最后合并 +- 和后序遍历类似,不过不只处理根节点,要和子节点的处理一起操作。 + +#### 非递归二叉树通用遍历模板 + +[颜色标记法,一种通用且简明的树遍历方法](https://leetcode.cn/problems/binary-tree-inorder-traversal/solutions/25220/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming) +[迭代的核心思想是采用栈模拟递归](https://leetcode.cn/problems/binary-tree-inorder-traversal/solutions/25220/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/comments/2347570) +[方法还可以优化](https://leetcode.cn/problems/binary-tree-inorder-traversal/solutions/25220/yan-se-biao-ji-fa-yi-chong-tong-yong-qie-jian-ming/comments/204119) + +迭代的核心思想是采用栈模拟递归。对于任意一棵树,都将其抽象为(左子树,中节点,右子树),也可以简称为左-中-右三个 "节点"。 + +遍历一棵树时,按照递归的思路理解,分为 进入 和 回溯 两个阶段,用栈模拟可以理解为 "两次入栈"。 以中序遍历为例: + +- 第一次入栈时是以当前节点为根节点的**整棵子树**入栈; +- 通过栈中序遍历该子树,就要对其进行**展开**,第二次入栈代表展开。对于任意一棵树,中序遍历都是先递归左子树,因此需要按照**右子树-中节点-左子树**的顺序入栈展开; + +两次入栈就同样对应着两次出栈: + +- 第一次出栈是展开前将代表子树的栈顶节点出栈; +- 第二次出栈是展开后栈顶的中节点加入遍历序列; + +具体地说,采用变量 flag 标记节点两次入栈的过程,flag = 0 代表第一次入栈,flag = 1 代表第二次入栈。首先根节点标记为 0 入栈,迭代取出栈顶节点时: + +- 当栈顶节点的 flag = 0 时,代表子树递归进入的过程,先将栈顶节点出栈,然后按照 右子树-中节点-左子树 的顺序将该子树展开入栈,其中右子树和左子树标记为 0,中节点标记为 1 +- 当 flag = 1 时,代表递归回溯的过程,将栈顶节点加入到中序遍历序列 + +对于python,不需要使用标记。如果栈中是树,则传入节点,如果是值,则传入值。出栈时判断元素类型。 + +前序遍历的具体代码,其他遍历方式倒着更改入栈顺序就好: + +```python +def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]: + stack, values = [root], [] + while stack: + node = stack.pop() + if isinstance(node, TreeNode): + stack.extend([node.right, node.left, node.val, ]) + elif isinstance(node, int): + values.append(node) + return values +``` #### [BFS 层次遍历](https://leetcode-cn.com/problems/binary-tree-level-order-traversal/) @@ -161,6 +203,27 @@ class Solution: return levels ``` +关键点:想办法只遍历当前层。上面的方法是在遍历前,计算当前层的节点数。下面的方法是在遍历时,将新产生的节点加入一个临时数组,结束后再合并。双端队列并非关键。 + +```python +class Solution: + def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]: + outs = [] + nodes = [root] + while nodes: + nodes_t = [] + values = [] + for node in nodes: + if node is None: + continue + nodes_t.extend([node.left, node.right]) + values.append(node.val) + if values: + outs.append(values) + nodes = nodes_t + return outs +``` + ### 分治法应用 先分别处理局部,再合并结果 @@ -183,7 +246,7 @@ class Solution: > 给定一个二叉树,找出其最大深度。 -- 思路 1:分治法 +- 思路 1:分治法(递归) ```Python class Solution: @@ -228,55 +291,53 @@ class Solution: ```Python class Solution: - def isBalanced(self, root: TreeNode) -> bool: - - def depth(root): - - if root is None: - return 0, True - - dl, bl = depth(root.left) - dr, br = depth(root.right) - - return max(dl, dr) + 1, bl and br and abs(dl - dr) < 2 - - _, out = depth(root) - - return out + def isBalanced(self, root: Optional[TreeNode]) -> bool: + balanced = True + + def isBalancedS(root): + nonlocal balanced + if not root: return + stack = [(root, 1)] # node, isTree + heights = {} + while balanced and stack: + node, isTree = stack.pop() + if isTree: + stack.append((node, 0)) + if n:=node.right: + stack.append((n, 1)) + if n:=node.left: + stack.append((n, 1)) + else: + left_height = heights.get(node.left, 0) if node.left else 0 + right_height = heights.get(node.right, 0) if node.right else 0 + if abs(left_height - right_height) > 1: + balanced = False + return + heights[node] = 1 + max(left_height, right_height) + + def isBalancedC(node): + nonlocal balanced + if (not balanced) or (node is None) : + return 0 + + l_d = isBalancedC(node.left) + r_d = isBalancedC(node.right) + + if abs(l_d - r_d) > 1: + balanced = False + return 0 + + return 1 + max(l_d, r_d) + + isBalancedS(root) + return balanced ``` - 思路 2:使用后序遍历实现分治法的迭代版本 +- 为什么要后序遍历呢?观察上面的分治法(递归法),自然而然的就是后序遍历递归的变体。 +- 非递归法的难点:存储高度信息。解法:引入额外字典结构,以节点为键存储高度信息。 -```Python -class Solution: - def isBalanced(self, root: TreeNode) -> bool: - - s = [[TreeNode(), -1, -1]] - node, last = root, None - while len(s) > 1 or node is not None: - if node is not None: - s.append([node, -1, -1]) - node = node.left - if node is None: - s[-1][1] = 0 - else: - peek = s[-1][0] - if peek.right is not None and last != peek.right: - node = peek.right - else: - if peek.right is None: - s[-1][2] = 0 - last, dl, dr = s.pop() - if abs(dl - dr) > 1: - return False - d = max(dl, dr) + 1 - if s[-1][1] == -1: - s[-1][1] = d - else: - s[-1][2] = d - - return True -``` +TODO: 似乎还可以使用中序遍历的非递归方法来写:[https://leetcode.cn/problems/balanced-binary-tree/solutions/2099334/tu-jie-leetcode-ping-heng-er-cha-shu-di-gogyi] ### [binary-tree-maximum-path-sum](https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/) @@ -310,6 +371,7 @@ class Solution: > 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。 - 思路:分治法,有左子树的公共祖先或者有右子树的公共祖先,就返回子树的祖先,否则返回根节点 +- 考虑:后置递归的一种变体,遍历到目标节点后,类似冒泡,往祖先节点传播。当两个节点相遇,就说明找到了公共祖先。 ```Python class Solution: @@ -332,6 +394,13 @@ class Solution: return right else: return None + + # 可以简写为: + # if not left: return right + # if not right: return left + # return root + # 为什么要这么写?考虑左右都找到的情况,这个时候应该返回root。 + # 一边为None,另一边不管None还是有节点,都可以直接返回。 ``` ### BFS 层次应用 @@ -388,7 +457,7 @@ class Solution: - 思路 1:中序遍历后检查输出是否有序,缺点是如果不平衡无法提前返回结果, 代码略 -- 思路 2:分治法,一个二叉树为合法的二叉搜索树当且仅当左右子树为合法二叉搜索树且根结点值大于右子树最小值小于左子树最大值。缺点是若不用迭代形式实现则无法提前返回,而迭代实现右比较复杂。 +- 思路 2:分治法,一个二叉树为合法的二叉搜索树当且仅当左右子树为合法二叉搜索树且根结点值大于右子树最小值小于左子树最大值。缺点是若不用迭代形式实现则无法提前返回,而迭代实现又比较复杂。 ```Python class Solution: @@ -440,11 +509,12 @@ class Solution: return True ``` -#### [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) +### [insert-into-a-binary-search-tree](https://leetcode-cn.com/problems/insert-into-a-binary-search-tree/) > 给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 -- 思路:如果只是为了完成任务则找到最后一个叶子节点满足插入条件即可。但此题深挖可以涉及到如何插入并维持平衡二叉搜索树的问题,并不适合初学者。 +- 思路:如果只是为了完成任务则找到最后一个叶子节点满足插入条件即可。 +- TODO: 但此题深挖可以涉及到如何插入并维持平衡二叉搜索树的问题,并不适合初学者。 ```Python class Solution: @@ -464,7 +534,7 @@ class Solution: else: if node.left is None: node.left = TreeNode(val) - return root + return root else: node = node.left ``` @@ -475,6 +545,8 @@ class Solution: - 理解 DFS 前序遍历与分治法 - 理解 BFS 层次遍历 +解题没有思路时,可以尝试把遍历方法都试一遍,看能否找到规律。 + ## 练习 - [ ] [maximum-depth-of-binary-tree](https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/) diff --git a/data_structure/linked_list.md b/2.data_structure/2.linked_list.md similarity index 56% rename from data_structure/linked_list.md rename to 2.data_structure/2.linked_list.md index f77e707f..0e4d04a0 100644 --- a/data_structure/linked_list.md +++ b/2.data_structure/2.linked_list.md @@ -4,14 +4,14 @@ 链表相关的核心点 -- null/nil 异常处理 -- dummy node 哑巴节点 -- 快慢指针 +- null/nil 异常处理,多写点None的分支没事的; +- dummy node 哑节点:避免处理head导致的问题,反正最后返回就是返回dummy.next; +- 快慢指针,比如找中点。有时也会有三个指针的情况,比如反转链表。 - 插入一个节点到排序链表 - 从一个链表中移除一个节点 -- 翻转链表 +- 翻转链表:不好搞了就创一个新的链表从头开始,只操作头节点。链表问题搞不清楚都可以这么思考; - 合并两个链表 -- 找到链表的中间节点 +- 找到链表的中间节点。 ## 常见题型 @@ -39,7 +39,7 @@ class Solution: ### [remove-duplicates-from-sorted-list-ii](https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list-ii/) -> 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中   没有重复出现的数字。 +> 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现的数字。 - 思路:链表头结点可能被删除,所以用 dummy node 辅助删除 @@ -72,16 +72,39 @@ class Solution: return dummy.next ``` -注意点 -• A->B->C 删除 B,A.next = C -• 删除用一个 Dummy Node 节点辅助(允许头节点可变) -• 访问 X.next 、X.value 一定要保证 X != nil +注意点: + +- A->B->C 删除 B,A.next = C +- 删除用一个 Dummy Node 节点辅助(允许头节点可变) +- 访问 X.next 、X.value 一定要保证 X != nil ### [reverse-linked-list](https://leetcode-cn.com/problems/reverse-linked-list/) > 反转一个单链表。 -- 思路:将当前结点放置到头结点 +- 思路:将当前结点放置到头结点,新创一个链表,把原来链表中的节点一个一个挪过去。 +- 注意:`next_ = curr.next`步骤,没有这一步的话,`cur.next`会在`dummy_new_head.next = dummy_old_head`的操作中被修改掉。 +- 上面的解法是下面解法的更清晰的版本。 + +```python + def reverseListI(self, head: Optional[ListNode]) -> Optional[ListNode]: + + dummy = ListNode() + curr = head + + while curr: + + next_ = curr.next + + dummy_old_head = dummy.next + dummy.next = curr + dummy_new_head = dummy.next + dummy_new_head.next = dummy_old_head + + curr = next_ + + return dummy.next +``` ```Python class Solution: @@ -100,7 +123,9 @@ class Solution: return head ``` + - Recursive method is tricky + ```Python class Solution: def reverseList(self, head: ListNode) -> ListNode: @@ -117,7 +142,7 @@ class Solution: ### [reverse-linked-list-ii](https://leetcode-cn.com/problems/reverse-linked-list-ii/) -> 反转从位置  *m*  到  *n*  的链表。请使用一趟扫描完成反转。 +> 反转从位置 *m* 到 *n* 的链表。请使用一趟扫描完成反转。 - 思路:先找到 m 处, 再反转 n - m 次即可 @@ -147,7 +172,7 @@ class Solution: ### [merge-two-sorted-lists](https://leetcode-cn.com/problems/merge-two-sorted-lists/) -> 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 +> 将两个升序链表合并为一个新的升序链表并返回。**新链表**是通过拼接给定的两个链表的所有节点组成的。 - 思路:通过 dummy node 链表,连接各个元素 @@ -177,78 +202,86 @@ class Solution: > 给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于  *x*  的节点都在大于或等于  *x*  的节点之前。 -- 思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表 +- 思路:将大于 x 的节点,放到另外一个链表,最后连接这两个链表。 +- 注意:依然是注意curr.next = None,切断和原来链表的链接,避免成环,导致超时和爆内存(无限循环)。 -```go +```python class Solution: - def partition(self, head: ListNode, x: int) -> ListNode: - - p = l = ListNode() - q = s = ListNode(next=head) - - while q.next is not None: - if q.next.val < x: - q = q.next + def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]: + + gt_dummy, lt_dummy = ListNode(), ListNode() + gt_curr, lt_curr = gt_dummy, lt_dummy + + curr = head + while curr: + next_ = curr.next + curr.next = None + if curr.val < x: + lt_curr.next = curr + lt_curr = lt_curr.next else: - p.next = q.next - q.next = q.next.next - p = p.next - - p.next = None - q.next = l.next - - return s.next -``` + gt_curr.next = curr + gt_curr = gt_curr.next + curr = next_ -哑巴节点使用场景 + lt_curr.next = gt_dummy.next -> 当头节点不确定的时候,使用哑巴节点 + return lt_dummy.next +``` + +哑巴节点使用场景:当头节点不确定的时候,使用哑巴节点 ### [sort-list](https://leetcode-cn.com/problems/sort-list/) -> 在  *O*(*n* log *n*) 时间复杂度和常数级空间复杂度下,对链表进行排序。 +> 在 *O*(*n* log *n*) 时间复杂度和常数级空间复杂度下,对链表进行排序。 - 思路:归并排序,slow-fast找中点 +- 为什么看不到排序就排序了? + - 因为递归到链表长度为 1 的时候,才会返回; + - 这时就是排序好的返回了,长度为2; + - 再到后面就是输入排序好的短链表,可以直接使用合并有序链表。 ```Python class Solution: - - def _merge(self, l1, l2): - tail = l_merge = ListNode() - - while l1 is not None and l2 is not None: - if l1.val > l2.val: - tail.next = l2 - l2 = l2.next + + def _merge(self, head1: ListNode, head2: ListNode): + dummy = ListNode() + pointer = dummy + curr1, curr2 = head1, head2 + while curr1 or curr2: + if curr1 is None: + pointer.next = curr2 + curr2 = curr2.next + elif curr2 is None: + pointer.next = curr1 + curr1 = curr1.next else: - tail.next = l1 - l1 = l1.next - tail = tail.next + if curr1.val < curr2.val: + pointer.next = curr1 + curr1 = curr1.next + else: + pointer.next = curr2 + curr2 = curr2.next + pointer = pointer.next + return dummy.next - if l1 is not None: - tail.next = l1 - else: - tail.next = l2 - - return l_merge.next - - def _findmid(self, head): + def _find_mid(self, head: ListNode): slow, fast = head, head.next - while fast is not None and fast.next is not None: - fast = fast.next.next + while fast and fast.next: slow = slow.next - + fast = fast.next.next return slow - - def sortList(self, head: ListNode) -> ListNode: + + def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: if head is None or head.next is None: return head - - mid = self._findmid(head) - tail = mid.next - mid.next = None # break from middle - - return self._merge(self.sortList(head), self.sortList(tail)) + mid = self._find_mid(head) + sub_head = mid.next + mid.next = None + return self._merge( + self.sortList(head), + self.sortList(sub_head), + ) ``` 注意点 @@ -263,44 +296,46 @@ class Solution: > 将其重新排列后变为: *L*→*L\_\_n*→*L*→*L\_\_n*→*L*→*L\_\_n*→… - 思路:找到中点断开,翻转后面部分,然后合并前后两个链表 +- 注意点:快慢指针,翻转链表,合并链表,前面几道题目的组合 +- 拿到题目时不要慌,纸上推一下,就会发现动的是后面一半的节点,不要被题目示意图迷惑。 ```Python class Solution: - - def reverseList(self, head: ListNode) -> ListNode: - - prev, curr = None, head - - while curr is not None: - curr.next, prev, curr = prev, curr, curr.next - - return prev - - def reorderList(self, head: ListNode) -> None: + def reorderList(self, head: Optional[ListNode]) -> None: """ Do not return anything, modify head in-place instead. """ - if head is None or head.next is None or head.next.next is None: + if head is None or head.next is None: return slow, fast = head, head.next - while fast is not None and fast.next is not None: - fast = fast.next.next + while fast and fast.next: slow = slow.next - - h, m = head, slow.next + fast = fast.next.next + mid = slow.next slow.next = None - - m = self.reverseList(m) - - while h is not None and m is not None: - p = m.next - m.next = h.next - h.next = m - h = h.next.next - m = p - - return + + dummy = ListNode() + curr = mid + while curr: + curr_next = curr.next + curr.next = None + old_dummy_head = dummy.next + new_dummy_head = curr + dummy.next = new_dummy_head + new_dummy_head.next = old_dummy_head + curr = curr_next + + curr = dummy.next + pointer = head + while curr: + curr_next = curr.next + curr.next = None + pointer_next = pointer.next + pointer.next = curr + curr.next = pointer_next + curr = curr_next + pointer = pointer_next ``` ### [linked-list-cycle](https://leetcode-cn.com/problems/linked-list-cycle/) @@ -309,7 +344,7 @@ class Solution: - 思路1:Hash Table 记录所有结点判断重复,空间复杂度 O(n) 非最优,时间复杂度 O(n) 但必然需要 n 次循环 - 思路2:快慢指针,快慢指针相同则有环,证明:如果有环每走一步快慢指针距离会减 1,空间复杂度 O(1) 最优,时间复杂度 O(n) 但循环次数小于等于 n - ![fast_slow_linked_list](https://img.fuiboom.com/img/fast_slow_linked_list.png) + - 另一种理解方法:快慢指针进入环中,必然会相遇,相遇就是有环。 ```Python class Solution: @@ -319,7 +354,7 @@ class Solution: while fast is not None and fast.next is not None: slow = slow.next - fast = fast.next.next + fast = fast.next.next if fast == slow: return True @@ -331,6 +366,7 @@ class Solution: > 给定一个链表,返回链表开始入环的第一个节点。  如果链表无环,则返回  `null`。 - 思路:快慢指针,快慢相遇之后,慢指针回到头,快慢指针步调一致一起移动,相遇点即为入环点。 +- 为什么呢?因为快指针每次移动 2 步,慢指针每次移动 1 步,所以快指针走过的路程是慢指针的 2 倍,所以快指针走过的路程为慢指针的 2 倍时,快慢指针相遇。 ![cycled_linked_list](https://img.fuiboom.com/img/cycled_linked_list.png) @@ -354,13 +390,14 @@ class Solution: return None ``` -坑点 +坑点: -- 指针比较时直接比较对象,不要用值比较,链表中有可能存在重复值情况 -- 第一次相交后,快指针需要从下一个节点开始和头指针一起匀速移动 +1. 指针比较时直接比较对象,不要用值比较,链表中有可能存在重复值情况 +2. 第一次相交后,快指针需要从下一个节点开始和头指针一起匀速移动 +**注意**:*找中点和找环的快慢指针起始不一样* -注意,此题中使用 slow = fast = head 是为了保证最后找环起始点时移动步数相同,但是作为找中点使用时**一般用 fast=head.Next 较多**,因为这样可以知道中点的上一个节点,可以用来删除等操作。 +此题中使用 slow = fast = head 是为了保证最后找环起始点时移动步数相同,但是作为找中点使用时**一般用 fast=head.Next 较多**,因为这样可以知道中点的上一个节点,可以用来删除等操作。 - fast 如果初始化为 head.Next 则中点在 slow.Next - fast 初始化为 head,则中点在 slow @@ -369,7 +406,17 @@ class Solution: > 请判断一个链表是否为回文链表。 -- 思路:O(1) 空间复杂度的解法需要破坏原链表(找中点 -> 反转后半个list -> 判断回文),在实际应用中往往还需要复原(后半个list再反转一次后拼接),操作比较复杂,这里给出更工程化的做法 +- 思路:O(1) 空间复杂度的解法需要破坏原链表 + - 找中点 -> 反转后半个list -> 判断回文 + - 在实际应用中往往还需要复原(后半个list再反转一次后拼接) + - 操作比较复杂 +- 这里给出更工程化的做法: + - 利用到栈结构 + - 找中点时在栈中记录节点值 + - 然后遍历后半部分链表,借用栈的先进后出刚好反着判断 + - 注意 fast is not None 情况,这表明链表长度为奇数,slow 指针需要再走一步 + - 反正是注意检查链表的奇偶情况 + - 这种方法虽然不会破坏链表,但空间复杂度为 O(n/2) ```Python class Solution: @@ -399,66 +446,68 @@ class Solution: > 要求返回这个链表的 深拷贝。 - 思路1:hash table 存储 random 指针的连接关系 + - 对于python这种解释性语言,可以在原始节点对象上,直接利用属性存储random链接信息 ```Python class Solution: - def copyRandomList(self, head: 'Node') -> 'Node': - - if head is None: - return None - - parent = collections.defaultdict(list) - - out = Node(0) - o, n = head, out - while o is not None: - n.next = Node(o.val) - n = n.next - if o.random is not None: - parent[o.random].append(n) - o = o.next - - o, n = head, out.next - while o is not None: - if o in parent: - for p in parent[o]: - p.random = n - o = o.next - n = n.next - - return out.next + def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': + dummy = Node(x=-1) + old_curr, new_curr = head, dummy + while old_curr: + new_node = Node(x=old_curr.val) + new_curr.next = new_node + old_curr.new = new_node + old_curr = old_curr.next + new_curr = new_curr.next + + old_curr, new_curr = head, dummy.next + while old_curr: + old_random = old_curr.random + if old_random: + new_curr.random = old_random.new + else: + new_curr.random = None + old_curr = old_curr.next + new_curr = new_curr.next + + return dummy.next ``` - 思路2:复制结点跟在原结点后面,间接维护连接关系,优化空间复杂度,建立好新 list 的 random 链接后分离 + - 三次遍历:复制节点、建立 random 链接、分离新 list + - 注意:复制节点时维护好原来的 next 指针,避免混乱 ```Python class Solution: - def copyRandomList(self, head: 'Node') -> 'Node': - - if head is None: + def copyRandomList(self, head: 'Optional[Node]') -> 'Optional[Node]': + + if not head: return None - p = head - while p is not None: - p.next = Node(p.val, p.next) - p = p.next.next - - p = head - while p is not None: - if p.random is not None: - p.next.random = p.random.next - p = p.next.next - - new = head.next - o, n = head, new - while n.next is not None: - o.next = n.next - n.next = n.next.next - o = o.next - n = n.next - o.next = None - - return new + old_curr = head + while old_curr: + old_next = old_curr.next + old_curr.next = Node( + x=old_curr.val, + next=old_curr.next, + ) + old_curr = old_next + + old_curr = head + while old_curr: + new_curr = old_curr.next + if old_curr.random: + new_curr.random = old_curr.random.next + old_curr = new_curr.next + + new_head = new_curr = head.next + while new_curr: + old_curr = new_curr.next + if old_curr: + new_curr.next = old_curr.next + new_curr = new_curr.next + + return new_head ``` ## 总结 diff --git a/data_structure/stack_queue.md b/2.data_structure/3.stack_queue.md similarity index 100% rename from data_structure/stack_queue.md rename to 2.data_structure/3.stack_queue.md diff --git a/data_structure/heap.md b/2.data_structure/4.heap.md similarity index 100% rename from data_structure/heap.md rename to 2.data_structure/4.heap.md diff --git a/data_structure/union_find.md b/2.data_structure/5.union_find.md similarity index 100% rename from data_structure/union_find.md rename to 2.data_structure/5.union_find.md diff --git a/data_structure/binary_op.md b/2.data_structure/6.binary_op.md similarity index 100% rename from data_structure/binary_op.md rename to 2.data_structure/6.binary_op.md diff --git a/README.md b/README.md index 13f81e75..989e85c7 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ + # 说明 本项目为原项目 [algorithm-pattern](https://github.com/greyireland/algorithm-pattern) 的 Python3 语言实现版本,原项目使用 go 语言实现,目前已获 ![GitHub stars](https://img.shields.io/github/stars/greyireland/algorithm-pattern?style=social)。在原项目基础上,本项目添加了优先级队列,并查集,图相关算法等内容,基本覆盖了所有基础数据结构和算法,非常适合找工刷题的同学快速上手。以下为原项目 README,目录部分增加了本项目的新内容。 @@ -20,17 +21,17 @@ ### 入门篇 🐶 -- [使用 Python3 写算法题](./introduction/python.md) -- [算法快速入门](./introduction/quickstart.md) +- [使用 Python3 写算法题](./1.introduction/python.md) +- [算法快速入门](./1.introduction/quickstart.md) ### 数据结构篇 🐰 -- [二叉树](./data_structure/binary_tree.md) -- [链表](./data_structure/linked_list.md) -- [栈和队列](./data_structure/stack_queue.md) -- [优先级队列 (堆)](./data_structure/heap.md) -- [并查集](./data_structure/union_find.md) -- [二进制](./data_structure/binary_op.md) +- [二叉树](./2.data_structure/1.binary_tree.md) +- [链表](./2.data_structure/2.linked_list.md) +- [栈和队列](./2.data_structure/3.stack_queue.md) +- [优先级队列 (堆)](./2.data_structure/4.heap.md) +- [并查集](./2.data_structure/5.union_find.md) +- [二进制](./2.data_structure/6.binary_op.md) ### 基础算法篇 🐮 diff --git a/SUMMARY.md b/SUMMARY.md deleted file mode 100644 index 8257ee7e..00000000 --- a/SUMMARY.md +++ /dev/null @@ -1,26 +0,0 @@ -# 算法模板 - -## 入门篇 - -- [go 语言入门](introduction/golang.md) -- [算法快速入门](introduction/quickstart.md) - -## 数据结构篇 - -- [二叉树](data_structure/binary_tree.md) -- [链表](data_structure/linked_list.md) -- [栈和队列](data_structure/stack_queue.md) -- [二进制](data_structure/binary_op.md) - -## 基础算法篇 - -- [二分搜索](basic_algorithm/binary_search.md) -- [排序算法](basic_algorithm/sort.md) -- [动态规划](basic_algorithm/dp.md) - -## 算法思维 - -- [递归思维](advanced_algorithm/recursion.md) -- [滑动窗口思想](advanced_algorithm/slide_window.md) -- [二叉搜索树](advanced_algorithm/binary_search_tree.md) -- [回溯法](advanced_algorithm/backtrack.md) diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 044090f8..00000000 --- a/TODO.md +++ /dev/null @@ -1,8 +0,0 @@ -# 计划 - -## v1 - -- [ ] 完善文档细节 -- [ ] 工程实现用到的算法解析 -- [ ] 周赛计划 -- [ ] 面试体系计划 diff --git a/src/main.go b/src/main.go deleted file mode 100644 index c0481191..00000000 --- a/src/main.go +++ /dev/null @@ -1,7 +0,0 @@ -package main - -import "fmt" - -func main() { - fmt.Println("hello world") -} diff --git a/src/sort/heap_sort.go b/src/sort/heap_sort.go deleted file mode 100644 index cc21018b..00000000 --- a/src/sort/heap_sort.go +++ /dev/null @@ -1,47 +0,0 @@ -package sort - -func HeapSort(a []int) []int { - // 1、无序数组a - // 2、将无序数组a构建为一个大根堆 - for i := len(a)/2 - 1; i >= 0; i-- { - sink(a, i, len(a)) - } - // 3、交换a[0]和a[len(a)-1] - // 4、然后把前面这段数组继续下沉保持堆结构,如此循环即可 - for i := len(a) - 1; i >= 1; i-- { - // 从后往前填充值 - swap(a, 0, i) - // 前面的长度也减一 - sink(a, 0, i) - } - return a -} -func sink(a []int, i int, length int) { - for { - // 左节点索引(从0开始,所以左节点为i*2+1) - l := i*2 + 1 - // 有节点索引 - r := i*2 + 2 - // idx保存根、左、右三者之间较大值的索引 - idx := i - // 存在左节点,左节点值较大,则取左节点 - if l < length && a[l] > a[idx] { - idx = l - } - // 存在有节点,且值较大,取右节点 - if r < length && a[r] > a[idx] { - idx = r - } - // 如果根节点较大,则不用下沉 - if idx == i { - break - } - // 如果根节点较小,则交换值,并继续下沉 - swap(a, i, idx) - // 继续下沉idx节点 - i = idx - } -} -func swap(a []int, i, j int) { - a[i], a[j] = a[j], a[i] -} diff --git a/src/sort/heap_sort_test.go b/src/sort/heap_sort_test.go deleted file mode 100644 index f853c69b..00000000 --- a/src/sort/heap_sort_test.go +++ /dev/null @@ -1,26 +0,0 @@ -package sort - -import ( - "reflect" - "testing" -) - -func TestHeapSort(t *testing.T) { - type args struct { - a []int - } - tests := []struct { - name string - args args - want []int - }{ - {"", args{a: []int{7, 8, 9, 2, 3, 5}}, []int{2, 3, 5, 7, 8, 9}}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := HeapSort(tt.args.a); !reflect.DeepEqual(got, tt.want) { - t.Errorf("HeapSort() = %v, want %v", got, tt.want) - } - }) - } -}