|
24 | 24 | "写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。\n",
|
25 | 25 | "\n",
|
26 | 26 | "3. **确定单层递归的逻辑:**\n",
|
27 |
| - "确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。\n" |
| 27 | + "确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。\n", |
| 28 | + "\n", |
| 29 | + "### 入栈前(其实也就是处理前)一定要判断是否是空节点,访问时则不需要\n" |
28 | 30 | ]
|
29 | 31 | },
|
30 | 32 | {
|
|
454 | 456 | "source": [
|
455 | 457 | "# BFS 层次遍历\n",
|
456 | 458 | "\n",
|
457 |
| - "## 递归(最朴素)" |
| 459 | + "## 递归(最朴素)\n", |
| 460 | + "但其实访问顺序还是前序遍历(也可以写成中序/后序遍历),只是由于depth的存在使其结果返回的是层次遍历而已" |
458 | 461 | ]
|
459 | 462 | },
|
460 | 463 | {
|
|
473 | 476 | " res.append([])\n",
|
474 | 477 | " res[depth].append(root.val)\n",
|
475 | 478 | " helper(root.left, depth + 1)\n",
|
476 |
| - " helper(root.right, depth + 1)\n", |
| 479 | + " helper(root.right,depth + 1)\n", |
477 | 480 | " helper(root, 0)\n",
|
478 | 481 | " return res"
|
479 | 482 | ]
|
|
554 | 557 | "\n",
|
555 | 558 | "适用场景\n",
|
556 | 559 | "\n",
|
557 |
| - "- 快速排序\n", |
| 560 | + "- 快速排序(快排自顶而下不能算是分冶法吧)\n", |
558 | 561 | "- 归并排序\n",
|
559 | 562 | "- 二叉树相关问题\n",
|
560 | 563 | "\n",
|
|
644 | 647 | "cell_type": "markdown",
|
645 | 648 | "metadata": {},
|
646 | 649 | "source": [
|
647 |
| - "#### 思路 1:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1,\n", |
648 |
| - "(自顶而下)有了计算节点高度的函数,即可判断二叉树是否平衡。具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,首先计算左右子树的高度,如果左右子树的高度差不超过 1,再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。这是一个自顶向下的递归的过程,时间复杂度O(n^2)\n", |
| 650 | + "#### 思路 0: 自顶而下递归判断是否左右子树高度绝对值是否差1(复杂度过高,废弃)\n", |
| 651 | + "(自顶而下)有了计算节点高度的函数,即可判断二叉树是否平衡。具体做法类似于二叉树的前序遍历,即对于当前遍历到的节点,首先计算左右子树的高度,如果左右子树的高度差不超过 1,再分别递归地遍历左右子节点,并判断左子树和右子树是否平衡。这是一个自顶向下的递归的过程,时间复杂度O(n^2)(因为每一层都要重复计算height)" |
| 652 | + ] |
| 653 | + }, |
| 654 | + { |
| 655 | + "cell_type": "code", |
| 656 | + "execution_count": null, |
| 657 | + "metadata": {}, |
| 658 | + "outputs": [], |
| 659 | + "source": [ |
| 660 | + "class Solution:\n", |
| 661 | + " def isBalanced(self, root: TreeNode) -> bool:\n", |
| 662 | + " def height(root: TreeNode) -> int:\n", |
| 663 | + " if not root:\n", |
| 664 | + " return 0\n", |
| 665 | + " return max(height(root.left), height(root.right)) + 1\n", |
649 | 666 | "\n",
|
| 667 | + " if not root:\n", |
| 668 | + " return True\n", |
| 669 | + " return abs(height(root.left) - height(root.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)\n" |
| 670 | + ] |
| 671 | + }, |
| 672 | + { |
| 673 | + "cell_type": "markdown", |
| 674 | + "metadata": {}, |
| 675 | + "source": [ |
| 676 | + "#### 思路 1:分治法,左边平衡 && 右边平衡 && 左右两边高度 <= 1,\n", |
650 | 677 | "(这里是自底而上,复杂度O(n),类似于后序遍历)"
|
651 | 678 | ]
|
652 | 679 | },
|
|
672 | 699 | "cell_type": "markdown",
|
673 | 700 | "metadata": {},
|
674 | 701 | "source": [
|
675 |
| - "#### 思路 2:使用后序遍历实现分治法的迭代版本(过于复杂,此处不显示)" |
| 702 | + "#### 思路 1的改进:提前阻断的分治法\n", |
| 703 | + "左边平衡 && 右边平衡 && 左右两边高度 <= 1,但是是提前阻断的,只返回\n", |
| 704 | + "1. -1,代表左子树/右子树/该树本身不是平衡树,具体而言:要么左子树的结果是-1,要么右子树的结果是-1,要么左右子树高度之差>1,这种情况下提前返回-1\n", |
| 705 | + "2. 自己的高度,代表该树及其左右子树都是平衡树" |
| 706 | + ] |
| 707 | + }, |
| 708 | + { |
| 709 | + "cell_type": "code", |
| 710 | + "execution_count": null, |
| 711 | + "metadata": {}, |
| 712 | + "outputs": [], |
| 713 | + "source": [ |
| 714 | + "class Solution:\n", |
| 715 | + " def isBalanced(self, root: TreeNode) -> bool:\n", |
| 716 | + " def balance(root):\n", |
| 717 | + " if root is None:\n", |
| 718 | + " return 0\n", |
| 719 | + " left = balance(root.left)\n", |
| 720 | + " if left == -1: return -1\n", |
| 721 | + " right = balance(root.right)\n", |
| 722 | + " if right == -1: return -1\n", |
| 723 | + "\n", |
| 724 | + " return max(left,right) + 1 if abs(left - right) <= 1 else -1\n", |
| 725 | + " return balance(root) != -1" |
| 726 | + ] |
| 727 | + }, |
| 728 | + { |
| 729 | + "cell_type": "markdown", |
| 730 | + "metadata": {}, |
| 731 | + "source": [ |
| 732 | + "#### 思路 2:使用后序遍历实现分治法的迭代版本\n", |
| 733 | + "优点是即使前一个方法的提前阻断也还是需要逐层返回,而这个只要找到了不平衡的子树就可以直接停止\n", |
| 734 | + "缺点是写起来太麻烦,要维护一个三元组的栈\n", |
| 735 | + "\n", |
| 736 | + "0. s多建一个节点是为了防止迭代到根节点时out of range,没这一个节点迭代到整棵树的根节点时stack就空了,就没办法更新它的“不存在的父节点”的树深了\n", |
| 737 | + "1. -1是没有求树深的左右子树的标记\n", |
| 738 | + "2. 若左右节点为空,对应深度为0。当然这个问题是求平衡树所以无所谓,但是求深度的话如果不管结果会少1,并且-1作为没有求树深的左右的标记,如果不把空子树置0,会存在如下情况:比如某节点左子树为空,右子树深度为1,结果把这个深度归结到左子树上,这当然对平衡没有影响,但还是不够严谨" |
| 739 | + ] |
| 740 | + }, |
| 741 | + { |
| 742 | + "cell_type": "code", |
| 743 | + "execution_count": null, |
| 744 | + "metadata": {}, |
| 745 | + "outputs": [], |
| 746 | + "source": [ |
| 747 | + "class Solution:\n", |
| 748 | + " def isBalanced(self, root: TreeNode) -> bool:\n", |
| 749 | + "\n", |
| 750 | + " s = [[TreeNode(), -1, -1]] \n", |
| 751 | + " node,visit = root,None\n", |
| 752 | + " \n", |
| 753 | + " #这里是len(s)>1防止迭代到根节点时爆栈\n", |
| 754 | + " while len(s) > 1 or node is not None:\n", |
| 755 | + " if node is not None:\n", |
| 756 | + " s.append([node,-1,-1])\n", |
| 757 | + " node = node.left\n", |
| 758 | + " #若左节点为空,左子树深度为0\n", |
| 759 | + " if node is None:\n", |
| 760 | + " s[-1][1] = 0\n", |
| 761 | + " else:\n", |
| 762 | + " peak = s[-1][0]\n", |
| 763 | + " if peak.right is not None and visit != peak.right:\n", |
| 764 | + " node = peak.right\n", |
| 765 | + " else:\n", |
| 766 | + " #若右节点为空,右子树深度为0\n", |
| 767 | + " if peak.right is None:\n", |
| 768 | + " s[-1][2] = 0\n", |
| 769 | + " visit, dl, dr = s.pop()\n", |
| 770 | + " if abs(dl - dr) > 1:\n", |
| 771 | + " return False\n", |
| 772 | + " d = max(dl,dr) + 1\n", |
| 773 | + " if s[-1][1] == -1:\n", |
| 774 | + " s[-1][1] = d\n", |
| 775 | + " else:\n", |
| 776 | + " s[-1][2] = d\n", |
| 777 | + " return True" |
676 | 778 | ]
|
677 | 779 | },
|
678 | 780 | {
|
|
0 commit comments