diff --git a/136.single-number.md b/136.single-number.md new file mode 100644 index 0000000000000000000000000000000000000000..f0285017a783d97b5be174e288bb05d8640153e5 --- /dev/null +++ b/136.single-number.md @@ -0,0 +1,115 @@ + +## 题目地址 +https://leetcode.com/problems/single-number/description/ + +## 题目描述 + +``` +Given a non-empty array of integers, every element appears twice except for one. Find that single one. + +Note: + +Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? +``` +## 思路 + +根据题目描述,由于加上了时间复杂度必须是O(n),并且空间复杂度为O(1)的条件,因此不能用排序方法,也不能使用map数据结构。 + +我们可以利用二进制异或的性质来完成,将所有数字异或即得到唯一出现的数字。 + +## 关键点 +1. 异或的性质 +两个数字异或的结果`a^b`是将a和b的二进制每一位进行运算,得出的数字。 运算的逻辑是 +如果同一位的数字相同则为0,不同则为1 + +2. 异或的规律 + +- 任何数和本身异或则为`0` + +- 任何数和0异或是`本身` + +3. 很多人只是记得异或的性质和规律,但是缺乏对其本质的理解,导致很难想到这种解法(我本人也没想到) + +## 代码 + +```js +/* + * @lc app=leetcode id=136 lang=javascript + * + * [136] Single Number + * + * https://leetcode.com/problems/single-number/description/ + * + * algorithms + * Easy (59.13%) + * Total Accepted: 429.3K + * Total Submissions: 724.1K + * Testcase Example: '[2,2,1]' + * + * Given a non-empty array of integers, every element appears twice except for + * one. Find that single one. + * + * Note: + * + * Your algorithm should have a linear runtime complexity. Could you implement + * it without using extra memory? + * + * Example 1: + * + * + * Input: [2,2,1] + * Output: 1 + * + * + * Example 2: + * + * + * Input: [4,1,2,1,2] + * Output: 4 + * + * + */ +/** + * @param {number[]} nums + * @return {number} + */ +var singleNumber = function(nums) { + let ret = 0; + for (let index = 0; index < nums.length; index++) { + const element = nums[index]; + ret = ret ^ element; + + } + return ret; +}; + +``` + +## 延伸 + +有一个 n 个元素的数组,除了两个数只出现一次外,其余元素都出现两次,让你找出这两个只出现一次的数分别是几,要求时间复杂度为 O(n) 且再开辟的内存空间固定(与 n 无关)。 + + +和上面一样,只是这次不是一个数字,而是两个数字。还是按照上面的思路,我们进行一次全员异或操作, +得到的结果就是那两个只出现一次的不同的数字的异或结果。 + +我们刚才讲了异或的规律中有一个`任何数和本身异或则为0`, 因此我们的思路是能不能将这两个不同的数字分成两组A和B。 +分组需要满足两个条件. + +1. 两个独特的的数字分成不同组 + +2. 相同的数字分成相同组 + +这样每一组的数据进行异或即可得到那两个数字。 + +问题的关键点是我们怎么进行分组呢? + +由于异或的性质是,同一位相同则为0,不同则为1. 我们将所有数字异或的结果一定不是0,也就是说至少有一位是1. + +我们随便取一个, 分组的依据就来了, 就是你取的那一位是0分成1组,那一位是1的分成一组。 +这样肯定能保证`2. 相同的数字分成相同组`, 不同的数字会被分成不同组么。 很明显当然可以, 因此我们选择是1,也就是 +说`两个独特的的数字`在那一位一定是不同的,因此两个独特元素一定会被分成不同组。 + +Done! + + diff --git a/144.binary-tree-preorder-traversal.md b/144.binary-tree-preorder-traversal.md new file mode 100644 index 0000000000000000000000000000000000000000..f06b6662c372dda0bb9f2c0a95d87e99d72f0168 --- /dev/null +++ b/144.binary-tree-preorder-traversal.md @@ -0,0 +1,124 @@ +## 题目地址 +https://leetcode.com/problems/binary-tree-preorder-traversal/description/ + +## 题目描述 + +``` +Given a binary tree, return the preorder traversal of its nodes' values. + +Example: + +Input: [1,null,2,3] + 1 + \ + 2 + / + 3 + +Output: [1,2,3] +Follow up: Recursive solution is trivial, could you do it iteratively? + +``` + +## 思路 + +这道题目是前序遍历,这个和之前的`leetcode 94 号问题 - 中序遍历`完全不一回事。 + +前序遍历是`根左右`的顺序,注意是`根`开始,那么就很简单。直接先将根节点入栈,然后 +看有没有右节点,有则入栈,再看有没有左节点,有则入栈。 然后出栈一个元素,重复即可。 + +> 其他树的非递归遍历课没这么简单 + +## 关键点解析 + +- 二叉树的基本操作(遍历) +> 不同的遍历算法差异还是蛮大的 +- 如果非递归的话利用栈来简化操作 + +- 如果数据规模不大的话,建议使用递归 + +- 递归的问题需要注意两点,一个是终止条件,一个如何缩小规模 + +1. 终止条件,自然是当前这个元素是null(链表也是一样) + +2. 由于二叉树本身就是一个递归结构, 每次处理一个子树其实就是缩小了规模, +难点在于如何合并结果,这里的合并结果其实就是`mid.concat(left).concat(right)`, +mid是一个具体的节点,left和right`递归求出即可` + + +## 代码 + +```js +/* + * @lc app=leetcode id=144 lang=javascript + * + * [144] Binary Tree Preorder Traversal + * + * https://leetcode.com/problems/binary-tree-preorder-traversal/description/ + * + * algorithms + * Medium (50.36%) + * Total Accepted: 314K + * Total Submissions: 621.2K + * Testcase Example: '[1,null,2,3]' + * + * Given a binary tree, return the preorder traversal of its nodes' values. + * + * Example: + * + * + * Input: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * Output: [1,2,3] + * + * + * Follow up: Recursive solution is trivial, could you do it iteratively? + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var preorderTraversal = function(root) { + // 1. Recursive solution + + // if (!root) return []; + + // return [root.val].concat(preorderTraversal(root.left)).concat(preorderTraversal(root.right)); + + // 2. iterative solutuon + + if (!root) return []; + const ret = []; + const stack = [root]; + let left = root.left; + let t = stack.pop(); + + while(t) { + ret.push(t.val); + if (t.right) { + stack.push(t.right); + } + if (t.left) { + stack.push(t.left); + } + t =stack.pop(); + } + + return ret; +}; + +``` + diff --git a/145.binary-tree-postorder-traversal.md b/145.binary-tree-postorder-traversal.md new file mode 100644 index 0000000000000000000000000000000000000000..0b20ea243e2553f43a9e62f5f108d76e775e6412 --- /dev/null +++ b/145.binary-tree-postorder-traversal.md @@ -0,0 +1,135 @@ +## 题目地址 +https://leetcode.com/problems/binary-tree-postorder-traversal/description/ + +## 题目描述 + +``` +Given a binary tree, return the postorder traversal of its nodes' values. + +For example: +Given binary tree {1,#,2,3}, + + 1 + \ + 2 + / + 3 + + +return [3,2,1]. + +Note: Recursive solution is trivial, could you do it iteratively? + +``` + +## 思路 + +相比于前序遍历,后续遍历思维上难度要大些,前序遍历是通过一个stack,首先压入父亲结点,然后弹出父亲结点,并输出它的value,之后压人其右儿子,左儿子即可。 + +然而后序遍历结点的访问顺序是:左儿子 -> 右儿子 -> 自己。那么一个结点需要两种情况下才能够输出: +第一,它已经是叶子结点; +第二,它不是叶子结点,但是它的儿子已经输出过。 + +那么基于此我们只需要记录一下当前输出的结点即可。对于一个新的结点,如果它不是叶子结点,儿子也没有访问,那么就需要将它的右儿子,左儿子压入。 +如果它满足输出条件,则输出它,并记录下当前输出结点。输出在stack为空时结束。 + + +## 关键点解析 + +- 二叉树的基本操作(遍历) +> 不同的遍历算法差异还是蛮大的 +- 如果非递归的话利用栈来简化操作 + +- 如果数据规模不大的话,建议使用递归 + +- 递归的问题需要注意两点,一个是终止条件,一个如何缩小规模 + +1. 终止条件,自然是当前这个元素是null(链表也是一样) + +2. 由于二叉树本身就是一个递归结构, 每次处理一个子树其实就是缩小了规模, +难点在于如何合并结果,这里的合并结果其实就是`left.concat(right).concat(mid)`, +mid是一个具体的节点,left和right`递归求出即可` + + +## 代码 + +```js +/* + * @lc app=leetcode id=145 lang=javascript + * + * [145] Binary Tree Postorder Traversal + * + * https://leetcode.com/problems/binary-tree-postorder-traversal/description/ + * + * algorithms + * Hard (47.06%) + * Total Accepted: 242.6K + * Total Submissions: 512.8K + * Testcase Example: '[1,null,2,3]' + * + * Given a binary tree, return the postorder traversal of its nodes' values. + * + * Example: + * + * + * Input: [1,null,2,3] + * ⁠ 1 + * ⁠ \ + * ⁠ 2 + * ⁠ / + * ⁠ 3 + * + * Output: [3,2,1] + * + * + * Follow up: Recursive solution is trivial, could you do it iteratively? + * + */ +/** + * Definition for a binary tree node. + * function TreeNode(val) { + * this.val = val; + * this.left = this.right = null; + * } + */ +/** + * @param {TreeNode} root + * @return {number[]} + */ +var postorderTraversal = function(root) { + // 1. Recursive solution + + // if (!root) return []; + + // return postorderTraversal(root.left).concat(postorderTraversal(root.right)).concat(root.val); + + // 2. iterative solutuon + + if (!root) return []; + const ret = []; + const stack = [root]; + let p = root; // 标识元素,用来判断节点是否应该出栈 + + while (stack.length > 0) { + const top = stack[stack.length - 1]; + if ( + top.left === p || + top.right === p || // 子节点已经遍历过了 + (top.left === null && top.right === null) // 叶子元素 + ) { + p = stack.pop(); + ret.push(p.val); + } else { + if (top.right) { + stack.push(top.right); + } + if (top.left) { + stack.push(top.left); + } + } + } + + return ret; +}; + +```