目录
数组方法
数组拍平Array.prototype.flat()
详见https://segmentfault.com/a/1190000021366004
Array.prototype.reduce
注意考虑没有传入初始值的情况
// 测试数据arr
let arr = [ 1, 2, 3, 4]
Array.prototype.myReduce = function (fn, initValue ) {
let result
if (initValue) {
result = fn(initValue, this[0], 0, this)
for (let i = 1; i < this.length; i++) {
result = fn(result, this[i], i, this)
}
} else {
result = fn(this[0], this[1], 1, this)
for (let i = 2; i < this.length; i++) {
result = fn(result, this[i], i, this)
}
}
// console.log(result);
return result
}
// 调用示例
arr.myReduce((accumulator, currentValue, i, array) => {
console.log(accumulator, currentValue, i, array + '-------');
return accumulator + currentValue
},1)
实现Function.prototype.call()
对于没有参数的情况,args会为空数组[]
Function.prototype.myCall=function(context,...args){
context= context || window // context为空则默认为window对象
const fnSymbol= Symbol() // 创建一个唯一的属性用于存储上下文,用Symbol()能避免与原有属性同名而覆盖context对象原有的属性(比如context.fn=this,万一context原本就有fn属性,这种写法有可能会覆盖context原来的属性)
context[fnSymbol]=this // 这里this是调用myCall的函数,将这个函数存在新建的属性里
const result=context[fnSymbol](...args) // 以对象属性的形式调用函数,函数内的this指向context
delete context[fnSymbol] // 记得删除临时添加的属性,避免修改context对象
return result
}
// 以下是测试代码
function log(...args){
console.log(this.name)
console.log(...args);
}
let person={
name:'Tom'
}
log.myCall(person,1,'a',[12,323,true])
实现Function.prototype.apply()
Function.prototype.myApply = function (context, args) {
context = context || window // context为空则默认为window对象
const fnSymbol = Symbol() // 创建一个唯一的属性用于存储上下文,用Symbol()能避免与原有属性同名而覆盖context对象原有的属性(比如context.fn=this,万一context原本就有fn属性,这种写法有可能会覆盖context原来的属性)
context[fnSymbol] = this // 这里this是调用myCall的函数,将这个函数存在新建的属性里
// 若未传入参数,args为undefined,...args会报错,注意考虑该情况
const result = args ? context[fnSymbol](...args) : context[fnSymbol]() // 以对象属性的形式调用函数,函数内的this指向context
delete context[fnSymbol] // 记得删除临时添加的属性,避免修改context对象
return result
}
// 以下是测试代码
function log(...args) {
console.log(this.name)
console.log(...args);
}
let person = {
name: 'Tom'
}
log.myApply(person,[1,'a',[12,"323",true]])
实现Function.prototype.bind()
Function.prototype.myBind = function (context, ...args1) {
const that=this
// 返回一个函数
return function(...args2){
return that.call(context,...args1,...args2)
}
}
// 以下是测试代码
function log(...args) {
console.log(this.name)
console.log(...args);
}
let person = {
name: 'Tom'
}
const fn=log.myBind(person,1,2)
fn(3,4)
颜色值生成
随机生成十六进制的颜色,比如#c1c1c1
方法一:移位
数值在计算机中是以二进制形式存储的,<<
是左移运算符,<<
左边是数值,右边是要左移的位数,每移移位就相当于乘以2。颜色值由6个十六进制数,一个十六进制数由4个二进制数表示。1 << 24
表示1 0000 0000 0000 0000 0000 0000
由下图可知R、G、B通道值所在的范围,所以它们各左移16位,8位,0位
由于最终相加的值多出了最高位的1,所以最终用slice(1)
截取1后面的十六进值
function rgbToHex() {
let r=parseInt(Math.random()*256)
let g=parseInt(Math.random()*256)
let b=parseInt(Math.random()*256)
// Number.prototype.toString() 和 BigInt.prototype.toString() 方法接受一个可选的 radix 参数,radix表示进制
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
方法二:字符映射
let arr=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f']
let s='#'
for(let i=1;i<=6;i++){
let index=parseInt(Math.random()*16)
s=s+arr[index]
}
console.log(s)
颜色格式转换
十六进制转RGB
function hexToRgba(hex, opacity) {
if (!hex) hex = '#ededed';
let rgba = 'rgba(' + parseInt('0x' + hex.slice(1,3)) + ',' +
parseInt('0x' + hex.slice(3,5)) + ',' +
parseInt('0x' + hex.slice(5,7)) + ',' +
(opacity || "1") + ')'
return rgba
}
RGB 转 十六进制
给一RGB格式的颜色值,要求转成十六进制的格式。比如
RGB(255, 255,255)
,RGB(0 ,16, 38)
,数字之间可能会有空格
function RGBToHex(rgb) {
var regexp = /[0-9]{1,3}/g
var res = rgb.match(regexp) // 利用正则表达式去掉多余的部分,将rgb中的数字提取
var hexRes = '#'
// 注意要设置成字符串格式
var hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
// var hexArr = []
for (let i = 0; i < res.length; i++) {
let leftIndex = Math.floor(res[i] / 16) // 向下取整
let rightIndex = res[i] % 16
hexRes += hex[leftIndex] + hex[rightIndex]
}
console.log(hexRes)
return hexRes
}
RGBToHex('RGB(255, 255, 255)')
RGBToHex('RGB(0, 16, 38)')
防抖
防抖通用模板
function debounce(fn,delay){
let timer = null //借助闭包
return function() {
let context=this, args=[...arguments] // 保存this对象及参数
if(timer){
clearTimeout(timer) //清除之前的定时器,重新计时
// timer=null // 测试过了,这一句没有也行
}
timer = setTimeout(()=>{
fn.apply(context,args)
},delay) //开启新的定时器
}
}
// 原来事件的回调
function fn(<事件回调的一些参数>) {
// 回调函数的操作
}
事件绑定的对象.事件类型 = debounce(fn,<时间>,<事件回调的一些参数>)//滚动的时候会触发多次
调用示例,在规定时间间隔内的滚动事件,只会输出最后一次滚动事件滚动条的位置
function showTop () {
var scrollTop = document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,300)
节流
个人推荐时间戳版,因为定时器版第一次触发的回调会在delay时间后执行
时间戳版
function throttle(fn, delay) {
var preTime=Date.now() // 获取当前时间戳,这个变量必须在外层函数和内层函数之间定义。否则每次调用throttle函数preTime的值都是调用throttle函数时的时间,无法达到计算时间段的效果
return function(){
var context =this, args=[...arguments], nowTime=Date.now()
if(nowTime-preTime>delay){
fn.apply(context,args)
preTime=Date.now()
}
}
}
定时器版
function throttle(fn,delay){
let timer = null // 初始肯定是不被冻结的
return function() {
let context=this, args=[...arguments];
if(!timer){
timer=setTimeout(function(){
fn.apply(context,args)
timer = null // 记得执行完定时器中的操作后将timer置空,timer为null的时候表示在设定的时间外,没有被冻结
}, delay)
}
}
}
调用示例,减少输出滚动位置的次数
function showTop () {
var scrollTop = document.documentElement.scrollTop;
console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,300)
数组
数组旋转
LeetCode第48题:https://leetcode.cn/problems/rotate-image/description/
方法一:借助额外数组
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var rotate = function(matrix) {
let n=matrix.length
let newM=new Array(n).fill(0).map(() => new Array(n).fill(0));
// console.log(newM)
for(let i=0;i<n;i++){
for(let j=0;j<n;j++){
newM[j][n-1-i]=matrix[i][j]
}
}
for(let i=0;i<n;i++){
for(let j=0;j<n;j++){
matrix[i][j]=newM[i][j]
}
}
};
方法二:不借助额外数组,借用一个临时变量(但是不是两个数交换),需要找到一定规律,具体见官方题解
螺旋遍历二维数组
力扣LCR146题:https://leetcode.cn/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/description/
或力扣第54题:https://leetcode.cn/problems/spiral-matrix/
题目描述
给定一个二维数组 array,请返回「螺旋遍历」该数组的结果。
螺旋遍历:从左上角开始,按照 向右、向下、向左、向上 的顺序 依次 提取元素,然后再进入内部一层重复相同的步骤,直到提取完所有元素。
示例 1:
输入:array = [[1,2,3],[8,9,4],[7,6,5]]
输出:[1,2,3,4,5,6,7,8,9]
示例 2:
输入:array = [[1,2,3,4],[12,13,14,5],[11,16,15,6],[10,9,8,7]]
输出:[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16]
限制:
0 <= array.length <= 100
0 <= array[i].length <= 100
题解:
每一个while循环就是一个完整的环,假设每个循环的边界遍历该边界的(length-1)个元素,就是每个边界不遍历到底。每个这样的循环导致圈往内缩小一层(体现为4个边界的变化),直至其中有一对相对的边界值相等(就是只剩一行或一列或一个元素)
初始边界的位置:
- 上边界 top : 0
- 下边界 bottom : array.length - 1
- 左边界 left : 0
- 右边界 right : array[0].length - 1
注意矩阵不一定是方阵
top < bottom && left < right 是成环条件,即while循环的条件。
结束循环时,分 3 种情况:
top == bottom && left < right —— 剩一行。
top < bottom && left == right —— 剩一列。
top == bottom && left == right —— 剩一项。
如何处理剩下的单行或单列?因为是按顺时针推入结果数组的,所以:
- 剩下的一行,从左至右 依次推入结果数组。
- 剩下的一列,从上至下 依次推入结果数组。
代码
每个元素访问一次,时间复杂度 O(mn),m、n 分别是行数和列数。空间复杂度 O(mn)。
/**
* @param {number[][]} array
* @return {number[]}
*/
var spiralArray = function(array) {
// 处理空数组的情况
if(array.length==0) return [];
// res用来存储遍历过的元素
let res=[];
// t,r,b,l分别是上,右,下,左边界值
let t=0,r=array[0].length-1,b=array.length-1,l=0
// t<b&&l<r是成环条件
while(t<b&&l<r){
for(let i=l;i<r;i++) res.push(array[t][i]); // 左至右,上边界
for(let i=t;i<b;i++) res.push(array[i][r]); // 上至下,右边界
for(let i=r;i>l;i--) res.push(array[b][i]); // 右至左,下边界
for(let i=b;i>t;i--) res.push(array[i][l]); // 下至上,左边界
// 改变边界值
t++;
r--;
b--;
l++;
}
// 只剩一行或一列的时候注意遍历到底,所以跳出for循环的条件相较于while循环中的上边界或右边界遍历有差别(如i<r变成i<=r)
if(t==b){// 只剩一行或一项,从左到右遍历(这个条件包含了只剩一项的情况)
for(let i=l;i<=r;i++) res.push(array[t][i]);
}else if(l==r){ // 只剩一列
for(let i=t;i<=b;i++) res.push(array[i][r]);
}
return res
};
以下是讯飞AI平台的思路及解法:
要实现螺旋遍历二维数组,我们可以使用四个指针,分别表示当前遍历的上下左右边界。具体步骤如下:
- 初始化四个指针:top、bottom、left、right,分别指向数组的第一行、最后一行、第一列和最后一列。
- 按照右、下、左、上的顺序依次遍历数组元素,并将遍历到的元素添加到结果数组中。
- 每遍历完一层后,更新相应的指针,使其指向下一层的边界。
- 当所有元素都被遍历完后,返回结果数组。
/**
* @param {number[][]} matrix
* @return {number[]}
*/
var spiralOrder = function(matrix) {
// 测试讯飞AI写的代码
if (matrix.length === 0) return [];
const rows = matrix.length;
const cols = matrix[0].length;
const result = [];
let top = 0, bottom = rows - 1, left = 0, right = cols - 1;
while (top <= bottom && left <= right) {
// Traverse from left to right.
for (let i = left; i <= right; i++) {
result.push(matrix[top][i]);
}
top++;
// Traverse from top to bottom.
for (let i = top; i <= bottom; i++) {
result.push(matrix[i][right]);
}
right--;
// Traverse from right to left.
if (top <= bottom) {
for (let i = right; i >= left; i--) {
result.push(matrix[bottom][i]);
}
bottom--;
}
// Traverse from bottom to top.
if (left <= right) {
for (let i = bottom; i >= top; i--) {
result.push(matrix[i][left]);
}
left++;
}
}
return result;
};
貌似AI比我们更懂这道题[捂脸笑]
判断是否有重复元素
给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
示例 1:
输入:nums = [1,2,3,1]
输出:true
示例 2:
输入:nums = [1,2,3,4]
输出:false
示例 3:
输入:nums = [1,1,1,3,3,4,3,2,4,2]
输出:true
提示:
1 <= nums.length <= 105
-109 <= nums[i] <= 109
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/contains-duplicate
题解
- 目前最优解
类数组对象一般具有length属性,Set对象比较特殊,它没有length属性,而是size属性
var声明的变量没有块级作用域,在全局有效
解题思路:Set对象可以看做是键和值相同的Map对象,所以它没有重复元素。先将数组转换成Set对象实现去重,若有重复元素,Set对象的元素个数会比原数组的元素个数少,据此判断数组中是否有重复元素
/**
* @param {number[]} nums
* @return {boolean}
*/
var containsDuplicate = function(nums) {
let set=new Set(nums)
if(set.size!==nums.length)
return true
return false
};
indexOf()从前往后查找数组元素,返回数组下标,没找到返回-1
lastIndexOf()与indexOf的区别是从后往前查找
若有重复元素,二者返回的下标必定不相等。所以可以通过比较返回下标判断数组是否有重复元素
/**
* @param {number[]} nums
* @return {boolean}
*/
var containsDuplicate = function(nums) {
for(let i of nums){
if(nums.indexOf(i)!==nums.lastIndexOf(i))
return true
}
return false
};
数组去重
题:假设有一个数组arr = [1,2,1,3,2,4,5,5,6,7],要求去除其重复元素
方法一:...
|Array.from()
+Set对象
可以通过Set对象实现数组去重。思路:重复的数组 -> 通过Set()函数转换成没有重复元素的Set对象 -> 再把该Set对象转换成数组
let arr = [1,2,1,3,2,4,5,5,6,7]
arr = [...(new Set(arr))]; // 或Array.from(new Set(arr))
/* arr = [...(new Set(arr))];等效于下面2句
let set = new Set(arr);
arr =[...set];
*/
console.log(arr);
运行结果
方法二:不额外创建数组,在原数组本身中去除重复元素
const arr = [1, 2, 1, 3, 2, 4, 5, 5, 6, 7]
for (let i = 1; i < arr.length;) {
//从元素前面搜索是否有重复元素,如果搜索到重复元素则返回值不为-1
if (arr.lastIndexOf(arr[i], i - 1) != -1) {
//删除arr[i],注意删除该元素后,后面的元素会往前移一位,所以有元素被删除时i不加1,以免遗漏相邻重复的元素
arr.splice(i, 1)
}
else i++;//只有未找到与当前元素arr[i]重复的元素时i才自增1
}
console.log(arr)
运行结果
方法三:额外创建一个数组存储去重的数组,indexOf
|lastIndexOf
|includes
const newArr = []
for(let ele of arr){
if(newArr.indexOf(ele) === -1){ // 或!newArr.includes(ele)或newArr.lastIndexOf(ele) === -1
//未在newArr中找到重复元素就将其加入到newArr中
newArr.push(ele)
}
}
console.log(newArr)
运行结果
方法四:filter
+indexOf
function removeDuplicate(arr) {
return arr.filter((item, index) => {
return arr.indexOf(item) === index // 这样只会过滤出第一次出现的元素
})
}
const result = removeDuplicate(arr)
console.log(result) // [ 1, 2, 'abc', true, false, undefined ]
找出数组中重复的元素
function fn(arr) {
let a = []
for (let i in arr) {
if (arr.indexOf(arr[i]) != arr.lastIndexOf(arr[i])) {
if(a.indexOf(arr[i])==-1) a.push(arr[i]) // 注意不要漏了这一句,否则会导致新的数组有重复的元素
}
}
return a
}
// 测试代码
console.log(fn([1, 2, 3, 4, 2, 2, 4, 5, 3])); // [2, 3, 4]
数组排序
题:已知数组arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4],要求从小到大排序
以下算法用JavaScript实现
冒泡排序
不适用于大规模的数组,多次交换了相邻的元素,性能不太好
const arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4]
for (let i = 0, temp; i < arr.length; i++) {
for (let j = 0; j < arr.length - i; j++)//大的元素逐渐往后冒泡
if (arr[j] > arr[j + 1]) {
//交换相邻元素的位置
temp = arr[j]
arr[j] = arr[j + 1]
arr[j + 1] = temp;
}
}
console.log(arr)
选择排序
选择排序与冒泡排序的比较次数相同,但选择排序的交换次数比冒泡排序明显少,所以它的性能比冒泡排序好
const arr = [9, 1, 3, 2, 8, 0, 5, 7, 6, 4]
for (let i = 0, temp, min; i < arr.length - 1; i++) {
//每个子循环筛选出在arr[i]~arr[arr.length-1]中最小的元素排到第i个位置上
for (let j = i + 1; j < arr.length; j++) {
if (arr[i] > arr[j]) {
temp = arr[i]
arr[i] = arr[j]
arr[j] = temp;
}
}
}
console.log(arr)
二分法
是这3种当中最快的
运行结果都如下图所示
Array.prototype.sort()
sort(compareFn)
compareFn有如下2种情况,根据compareFn
的返回值来排序。a,b是参与排序的 数组元素/键值对,a-b的正负决定是升序还是降序排序:
- (a, b) => a - b 升序排列,返回值>0
- (a, b) => b - a 降序排列, 返回值<0
以下是对数组排序
let arr = ["a", "c", "e", "f", "d", "b"]
arr.sort()
console.log(arr)
arr = [2, 3, 1, 9, 0, 4, 5, 7, 8, 6, 10]
arr.sort()//在不传递回调函数作为参数时,sort()在数字上的排序并不准确
console.log(arr)
//需要记住传递以下2种类型的回调函数来对数值型数组排序
arr.sort((a, b) => a - b)//升序排列
console.log(arr)
arr.sort((a, b) => b - a)//降序排列
console.log(arr)
以下是对数组元素为对象的数组排序, 按照对象的某个属性排序:
const items = [
{ name: 'Edward', value: 21 },
{ name: 'Sharpe', value: 37 },
{ name: 'And', value: 45 },
{ name: 'The', value: -12 },
{ name: 'Magnetic', value: 13 },
{ name: 'Zeros', value: 37 }
];
// sort by value,升序
// 形参a,b表示参与排序的元素
// 注意是a.age与b.age不是a与b,a.value与b.value表示排序的依据
items.sort((a, b) => a.value - b.value);
字符串
回文串
求最长回文串
题目链接
给你一个字符串 s,找到 s 中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2:
输入:s = “cbbd”
输出:“bb”
提示:
1 <= s.length <= 1000
s 仅由数字和英文字母组成
解析
中心扩散法
思路:由回文串的特点可知回文串关于中心两边相等。寻找回文串的中心,然后向两端扩展,直到两端不等。因为不能确定回文串的中心在哪,所以需要从字符串的[0,length-1]位循环假设回文串中心,因为每次循环并不知道回文串长度是奇数还是偶数,所以helper(i,i)
和helper(i,i+1)
都要执行。这里设置了双指针l, r暂存回文串的边界(开区间)
- 核心代码模式
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function(s) {
// 如果字符串只有一个字符或是空串就返回其本身
if(s.length<=1){
return s
}
// l, r记录当前回文串的边界(开区间)
let l=0
let r=0
// m,n是回文串扩展起始位(中心),函数helper以m,n开始向两端扩展求最大回文串
function helper(m,n){
// while循环找出回文串边界m,n(开区间)
while(m>=0&&n<s.length&&s[m]==s[n]){
m--;
n++;
}
// 跳出循环后,m,n为本轮i循环找到的边界值(开区间)
// n-m-1是回文串的长度
if(n-m-1>r-l-1){
// 更新l, r所记录的当前回文串的边界
l=m
r=n
}
}
// 因为不知道回文串的中心在哪,所以字符串的所有位置都要试一遍
for(let i=0;i<s.length;i++){
// 因为不知道回文串长度是奇数还是偶数,所以每次循环对于奇数的helper和对于偶数的helper都要执行一遍
// 若回文串长度是奇数
helper(i,i)
// 若回文串长度是偶数
helper(i,i+1)
}
// 注意slice的下限是闭区间,上限是开区间,所以实际截取的范围是[l+1,r-1]
return s.slice(l+1,r)
};
- ACM
// readline()读入一行的输入,注意读入的是字符串
let s = readline();
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function(s) {
// 如果字符串只有一个字符或是空串就返回其本身
if(s.length<=1){
return s
}
// l, r记录当前回文串的边界(开区间)
let l=0
let r=0
// m,n是回文串扩展起始位(中心),函数helper以m,n开始向两端扩展求最大回文串
function helper(m,n){
// while循环找出回文串边界m,n(开区间)
while(m>=0&&n<s.length&&s[m]==s[n]){
m--;
n++;
}
// 跳出循环后,m,n为本轮i循环找到的边界值(开区间)
// n-m-1是回文串的长度
if(n-m-1>r-l-1){
// 更新l, r所记录的当前回文串的边界
l=m
r=n
}
}
// 因为不知道回文串的中心在哪,所以字符串的所有位置都要试一遍
for(let i=0;i<s.length;i++){
// 因为不知道回文串长度是奇数还是偶数,所以每次循环对于奇数的helper和对于偶数的helper都要执行一遍
// 若回文串长度是奇数
helper(i,i)
// 若回文串长度是偶数
helper(i,i+1)
}
// 注意slice的下限是闭区间,上限是开区间,所以实际截取的范围是[l+1,r-1]
return s.slice(l+1,r)
};
求回文串个数
题目链接
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:s = “abc”
输出:3
解释:三个回文子串: “a”, “b”, “c”
示例 2:
输入:s = “aaa”
输出:6
解释:6个回文子串: “a”, “a”, “a”, “aa”, “aa”, “aaa”
提示:
1 <= s.length <= 1000
s 由小写英文字母组成
题解
思路:字符串s的每个位置都假设一遍是中心,求以当前位置为中心的最大奇数长和偶数长的回文串。然后再统计相应中心位置的最大回文串包含的子回文串个数,这样累加就是总回文串个数
/**
* @param {string} s
* @return {number}
*/
var countSubstrings = function(s) {
// 首先字符串s的每一个单独的字符是一个回文串,单独的字符个数就等于字符串长度
let count=s.length;
// m,n是回文串扩展起始位(中心),函数helper以m,n开始向两端扩展求最大回文串
function helper(m,n){
// while循环找出回文串边界m,n(开区间)
while(m>=0&&n<s.length&&s[m]==s[n]){
m--;
n++;
}
// 跳出循环后,m,n为本轮i循环找到的边界值(开区间)
// n-m-1是回文串的长度
// 求出不同中心位置的最大回文串s[m+1,n-1]后,再计算该回文串可分解为多少个子回文串(单独一个的字符除外,因为已经计算过了)。个数为长度一半的整数
count+=parseInt((n-m-1)/2)
}
// 字符串的每个位置都假设一遍是中心,求以当前位置为中心的最大奇数长和偶数长的回文串。
for(let i=0;i<s.length;i++){
// 求以当前i为中心的最大奇数长回文串
helper(i,i)
// 求以当前i为中心的最大偶数长回文串
helper(i,i+1)
}
return count
};
判断是否为回文数
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。
回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。
例如,121 是回文,而 123 不是。
示例 1:
输入:x = 121
输出:true
示例 2:
输入:x = -121
输出:false
解释:从左向右读, 为 -121 。 从右向左读, 为 121- 。因此它不是一个回文数。
示例 3:
输入:x = 10
输出:false
解释:从右向左读, 为 01 。因此它不是一个回文数。
提示:-231 <= x <= 231 - 1
题解
- 方法一:将数字转换成字符串,然后转成数组再反转再转换回字符串
/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function(x) {
return String(x)==String(x).split('').reverse().join('')
};
- 方法二:已知负数不可能是回文数,0是回文数
加上判断内存耗费得少一些,但时间也慢了些
/**
* @param {number} x
* @return {boolean}
*/
var isPalindrome = function(x) {
if(x<0) return false
if(x==0) return true
return String(x)==String(x).split('').reverse().join('')
};
链表
链表反转
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表
示例 1:
示例2
示例3
空链表反转后仍为空
题解
- 遍历节点,将每个节点的指向反转
/**
* 定义单链表节点
* 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) {
// cur指向当前遍历的节点,pre指向前一个节点,
let next=undefined,pre=null,cur=head;
while(cur){
// 顺序不能颠倒
// next存储当前遍历节点的下一个结点的引用
next=cur.next
// 反转指向
cur.next=pre
// pre指向后移一位
pre=cur;
// cur指向后移一位
cur=next
}
// 最终的pre成为新的头指针
head=pre
return head
};
复杂度分析
时间复杂度:O(n),其中 n 是链表的长度。需要遍历链表一次。
空间复杂度:O(1)
- 递归
/**
* 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 pre=head,cur=head.next;
// 考虑空链表和只有一个节点的情况
if(pre==null || cur==null){
head.next=null
return pre
};
cur.next=pre
return reverseList(cur)// 此时cur就相当于head
};
回溯法
解向量
n皇后问题
暂未解决
/**
* @param {number} n
* @return {number}
*/
var totalNQueens = function(n) {
let res = [] //存储多组解向量
let x = [] // 存储一组解向量
let sum = 0
let layer = 0 //标记当前在求解树的第几层(即棋盘第几行,layer=0对应棋盘第一行)
function isValid() { //
if(layer!=0&&Math.abs(x[layer]-x[layer-1])==1) return false;
if(x[layer]==x[layer-1]) return false;
return true
}
if (layer >= 0) {
x[0]=0 // 列的起始值
// while循环用来找到当前层符合条件的解(即当前行合适的列)
while(!isValid()&&x[layer]<n) x[layer]++;
if (x[layer]<n) {
if (layer = n - 1) {
res.push(x)
sum++
} else {
x[++layer]=0
}
} else {
layer--; // 回溯到上一层
}
} else { // layer<0表示到根结点处都没有解
console.log('无解!')
}
return sum
};
/**
* @param {number} n
* @return {string[][]}
*/
// var solveNQueens = function (n) {
// };