-
Notifications
You must be signed in to change notification settings - Fork 0
/
content.json
1 lines (1 loc) · 96.2 KB
/
content.json
1
{"meta":{"title":"Yc的博客","subtitle":"","description":"stay hungry, stay foolish","author":"ycblue","url":"http://yc-sky.top","root":"/"},"pages":[{"title":"","date":"2020-08-22T02:38:03.388Z","updated":"2020-08-22T02:38:03.388Z","comments":true,"path":"404.html","permalink":"http://yc-sky.top/404.html","excerpt":"","text":""},{"title":"categories","date":"2020-08-22T02:52:09.000Z","updated":"2020-08-22T02:52:46.060Z","comments":true,"path":"categories/index.html","permalink":"http://yc-sky.top/categories/index.html","excerpt":"","text":""},{"title":"tags","date":"2020-08-22T02:48:30.000Z","updated":"2020-08-22T02:50:49.800Z","comments":true,"path":"tags/index.html","permalink":"http://yc-sky.top/tags/index.html","excerpt":"","text":""}],"posts":[{"title":"剑指offer-对称的二叉树","slug":"剑指offer-对称的二叉树","date":"2020-09-21T10:21:49.000Z","updated":"2020-09-21T10:25:14.007Z","comments":true,"path":"2020/09/21/剑指offer-对称的二叉树/","link":"","permalink":"http://yc-sky.top/2020/09/21/%E5%89%91%E6%8C%87offer-%E5%AF%B9%E7%A7%B0%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91/","excerpt":"请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。","text":"请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。 原题描述访问:https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/ 思路对于树中 任意两个对称节点L和R 一定有: - L.val=R.val :即此两对称节点值相等。 - L.left.val=R.right.val - L.right.val=R.left.val 代码 1234567891011121314151617181920/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } *//** * @param {TreeNode} root * @return {boolean} */var isSymmetric = function(root) { var helper = function(left_root, right_root){ if(!left_root && !right_root) return true if(!left_root || !right_root) return false if(left_root.val !== right_root.val) return false return helper(left_root.left, right_root.right) && helper(left_root.right, right_root.left) } return !root ? true : helper(root.left, root.right)};","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"剑指offer","slug":"剑指offer","permalink":"http://yc-sky.top/tags/%E5%89%91%E6%8C%87offer/"}]},{"title":"剑指offer-二叉树的镜像","slug":"剑指offer-二叉树的镜像","date":"2020-09-21T10:17:23.000Z","updated":"2020-09-21T10:20:52.764Z","comments":true,"path":"2020/09/21/剑指offer-二叉树的镜像/","link":"","permalink":"http://yc-sky.top/2020/09/21/%E5%89%91%E6%8C%87offer-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E9%95%9C%E5%83%8F/","excerpt":"请完成一个函数,输入一个二叉树,该函数输出它的镜像。","text":"请完成一个函数,输入一个二叉树,该函数输出它的镜像。 原题描述访问:https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/ 递归根据二叉树镜像的定义,考虑递归遍历(dfs)二叉树,交换每个节点的左 / 右子节点,即可生成二叉树的镜像。 123456789101112131415161718/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } *//** * @param {TreeNode} root * @return {TreeNode} */var mirrorTree = function(root) { if(!root || root.length == 0) return null let tmp = root.left root.left = mirrorTree(root.right) root.right = mirrorTree(tmp) return root}; 辅助栈利用栈(或队列)遍历树的所有节点 node,并交换每个 node 的左 / 右子节点。 12345678910111213141516171819202122232425262728/** * Definition for a binary tree node. * function TreeNode(val) { * this.val = val; * this.left = this.right = null; * } *//** * @param {TreeNode} root * @return {TreeNode} */var mirrorTree = function(root) { if(!root || root.length == 0) return null let stack = [root] while(stack.length){ let node = stack.pop() if(node.left){ stack.push(node.left) } if(node.right){ stack.push(node.right) } let tmp = node.left node.left = node.right node.right = tmp } return root};","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"bfs","slug":"bfs","permalink":"http://yc-sky.top/tags/bfs/"},{"name":"剑指offer","slug":"剑指offer","permalink":"http://yc-sky.top/tags/%E5%89%91%E6%8C%87offer/"}]},{"title":"剑指offer-重建二叉树","slug":"剑指offer-重建二叉树","date":"2020-09-21T09:57:55.000Z","updated":"2020-09-21T10:09:45.988Z","comments":true,"path":"2020/09/21/剑指offer-重建二叉树/","link":"","permalink":"http://yc-sky.top/2020/09/21/%E5%89%91%E6%8C%87offer-%E9%87%8D%E5%BB%BA%E4%BA%8C%E5%8F%89%E6%A0%91/","excerpt":"输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。","text":"输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 原题描述访问:https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/ 代码 123456789var buildTree = function(preorder, inorder) { if(!preorder.length || !inorder.length) return null let root = preorder[0] let node = new TreeNode(root) let idx = inorder.indexOf(root) node.left = buildTree(preorder.slice(1, idx+1), inorder.slice(0, idx)) node.right = buildTree(preorder.slice(idx+1), inorder.slice(idx+1)) return node};","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"二叉树","slug":"二叉树","permalink":"http://yc-sky.top/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"},{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"剑指offer","slug":"剑指offer","permalink":"http://yc-sky.top/tags/%E5%89%91%E6%8C%87offer/"}]},{"title":"leetcode-单词搜索","slug":"leetcode-单词搜索","date":"2020-09-16T12:30:03.000Z","updated":"2020-09-16T12:38:58.486Z","comments":true,"path":"2020/09/16/leetcode-单词搜索/","link":"","permalink":"http://yc-sky.top/2020/09/16/leetcode-%E5%8D%95%E8%AF%8D%E6%90%9C%E7%B4%A2/","excerpt":"给定一个二维网格和一个单词,找出该单词是否存在于网格中。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。","text":"给定一个二维网格和一个单词,找出该单词是否存在于网格中。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 原题描述访问:https://leetcode-cn.com/problems/word-search/题解转自:https://leetcode-cn.com/problems/word-search/solution/shou-hua-tu-jie-79-dan-ci-sou-suo-dfs-si-lu-de-cha/ 题意给你一个由字母组成的二维矩阵,和一个单词,能否在矩阵中“勾勒”出一条路径,路径上的字母组成了这个单词。 思路比如单词 “SEE”,首先起点要对,遍历一遍矩阵,看看哪里有起点 S。起点可能不止一个,基于其中一个 S,能否找出剩下的 “EE” 路径。其实已经有了深搜的思路了。下一个字符 E,有四个可选点:当前 S 点的上、下、左、右。逐个尝试每一种选择,去探索。基于其中一种选择,又要为下一个字符选点,又有四种选择,继续尝试,探索。回溯的思路就有了。每到一个点,做的事情是一样的,是递归DFS。路径类问题经常是 DFS——往下选点,构建路径。当发现某个选择不对,不要继续选下去了,结束当前递归,考察别的选择。 递归总是关注当前我们在写递归函数时,关注当前,当前考察的点,哪些是当前递归该处理的,哪些是丢给递归子调用去做的。当前递归本身做的事:判断当前选择的点,本身有没有问题,是不是错的。至于剩下的字符,能否找到路径,这件事,交给递归子调用去深搜。如果当前点是错的,也不用往下递归了,直接返回false。否则继续递归搜四个方向,为剩下的字符选点。 哪些情况说明这是一个错的选择呢?(递归的结束条件) 当前的点,超出矩阵,不存在。 当前的点,之前来过了,不符合「同一个单元格内的字母不允许被重复使用」 当前的点,不是目标点,比如你想找 E,却来了 D。 当前的点,不是目标点,比如你想找 E,却来了 D。用一个二维矩阵 used,记录已经访问过的点,下次再选择访问这个点,就直接返回 false。 代码 123456789101112131415161718192021222324252627282930313233343536373839404142const exist = (board, word) => { const m = board.length; const n = board[0].length; const used = new Array(m); // 二维矩阵used for (let i = 0; i < m; i++) { used[i] = new Array(n); } // 判断当前点是否是目标路径上的点 const dfs = (row, col, i) => { // row col是当前点的坐标,i是当前考察的字符索引 if (i > word.length - 1) { // 递归的出口 return true; } if (row < 0 || row >= m || col < 0 || col >= n) { // 当前点要存在 return false; } if (used[row][col] || board[row][col] != word[i]) { // 当前的点已经走过,或当前点就不是目标点 return false; } // 排除掉这些false情况,当前点是没问题的,可以继续递归考察 used[row][col] = true; // used记录一下当前点被访问了 const canFindRest = dfs(row + 1, col, i + 1) || dfs(row - 1, col, i + 1) || dfs(row, col + 1, i + 1) || dfs(row, col - 1, i + 1); if (canFindRest) { // 基于当前点,可以为剩下的字符找到路径 return true; } used[row][col] = false; // 找不出,返回false,继续考察别的分支,并撤销当前点的访问状态。 return false; }; for (let i = 0; i < m; i++) { for (let j = 0; j < n; j++) { if (board[i][j] == word[0] && dfs(i, j, 0)) { // 找到dfs的起点 return true; // 找到起点,且dfs的结果也true,则找到了目标路径 } } } return false; // 怎么样都没有返回true,则返回false};","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"},{"name":"dfs","slug":"dfs","permalink":"http://yc-sky.top/tags/dfs/"},{"name":"回溯","slug":"回溯","permalink":"http://yc-sky.top/tags/%E5%9B%9E%E6%BA%AF/"}]},{"title":"leetcode-组合总和3","slug":"leetcode-组合总和3","date":"2020-09-11T16:33:43.000Z","updated":"2020-09-11T16:36:01.240Z","comments":true,"path":"2020/09/12/leetcode-组合总和3/","link":"","permalink":"http://yc-sky.top/2020/09/12/leetcode-%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C3/","excerpt":"找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。","text":"找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。 原题描述访问:https://leetcode-cn.com/problems/combination-sum-iii/题解转自:https://leetcode-cn.com/problems/combination-sum-iii/solution/shou-hua-tu-jie-216-zu-he-zong-he-iii-by-xiao_ben_/ 题意此题与组合总和1,2相似,解法可参考前面代码 1234567891011121314151617181920const combinationSum3 = (k, n) => { const res = []; const dfs = (start, temp, sum) => { if (temp.length == k) { // You've selected k numbers. End recursion if (sum == n) { // The sum of numbers in a combination equals n res.push(temp.slice()); // Add its copy to the solution set } return; } for (let i = start; i <= 9; i++) { // Enumerate the options temp.push(i); // Make a choice dfs(i + 1, temp, sum + i); // Explore temp.pop(); // Undo the choice } }; dfs(1, [], 0); // press the search button return res;};","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"},{"name":"dfs","slug":"dfs","permalink":"http://yc-sky.top/tags/dfs/"},{"name":"回溯","slug":"回溯","permalink":"http://yc-sky.top/tags/%E5%9B%9E%E6%BA%AF/"}]},{"title":"回溯算法","slug":"回溯算法","date":"2020-09-11T14:47:56.000Z","updated":"2020-09-11T14:55:04.219Z","comments":true,"path":"2020/09/11/回溯算法/","link":"","permalink":"http://yc-sky.top/2020/09/11/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95/","excerpt":"回溯跟递归,深度搜索遍历结合使用,是非常普遍的算法思路。下面介绍回溯思路","text":"回溯跟递归,深度搜索遍历结合使用,是非常普遍的算法思路。下面介绍回溯思路 转自题解:https://leetcode-cn.com/problems/combination-sum-ii/solution/man-tan-wo-li-jie-de-hui-su-chang-wen-shou-hua-tu-/ 回溯的要素我们关心,当前局面下,我们有什么选择,作了一个选择之后,下一个选择会有什么限制。 解空间树的节点是动态的,当前的选择决定了下一个选择是怎么展开的。 所以,不仅要关注 options,还要关注 restraints 约束。 前者用于展开出一棵解的空间树,后者用于为这棵树剪枝,剪去不能产生正确解的节点,避免无效搜索。 第三个要素:目标(结束条件),明确了目标,就知道何时去将解加入解集。 并且让你知道:探索到某一步时,发现当前的部分解不能通向正确的完整解,搜下去没有意义。 此时回退一步,撤销当前的选择,回到上一个选择的状态,做别的选择。 此路不通,退回去,尝试别的路,是一个「choose, explore, unchoose」的过程。 套路做法 用 for 循环去枚举出所有的选择 做出一个选择 基于这个选择,继续往下选择(递归) 上面的递归结束了,撤销这个选择,进入 for 循环的下一次迭代 回溯与嵌套循环回溯是一种算法,递归不是算法,是一种计算机解决问题的方式。 回溯是借助递归实现的。如果回溯不借助递归,它只能借助循环。 用 for 循环枚举出当前的选项,选了一个,选下一个,又要嵌套一层循环,枚举出可选的选项。 如果要选很多个,就导致多重的循环嵌套,很费力也很丑。 于是借助递归解决,因为递归和子递归就是层级嵌套的关系。 而且,树在结构上,具有高度的重复性,每一个节点,都是当前子树的根节点,调用递归函数负责当前子树的搜索。 虚拟的解空间树回溯算法并没有显式地创建数据结构,也不是基于已有的数据结构做搜索。 它是隐式地,通过递归,构建出一棵解的空间树。这个空间树中包含了所有的解。 然后通过 dfs 的搜索方式,把解给全部找出来。 如果说它难,应该难在确定出容易搜索(经过充分的剪枝)的解空间结构,剩下的 dfs 和回溯就比较简单了。","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"dfs","slug":"dfs","permalink":"http://yc-sky.top/tags/dfs/"},{"name":"回溯","slug":"回溯","permalink":"http://yc-sky.top/tags/%E5%9B%9E%E6%BA%AF/"}]},{"title":"leetcode-组合总和2","slug":"leetcode-组合总和2","date":"2020-09-11T14:40:54.000Z","updated":"2020-09-11T14:47:33.989Z","comments":true,"path":"2020/09/11/leetcode-组合总和2/","link":"","permalink":"http://yc-sky.top/2020/09/11/leetcode-%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C2/","excerpt":"给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。","text":"给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。 原题描述访问:https://leetcode-cn.com/problems/combination-sum-ii/题解转自:https://leetcode-cn.com/problems/combination-sum-ii/solution/man-tan-wo-li-jie-de-hui-su-chang-wen-shou-hua-tu-/ 思路以 [2,5,2,1,2], target = 5 为例。 每个节点都作出选择,选一个数,看看下一个选择受到哪些限制:选过的不能再选,且不能产生相同的组合。去做剪枝(如下图所标)。 当和为 target,找到一个正确的解,加入解集。且当和 >= target,已经爆掉了,不用继续选了,结束当前递归,继续搜别的分支,找齐所有的解。 因此,回到上一个节点,撤销当前选择的数字,去进入下一个分支。与组合总和区别 组合总和:元素可以重复使用,组合不能重复。 本题:元素不可以重复使用,组合不能重复。 本题只需改动三点: 给定的数组可能有重复的元素,先排序,使得重复的数字相邻,方便去重。 for 循环枚举出选项时,加入下面判断,忽略掉同一层重复的选项,避免产生重复的组合。比如[1,2,2,2,5]中,选了第一个 2,变成 [1,2],第一个 2 的下一选项也是 2,跳过它,因为选它,就还是 [1,2]。123if (candidates[i - 1] == candidates[i] && i - 1 >= start) { continue;} 当前选择的数字不能和下一个选择的数字重复,递归传入i+1,避免与当前选的i重复,这样每次选,就不会选过往选过的同一个数。1dfs(i + 1, temp, sum + candidates[i]); 代码 123456789101112131415161718192021222324const combinationSum2 = (candidates, target) => { candidates.sort(); // 排序 const res = []; const dfs = (start, temp, sum) => { // start是索引 当前选择范围的第一个 if (sum >= target) { // 爆掉了,不用继续选了 if (sum == target) { // 满足条件,加入解集 res.push(temp.slice()); // temp是地址引用,后续还要用,所以拷贝一份 } return; // 结束当前递归 } for (let i = start; i < candidates.length; i++) { // 枚举出选择 if (candidates[i - 1] == candidates[i] && i - 1 >= start) { // 当前选项和隔壁选项一样,跳过 continue; } temp.push(candidates[i]); // 作出选择 dfs(i + 1, temp, sum + candidates[i]); // 递归,向下选择,并更新sum temp.pop(); // 撤销选择, } }; dfs(0, [], 0); return res;};","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"},{"name":"dfs","slug":"dfs","permalink":"http://yc-sky.top/tags/dfs/"},{"name":"回溯","slug":"回溯","permalink":"http://yc-sky.top/tags/%E5%9B%9E%E6%BA%AF/"}]},{"title":"display,visibility和opacity","slug":"display-visibility和opacity","date":"2020-09-09T08:43:48.000Z","updated":"2020-09-09T08:53:31.339Z","comments":true,"path":"2020/09/09/display-visibility和opacity/","link":"","permalink":"http://yc-sky.top/2020/09/09/display-visibility%E5%92%8Copacity/","excerpt":"以下为三者设置样式的区别","text":"以下为三者设置样式的区别 display: none DOM结构:浏览器不会渲染display属性为none的元素,不占用空间 事件监听:无法进行DOM事件监听 性能:动态改变此属性会引起重排,性能较差 继承:不会被子元素继承,毕竟子类也不会被渲染 transition:transition不支持display visibility: hidden DOM结构:元素被隐藏,但是会被渲染,不会消失,占用空间 事件监听:无法进行DOM事件监听 性能:动态改变此属性会引起重绘,性能较高 继承:会被子元素继承,自元素可以通过设置visibility: visible来取消隐藏 transition:visibility会立即显示,隐藏时会延时 opacity: 0 DOM结构:透明度为100%,元素隐藏,占用空间 事件监听:可以进行DOM事件监听 性能:提升为合成层,不会触发重绘,性能较高 继承:会被子元素继承且子元素不能通过opacity: 1来取消隐藏 transition:opacity可以延时显示和隐藏","categories":[{"name":"CSS","slug":"CSS","permalink":"http://yc-sky.top/categories/CSS/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"leetcode-组合总和","slug":"leetcode-组合总和","date":"2020-09-09T06:44:49.000Z","updated":"2020-09-09T07:18:21.762Z","comments":true,"path":"2020/09/09/leetcode-组合总和/","link":"","permalink":"http://yc-sky.top/2020/09/09/leetcode-%E7%BB%84%E5%90%88%E6%80%BB%E5%92%8C/","excerpt":"给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。","text":"给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。 原题描述访问:https://leetcode-cn.com/problems/combinations/题解转自:https://leetcode-cn.com/problems/combination-sum/solution/shou-hua-tu-jie-zu-he-zong-he-combination-sum-by-x/ 题意给你一个数组,里面都是不带重复的正数,还给你一个 target,求出所有和为 target 的组合。元素可以重复使用,但组合不能重复,比如 [2, 2, 3] 与 [2, 3, 2] 是重复的。参考图解代码 1234567891011121314151617181920212223const combinationSum = (candidates, target) => { const res = []; let sum = 0; const dfs = (start, temp) => { if (sum >= target) { if (sum == target) { res.push(temp.slice()); } return; // 结束当前递归 } for (let i = start; i < candidates.length; i++) { // 枚举出选择,从start开始 sum += candidates[i]; // 累加给sum temp.push(candidates[i]); // 加入“部分解” dfs(i, temp); // 往下递归,继续选择,注意是i,不是i+1 sum -= candidates[i]; // 撤销选择,sum变回来 temp.pop(); // 撤销选择 } }; dfs(0, []); return res;}; 总结 回溯:在包含问题的所有的解的空间树中,按DFS的方法,从根节点出发,搜索整棵解空间树。 搜索至任何一个节点时,总是会先判断当前节点是否可以 lead us to a complete solution。如果不可以,则结束对「以当前节点为根节点的子树」的搜索,向父节点回溯,回到之前的状态,搜索下一个分支。 否则,进入该子树,继续以DFS的方式搜索。 空间树中的节点是动态的,当前有哪些节点可选择,是根据上一步的选择(上一步的状态)得出的,所以做回溯时,要把状态还原成进入当前节点之前的状态。 我们在做题时,要确定出问题的解空间树,它是隐式的,不是显式的一棵树。不熟练的就画图看看。 然后,明确每个节点的扩展搜索规则。 然后进行DFS搜索,并注意剪枝,避免无效的搜索。","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"},{"name":"dfs","slug":"dfs","permalink":"http://yc-sky.top/tags/dfs/"},{"name":"回溯","slug":"回溯","permalink":"http://yc-sky.top/tags/%E5%9B%9E%E6%BA%AF/"}]},{"title":"leetcode-组合","slug":"leetcode-组合","date":"2020-09-08T16:43:11.000Z","updated":"2020-09-08T16:59:48.756Z","comments":true,"path":"2020/09/09/leetcode-组合/","link":"","permalink":"http://yc-sky.top/2020/09/09/leetcode-%E7%BB%84%E5%90%88/","excerpt":"给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。","text":"给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。 原题描述访问:https://leetcode-cn.com/problems/combinations/ 回溯剪枝直接看代码 123456789101112131415161718const combine = (n, k) => { const res = []; const dfs = (start, path) => { // start是枚举选择的起点 path是当前构建的路径(组合) if (path.length == k) { res.push(path.slice()); // 拷贝一份path,推入res return; // 结束当前递归 } for (let i = start; i <= n; i++) { // 枚举出所有选择 path.push(i); // 选择 dfs(i + 1, path); // 向下继续选择 path.pop(); // 撤销选择 } }; dfs(1, []); // 递归的入口,从数字1开始选 return res;}","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"},{"name":"dfs","slug":"dfs","permalink":"http://yc-sky.top/tags/dfs/"},{"name":"回溯","slug":"回溯","permalink":"http://yc-sky.top/tags/%E5%9B%9E%E6%BA%AF/"}]},{"title":"leetcode-前k个高频元素","slug":"leetcode-前k个高频元素","date":"2020-09-08T14:10:18.000Z","updated":"2020-09-08T16:42:12.515Z","comments":true,"path":"2020/09/08/leetcode-前k个高频元素/","link":"","permalink":"http://yc-sky.top/2020/09/08/leetcode-%E5%89%8Dk%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0/","excerpt":"给定一个非空的整数数组,返回其中出现频率前 k 高的元素。","text":"给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 原题描述访问:https://leetcode-cn.com/problems/top-k-frequent-elements/ map+数组利用 map 记录每个元素出现的频率,利用数组来比较排序元素代码实现 123456789let topKFrequent = function(nums, k) { let map = new Map(), arr = [...new Set(nums)] nums.map((num) => { if(map.has(num)) map.set(num, map.get(num)+1) else map.set(num, 1) }) return arr.sort((a, b) => map.get(b) - map.get(a)).slice(0, k);}; 复杂度分析 时间复杂度:O(nlogn) 空间复杂度:O(n)题目要求算法的时间复杂度必须优于 O(n log n) ,所以这种实现不合题目要求 map+小顶堆遍历一遍数组统计每个元素的频率,并将元素值( key )与出现的频率( value )保存到 map 中通过 map 数据构建一个前 k 个高频元素小顶堆,小顶堆上的任意节点值都必须小于等于其左右子节点值,即堆顶是最小值。具体步骤如下: - 遍历数据,统计每个元素的频率,并将元素值( key )与出现的频率( value )保存到 map 中 - 遍历 map ,将前 k 个数,构造一个小顶堆 - 从 k 位开始,继续遍历 map ,每一个数据出现频率都和小顶堆的堆顶元素出现频率进行比较,如果小于堆顶元素,则不做任何处理,继续遍历下一元素;如果大于堆顶元素,则将这个元素替换掉堆顶元素,然后再堆化成一个小顶堆。 - 遍历完成后,堆中的数据就是前 k 大的数据代码实现 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768let topKFrequent = function(nums, k) { let map = new Map(), heap = [,] nums.map((num) => { if(map.has(num)) map.set(num, map.get(num)+1) else map.set(num, 1) }) // 如果元素数量小于等于 k if(map.size <= k) { return [...map.keys()] } // 如果元素数量大于 k,遍历map,构建小顶堆 let i = 0 map.forEach((value, key) => { if(i < k) { // 取前k个建堆, 插入堆 heap.push(key) // 原地建立前 k 堆 if(i === k-1) buildHeap(heap, map, k) } else if(map.get(heap[1]) < value) { // 替换并堆化 heap[1] = key // 自上而下式堆化第一个元素 heapify(heap, map, k, 1) } i++ }) // 删除heap中第一个元素 heap.shift() return heap};// 原地建堆,从后往前,自上而下式建小顶堆let buildHeap = (heap, map, k) => { if(k === 1) return // 从最后一个非叶子节点开始,自上而下式堆化 for(let i = Math.floor(k/2); i>=1 ; i--) { heapify(heap, map, k, i) }}// 堆化let heapify = (heap, map, k, i) => { // 自上而下式堆化 while(true) { let minIndex = i if(2*i <= k && map.get(heap[2*i]) < map.get(heap[i])) { minIndex = 2*i } if(2*i+1 <= k && map.get(heap[2*i+1]) < map.get(heap[minIndex])) { minIndex = 2*i+1 } if(minIndex !== i) { swap(heap, i, minIndex) i = minIndex } else { break } }}// 交换let swap = (arr, i , j) => { let temp = arr[i] arr[i] = arr[j] arr[j] = temp} 复杂度分析: 时间复杂度:遍历数组需要 O(n) 的时间复杂度,一次堆化需要 O(logk) 时间复杂度,所以利用堆求 Top k 问题的时间复杂度为 O(nlogk) 空间复杂度:O(n) 桶排序这里取前k个高频元素,使用计数排序不再适合,在上题目中使用计数排序,将 i 元素出现的次数存储在 bucket[i] ,但这种存储不能保证 bucket 数组上值是有序的,例如 bucket=[0,3,1,2] ,即元素 0 未出现,元素 1 出现 3 次,元素 2 出现 1 次,元素 3 出现 2 次,所以计数排序不适用于取前k个高频元素,不过,不用怕,计数排序不行,还有桶排序。 桶排序是计数排序的升级版。它也是利用函数的映射关系。 桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。 首先使用 map 来存储频率 然后创建一个数组(有数量的桶),将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标(桶内)即可。 代码实现 12345678910111213141516171819202122232425262728293031323334let topKFrequent = function(nums, k) { let map = new Map(), arr = [...new Set(nums)] nums.map((num) => { if(map.has(num)) map.set(num, map.get(num)+1) else map.set(num, 1) }) // 如果元素数量小于等于 k if(map.size <= k) { return [...map.keys()] } return bucketSort(map, k)};// 桶排序let bucketSort = (map, k) => { let arr = [], res = [] map.forEach((value, key) => { // 利用映射关系(出现频率作为下标)将数据分配到各个桶中 if(!arr[value]) { arr[value] = [key] } else { arr[value].push(key) } }) // 倒序遍历获取出现频率最大的前k个数 for(let i = arr.length - 1;i >= 0 && res.length < k;i--){ if(arr[i]) { res.push(...arr[i]) } } return res} 复杂度分析: 时间复杂度:O(n) 空间复杂度:O(n)","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"Map","slug":"Map","permalink":"http://yc-sky.top/tags/Map/"},{"name":"小顶堆","slug":"小顶堆","permalink":"http://yc-sky.top/tags/%E5%B0%8F%E9%A1%B6%E5%A0%86/"},{"name":"桶排序","slug":"桶排序","permalink":"http://yc-sky.top/tags/%E6%A1%B6%E6%8E%92%E5%BA%8F/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"}]},{"title":"防抖和节流","slug":"防抖和节流","date":"2020-09-07T15:44:55.000Z","updated":"2020-09-07T16:13:14.264Z","comments":true,"path":"2020/09/07/防抖和节流/","link":"","permalink":"http://yc-sky.top/2020/09/07/%E9%98%B2%E6%8A%96%E5%92%8C%E8%8A%82%E6%B5%81/","excerpt":"前端开发中,我们的一些事件的响应比较慢或者需要请求接口完成的,我们不希望这些事件频繁执行,比如说需要对input输入的数据保存,监听keyup事件,如果每次键盘输入就执行保存请求,那样可能会产生很多频繁的请求,针对这种连续触发的高频率事件,函数防抖和函数节流给出了两种解决方法","text":"前端开发中,我们的一些事件的响应比较慢或者需要请求接口完成的,我们不希望这些事件频繁执行,比如说需要对input输入的数据保存,监听keyup事件,如果每次键盘输入就执行保存请求,那样可能会产生很多频繁的请求,针对这种连续触发的高频率事件,函数防抖和函数节流给出了两种解决方法 防抖(debounce)去抖动,方法是在函数触发时,设定一个周期延迟执行函数,若在周期内函数再次执行、则刷新延迟时间,直到最后执行函数,这里函数收集到的结果是最后一次操作的结果 简单的实现1234567891011var timer; // 定时器function change (e) { if (timer) { clearTimeout(timer); } timer = setTimeout(function () { console.log(e.target.value); timer = void 0; }, 1000)}document.querySelector(\"#test\").addEventListener('keyup', change); 这里监听input的keyup事件,change方法执行的时候会首先判断定时器是否存在、如果存在则clear掉,如果不则新建一个定时器延迟1s执行 封装上面这样实现没毛病,但是却有一个问题,没有复用性,现在我来把他封装成一个公共的方法 1234567891011121314151617function keyup (e) { console.log(e.target.value);}function debounce (method, delay) { var timer = void 0; return function () { var self = this; var args = arguments; timer && clearTimeout(timer); timer = setTimeout(function () { method.apply(self, args) timer = void 0; }, delay) }}document.querySelector(\"#test\").addEventListener('keyup', debounce(keyup, 1000)); 节流(throttling)节流的概念是设定一个周期,周期内只执行一次,若有新的事件触发则不执行,周期结束后又有新的事件触发开始新的周期 实现比如说我们监听onscroll判断获取当前的scrollTop、可以用到节流 1234567891011121314151617181920var start, timer, wait = 200function scroll() { var self = this; var args = arguments; if (!start) { //第一次触发,设置start时间 start = Date.now() } // 当前时间减去开始时间大于等于设定的周期则执行并且初始化start、timer if (Date.now() - start >= wait) { console.log('触发了') start = timer = void 0; } else { timer && clearTimeout(timer) timer = setTimeout(function () { scroll.apply(self, arguments) },wait) }}document.addEventListener('scroll', scroll) 封装1234567891011121314151617181920212223function throttling (method, wait) { var start, timer return function run () { var self = this; var args = arguments; if (!start) { start = Date.now(); } if (Date.now() - start >= wait) { method.apply(self, args) start = timer = void 0 } else { timer && clearTimeout(timer) timer = setTimeout(function () { run.apply(self, args) }, wait) } }}function scroll() { console.log('触发了')}document.addEventListener('scroll', throttling(scroll, 200)) 防抖和节流,通俗来说就是,函数防抖的时候,每次调用事件都是在正常执行暂停后一段时间(等你歇菜了我再上) 函数节流的时候,则是每隔一定的时间间隔就触发一次(管你频率那么快,我就保持自己的节奏) 防抖:在设定时间间隔内,再次触发事件时,定时器会清除,函数重新执行 节流:在设定时间间隔内,再次触发事件时,判断定时器是否为null,若为null,则执行函数。否则不执行","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"JS的执行机制","slug":"JS的执行机制","date":"2020-09-07T13:58:46.000Z","updated":"2020-09-07T14:09:35.670Z","comments":true,"path":"2020/09/07/JS的执行机制/","link":"","permalink":"http://yc-sky.top/2020/09/07/JS%E7%9A%84%E6%89%A7%E8%A1%8C%E6%9C%BA%E5%88%B6/","excerpt":"网上很多js机制相关的讲解,本篇只列出相关题目以便以后复习巩固","text":"网上很多js机制相关的讲解,本篇只列出相关题目以便以后复习巩固 js执行机制相关讲解可参考以下博客https://juejin.im/post/6855129007558492174https://juejin.im/post/6844903568814800904 任务队列任务队列就是一个事件队列,我们前面提到的有些耗时的任务或一些异步任务,我们将它放到任务队列中.当满足某些条件时就可以从任务队列中取出去放到主线程中执行. 我们先来看下面的代码,类似的问题是有可能出现在一些面试题中的.问的就是打印出来的顺序是多少? 1234567891011setTimeout(() => { console.log(1)})console.log(2)new Promise((resolve, reject) => { console.log(3) resolve()}).then(() => { console.log(4)})// 2 3 4 1 为什么是 2341 的顺序呢?我们来分析下,JS中代码的执行是从上到下一行一行执行的.首先执行的是 setTimeout 这段代码,发现这是定时器任务,于是便把内部的具体执行内容 console.log(1) 先拿出来放到其他地方,准备待会儿再执行.继续执行到 console.log(2) 这句,于是先输出一个2.继续执行,遇到了一个 Promise .注意在这个 Promise中 , console.log(3) 以及之后的 resolve() 这两句都是同步执行的,但是 then 里面的代码却是异步执行的.于是在输出了一个3之后,又把 console.log(4) 拿出来放到其他地方,准备晚点再去执行它.好了,现在我们已经把 console.log(1) 和 console.log(4) 扔进了一个地方,那么为什么是先输出4然后再是1呢?这是因为虽然1和4都被我们扔进了一个地方,我们可以把这个地方理解为一个大房子.1和4被扔进了不同的房间.其中1被扔进了一个叫做 宏任务队列 的房间.4被扔进了另一个叫做 微任务队列 的房间. 总结起来就是:不同类型的任务会进入到对应的事件队列(Event Queue)中.每次执行下一个宏任务之前先去微任务队列里面查看,直到把微任务队列清空后再去执行宏任务队列中的任务. 微任务和宏任务 微任务(micro-task): Promise ,process.nextTick 等 宏任务(macro-task): script, setTimeout, setInterval 等 12345678setTimeout(() => { console.log(3)})process.nextTick(() => { console.log(1)})console.log(2)// 2 1 3 可以看到输出的结果为 2 1 3 ,这就是因为 setTimeout 是个宏任务, 而 nextTick 则是个微任务.所以先清空微任务队列,即先执行 process.nextTick 的回调. 运用微任务和宏任务来解题到这里我们已经介绍了同步异步以及微任务队列和宏任务队列的相关内容了,我们再来看一看一道复杂一点的题目. 1234567891011121314151617181920212223242526272829303132setTimeout(() => { console.log(1)})setTimeout(() => { new Promise((resolve, reject) => { console.log(2) resolve() }).then(() => { console.log(3) })})console.log(4)new Promise((resolve, reject) => { console.log(5) resolve()}).then(() => { console.log(6)})new Promise((resolve, reject) => { console.log(7) setTimeout(() => { console.log(8) }) resolve()}).then(() => { console.log(9)})// 4 5 7 6 9 1 2 3 8 首先执行前面两个 setTimeout ,于是把123放到了宏任务队列中.执行到4的时候,先打印出一个4.然后是两个Promise,先打印出5,然后把6放到了微任务队列中.再之后打印出7,把8放到宏任务中,然后就是9放到微任务中.此时已经打印出457,并且微任务中有[6,9],宏任务中有[1,2,3,8].代码第一遍已经执行完毕,前面提到了整个script 脚本相当于一个宏任务.于是便去执行微任务,接着打印出69.此时微任务已经清空,去执行宏任务.选取宏任务队列中的第一个任务,打印出1之后.回过头去看看微任务队列是否还有未执行的任务,现在已经没有了.于是便继续执行宏任务队列中的下一个任务即2.打印出2之后,因为这是一个 Promise ,所以将then里面的3放到微任务队列,此次宏任务执行完毕.此时的微任务队列有[3],宏任务队列有[8].再去执行微任务队列,打印出3.最后再次执行宏任务队列,打印出8.","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"排序算法","slug":"排序算法","date":"2020-09-07T07:24:19.000Z","updated":"2020-09-08T05:50:23.273Z","comments":true,"path":"2020/09/07/排序算法/","link":"","permalink":"http://yc-sky.top/2020/09/07/%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95/","excerpt":"目前经典的排序算法包括(冒泡排序,选择排序,插入排序),合并排序和快速排序,其中效率最高的为快速排序,以下为详细讲解","text":"目前经典的排序算法包括(冒泡排序,选择排序,插入排序),合并排序和快速排序,其中效率最高的为快速排序,以下为详细讲解 转自阮一峰博客:https://javascript.ruanyifeng.com/library/sorting.html参考:https://humanwhocodes.com/blog/2012/11/27/computer-science-in-javascript-quicksort/ 尼古拉斯·泽卡斯版简介快速排序(quick sort)是公认最快的排序算法之一,有着广泛的应用。 它的基本思想很简单:先确定一个“支点”(pivot),将所有小于“支点”的值都放在该点的左侧,大于“支点”的值都放在该点的右侧,然后对左右两侧不断重复这个过程,直到所有排序完成。 具体做法是: 确定“支点”(pivot)。虽然数组中任意一个值都能作为“支点”,但通常是取数组的中间值。 建立两端的指针。左侧的指针指向数组的第一个元素,右侧的指针指向数组的最后一个元素。 左侧指针的当前值与“支点”进行比较,如果小于“支点”则指针向后移动一位,否则指针停在原地。 右侧指针的当前值与“支点”进行比较,如果大于“支点”则指针向前移动一位,否则指针停在原地。 左侧指针的位置与右侧指针的位置进行比较,如果前者大于等于后者,则本次排序结束;否则,左侧指针的值与右侧指针的值相交换。 对左右两侧重复第2至5步。 以对数组[3, 2, 4, 5, 1] 进行从小到大排序为例,步骤如下: 选择中间值“4”作为“支点”。 第一个元素3小于4,左侧指针向后移动一位;第二个元素2小于4,左侧指针向后移动一位;第三个元素4等于4,左侧指针停在这个位置(数组的第2位)。 倒数第一个元素1小于4,右侧指针停在这个位置(数组的第4位)。 左侧指针的位置(2)小于右侧指针的位置(4),两个位置的值互换,数组变成[3, 2, 1, 5, 4]。 左侧指针向后移动一位,第四个元素5大于4,左侧指针停在这个位置(数组的第3位)。 右侧指针向前移动一位,第四个元素5大于4,右侧指针移动向前移动一位,第三个元素1小于4,右侧指针停在这个位置(数组的第3位)。 左侧指针的位置(3)大于右侧指针的位置(2),本次排序结束。 对 [3, 2, 1]和[5, 4]两部分各自不断重复上述步骤,直到排序完成。 算法实现首先部署一个swap函数,用于互换两个位置的值。 12345function swap(myArray, firstIndex, secondIndex){ var temp = myArray[firstIndex]; myArray[firstIndex] = myArray[secondIndex]; myArray[secondIndex] = temp;} 然后,部署一个partition函数,用于完成一轮排序。 1234567891011121314151617181920212223242526function partition(myArray, left, right) { var pivot = myArray[Math.floor((right + left) / 2)], i = left, j = right; while (i <= j) { while (myArray[i] < pivot) { i++; } while (myArray[j] > pivot) { j--; } if (i <= j) { swap(myArray, i, j); i++; j--; } } return i;} 接下来,就是递归上面的过程,完成整个排序。 123456789101112131415161718192021function quickSort(myArray, left, right) { if (myArray.length < 2) return myArray; left = (typeof left !== \"number\" ? 0 : left); right = (typeof right !== \"number\" ? myArray.length - 1 : right); var index = partition(myArray, left, right); if (left < index - 1) { quickSort(myArray, left, index - 1); } if (index < right) { quickSort(myArray, index, right); } return myArray;} 阮一峰版123456789101112131415function quickSort(arr){ if(arr.length<2) return arr let pivotIndex = Math.floor(arr.length/2) let pivot = arr.splice(pivotIndex, 1)[0] let left = [] let right = [] for(let i=0;i<arr.length;i++){ if(arr[i]<pivot){ left.push(arr[i]) }else{ right.push(arr[i]) } } return quickSort(left).concat([pivot], quickSort(right))} 简单好记版(ES6)1234567function quickSort(arr){ if(!arr || arr.length<2) return arr let pivot = arr.shift() let left = arr.filter(item => item < pivot) let right = arr.filter(item => item>=pivot) return quickSort(left).concat([pivot], quickSort(right))}","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"排序","slug":"排序","permalink":"http://yc-sky.top/tags/%E6%8E%92%E5%BA%8F/"}]},{"title":"二叉树解题总结","slug":"二叉树解题总结","date":"2020-09-06T15:22:50.000Z","updated":"2020-09-06T15:40:00.029Z","comments":true,"path":"2020/09/06/二叉树解题总结/","link":"","permalink":"http://yc-sky.top/2020/09/06/%E4%BA%8C%E5%8F%89%E6%A0%91%E8%A7%A3%E9%A2%98%E6%80%BB%E7%BB%93/","excerpt":"二叉树是实现递归的经典数据结构,关于二叉树的解题套路请看下文","text":"二叉树是实现递归的经典数据结构,关于二叉树的解题套路请看下文 二叉树算法的设计的总路线:明确一个节点要做的事情,然后剩下的事抛给框架。 123456void traverse(TreeNode root) { // root 需要做什么?在这做。 // 其他的不用 root 操心,抛给框架 traverse(root.left); traverse(root.right);} 1. 如何把二叉树所有的节点中的值加一? 1234567void plusOne(TreeNode root) { if (root == null) return; root.val += 1; plusOne(root.left); plusOne(root.right);} 2. 如何判断两棵二叉树是否完全相同? 123456789101112boolean isSameTree(TreeNode root1, TreeNode root2) { // 都为空的话,显然相同 if (root1 == null && root2 == null) return true; // 一个为空,一个非空,显然不同 if (root1 == null || root2 == null) return false; // 两个都非空,但 val 不一样也不行 if (root1.val != root2.val) return false; // root1 和 root2 该比的都比完了 return isSameTree(root1.left, root2.left) && isSameTree(root1.right, root2.right);} 3. 零、判断 BST 的合法性这里是有坑的哦,我们按照刚才的思路,每个节点自己要做的事不就是比较自己和左右孩子吗?看起来应该这样写代码: 12345678boolean isValidBST(TreeNode root) { if (root == null) return true; if (root.left != null && root.val <= root.left.val) return false; if (root.right != null && root.val >= root.right.val) return false; return isValidBST(root.left) && isValidBST(root.right);} 但是这个算法出现了错误,BST 的每个节点应该要小于右边子树的所有节点,下面这个二叉树显然不是 BST,但是我们的算法会把它判定为 BST。我们重新看一下 BST 的定义,root 需要做的不只是和左右子节点比较,而是要整个左子树和右子树所有节点比较。这种情况,我们可以使用辅助函数,增加函数参数列表,在参数中携带额外信息,请看正确的代码: 1234567891011boolean isValidBST(TreeNode root) { return isValidBST(root, null, null);}boolean isValidBST(TreeNode root, TreeNode min, TreeNode max) { if (root == null) return true; if (min != null && root.val <= min.val) return false; if (max != null && root.val >= max.val) return false; return isValidBST(root.left, min, root) && isValidBST(root.right, root, max);} 4. 在 BST 中查找一个数是否存在 12345678void BST(TreeNode root, int target) { if (root.val == target) // 找到目标,做点什么 if (root.val < target) BST(root.right, target); if (root.val > target) BST(root.left, target);} 5. 在 BST 中插入一个数对数据结构的操作无非遍历 + 访问,遍历就是“找”,访问就是“改”。具体到这个问题,插入一个数,就是先找到插入位置,然后进行插入操作。上一个问题,我们总结了 BST 中的遍历框架,就是“找”的问题。直接套框架,加上“改”的操作即可。一旦涉及“改”,函数就要返回 TreeNode 类型,并且对递归调用的返回值进行接收。 1234567891011TreeNode insertIntoBST(TreeNode root, int val) { // 找到空位置插入新节点 if (root == null) return new TreeNode(val); // if (root.val == val) // BST 中一般不会插入已存在元素 if (root.val < val) root.right = insertIntoBST(root.right, val); if (root.val > val) root.left = insertIntoBST(root.left, val); return root;} 6. 在 BST 中删除一个数这个问题稍微复杂,不过你有框架指导,难不住你。跟插入操作类似,先“找”再“改”,先把框架写出来再说: 12345678910TreeNode deleteNode(TreeNode root, int key) { if (root.val == key) { // 找到啦,进行删除 } else if (root.val > key) { root.left = deleteNode(root.left, key); } else if (root.val < key) { root.right = deleteNode(root.right, key); } return root;} 找到目标节点了,比方说是节点 A,如何删除这个节点,这是难点。因为删除节点的同时不能破坏 BST 的性质。有三种情况,用图片来说明。 情况 1:A 恰好是末端节点,两个子节点都为空,那么它可以当场去世了。12if (root.left == null && root.right == null) return null; 情况 2:A 只有一个非空子节点,那么它要让这个孩子接替自己的位置。123// 排除了情况 1 之后if (root.left == null) return root.right;if (root.right == null) return root.left; 情况 3:A 有两个子节点,麻烦了,为了不破坏 BST 的性质,A 必须找到左子树中最大的那个节点,或者右子树中最小的那个节点来接替自己。我们以第二种方式讲解。12345678if (root.left != null && root.right != null) { // 找到右子树的最小节点 TreeNode minNode = getMin(root.right); // 把 root 改成 minNode root.val = minNode.val; // 转而去删除 minNode root.right = deleteNode(root.right, minNode.val);} 简化代码如下 1234567891011121314151617181920212223TreeNode deleteNode(TreeNode root, int key) { if (root == null) return null; if (root.val == key) { // 这两个 if 把情况 1 和 2 都正确处理了 if (root.left == null) return root.right; if (root.right == null) return root.left; // 处理情况 3 TreeNode minNode = getMin(root.right); root.val = minNode.val; root.right = deleteNode(root.right, minNode.val); } else if (root.val > key) { root.left = deleteNode(root.left, key); } else if (root.val < key) { root.right = deleteNode(root.right, key); } return root;}TreeNode getMin(TreeNode node) { // BST 最左边的就是最小的 while (node.left != null) node = node.left; return node;} 注:本文章来源自作者labuladong","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"二叉树","slug":"二叉树","permalink":"http://yc-sky.top/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"}]},{"title":"leetcode-二叉树的层次遍历","slug":"leetcode-二叉树的层次遍历","date":"2020-09-06T14:42:33.000Z","updated":"2020-09-08T16:42:43.040Z","comments":true,"path":"2020/09/06/leetcode-二叉树的层次遍历/","link":"","permalink":"http://yc-sky.top/2020/09/06/leetcode-%E4%BA%8C%E5%8F%89%E6%A0%91%E7%9A%84%E5%B1%82%E6%AC%A1%E9%81%8D%E5%8E%86/","excerpt":"给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。","text":"给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。 原题描述访问:https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ bfs首先定义一个存储结果的数组,将二叉树的每一层分级lv,遍历每个跟节点,将节点的值存入数组,遍历一层lv+1,递归左右子节点将值存入同级数组中,代码如下: 1234567891011121314var levelOrder = function(root) { if(!root || root.length === 0) return [] let res = [] let bfs = (curr, lv) => { !res[lv] && (res[lv] = []) if(curr){ res[lv].push(curr.val) if(curr.left) bfs(curr.left, lv+1) if(curr.right) bfs(curr.right, lv+1) } } bfs(root, 0) return res}; 队列将初始二叉树存入quene中,通过双层while循环存入每层级的值 外层while循环的是二叉树每层的数量 内层while目的是将每层的每项存入各层数组中 代码如下: 12345678910111213141516171819202122var levelOrder = function(root) { if(!root || root.length === 0) return [] var result = [] var quene = [root] while(quene.length){ var len = quene.length var layer = [] while(len){ var node = quene.shift() layer.push(node.val) if(node.left){ quene.push(node.left) } if(node.right){ quene.push(node.right) } len-- } result.push(layer) } return result};","categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"}],"tags":[{"name":"二叉树","slug":"二叉树","permalink":"http://yc-sky.top/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"},{"name":"队列","slug":"队列","permalink":"http://yc-sky.top/tags/%E9%98%9F%E5%88%97/"},{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"}]},{"title":"常见面试题","slug":"常见面试题","date":"2020-09-06T10:06:01.000Z","updated":"2020-09-09T09:04:26.916Z","comments":true,"path":"2020/09/06/常见面试题/","link":"","permalink":"http://yc-sky.top/2020/09/06/%E5%B8%B8%E8%A7%81%E9%9D%A2%E8%AF%95%E9%A2%98/","excerpt":"常见面试题汇总","text":"常见面试题汇总 手写一个sleep函数(可从Promise,Generator,async/await等角度实现)12345678910function sleep(ms){ return new Promise(resolve=>{ setTimeout(resolve, ms) })}~(async ()=>{ console.log(111) await sleep(2000) console.log(222)})() 已知如下数组,编写一个程序将数组扁平化去除其中重复部分数据,最终得到一个升序且不重复的数组(不使用es6的flat()方法)1234567891011121314151617181920212223242526let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10]let res = []function flat(arr){ for(let i=0;i<arr.length;i++){ if(typeof arr[i] == 'number'){ if(res.indexOf(arr[i]) == -1){ res.push(arr[i]) } }else{ flat(arr[i]) } } let pivot = res[0] let left = [],right = [] for(let i=0;i<res.length;i++){ if(res[i]>pivot){ right.push(res[i]) }else if(res[i]<pivot){ left.push(res[i]) } } console.log(left.concat(pivot, right))}flat(arr) 在不改变当前代码的情况下,使这张图片的宽度为300px 1<img src=\"1.png\" style=\"width: 480px !important\"/> css方法123<img src=\"1.png\" style=\"width: 480px !important; max-width: 300px \"/><img src=\"1.png\" style=\"width: 480px !important; transform: scale(0.65, 1);\"/><img src=\"1.png\" style=\"width: 480px !important; width: 300px !important\"/> js方法1document.getElementsByTagName('img')[0].setAttribute('style', 'width: 300px !important')","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"JS连等运算","slug":"JS连等运算","date":"2020-09-06T08:29:39.000Z","updated":"2020-09-06T08:47:53.128Z","comments":true,"path":"2020/09/06/JS连等运算/","link":"","permalink":"http://yc-sky.top/2020/09/06/JS%E8%BF%9E%E7%AD%89%E8%BF%90%E7%AE%97/","excerpt":"js连等运算知识点考查","text":"js连等运算知识点考查 输出以下代码的结果并解释为什么 12345var a = { n: 1 }var b = aa.x = a = { n: 2 } // a = { n: 2 } => { n: 2 }.x = aconsole.log(a.x) // undefinedconsole.log(b.x) // { n: 2 } 解释:这里的重点是a.x到底是谁简单来说,在赋值过程开始时,a其实是{n:1}a.x=a={n:2}其实在计算机眼中是长成这样的:{n:1}.x=a={n:2}所以,这个赋值发生了两件事//1.把”a”变成了{n:2}//2.把{n:1}的x变成了{n:2}——————–也就是说:a.x = a = {n: 2};其实被计算机执行成了{n:1}.x={n: 2};a={n: 2};所以最后的结果变成了a=={n: 2};console.log(a.x) => undefined //因为a没有x属性b=={n: 1, x: {n: 2}}console.log(b.x) => {n: 2} 连等开始之前,程序会把所有引用都保存起来 连等的过程中,这些值是不变的 等到整个连等结束了,再一起变","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"跨域常用解决方案","slug":"跨域常用解决方案","date":"2020-09-06T06:55:05.000Z","updated":"2020-09-06T08:04:47.233Z","comments":true,"path":"2020/09/06/跨域常用解决方案/","link":"","permalink":"http://yc-sky.top/2020/09/06/%E8%B7%A8%E5%9F%9F%E5%B8%B8%E7%94%A8%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88/","excerpt":"在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。","text":"在前端领域中,跨域是指浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。 什么是同源策略?同源策略是一种约定,由Netscape公司1995年引入浏览器,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。所谓同源是指”协议+域名+端口”三者相同,即便两个不同的域名指向同一个ip地址,也非同源。 同源策略限制以下几种行为: Cookie、LocalStorage 和 IndexDB 无法读取 DOM和JS对象无法获得 AJAX 请求不能发送 常见跨域解决方案1. JSONP跨域jsonp的原理就是利用script标签没有跨域限制,通过script标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。 原生JS实现12345678910111213<script> var script = document.createElement('script'); script.type = 'text/javascript'; // 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数 script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback'; document.head.appendChild(script); // 回调执行函数 function handleCallback(res) { alert(JSON.stringify(res)); } </script> 服务端返回如下(返回时即执行全局函数):1handleCallback({\"success\": true, \"user\": \"admin\"}) 后端node实现1234567891011121314151617var querystring = require('querystring');var http = require('http');var server = http.createServer();server.on('request', function(req, res) { var params = querystring.parse(req.url.split('?')[1]); var fn = params.callback; // jsonp返回设置 res.writeHead(200, { 'Content-Type': 'text/javascript' }); res.write(fn + '(' + JSON.stringify(params) + ')'); res.end();});server.listen('8080');console.log('Server is running at port 8080...'); jsonp的缺点:只能发送get一种请求。 2. 跨域资源共享(CORS)CORS是一个W3C标准,全称是”跨域资源共享”(Cross-origin resource sharing)。它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。 浏览器将CORS跨域请求分为简单请求和非简单请求。只要同时满足一下两个条件,就属于简单请求 使用下列方法之一 head get post 请求的Heder是 Accept Accept-Language Content-Language Content-Type: 只限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain 不同时满足上面的两个条件,就属于非简单请求。浏览器对这两种的处理,是不一样的。 简单请求对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。 123456GET /cors HTTP/1.1Origin: http://api.bob.comHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0... 上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。CORS请求设置的响应头字段,都以 Access-Control-开头: Access-Control-Allow-Origin:必选 Access-Control-Allow-Credentials:可选 Access-Control-Expose-Headers:可选 非简单请求非简单请求是那种对服务器有特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为”预检”请求(preflight)。预检请求预检”请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪个源。除了Origin字段,”预检”请求的头信息包括两个特殊字段。 12345678OPTIONS /cors HTTP/1.1Origin: http://api.bob.comAccess-Control-Request-Method: PUTAccess-Control-Request-Headers: X-Custom-HeaderHost: api.alice.comAccept-Language: en-USConnection: keep-aliveUser-Agent: Mozilla/5.0.. 原生JS实现1234567891011121314var xhr = new XMLHttpRequest(); // IE8/9需用window.XDomainRequest兼容// 前端设置是否带cookiexhr.withCredentials = true;xhr.open('post', 'http://www.domain2.com:8080/login', true);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.send('user=admin');xhr.onreadystatechange = function() { if (xhr.readyState == 4 && xhr.status == 200) { alert(xhr.responseText); }}; 后端node代码1234567891011121314151617181920212223242526272829var http = require('http');var server = http.createServer();var qs = require('querystring');server.on('request', function(req, res) { var postData = ''; // 数据块接收中 req.addListener('data', function(chunk) { postData += chunk; }); // 数据接收完毕 req.addListener('end', function() { postData = qs.parse(postData); // 跨域后台设置 res.writeHead(200, { 'Access-Control-Allow-Credentials': 'true', // 后端允许发送Cookie 'Access-Control-Allow-Origin': 'http://www.domain1.com', // 允许访问的域(协议+域名+端口) /* * 此处设置的cookie还是domain2的而非domain1,因为后端也不能跨域写cookie(nginx反向代理可以实现), * 但只要domain2中写入一次cookie认证,后面的跨域接口都能从domain2中获取cookie,从而实现所有的接口都能跨域访问 */ 'Set-Cookie': 'l=a123456;Path=/;Domain=www.domain2.com;HttpOnly' // HttpOnly的作用是让js无法读取cookie }); res.write(JSON.stringify(postData)); res.end(); });});server.listen('8080');console.log('Server is running at port 8080...'); 3. nginx代理跨域nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin…等字段。 nginx配置解决iconfont跨域浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。123location / { add_header Access-Control-Allow-Origin *;} nginx反向代理接口跨域跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不需要同源策略,也就不存在跨域问题。 4. nodejs中间件代理跨域node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。vue框架的跨域node + vue + webpack + webpack-dev-server搭建的项目,跨域请求接口,直接修改webpack.config.js配置。开发环境下,vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域。webpack.config.js部分配置: 12345678910111213141516module.exports = { entry: {}, module: {}, ... devServer: { historyApiFallback: true, proxy: [{ context: '/login', target: 'http://www.domain2.com:8080', // 代理跨域目标接口 changeOrigin: true, secure: false, // 当代理某些https服务报错时用 cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改 }], noInfo: true }} 声明:以上内容转自https://segmentfault.com/a/1190000011145364","categories":[],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"回流和重绘","slug":"回流和重绘","date":"2020-09-05T15:03:53.000Z","updated":"2020-09-05T15:16:30.112Z","comments":true,"path":"2020/09/05/回流和重绘/","link":"","permalink":"http://yc-sky.top/2020/09/05/%E5%9B%9E%E6%B5%81%E5%92%8C%E9%87%8D%E7%BB%98/","excerpt":"网页的回流和重绘知识点如下:","text":"网页的回流和重绘知识点如下: 回流回流又称之为重排,当Render Tree中的一部分(或者全部)因元素的规模,尺寸,布局等改变,而需要重新构建页面,就会触发回流具体总结为: 页面初始渲染 添加、删除可见的DOM元素 改变元素位置,尺寸,内容触发回流的属性: 盒子模型相关属性:width、height、display、border、border-width… 定位及浮动:position、left、right、top、bottom、float、padding、margin… 文字相关:text-align、overflow、font-weight、font-family、line-height,vertical-align、font-size、white-space… 重绘当Render Tree中的一些元素需要更新属性,而这些属性只是影响到元素的外观,风格而不影响布局,就会触发重绘回流一定重绘,但是重绘不一定回流触发重绘的属性: color、border-style、border-radius、outline、visibility、background-color、text-decoration、background、background-image、box-shadow… 如何减少回流和重绘 用translate代替top 用opacity代替visibility 预先定义好className,然后统一修改Dom的className 不要把Dom结点的属性值放在一个循环里面变成循环变量 让要操作的元素进行“离线处理”,处理完后一起更新","categories":[{"name":"CSS","slug":"CSS","permalink":"http://yc-sky.top/categories/CSS/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"undefined和null","slug":"undefined和null","date":"2020-09-04T18:49:41.000Z","updated":"2020-09-05T15:18:00.076Z","comments":true,"path":"2020/09/05/undefined和null/","link":"","permalink":"http://yc-sky.top/2020/09/05/undefined%E5%92%8Cnull/","excerpt":"undefined和null出现场景总结如下:","text":"undefined和null出现场景总结如下: undefined 变量提升:只声明,未定义,其默认值为undefined 严格模式下,没有明确的执行主体,this的值为undefined 对象没有这个属性名,属性值为undefined 对象没有这个属性名,typeof obj[item]值为字符串’undefined’ 函数定义形参不传值,默认值为undefined 函数没有返回值(没有return语句或者return;) … null 手动设置变量的值或者对象某一属性的值为null(后面再赋值) 在JS的DOM元素获取中,如果没有获取到指定的元素对象,结果一般为null Object.prototype._proto_的值为null 正则捕获的时候,如果没有捕获到结果,默认值是null …","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"如何实现数组去重","slug":"如何实现数组去重","date":"2020-09-04T18:12:58.000Z","updated":"2020-09-05T15:17:43.232Z","comments":true,"path":"2020/09/05/如何实现数组去重/","link":"","permalink":"http://yc-sky.top/2020/09/05/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E6%95%B0%E7%BB%84%E5%8E%BB%E9%87%8D/","excerpt":"实现数组去重有多种方式实现,具体可见如下代码:","text":"实现数组去重有多种方式实现,具体可见如下代码: 循环迭代+map123456789101112131415161718~(function(){ let pro = Array.prototype pro.myDistinct = function(){ let obj = {} for(let i=0;i<this.length;i++){ let item = this[i] if(typeof obj[item] !== 'undefined'){ // 括号中也可写做 obj[item] !== undefined this[i] = this[this.length-1] this.length-- i-- continue } obj[item] = item } obj = null return this }})() ES6的Array.filter()12345678~(function(){ let pro = Array.prototype pro.myDistinct = function(){ return this.filter((item, index) => { return this.indexOf(item) === index }) }})() ES6的Set()(高性能)set结构成员的值都是唯一的,可以接受数组作为参数1234567~(function(){ let pro = Array.prototype pro.myDistinct = function(){ var set = new Set(this) return [...set] }})()","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"多个按钮单击事件","slug":"多个按钮单击事件","date":"2020-09-04T17:34:01.000Z","updated":"2020-09-05T15:17:28.450Z","comments":true,"path":"2020/09/05/多个按钮单击事件/","link":"","permalink":"http://yc-sky.top/2020/09/05/%E5%A4%9A%E4%B8%AA%E6%8C%89%E9%92%AE%E5%8D%95%E5%87%BB%E4%BA%8B%E4%BB%B6/","excerpt":"关于多个按钮单击弹出结果面试题考察分析如下:","text":"关于多个按钮单击弹出结果面试题考察分析如下: 题目部分代码片段如下 12345678var btnBox = document.getElementById('btnBox')var inputs = btnBox.getElementsByTagName('input')var l = inputs.lengthfor(var i=0;i<l;i++){ // 点击时候循环已结束, i = 5 inputs[i].click = function(){ alert(i) }} 以上题目涉及到如下知识 所有的事件绑定都是异步编程(绑定的时候方法并没有执行),当点击触发的时候,循环早已经结束 同步:Js中当前任务没有完成,之后的任务不会执行 异步:Js中当前任务没有完成,可以继续执行其他任务 三种解决方案如下: 自定义属性123456for(var i=0;i<l;i++){ inputs[i].MyIndex = i inputs[i].click = function(){ alert(this..MyIndex) }} 闭包123456789101112131415for(var i=0;i<l;i++){ ~(function(i){ inputs[i].click = function(){ alert(this..MyIndex) } })(i)}// 另一种写法, 形成多个不销毁的闭包,性能不好for(var i=0;i<l;i++){ inputs[i].click = (function(i){ return function(){ alert(i) } })(i)} ES6(块级作用域)12345for(let i=0;i<l;i++){ inputs[i].click = function(){ alert(i) }}","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"原型和原型链","slug":"原型和原型链","date":"2020-09-04T14:42:39.000Z","updated":"2020-09-05T15:18:28.993Z","comments":true,"path":"2020/09/04/原型和原型链/","link":"","permalink":"http://yc-sky.top/2020/09/04/%E5%8E%9F%E5%9E%8B%E5%92%8C%E5%8E%9F%E5%9E%8B%E9%93%BE/","excerpt":"关于原型和原型链的考查部分题目如下:","text":"关于原型和原型链的考查部分题目如下: 输出代码结果123456789101112131415161718192021222324252627282930313233function Fn(){ this.x = 100 this.y = 200 this.getX = function(){ console.log(this.x) }}Fn.prototype = { // 批量扩展原型会改变Fn的constructor指向 y: 400, getX = function(){ console.log(this.x) }, getY = function(){ console.log(this.y) }, sum = function(){ console.log(this.x + this.y) }}var f1 = new Fnvar f2 = new Fnconsole.log(f1.getX === f2.getX) // 均为私有 falseconsole.log(f1.getY === f2.getY) // 公有getY true console.log(f1.__proto__.getY === Fn.prototype.getY) // trueconsole.log(f1.__proto__.getX === f2.getX) // falseconsole.log(f1.getX === Fn.prototype.getX) // falseconsole.log(f1.constructor) // Objectconsole.log(Fn.prototype.__proto__.constructor) // Objectf1.getX() // this: f1, f1.x => 100 f1.__proto__.getX() // this: f1.__proto(Fn.prototype) => Fn.prototype.x => undefinedFn.prototype.getY() // this: Fn.prototype => Fn.prototype.y = 400f1.sum() // this: f1 => f1.x+f1.y = 100 + 200 = 300Fn.prototype.sum() // this: Fn.prototype => undefined + 400 = Nan 根据题意,可画原型图如下所示:","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"闭包的考查","slug":"闭包的考查","date":"2020-09-04T12:24:04.000Z","updated":"2020-09-05T15:17:10.105Z","comments":true,"path":"2020/09/04/闭包的考查/","link":"","permalink":"http://yc-sky.top/2020/09/04/%E9%97%AD%E5%8C%85%E7%9A%84%E8%80%83%E6%9F%A5/","excerpt":"关于闭包的考查部分题目如下:","text":"关于闭包的考查部分题目如下: 首先抛出一个问题 你理解的闭包是什么?优缺点有哪些? 保护: 形成一个私有作用域,保护里面私有变量不受外界干扰 保存: 形成一个不销毁的栈内存,把所需要的变量保存起来以便后续使用 缺点: 形成不销毁的栈内存,比较耗性能 输出代码结果1234567891011121314151617181920console.log(a)var a = 12function fn(){ console.log(a) var a = 13 // 若此处去掉var,则答案选A}fn()console.log(a)/** * A、undefined, 12, 13 * B、undefined, undefined, 12 * C、undefined, undefined, 13 * D、有程序报错//** * 答案选B * 1,变量提升 * 2,顺序执行 * 3,执行fn,形成一个私有作用域=》形参赋值,变量提升,顺序执行/ 输出代码结果123456789101112131415var foo = 1function bar(){ if(!foo){ // 不管条件是否成立,都要进行变量提示 var foo = 10 } console.log(foo)}bar()/** * 答案选B 变量提升后foo= undefined,条件为true * A、1 * B、10 * C、undefined * D、报错/ 输出代码结果1234567891011121314151617181920212223// => 全局下的变量提升var n,var c, a=AAAFFFvar n = 0function a(){ // 私有作用域,形参赋值:无,变量提升:var n, b=BBBFFF var n = 10 // n=> 11 n=> 12 function b(){ // 私有作用域 n++ // n为上级作用域的 console.log(n) } b() // 输出11 return b // return BBBFFF}var c = a() // c为BBBFFF,此时a函数中的私有作用域不销毁c() //再次执行BBBFFF n=>12console.log(n) // 输出全局n, 即 0/** * 选C * A、1 1 1 * B、11 11 0 * C、11 12 0 * D、11 12 12/ 输出代码结果123456789101112// 全局下的变量提升var a, var b, var c, 声明并定义test = AAAFFFvar a = 10, b = 11, c = 12function test(a){ // 私有作用域a=10,var b a = 1 // 私有a由10变为1 var b = 2 // 私有b=2 c = 3 // 全局c由12变为3}test(10)console.log(a) // 全局a = 10console.log(b) // 全局b = 11console.log(c) // 全局c = 3 输出代码结果12345// 首先不管条件是否成立,都要进行变量提升if(!('a' in window)){ // 变量提升后window.a = undefined => 'a' in window 为true var a = 1 // a = 1 未执行}console.log(a) // undefined 输出代码结果12345678910// 全局变量提升 var a, 声明及定义b=BBBFFFvar a = 4function b(x, y, a){ // 执行b形成私有作用域x=1,y=2,a=3 console.log(a) // 私有a=3 arguments[2] = 10 // 非严格模式下,arguments和形参有映射关系 a 变为 10 console.log(a) // 私有a=10}a = b(1, 2, 3) // b函数没有返回值,此时a为undefinedconsole.log(a) // 全局a变为undefined 输出代码结果123456789101112// 全局变量提升 var a, var f, fn = AAAFFFvar a = 9function fn(){ a = 0 // 执行fn形成私有作用域,全局a变为0, return BBBFFF return function(b){ return b + a++ }}var f = fn() // 不销毁console.log(f(5)) // 5+a++ => 5+0++ => 5 // 此时全局a变为1 BBBFFF111销毁 AAAFFF不销毁console.log(fn()(5)) // fn重新开辟空间并执行,a重置为0=> fn()(5) => 5+0++ => 5 // 此时a为1 fn=AAAFFF临时不销毁,等fn()()即BBBFFF222执行完毕后再销毁console.log(f(5)) // 5+a++ => 5+1++ => 6 // 此时a为2 BBBFFF222销毁 AAAFFF不销毁 输出代码结果123456789101112// 全局变量提升var ary = AAAFFF111, var res, fn = AAAFFF222var ary = [1, 2, 3, 4]function fn(ary){ // 将ary的地址赋值给形参, 私有ary和全局ary指向同一个内存空间。没有直接关系,但是存在间接关系 ary[0] = 0 // 私有ary = [0, 2, 3, 4] =>全局ary的值也同时被改变 ary = [0] // 私有ary重新开辟空间 ary = [0],此时的ary与全局ary无任何关联 ary[0] = 100 // 私有ary重新赋值 ary = [100] return ary // 返回私有ary = [100]}var res = fn(ary)console.log(ary) // 全局ary = [0, 2, 3, 4]console.log(res) // ary = [100] 输出代码结果1234567891011function fn(i){ return function(n){ console.log(n+(--i)) }}var f = fn(2) // 不销毁f(3)//4 不销毁 i变为了1fn(4)(5)//8 重新形参赋值,开辟空间fn(6)(7)//12 重新形参赋值,开辟空间f(8)//8 不销毁 i变为了0// 此题与第7题类似 输出代码结果12345678910111213141516// 全局变量提升var num,var obj, var fnvar num = 10var obj = { num: 20 } // 开辟空间obj.fn = (function (num){ // 添加属性fn= this.num = num*3 // this: window =>windwow.num = 60 num++ // => num = 21 return function(n){ //开辟空间BBBFFF111 this.num += n num++ // 让上级作用域中num++ => 22 console.log(num) }})(obj.num)var fn = obj.fn // BBBFFF111 不销毁fn(5) // window.num = window.num+5 => window.num = 60+5 = 65obj.fn(10) // this: obj => num+=n => num = 30 // num++ => 23console.log(num, obj.num) // 65 30","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"this的考查","slug":"this的考查","date":"2020-09-04T12:00:27.000Z","updated":"2020-09-05T15:18:15.456Z","comments":true,"path":"2020/09/04/this的考查/","link":"","permalink":"http://yc-sky.top/2020/09/04/this%E7%9A%84%E8%80%83%E6%9F%A5/","excerpt":"关于this的考查部分题目如下:","text":"关于this的考查部分题目如下: 输出代码结果123456789101112var fullName = 'language'var obj = { fullName = 'javascript', prop = { getFullName = function(){ return this.fullName } }}console.log(obj.prop.getFullName()) // this为obj.prop,故输出undefinedvar test = obj.prop.getFullNameconsole.log(test()) // this为window,故输出'language' 输出代码结果123456789101112var name = 'window'var Tom = { name: 'Tom', show: function(){ console.log(this.name) }, wait: function(){ var fun = this.show fun() }}Tom.wait() // this: Tom => fun = Tom.show =>fun() => this: window => 输出window.name => 'window' 输出代码结果12345678910111213141516171819function fun(){ this.a = 0 this.b = function(){ alert(this.a) }}fun.prototype = { // 此时fun的constructor改变,指向了Object b: function(){ this.a = 20 alert(this.a) }, c: function(){ this.a = 30 alert(this.a) }}var my_fun = new fun()my_fun.b() // 私有的方法b this: my_fun => my_fun.a => '0'my_fun.c() // 公有的方法c this: my_fun => my_fun.c => '30'(把当前示例私有属性由0改为了30)","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"面经(虾皮)","slug":"面经-虾皮","date":"2020-09-04T10:42:32.000Z","updated":"2020-09-04T10:47:54.244Z","comments":true,"path":"2020/09/04/面经-虾皮/","link":"","permalink":"http://yc-sky.top/2020/09/04/%E9%9D%A2%E7%BB%8F-%E8%99%BE%E7%9A%AE/","excerpt":"以下为虾皮一面部分面试题:","text":"以下为虾皮一面部分面试题: 输出以下结果123456789101112131415function Parent(){ this.a = 'Parent';}function Tom() { this.a = 'Tom'}Parent.__proto__.print = function(){ // Parent.__proto__实际上指向Object.prototype this.a = 4 console.log(this.a)}Parent.print() // 4Tom.print() // 4var child = new Parent()console.log(child.a) // Parentchild.print() // 报错 child.print is not a function f能不能拿到a方法和b方法?12345var F = function(){};Object.prototype.a = function(){};Function.prototype.b = function(){};var f = new F();// f最终指向为Object的prototype,所有只能拿到a方法","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"面经(yz)","slug":"面经-yz","date":"2020-09-01T09:47:45.000Z","updated":"2020-09-06T10:06:48.436Z","comments":true,"path":"2020/09/01/面经-yz/","link":"","permalink":"http://yc-sky.top/2020/09/01/%E9%9D%A2%E7%BB%8F-yz/","excerpt":"yz某公司部分面试题,学习记录如下","text":"yz某公司部分面试题,学习记录如下 svg是什么? SVG 意为可缩放矢量图形(Scalable Vector Graphics)。 SVG 使用 XML 格式定义图像。 什么情况下用vuex? 多个组件间需要传递参数或状态时 较大型项目使用 vue本身的更新机制了解吗? Vue 实现响应式并不是数据发生变化之后 DOM 立即变化,而是按一定的策略进行 DOM 的更新。 简单来说,Vue 在修改数据后,视图不会立刻更新,而是等同一事件循环中的所有数据变化完成之后,再统一进行视图更新。 同步里执行的方法,每个方法里做的事情组成一个事件循环;接下来再次调用的是另一个事件循环。 nextTick:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,会获取更新后的 DOM。 12345678//改变数据vm.message = 'changed'//想要立即使用更新后的DOM。这样不行,因为设置message后DOM还没有更新console.log(vm.$el.textContent) // 并不会得到'changed'//这样可以,nextTick里面的代码会在DOM更新后执行Vue.nextTick(function(){ console.log(vm.$el.textContent) //可以得到'changed'}) computed和watch的了解? computed 本质是一个惰性求值的观察者,具有缓存性,只有当依赖变化后,第一次访问 computed 属性,才会计算新的值,而 watch 则是当数据发生变化便会调用执行函数 从使用场景上说,computed 适用一个数据被多个数据影响,而 watch 适用一个数据影响多个数据; observer和watcher的了解? Vue 响应系统,其核心有三点:observe、watcher、dep: observe:遍历 data 中的属性,使用 Object.defineProperty 的 get/set 方法对其进行数据劫持; dep:每个属性拥有自己的消息订阅器 dep,用于存放所有订阅了该属性的观察者对象; watcher:观察者(对象),通过 dep 实现对响应属性的监听,监听到结果后,主动触发自己的回调进行响应。123456// 手动注销watchconst unwatch = app.$watch('text', { console.log(val);}, { deep: false}) vue3.0有什么特性? 此链接内容可供参考 vue中的Object.defineProperty()有什么缺陷? Object.defineProperty无法监控到数组下标的变化,导致通过数组下标添加元素,不能实时响应; Object.defineProperty只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍。Proxy可以劫持整个对象,并返回一个新的对象。 Proxy不仅可以代理对象,还可以代理数组。还可以代理动态增加的属性。 var与let、const的区别 var声明变量存在变量提升,let和const不存在变量提升, window可以访问到var声明的值 let、const都是块级局部变量 同一作用域下let和const不能声明同名变量,而var可以 什么是块级作用域? JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由 {} 包括,if语句和for语句里面的{}也属于块作用域。 js中的class是怎么实现的? 此链接内容可供参考 js基础类型和引用类型 es5中基础类型包括:number,string,null,undefined,Boolean。es6新增了一种基础类型symbol,基础类型的存储是存放在栈中,原因是基础类型存储的空间很小,存放在栈(stack)中方便查找,且不易于改变 引用类型是指有多个值构成的对象,也就是对象类型比如:Object,Array,Function,Data等,js的引用数据类型是存储在堆中(heap),也就是说存储的变量处的值是一个指针(point),指向存储对象的内存地址。存在堆中的原因是:引用值的大小会改变,所以不能放在栈中,否则会降低变量查询的速度 哪些方法判断值的类型? 此链接内容可供参考 typeof 运算符 instanceof 通过Object下的toString.call()方法来判断 根据对象的contructor判断 instance of底层实现机制 只要右边变量的 prototype 在左边变量的原型链上即可。因此,instanceof 在查找的过程中会遍历左边变量的原型链,直到找到右边变量的 prototype,如果查找失败,则会返回 false 水平居中的几种方式 此链接内容可供参考 BFC(block formatting context) 此链接内容可供参考 使 BFC 内部浮动元素不会到处乱跑 和浮动元素产生边界 如何创建BFC float的值不是none。 position的值不是static或者relative。 display的值是inline-block、table-cell、flex、table-caption或者inline-flex overflow的值不是visible 触发 BFC 只要元素满足下面任一条件即可触发 BFC 特性: body 根元素 浮动元素:float 除 none 以外的值 绝对定位元素:position (absolute、fixed) display 为 inline-block、table-cells、flex overflow 除了 visible 以外的值 (hidden、auto、scroll) 流式布局 此链接内容可供参考 此链接内容可供参考 css的选择器和对应的优先级 此链接内容可供参考 !important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性 移动端自适应布局与字体大小自适应 此链接内容可供参考 vw, vh 用js去计算并设置html标签的font-size大小 em和rem的区别 rem 单位翻译为像素值是由 html 元素的字体大小决定的。 此字体大小会被浏览器中字体大小的设置影响,除非显式重写一个具体单位 em 单位转为像素值,取决于他们使用的字体大小。 此字体大小受从父元素继承过来的字体大小,除非显式重写与一个具体单位 数组遍历方法 此链接内容可供参考 post和get Get产生一个TCP数据包;Post产生两个TCP数据包。 对于GET方式的请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);对于POST,浏览器先发送header,服务器响应100(continue),然后再发送data,服务器响应200(返回数据); GET幂等,POST不幂等(幂等是指同一个请求方法执行多次和仅执行一次的效果完全相同。) 强制缓存和协商缓存 此链接内容可供参考 http头部字段有哪些? 此链接内容可供参考","categories":[],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"为什么需要node作为中间层","slug":"为什么需要node作为中间层","date":"2020-09-01T08:55:09.000Z","updated":"2020-09-05T15:05:00.285Z","comments":true,"path":"2020/09/01/为什么需要node作为中间层/","link":"","permalink":"http://yc-sky.top/2020/09/01/%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81node%E4%BD%9C%E4%B8%BA%E4%B8%AD%E9%97%B4%E5%B1%82/","excerpt":"通常我们把Web领域分为客户端和服务端,也就是前端和后端,这里的后端就包含了网关,静态资源,接口,缓存,数据库等。而中间层呢,就是在后端这里再抽离一层出来,在业务上处理和客户端衔接更紧密的部分,比如页面渲染(SSR),数据聚合,接口转发等等。以SSR来说,在服务端将页面渲染好,可以加快用户的首屏加载速度,避免请求时白屏,还有利于网站做SEO,他的好处是比较好理解的。那么对于数据的聚合,接口转发来说,这样做有什么意义呢?","text":"通常我们把Web领域分为客户端和服务端,也就是前端和后端,这里的后端就包含了网关,静态资源,接口,缓存,数据库等。而中间层呢,就是在后端这里再抽离一层出来,在业务上处理和客户端衔接更紧密的部分,比如页面渲染(SSR),数据聚合,接口转发等等。以SSR来说,在服务端将页面渲染好,可以加快用户的首屏加载速度,避免请求时白屏,还有利于网站做SEO,他的好处是比较好理解的。那么对于数据的聚合,接口转发来说,这样做有什么意义呢? 用Node的原因有以下: 代理:在开发环境下,我们可以利用代理来,解决最常见的跨域问题;在线上环境下,我们可以利用代理,转发请求到多个服务端 缓存:缓存其实是更靠近前端的需求,用户的动作触发数据的更新,node中间层可以直接处理一部分缓存需求。 限流:node中间层,可以针对接口或者路由做响应的限流。 日志:相比其他服务端语言,node中间层的日志记录,能更方便快捷的定位问题(是在浏览器端还是服务端)。 监控:擅长高并发的请求处理,做监控也是合适的选项。 鉴权:有一个中间层去鉴权,也是一种单一职责的实现。 路由:前端更需要掌握页面路由的权限和逻辑。 服务端渲染:node中间层的解决方案更灵活,比如SSR、模板直出、利用一些JS库做预渲染等等。 更多的可能性","categories":[{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"}]},{"title":"Hello World","slug":"hello-world","date":"2020-05-07T07:24:10.236Z","updated":"2020-08-22T02:50:31.872Z","comments":true,"path":"2020/05/07/hello-world/","link":"","permalink":"http://yc-sky.top/2020/05/07/hello-world/","excerpt":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.","text":"Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub. Quick StartCreate a new post1$ hexo new \"My New Post\" More info: Writing Run server1$ hexo server More info: Server Generate static files1$ hexo generate More info: Generating Deploy to remote sites1$ hexo deploy More info: Deployment","categories":[],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"http://yc-sky.top/tags/Hexo/"}]}],"categories":[{"name":"算法与数据结构","slug":"算法与数据结构","permalink":"http://yc-sky.top/categories/%E7%AE%97%E6%B3%95%E4%B8%8E%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"},{"name":"CSS","slug":"CSS","permalink":"http://yc-sky.top/categories/CSS/"},{"name":"JavaScript","slug":"JavaScript","permalink":"http://yc-sky.top/categories/JavaScript/"}],"tags":[{"name":"递归","slug":"递归","permalink":"http://yc-sky.top/tags/%E9%80%92%E5%BD%92/"},{"name":"剑指offer","slug":"剑指offer","permalink":"http://yc-sky.top/tags/%E5%89%91%E6%8C%87offer/"},{"name":"bfs","slug":"bfs","permalink":"http://yc-sky.top/tags/bfs/"},{"name":"二叉树","slug":"二叉树","permalink":"http://yc-sky.top/tags/%E4%BA%8C%E5%8F%89%E6%A0%91/"},{"name":"leetcode","slug":"leetcode","permalink":"http://yc-sky.top/tags/leetcode/"},{"name":"dfs","slug":"dfs","permalink":"http://yc-sky.top/tags/dfs/"},{"name":"回溯","slug":"回溯","permalink":"http://yc-sky.top/tags/%E5%9B%9E%E6%BA%AF/"},{"name":"面试题","slug":"面试题","permalink":"http://yc-sky.top/tags/%E9%9D%A2%E8%AF%95%E9%A2%98/"},{"name":"Map","slug":"Map","permalink":"http://yc-sky.top/tags/Map/"},{"name":"小顶堆","slug":"小顶堆","permalink":"http://yc-sky.top/tags/%E5%B0%8F%E9%A1%B6%E5%A0%86/"},{"name":"桶排序","slug":"桶排序","permalink":"http://yc-sky.top/tags/%E6%A1%B6%E6%8E%92%E5%BA%8F/"},{"name":"排序","slug":"排序","permalink":"http://yc-sky.top/tags/%E6%8E%92%E5%BA%8F/"},{"name":"队列","slug":"队列","permalink":"http://yc-sky.top/tags/%E9%98%9F%E5%88%97/"},{"name":"Hexo","slug":"Hexo","permalink":"http://yc-sky.top/tags/Hexo/"}]}