最后
为了帮助大家更好的了解前端,特别整理了《前端工程师面试手册》电子稿文件。
开源分享:docs.qq.com/doc/DSmRnRG… for(var i = 0; i< nums1.length; i++){ nums1[i] = sortArr[i]; } };
#### 三、1.两数之和

var twoSum = function (nums, target) { //使用哈希表存储已经遍历过的数据 数据->索引 const map = new Map(); map.set(nums[0], 0); for (let i = 1; i < nums.length; i++) { let need = target - nums[i]; //map.get(key) 若不存在 返回undefined 存在返回对应的值 if (map.get(need) !== undefined) { //哈希表中存在这样的值 return [map.get(need), i]; } map.set(nums[i], i);
}
}
#### 四、3.无重复字符的最长子串

/** * @param {string} s * @return {number} */ var lengthOfLongestSubstring = function (s) { //用哈希表存储不重复的的字符和对应的索引 let map = new Map(); let max = 0; //滑动窗口 i为左指针 j为右指针 for (let i = 0, j = 0; j < s.length; j++) { if (map.has(s[j])) {//重复出现 i = Math.max(map.get(s[j]) + 1, i); } max = Math.max(max, j - i + 1); map.set(s[j], j); } console.log(max); }; lengthOfLongestSubstring("abcabcdebggbsdyujf");
#### 五、53.最大子序和

/** * @param {number[]} nums * @return {number} */ var maxSubArray = function (nums) { //动态规划算法 前一个元素<0 不加 >0 加 let max = nums[0]; for (let i = 1; i < nums.length; i++) { if (nums[i - 1] > 0) { nums[i] += nums[i - 1]; } max = max > nums[i] ? max : nums[i]; } console.log(max); }; maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4]); //6
#### 六、112.路径总和



/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @param {number} targetSum * @return {boolean} */ var hasPathSum = function(root, targetSum) { //当前节点为null节点时 if(root == null) return false; //当前节点为叶子节点时 if(root.left == null && root.right == null){ return targetSum == root.val; } //否则递归左右孩子 return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); };
#### 七、113.路经总和II


/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @param {number} targetSum * @return {number[][]} */ var pathSum = function(root, targetSum) { if(root == null) return []; let res = []; let dfs = (root, total, nums) => { total += root.val; nums.push(root.val); if(root.left || root.right){ if(root.left != null){ dfs(root.left, total, nums.slice()); } if(root.right != null){ dfs(root.right, total, nums.slice()); } } else if(total == targetSum){ res.push(nums); } } dfs(root, 0, []); return res; };
#### 八、415.字符串相加

好像不能用这种方法(大数相加的思路)
/** * @param {string} num1 * @param {string} num2 * @return {string} */ var addStrings = function(num1, num2) { //把num1、num2转化为数组并反转 let arr1 = num1.split("").reverse(); let arr2 = num2.split("").reverse(); let len1 = arr1.length; let len2 = arr2.length; let max = Math.max(len1,len2); //补0 if(len1 > len2){ for(let i = 0; i < len1 - len2; i++){ arr2.push(0); } }else { for(let i = 0; i < len2 - len1; i++){ arr1.push(0); } } let res = []; //存储相加的值 //初始化 for(let i = 0; i < max; i++){ res[i] = 0; } //相加 for(let i = 0; i< max;i++){ res[i] = res[i] + parseInt(arr1[i]) + parseInt(arr2[i]); if(res[i] > 9){ res[i] = res[i] - 10; res[i+1] = 1; } } return res.reverse().join(""); };
官方写法:
var addStrings = function(num1, num2) { //i、j定义目前字符相加位置 let i = num1.length - 1; let j = num2.length - 1; //进位 let add = 0; //存储结果 const res = [];
while (i >= 0 || j >= 0 || add != 0 ) {
//定义两个相加的数 注意字符串相减隐式转换为数字 若字符已经遍历完 则补0
const x = (i >= 0) ? num1.charAt(i) - '0' : 0;
const y = (j >= 0) ? num2.charAt(j) - '0' : 0;
const result = x + y + add;
res.push(result % 10);
add = Math.floor(result / 10);
i--;
j--;
}
return res.reverse().join('');
};
#### 九、剑指offer 62.圆圈中最后剩下的数字

约瑟夫环问题:公式法(迭代)
/** * @param {number} n * @param {number} m * @return {number} */ var lastRemaining = function(n, m) { //公式法 f(n,m) = (f(n-1,m) + m) % n //开始时f(1) = 0 一个人的位置为0 let f = 0; for(let i = 2; i <= n; i++){ //迭代 f = (f + m) % i; } return f; };
暴力链表法:无hhhh
#### 十、93.复原IP地址

/** * @param {string} s * @return {string[]} */ var restoreIpAddresses = function(s) { //使用回溯算法 const res = []; //存储结果 const segment = new Array(4); //存储一个符合要求的IP地址(四段)
//寻找一个符合的IP地址
//segId表示在找哪一段(0~3)segStart为起始位置
function dfs(s, segId, segStart){
if(segId == 4){ //前面已经找了4段了
if(segStart == s.length){
res.push(segment.join('.')); //加进结果数组
}
return;
}
//如果没找到四段就已经遍历完 提前回溯
if(segStart == s.length){
return;
}
//如果为0 则0必须为单独一段
if(s.charAt(segStart) == '0'){
segment[segId] = 0;
dfs(s, segId + 1, segStart + 1);
}
//普通情况
let addr = 0;
for(let segEnd = segStart; segEnd < s.length; segEnd++){
addr = addr\*10 + (s.charAt(segEnd)-'0'); //当前值应满足0~255
if(addr > 0 && addr <= 255){
segment[segId] = addr;
dfs(s, segId + 1, segEnd + 1);
} else {
break;
}
}
}
dfs(s, 0, 0);
return res;
};
#### 十一、160.相交链表
法一:暴力解法
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */
/** * @param {ListNode} headA * @param {ListNode} headB * @return {ListNode} */ var getIntersectionNode = function(headA, headB) { //暴力解法 if(!headA || !headB) return null; let pA = headA; while(pA){ let pB = headB; while(pB){ if(pA == pB) return pA; pB = pB.next; } pA = pA.next; } };
法二:哈希表法
var getIntersectionNode = function(headA, headB) { //哈希表法 if(!headA || !headB) return null; //先遍历A链表并存入哈希表 let pA = headA; const map = new Map(); while(pA){ map.set(pA,1); pA = pA.next; } //遍历B链表 let pB = headB; while(pB){ if(map.has(pB)) return pB; pB = pB.next; } return null; };
法三:双指针
var getIntersectionNode = function(headA, headB) { //双指针法 if(!headA || !headB) return null; let pA = headA; let pB = headB; while(pA!=pB){ pA = pA === null? headB : pA.next; pB = pB === null? headA : pB.next; } return pA;
};
#### 十二、695.岛屿的最大面积

递归 对每个为1的位置算上下左右 访问过的节点置0
/** * @param {number[][]} grid * @return {number} */
var maxAreaOfIsland = function(grid) { let max = 0; let x = grid.length; let y = grid[0].length; for(let i = 0; i < x; i++){ for(let j = 0; j < y; j++){ if(grid[i][j] == 1){ max = Math.max(max, count(grid, i, j, x, y)); } } } return max; }; function count(grid, i, j, x, y){ //计算上下左右 if(i<0 || i>=x || j<0 || j>=y || grid[i][j]==0) return 0; let cnt = 1; grid[i][j] = 0; //防止重复访问节点 cnt += count(grid, i+1, j, x, y); cnt += count(grid, i-1, j, x, y); cnt += count(grid, i, j+1, x, y); cnt += count(grid, i, j-1, x, y); return cnt; };
#### 十三、15.三数之和

/** * @param {number[]} nums * @return {number[][]} */ var threeSum = function(nums) { //类似双指针 let ans = []; let len = nums.length; //第一步先排序 nums.sort(function(a, b){ return a - b; }); //遍历数组 for(let i = 0; i < len; i++){ if(nums[i] > 0) break; // nums已排序 后面的也大于零 if(i > 0 && nums[i] == nums[i-1]) continue; //去重 跳出循环 //定义左右指针位置 let l = i + 1; let r = len - 1; //处理三者之和 while(l < r){ const sum = nums[i] + nums[l] + nums[r]; if(sum == 0){ ans.push([nums[i],nums[l],nums[r]]); while(l < r && nums[l] == nums[l+1])l++; //去重 while(l < r && nums[r] == nums[r-1])r--; //去重 l++; r--; }else if(sum < 0){ l++; }else{ r--; } } } return ans; };
#### 十四、141.环形链表


我用集合(或者哈希表)来做,时间空间O(N)做法
/** * @param {ListNode} head * @return {boolean} */ var hasCycle = function(head) { let now = head; const hashSet = new Set(); //let map = new Map(); //遍历 while(now){ if(hashSet.has(now)) return true; //map.has(now) hashSet.add(now); //map.set(now) now = now.next; } return false; };
快慢指针法:时间O(N)空间O(1)做法
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */
/** * @param {ListNode} head * @return {boolean} */ var hasCycle = function(head) { //快慢指针 let fast = head; let slow = head; while(fast){ if(fast.next == null) return false; //因为fast是跳两次 所以这里要判断一下 slow = slow.next; fast = fast.next.next; if(slow == fast) return true; } return false; };
#### 十五、142.环形链表II

注意这里找的是具体的节点,不仅仅是判断,时间空间和上题一样
哈希表做法:
/** * Definition for singly-linked list. * function ListNode(val) { * this.val = val; * this.next = null; * } */
/** * @param {ListNode} head * @return {ListNode} */ var detectCycle = function(head) { //哈希表做法 let map = new Map(); let curr = head; while(curr){ if(map.has(curr)) return curr; map.set(curr,true); curr = curr.next; } return null; };
快慢指针做法:
var detectCycle = function(head) { //快慢指针做法 let fast = head; let slow = head; while(fast){ if(fast.next == null) return null; fast = fast.next.next; slow = slow.next; if(fast == slow){ //快慢指针相遇 表明存在环 //定义相遇的位置 和 头节点的位置 两者一步一步移动总会相遇 let index1 = fast; let index2 = head; while(index1 != index2){ index1 = index1.next; index2 = index2.next; } return index1; } } return null; };
#### 十六、206.反转链表

/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} head * @return {ListNode} */ var reverseList = function(head) { //创建一个空节点 let nullhead = null; //定义prev、now、 next指针 let prev = nullhead; let now = head; while(now != null){ let next = now.next; now.next = prev; prev = now; now = next; } return prev; };
#### 十七、215.数组中的第k个最大元素
法一:暴力法:O(nlogn)
var findKthLargest = function(nums, k) { //暴力法 nums.sort(function(a,b){ return a - b; }) return nums[nums.length - k]; };
法二:建立做大堆并删除k-1个,最后取堆顶元素
建堆:O(n)删除O(k-1 logn) 总体O(nlogn)
/** * @param {number[]} nums * @param {number} k * @return {number} */ var findKthLargest = function(nums, k) { //最大堆法 //1.获取堆的大小 let heapSize = nums.length; //建立最大堆 buildHeap(nums, heapSize); //删除堆顶k-1个元素 for(let i = nums.length - 1; i > nums.length - k ; i--){ swap(nums, 0, i); //根元素和最后一个元素交换 heapSize--; //堆的大小减一 siftDown(nums, 0, heapSize); } //堆顶元素为所求 return nums[0]; }; function buildHeap(nums, heapSize){ //对根元素进行siftDown操作 //吐了 忘了js在这里直接除会有浮点数 向下取整 for(let i = Math.floor(heapSize/2) - 1; i >= 0; i--){ siftDown(nums, i, heapSize); } } function siftDown(nums, i, heapSize){ let largest = i; let left = 2*i + 1; let right = 2*i + 2; if(left < heapSize && nums[largest] < nums[left]) largest = left; if(right < heapSize && nums[largest] < nums[right]) largest = right; if(largest != i){ swap(nums, largest, i); siftDown(nums, largest, heapSize); } } function swap(nums, i, j){ let temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; }
法三:快速选择 O(n)
/** * @param {number[]} nums * @param {number} k * @return {number} */ var findKthLargest = function(nums, k) { //快速选择法 return quickSelect(nums, 0, nums.length - 1, nums.length - k); }; //返回index对应的值 function quickSelect(nums, l, r, index){ //取得一次划分的轴值下标 let q = partition(nums, l, r); //比较轴值下标和目标下标 如果相等 返回对应的值 if(q === index) { return nums[q]; }else { return q > index ? quickSelect(nums, l, q - 1, index) : quickSelect(nums, q + 1, r, index); } } //返回一次划分的轴值下标 function partition(nums, l, r){ //选择最左值为轴值 let pivot = nums[l]; //把轴值和放到最右 swap(nums, l, r); //i记录小于轴值的部分下标 j计数遍历 let i = l; for(let j = l; j < r; j++){ if(nums[j] < pivot){ swap(nums, i, j); i++; } } //交换轴值和第一个大于轴值的数 swap(nums, i, r); return i; } function swap(nums,i,j){ let temp = nums[i]; nums[i] = nums[j]; nums[j] = temp; }
#### 十八、230.二叉搜索树中第K小的元素


BST中序遍历可以得到升序结果
法一:递归做法 O(k)
/** * Definition for a binary tree node. * function TreeNode(val, left, right) { * this.val = (val===undefined ? 0 : val) * this.left = (left===undefined ? null : left) * this.right = (right===undefined ? null : right) * } */ /** * @param {TreeNode} root * @param {number} k * @return {number} */ var kthSmallest = function(root, k) { //中序遍历 递归 let ans = null; let inorder = function(node){ if(node != null && k != 0){ //只有当node不为空,才有k-- //左子树 inorder(node.left); //根(本身) k--; if(k == 0){ res = node.val; return; } //右子树 inorder(node.right); } }; inorder(root); return res; };
法二:迭代(难理解)O(H + K)H为树高
var kthSmallest = function(root, k) { //中序遍历 迭代 let stack = []; let node = root;
while(node || stack.length){
//遍历左子树 先存入栈
while(node){
stack.push(node);
node = node.left;
}
// 然后弹出
node = stack.pop();
//此时可以开始计数
k--;
if(k === 0){
return node.val;
}
node = node.right;
}
return null;
};
#### 十九、121.买卖股票的最佳时机

时间:O(n) 空间:O(1)
/** * @param {number[]} prices * @return {number} */ var maxProfit = function(prices) { //min存储已遍历的最小值 max存储当前最大利润 let min = prices[0], max = 0; for(let i = 1; i < prices.length; i++){ min=Math.min(min, prices[i-1]); max = Math.max(max, prices[i] - min); } return max; };
#### 二十、146.LRU缓存机制


这种方法直接利用了map的可以删除最久的的元素的性质
* @param {number} key * @return {number} */ LRUCache.prototype.get = function(key) { let map = this.map; if(!map.has(key)) { return -1; } else { //保存键对应的value const temp = map.get(key); //删除这个映射 map.delete(key); //再重新加入 map.set(key, temp); return temp; }
};
/** * @param {number} key * @param {number} value * @return {void} */ LRUCache.prototype.put = function(key, value) { let map = this.map; if(map.has(key)){ //存在 先删除原来的再加 map.delete(key); map.set(key,value); }else if(map.size == this.capacity) { // 满了 map.delete(map.keys().next().value); map.set(key,value); }else { //直接加 map.set(key,value); } };
/** * Your LRUCache object will be instantiated and called as such: * var obj = new LRUCache(capacity) * var param_1 = obj.get(key) * obj.put(key,value) */
更通用的做法:(我感觉还是要做一下,但是我现在不想做)
删除O(1)-> 双向链表 自定义数据结构来做
查找:O(1)-> 哈希表
#### 二十一、21.合并两个有序链表


法一:递归做法
/** * Definition for singly-linked list. * function ListNode(val, next) { * this.val = (val===undefined ? 0 : val) * this.next = (next===undefined ? null : next) * } */ /** * @param {ListNode} l1 * @param {ListNode} l2 * @return {ListNode} */ var mergeTwoLists = function(l1, l2) { //递归做法 if(l1 == null) { return l2; } else if(l2 == null) { return l1; } else if(l1.val < l2.val) { l1.next = mergeTwoLists(l1.next, l2); return l1; } else { l2.next = mergeTwoLists(l1, l2.next); return l2; } };
法二:迭代做法
var mergeTwoLists = function(l1, l2) { //迭代做法 //先让头节点指向一个-1的结点 const prehead = new ListNode(-1); //定义prev为未定义next的节点 let prev = prehead; //进行迭代 while(l1 != null && l2 != null) { if(l1.val <= l2.val) { prev.next = l1; l1 = l1.next; } else { prev.next = l2; l2 = l2.next; } prev = prev.next; } //还有谁没有被检测完 prev.next = l1 === null? l2:l1; return prehead.next; };
#### 二十二、剑指offer 10-I 斐波那契数列

/** * @param {number} n * @return {number} */ var fib = function(n) { let arr = new Array(n); arr[0] = 0, arr[1] = 1; for(let i = 2; i <= n; i++){ arr[i] = (arr[i-1] + arr[i-2]) % 1000000007; } return arr[n]; };
#### 二十三、609.在系统中查找重复文件(不会)

/** * @param {string[]} paths * @return {string[][]} */ var findDuplicate = function(paths) { let map = new Map(); let ans = []; for(let p of paths) { //注意for in是遍历index for of是遍历值 let pArr = p.split(' '); //用空格划分 pArr=["root/a","1.txt(abcd)","2.txt(edgh)"] let pHead = pArr[0]; //"root/a" for(let i = 1; i < pArr.length; i++){ //从1开始 let curS = pArr[i]; //"1.txt(abcd)" let s = curS.indexOf('('); let e = curS.indexOf(')'); let content = curS.slice(s+1, e); //abcd let path = pHead + '/' + curS.slice(0, s);//"root/a/1.txt"
if(map.has(content)){ //如果已经有了
map.get(content).push(path);
} else {
map.set(content, [path]); //存在数组里
}
}
}
for(let e of map.values()){
if(e.length > 1) { //输出重复的
ans.push(e);
}
}
return ans;
};
#### 二十四、232.用栈实现队列


/** * Initialize your data structure here. */ var MyQueue = function() { this.stack1 = []; //存放所有数据 this.stack2 = []; //过渡栈 };
/** * Push element x to the back of queue. * @param {number} x * @return {void} */ MyQueue.prototype.push = function(x) { let cur = null; while(cur = this.stack1.pop()){ this.stack2.push(cur); } this.stack2.push(x); while(cur = this.stack2.pop()){ this.stack1.push(cur); } };
/** * Removes the element from in front of queue and returns that element. * @return {number} */ MyQueue.prototype.pop = function() { return this.stack1.pop(); };
/** * Get the front element. * @return {number} */ MyQueue.prototype.peek = function() { return this.stack1[this.stack1.length-1]; };
/** * Returns whether the queue is empty. * @return {boolean} */ MyQueue.prototype.empty = function() { return this.stack1.length === 0; };
/** * Your MyQueue object will be instantiated and called as such: * var obj = new MyQueue() * obj.push(x) * var param_2 = obj.pop() * var param_3 = obj.peek() * var param_4 = obj.empty() */
#### 二十五、165.比较版本号


/** * @param {string} version1 * @param {string} version2 * @return {number} */ var compareVersion = function(version1, version2) { //把字符串按.分成数组 let arr1 = version1.split('.'); let arr2 = version2.split('.'); //把数组中的字符转换成数字型 arr1 = arr1.map((item) => Number(item)); arr2 = arr2.map((item) => Number(item));
//依次比较这两个数组里面的值
const len = Math.max(arr1.length, arr2.length);
//补0
while(arr1.length < len){
arr1.push(0);
Vue 编码基础
2.1.1. 组件规范
2.1.2. 模板中使用简单的表达式
2.1.3 指令都使用缩写形式
2.1.4 标签顺序保持一致
2.1.5 必须为 v-for 设置键值 key
2.1.6 v-show 与 v-if 选择
2.1.7 script 标签内部结构顺序
2.1.8 Vue Router 规范
Vue 项目目录规范
2.2.1 基础
2.2.2 使用 Vue-cli 脚手架
2.2.3 目录说明
2.2.4注释说明
2.2.5 其他
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】