前端笔试/面试常见编程题

数组方法

数组拍平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平台的思路及解法:
要实现螺旋遍历二维数组,我们可以使用四个指针,分别表示当前遍历的上下左右边界。具体步骤如下:

  1. 初始化四个指针:top、bottom、left、right,分别指向数组的第一行、最后一行、第一列和最后一列。
  2. 按照右、下、左、上的顺序依次遍历数组元素,并将遍历到的元素添加到结果数组中。
  3. 每遍历完一层后,更新相应的指针,使其指向下一层的边界。
  4. 当所有元素都被遍历完后,返回结果数组。
/**
 * @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

题解

  1. 目前最优解
    类数组对象一般具有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)

image

以下是对数组元素为对象的数组排序, 按照对象的某个属性排序:

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暂存回文串的边界(开区间)

  1. 核心代码模式
/**
 * @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)
};
  1. 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

题解

  1. 方法一:将数字转换成字符串,然后转成数组再反转再转换回字符串
/**
 * @param {number} x
 * @return {boolean}
 */
var isPalindrome = function(x) {
    return String(x)==String(x).split('').reverse().join('')
};

image.png

  1. 方法二:已知负数不可能是回文数,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('')
};

image.png

链表

链表反转

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表
示例 1:
在这里插入图片描述
示例2
在这里插入图片描述
示例3
空链表反转后仍为空
题解

  1. 遍历节点,将每个节点的指向反转
/**
 * 定义单链表节点
 * 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)

  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) {

// };
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值