虽然很多人都觉得前端算法弱,但其实 JavaScript 也可以刷题啊!最近两个月断断续续刷完了 leetcode 前 200 的 middle + hard ,总结了一些刷题常用的模板代码。走过路过发现 bug 请指出,拯救一个辣鸡(但很帅)的少年就靠您啦!

常用函数

包括打印函数和一些数学函数。

const _max = Math.max.bind(Math);
const _min = Math.min.bind(Math);
const _pow = Math.pow.bind(Math);
const _floor = Math.floor.bind(Math);
const _round = Math.round.bind(Math);
const _ceil = Math.ceil.bind(Math);
const log = console.log.bind(console);
//const log = _ => {}

log 在提交的代码中当然是用不到的,不过在调试时十分有用。但是当代码里面加了很多 log 的时候,提交时还需要一个个注释掉就相当麻烦了,只要将 log 赋值为空函数就可以了。

举一个简单的例子,下面的代码是可以直接提交的。

// 计算 1+2+...+n
// const log = console.log.bind(console);
const log = _ => {} function sumOneToN(n) {
let sum = 0;
for (let i = 1; i <= n; i++) {
sum += i;
log(`i=${i}: sum=${sum}`);
}
return sum;
} sumOneToN(10);

位运算的一些小技巧

判断一个整数 x 的奇偶性: x & 1 = 1  (奇数) ,  x & 1 = 0  (偶数)

求一个浮点数 x 的整数部分: ~~x ,对于正数相当于  floor(x)  对于负数相当于  ceil(-x)

计算  2 ^ n : 1 << n  相当于  pow(2, n)

计算一个数 x 除以 2 的 n 倍: x >> n  相当于  ~~(x / pow(2, n))

判断一个数 x 是 2 的整数幂(即  x = 2 ^ n ):  x & (x - 1) = 0

※注意※:上面的位运算只对32位带符号的整数有效,如果使用的话,一定要注意数!据!范!围!

记住这些技巧的作用:

提升运行速度 ❌

提升逼格 ✅

举一个实用的例子,快速幂(原理自行google)

// 计算x^n n为整数
function qPow(x, n) {
let result = 1;
while (n) {
if (n & 1) result *= x; // 同 if(n%2)
x = x * x;
n >>= 1; // 同 n=floor(n/2)
}
return result;
}

链表

刚开始做 LeetCode 的题就遇到了很多链表的题。恶心心。最麻烦的不是写题,是调试啊!!于是总结了一些链表的辅助函数。

/**
* 链表节点
* @param {*} val
* @param {ListNode} next
*/
function ListNode(val, next = null) {
this.val = val;
this.next = next;
}
/**
* 将一个数组转为链表
* @param {array} a
* @return {ListNode}
*/
const getListFromArray = (a) => {
let dummy = new ListNode()
let pre = dummy;
a.forEach(x => pre = pre.next = new ListNode(x));
return dummy.next;
}
/**
* 将一个链表转为数组
* @param {ListNode} node
* @return {array}
*/
const getArrayFromList = (node) => {
let a = [];
while (node) {
a.push(node.val);
node = node.next;
}
return a;
}
/**
* 打印一个链表
* @param {ListNode} node
*/
const logList = (node) => {
let str = 'list: ';
while (node) {
str += node.val + '->';
node = node.next;
}
str += 'end';
log(str);
}

还有一个常用小技巧,每次写链表的操作,都要注意判断表头,如果创建一个空表头来进行操作会方便很多。

let dummy = new ListNode();
// 返回
return dummy.next;

使用起来超爽哒~举个例子。@leetcode 82。题意就是删除链表中连续相同值的节点。

/*
* @lc app=leetcode id=82 lang=javascript
*
* [82] Remove Duplicates from Sorted List II
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var deleteDuplicates = function(head) {
// 空指针或者只有一个节点不需要处理
if (head === null || head.next === null) return head; let dummy = new ListNode();
let oldLinkCurrent = head;
let newLinkCurrent = dummy; while (oldLinkCurrent) {
let next = oldLinkCurrent.next;
// 如果当前节点和下一个节点的值相同 就要一直向前直到出现不同的值
if (next && oldLinkCurrent.val === next.val) {
while (next && oldLinkCurrent.val === next.val) {
next = next.next;
}
oldLinkCurrent = next;
} else {
newLinkCurrent = newLinkCurrent.next = oldLinkCurrent;
oldLinkCurrent = oldLinkCurrent.next;
}
}
newLinkCurrent.next = null; // 记得结尾置空~
logList(dummy.next);
return dummy.next;
}; deleteDuplicates(getListFromArray([1,2,3,3,4,4,5]));
deleteDuplicates(getListFromArray([1,1,2,2,3,3,4,4,5]));
deleteDuplicates(getListFromArray([1,1]));
deleteDuplicates(getListFromArray([1,2,2,3,3]));

本地运行结果

list: 1->2->5->end
list: 5->end
list: end
list: 1->end

是不是很方便!

矩阵(二维数组)

矩阵的题目也有很多,基本每一个需要用到二维数组的题,都涉及到初始化,求行数列数,遍历的代码。于是简单提取出来几个函数。

/**
* 初始化一个二维数组
* @param {number} r 行数
* @param {number} c 列数
* @param {*} init 初始值
*/
const initMatrix = (r, c, init = 0) => new Array(r).fill().map(_ => new Array(c).fill(init));
/**
* 获取一个二维数组的行数和列数
* @param {any[][]} matrix
* @return [row, col]
*/
const getMatrixRowAndCol = (matrix) => matrix.length === 0 ? [0, 0] : [matrix.length, matrix[0].length];
/**
* 遍历一个二维数组
* @param {any[][]} matrix
* @param {Function} func
*/
const matrixFor = (matrix, func) => {
matrix.forEach((row, i) => {
row.forEach((item, j) => {
func(item, i, j, row, matrix);
});
})
}
/**
* 获取矩阵第index个元素 从0开始
* @param {any[][]} matrix
* @param {number} index
*/
function getMatrix(matrix, index) {
let col = matrix[0].length;
let i = ~~(index / col);
let j = index - i * col;
return matrix[i][j];
}
/**
* 设置矩阵第index个元素 从0开始
* @param {any[][]} matrix
* @param {number} index
*/
function setMatrix(matrix, index, value) {
let col = matrix[0].length;
let i = ~~(index / col);
let j = index - i * col;
return matrix[i][j] = value;
}

找一个简单的矩阵的题示范一下用法。@leetcode 566。题意就是将一个矩阵重新排列为r行c列。

/*
* @lc app=leetcode id=566 lang=javascript
*
* [566] Reshape the Matrix
*/
/**
* @param {number[][]} nums
* @param {number} r
* @param {number} c
* @return {number[][]}
*/
var matrixReshape = function(nums, r, c) {
// 将一个矩阵重新排列为r行c列
// 首先获取原来的行数和列数
let [r1, c1] = getMatrixRowAndCol(nums);
log(r1, c1);
// 不合法的话就返回原矩阵
if (!r1 || r1 * c1 !== r * c) return nums;
// 初始化新矩阵
let matrix = initMatrix(r, c);
// 遍历原矩阵生成新矩阵
matrixFor(nums, (val, i, j) => {
let index = i * c1 + j; // 计算是第几个元素
log(index);
setMatrix(matrix, index, val); // 在新矩阵的对应位置赋值
});
return matrix;
}; let x = matrixReshape([[1],[2],[3],[4]], 2, 2);
log(x)

二叉树

当我做到二叉树相关的题目,我发现,我错怪链表了,呜呜呜这个更恶心。

当然对于二叉树,只要你掌握先序遍历,后序遍历,中序遍历,层序遍历,递归以及非递归版,先序中序求二叉树,先序后序求二叉树,基本就能AC大部分二叉树的题目了(我瞎说的)。

二叉树的题目 input 一般都是层序遍历的数组,所以写了层序遍历数组和二叉树的转换,方便调试。

function TreeNode(val, left = null, right = null) {
this.val = val;
this.left = left;
this.right = right;
}
/**
* 通过一个层次遍历的数组生成一棵二叉树
* @param {any[]} array
* @return {TreeNode}
*/
function getTreeFromLayerOrderArray(array) {
let n = array.length;
if (!n) return null;
let index = 0;
let root = new TreeNode(array[index++]);
let queue = [root];
while(index < n) {
let top = queue.shift();
let v = array[index++];
top.left = v == null ? null : new TreeNode(v);
if (index < n) {
let v = array[index++];
top.right = v == null ? null : new TreeNode(v);
}
if (top.left) queue.push(top.left);
if (top.right) queue.push(top.right);
}
return root;
}
/**
* 层序遍历一棵二叉树 生成一个数组
* @param {TreeNode} root
* @return {any[]}
*/
function getLayerOrderArrayFromTree(root) {
let res = [];
let que = [root];
while (que.length) {
let len = que.length;
for (let i = 0; i < len; i++) {
let cur = que.shift();
if (cur) {
res.push(cur.val);
que.push(cur.left, cur.right);
} else {
res.push(null);
}
}
}
while (res.length > 1 && res[res.length - 1] == null) res.pop(); // 删掉结尾的 null
return res;
}

一个例子,@leetcode 110,判断一棵二叉树是不是平衡二叉树。

/**
* @param {TreeNode} root
* @return {boolean}
*/
var isBalanced = function(root) {
if (!root) return true; // 认为空指针也是平衡树吧 // 获取一个二叉树的深度
const d = (root) => {
if (!root) return 0;
return _max(d(root.left), d(root.right)) + 1;
} let leftDepth = d(root.left);
let rightDepth = d(root.right); // 深度差不超过 1 且子树都是平衡树
if (_min(leftDepth, rightDepth) + 1 >= _max(leftDepth, rightDepth)
&& isBalanced(root.left) && isBalanced(root.right)) return true; return false;
}; log(isBalanced(getTreeFromLayerOrderArray([3,9,20,null,null,15,7])));
log(isBalanced(getTreeFromLayerOrderArray([1,2,2,3,3,null,null,4,4])));

二分查找

参考 C++ STL 中的 lower_bound  和 upper_bound 。这两个函数真的很好用的!

/**
* 寻找>=target的最小下标
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
function lower_bound(nums, target) {
let first = 0;
let len = nums.length; while (len > 0) {
let half = len >> 1;
let middle = first + half;
if (nums[middle] < target) {
first = middle + 1;
len = len - half - 1;
} else {
len = half;
}
}
return first;
} /**
* 寻找>target的最小下标
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
function upper_bound(nums, target) {
let first = 0;
let len = nums.length; while (len > 0) {
let half = len >> 1;
let middle = first + half;
if (nums[middle] > target) {
len = half;
} else {
first = middle + 1;
len = len - half - 1;
}
}
return first;
}

照例,举个例子,@leetcode 34。题意是给一个排好序的数组和一个目标数字,求数组中等于目标数字的元素最小下标和最大下标。不存在就返回 -1。

/*
* @lc app=leetcode id=34 lang=javascript
*
* [34] Find First and Last Position of Element in Sorted Array
*/
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
let lower = lower_bound(nums, target);
let upper = upper_bound(nums, target);
let size = nums.length;
// 不存在返回 [-1, -1]
if (lower >= size || nums[lower] !== target) return [-1, -1];
return [lower, upper - 1];
};

在 VS Code 中刷 LeetCode

前面说的那些模板,难道每一次打开新的一道题都要复制一遍么?当然不用啦。

首先配置代码片段 选择 Code -> Preferences -> User Snippets ,然后选择 JavaScript

然后把文件替换为下面的代码:

{
"leetcode template": {
"prefix": "@lc",
"body": [
"const _max = Math.max.bind(Math);","const _min = Math.min.bind(Math);","const _pow = Math.pow.bind(Math);","const _floor = Math.floor.bind(Math);","const _round = Math.round.bind(Math);","const _ceil = Math.ceil.bind(Math);","const log = console.log.bind(console);","// const log = _ => {}","/**************** 链表 ****************/","/**"," * 链表节点"," * @param {*} val"," * @param {ListNode} next"," */","function ListNode(val, next = null) {"," this.val = val;"," this.next = next;","}","/**"," * 将一个数组转为链表"," * @param {array} array"," * @return {ListNode}"," */","const getListFromArray = (array) => {"," let dummy = new ListNode()"," let pre = dummy;"," array.forEach(x => pre = pre.next = new ListNode(x));"," return dummy.next;","}","/**"," * 将一个链表转为数组"," * @param {ListNode} list"," * @return {array}"," */","const getArrayFromList = (list) => {"," let a = [];"," while (list) {"," a.push(list.val);"," list = list.next;"," }"," return a;","}","/**"," * 打印一个链表"," * @param {ListNode} list "," */","const logList = (list) => {"," let str = 'list: ';"," while (list) {"," str += list.val + '->';"," list = list.next;"," }"," str += 'end';"," log(str);","}","/**************** 矩阵(二维数组) ****************/","/**"," * 初始化一个二维数组"," * @param {number} r 行数"," * @param {number} c 列数"," * @param {*} init 初始值"," */","const initMatrix = (r, c, init = 0) => new Array(r).fill().map(_ => new Array(c).fill(init));","/**"," * 获取一个二维数组的行数和列数"," * @param {any[][]} matrix"," * @return [row, col]"," */","const getMatrixRowAndCol = (matrix) => matrix.length === 0 ? [0, 0] : [matrix.length, matrix[0].length];","/**"," * 遍历一个二维数组"," * @param {any[][]} matrix "," * @param {Function} func "," */","const matrixFor = (matrix, func) => {"," matrix.forEach((row, i) => {"," row.forEach((item, j) => {"," func(item, i, j, row, matrix);"," });"," })","}","/**"," * 获取矩阵第index个元素 从0开始"," * @param {any[][]} matrix "," * @param {number} index "," */","function getMatrix(matrix, index) {"," let col = matrix[0].length;"," let i = ~~(index / col);"," let j = index - i * col;"," return matrix[i][j];","}","/**"," * 设置矩阵第index个元素 从0开始"," * @param {any[][]} matrix "," * @param {number} index "," */","function setMatrix(matrix, index, value) {"," let col = matrix[0].length;"," let i = ~~(index / col);"," let j = index - i * col;"," return matrix[i][j] = value;","}","/**************** 二叉树 ****************/","/**"," * 二叉树节点"," * @param {*} val"," * @param {TreeNode} left"," * @param {TreeNode} right"," */","function TreeNode(val, left = null, right = null) {"," this.val = val;"," this.left = left;"," this.right = right;","}","/**"," * 通过一个层次遍历的数组生成一棵二叉树"," * @param {any[]} array"," * @return {TreeNode}"," */","function getTreeFromLayerOrderArray(array) {"," let n = array.length;"," if (!n) return null;"," let index = 0;"," let root = new TreeNode(array[index++]);"," let queue = [root];"," while(index < n) {"," let top = queue.shift();"," let v = array[index++];"," top.left = v == null ? null : new TreeNode(v);"," if (index < n) {"," let v = array[index++];"," top.right = v == null ? null : new TreeNode(v);"," }"," if (top.left) queue.push(top.left);"," if (top.right) queue.push(top.right);"," }"," return root;","}","/**"," * 层序遍历一棵二叉树 生成一个数组"," * @param {TreeNode} root "," * @return {any[]}"," */","function getLayerOrderArrayFromTree(root) {"," let res = [];"," let que = [root];"," while (que.length) {"," let len = que.length;"," for (let i = 0; i < len; i++) {"," let cur = que.shift();"," if (cur) {"," res.push(cur.val);"," que.push(cur.left, cur.right);"," } else {"," res.push(null);"," }"," }"," }"," while (res.length > 1 && res[res.length - 1] == null) res.pop(); // 删掉结尾的 null"," return res;","}","/**************** 二分查找 ****************/","/**"," * 寻找>=target的最小下标"," * @param {number[]} nums"," * @param {number} target"," * @return {number}"," */","function lower_bound(nums, target) {"," let first = 0;"," let len = nums.length;",""," while (len > 0) {"," let half = len >> 1;"," let middle = first + half;"," if (nums[middle] < target) {"," first = middle + 1;"," len = len - half - 1;"," } else {"," len = half;"," }"," }"," return first;","}","","/**"," * 寻找>target的最小下标"," * @param {number[]} nums"," * @param {number} target"," * @return {number}"," */","function upper_bound(nums, target) {"," let first = 0;"," let len = nums.length;",""," while (len > 0) {"," let half = len >> 1;"," let middle = first + half;"," if (nums[middle] > target) {"," len = half;"," } else {"," first = middle + 1;"," len = len - half - 1;"," }"," }"," return first;","}",
"$1"
],
"description": "LeetCode常用代码模板"
}
}

以后每一次写题之前,键入 @lc 就会出现提示,轻松加入代码模板。

当然,必须推荐刷题神器,vscode 中的一款插件 vscode-leetcode

最后我要大声说,前端真的有机会用到算法的(不只面试)!来一起快乐刷题!

用JavaScript刷LeetCode的正确姿势的更多相关文章

  1. 用 JavaScript 刷 LeetCode 的正确姿势【进阶】

    之前写了篇文章 用JavaScript刷LeetCode的正确姿势,简单总结一些用 JavaScript 刷力扣的基本调试技巧.最近又刷了点题,总结了些数据结构和算法,希望能对各为 JSer 刷题提供 ...

  2. GitHub 热点速览 Vol.18:刷 LeetCode 的正确姿势

    作者:HelloGitHub-小鱼干 摘要:找对路子,事半功倍,正如本周 GitHub Trending #刷 LeetCode# 主题想表达的那般,正确的学习姿势方能让人走得更远,走进大厂

  3. 刷LeetCode的正确姿势——第1、125题

    最近刷LeetCode比较频繁,就购买了官方的参考电子书 (CleanCodeHandbook),里面有题目的解析和范例源代码,可以省去非常多寻找免费经验分享内容和整理这些资料的时间.惊喜的是,里面的 ...

  4. 刷LeetCode的简易姿势

    近期抽空刷了刷LeetCode,算是补补课. 由于不是很习惯直接在网页上Coding&Debug,所以还是在本地环境下进行编码调试,觉得基本OK后再在网页上提交. 主要采用Python3进行提 ...

  5. 吃透Javascript数组操作的正确姿势—再读《Js高程》

    Javascript中关于数组对象的操作方法比较多也比较杂,正好再次捡起<Javascript高级程序设计>来读,把它们一一总结梳理了一下: 方法类别 方法名称 方法描述 参数 返回值 备 ...

  6. 刷leetcode是什么样的体验?【转】

    转自:https://www.zhihu.com/question/32322023 刷leetcode是什么样的体验? https://leetcode.com/ 1 条评论   默认排序 按时间排 ...

  7. jquery选中radio或checkbox的正确姿势

    jquery选中radio或checkbox的正确姿势 Intro 前几天突然遇到一个问题,没有任何征兆的..,jquery 选中radio button单选框时,一直没有办法选中,后来查了许多资料, ...

  8. 【JavaScript】Leetcode每日一题-最大整除子集

    [JavaScript]Leetcode每日一题-最大整除子集 [题目描述] 给你一个由 无重复 正整数组成的集合 nums ,请你找出并返回其中最大的整除子集 answer ,子集中每一元素对(an ...

  9. 【JavaScript】Leetcode每日一题-移除元素

    [JavaScript]Leetcode每日一题-移除元素 [题目描述] 给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度. 不要使用 ...

随机推荐

  1. [Example of Sklearn] - 分类对比

    refrence :http://cloga.info/python/2014/02/07/classify_use_Sklearn/ 加载数据集 这里我使用pandas来加载数据集,数据集采用kag ...

  2. [STM32] IAP不能跳转到APP

    用w5500做网页iap升级, 文件上传正确, 对比内部flash内容也正确, 就是不跳转. 查资料, 怀疑是中断的影响. 在跳转前关闭中断, 问题解决! // IAP程序使用了中断, 跳转前关闭 _ ...

  3. C# WPF 左侧菜单右侧内容布局效果实现

    原文:C# WPF 左侧菜单右侧内容布局效果实现 我们要做的效果是这样的,左侧是可折叠的菜单栏,右侧是内容区域,点击左侧的菜单项右侧内容区域则相应地切换. wpf实现的话,我的办法是用一个tabcon ...

  4. cookie登录功能实现

    站点经常使用的记住我,或是一周内自己主动登录,这些功能一般都是是用cookie进行登录的!以下对此功能进行分析: package cookielogin; import java.io.IOExcep ...

  5. HDU 4861(多校)1001 Couple doubi

    Problem Description DouBiXp has a girlfriend named DouBiNan.One day they felt very boring and decide ...

  6. 回调函数实现类似QT中信号机制

    1. 定义回调接口类: class UIcallBack { public: virtual void onAppActivated() = 0; virtual void onShowMore()  ...

  7. Robot Framework 快速入门_英文版

    Copyright © Nokia Siemens Networks 2008 Licensed under the Apache License, Version 2.0 Table of Cont ...

  8. java程序设计第二课

    抽象基类和接口 能够使用keywordabstact来创建抽象类,该抽象类不能被实例化 也能够使用keywordabstact来描写叙述一个尚未被详细实现的方法,该方法不能包括方法体 一个抽象方法仅仅 ...

  9. 嵌入式Linux开发环境的搭建

    一个.制造u-boot.bin文件:    tar xjf u-boot-1.1.6.tar.bz2    cd u-boot-1.1.6    patch -p1 < ../u-boot-1. ...

  10. Ubuntu更改 resolv.conf 重启失效

    更改Ubuntu的 resolv.conf的时候,重启的时候,经常又给重置了.google大法找到方法. sudo apt-get install resolvconf  原来Ubuntu的resol ...