JS算法题,LeetCode
前端JS算法题个人练习记录,含思路+详细注释,适合零基础算法小白。
文章目录
- JS算法题,LeetCode
- 一、排序算法
- 二、JS手写题
- 三、LeetCode
- 000.ACM模式输入
- 001.两数之和
- 002.两数相加
- 003.无重复字符的最长子串
- 005.最长回文子串
- 015.三数之和
- 019.删除链表的倒数第N个节点
- 020.有效的括号
- 021.合并两个有序链表
- 024.两两交换链表中的节点
- 049.字母异位词分组
- 053.最大子数组和
- 054.螺旋矩阵
- 055.跳跃游戏
- 056.合并区间
- 062.不同路径
- 066.加一
- 070.爬楼梯
- 073.矩阵置零
- 078.子集(元素互不相同)
- 090.子集Ⅱ(元素可以重复)
- 083.删除排序链表中的重复元素
- 206.反转链表(整个链表)
- 092.反转链表Ⅱ(区间链表)
- 121.买卖股票的最佳时机(只能交易一次)
- 122.买卖股票的最佳时机Ⅱ(可以交易多次)
提示:以下是本篇文章正文内容,下面代码可供参考
一、排序算法
01.冒泡排序
思路:每次先排出来一个最大的
平均时间复杂度:O(n^2),空间复杂度O(1)
外层循环:排好一个,即索引j减少
内层循环:i++至j
循环体:相邻两位比较大小并交换
ArrayList.prototype.bubbleSort = function () {
// 1.获取数组的长度
const length = this.array.length
// 第一次:j = length - 1,比较到倒数第一个位置
// 第二次:j = length - 2,比较到倒数第二个位置
for (let j = length - 1; j > 0; j--) {
// j是外层循环的标志,j后面的都已经有序了
for (let i = 0; i < j; i++) {
// 升序
if (this.array[i] > this.array[i + 1]) {
[this.array[i], this.array[i + 1]] = [this.array[i + 1], this.array[i]]
}
}
}
}
02.选择排序
思路:设置min标志,从后面选出最小的,和第min交换位置
平均时间复杂度:O(n^2),空间复杂度O(1)
外层循环:正常遍历j
内层循环:min+1开始
循环体:min>i,min=i
ArrayList.prototype.selectionSort = function () {
// 1.获取数组的长度
const length = this.array.length
// 2.外层循环:从0位置开始取数据,直到length-2的位置
for (let j = 0; j < length - 1; j++) {
// 内循环层:从i+1位置开始,和后面的数据进行比较,直至length-1
// min用于记录最小的位置
let min = j
for (let i = min + 1; i < length; i++) {
if (this.array[min] > this.array[i]) {
min = i
}
}
// 交换min位置的数据和j位置的数据
[this.array[min], this.array[j]] = [this.array[j], this.array[min]]
}
}
03.插入排序
思路:局部有序,前面挨个交换位置
平均时间复杂度:O(n^2),空间复杂度O(1)
外层循环:i从1开始,第一个默认有序
内层循环:设置temp,j=i是局部有序的零界点
循环体:只要j-1>temp,挪动j和j-1,将temp插入到该位置
ArrayList.prototype.insertSort = function () {
// 1.获取数组的长度
let length = this.array.length
// 2.外层循环:从第1个位置开始获取数据,像前面局部有序进行插入
for (let i = 1; i < length; i++) {
// 3.内层循环:获取i位置的元素,和前面的数据依次进行比较
// i和j是局部有序的临界点
let temp = this.array[i]
let j = i
// 前面局部有序的值>temp,则进行交换,再j--向前挪一位
while (this.array[j - 1] > temp && j > 0) {
this.array[j] = this.array[j - 1]
j--
}
// 4.将j位置的数据,放置temp即可
this.array[j] = temp
}
}
04.希尔排序
思路:隔个gap,插入排序,然后缩小gap差距
平均时间复杂度:O(n^(1.3-2)),空间复杂度O(1)
最外层循环:gap>=1,gap向下取整除2
外层循环:i从gap开始,第一个默认有序
内层循环:设置temp,j=i是局部有序的零界点
循环体:只要j-gap>temp,挪动j和j-gap,将temp插入到该位置
ArrayList.prototype.shellSort = function () {
// 1.获取数组的长度
let length = this.array.length
// 2. 初始化增量gap
let gap = Math.floor(length / 2)
// 3. while循环,gap不断减小
while (gap >= 1) {
// 4. 以gap作为间隔,进行分组,对分组进行插入排序
// 从第gap个位置开始获取数据,像前面的第一个局部有序进行插入
for (let i = gap; i < length; i++) {
let temp = this.array[i]
let j = i
while (j > gap - 1 && this.array[j - gap] > temp) {
this.array[j] = this.array[j - gap]
j -= gap
}
// 5. 将j位置的元素赋值temp
this.array[j] = temp
}
gap = Math.floor(gap / 2)
}
}
05.快速排序
思路:递归,双指针。在冒泡排序基础上使用分治思想
只要i<j, j i i j
右边比基数大,j–;比基数小则交换
左边比基数小,i++;比基数大则交换
将pivot放到正确的位置(一趟找到该值所在的正确位置),再分治两侧递归
function quickSort(arr, left = 0, right = arr.length - 1) {
// 递归条件,left < right
if (left < right) {
// 基准值
let pivot = arr[left];
// 左右指针
let i = left;
let j = right;
while (i < j) {
// 右边比基数大,j--
while (arr[j] > pivot && i < j) {
j--;
}
// 遇到比基数小,交换
arr[i] = arr[j];
// 左边比基数小,i++
while (arr[i] < pivot && i < j) {
i++;
}
// 遇到比基数大,交换
arr[j] = arr[i];
}
//此时i与j位置一致,将pivot放在此位置即可
arr[i] = pivot;
// 对基数左边递归
quickSort(arr, left, i - 1);
// 对基数右边递归
quickSort(arr, i + 1, right);
// 递归终止条件,left >= right
} else {
return;
}
// 返回数组
return arr
}
二、JS手写题
// 1.手写Object.create
// 返回一个obj的实例,即实例的proto指向obj的prototype
function create(obj) {
function F() { }
F.prototype = obj
return new F()
}
// 2.手写instanceof方法
// 使用方法:[] instanceof Array
// left是实例,right是构造函数类
function myInstanceof(left, right) {
let proto = Object.getPrototypeOf(left)
let prototype = right.prototype
while (true) {
if (proto === null) return false
if (proto === prototype) return true
// 注意此处不能let重复声明
proto = Object.getPrototypeOf(proto)
}
}
// 测试用例
const arr2 = [1]
console.log(myInstanceof(arr2, Array))
// 3.手写new
function _new() {
// 创建空对象;this指向obj;执行构造函数;return
let obj = {}
// 利用数组shift方法,将arguments的第一个参数,构造函数提出来
let constructor = Array.prototype.shift.call(arguments)
obj = Object.create(constructor.prototype)
// 剩余arguments是数组,使用apply方法
let result = constructor.apply(obj, arguments)
// result是构造函数返回的,若是对象,则直接result返回
return (result !== null && (typeof result === 'object')) ? result : obj
}
// 测试用例
function Person(name, age) {
this.name = name
this.age = age
return {
name: '张三',
age: 18
}
}
console.log(_new(Person, 'Hugh', 24))
// 4.手写Promise
// 5.手写promise.then
// 6.手写PromiseAll
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) {
throw new Error('参数类型应为数组')
}
let result = []
let nums = promises.length
let count = 0
for (let i = 0; i < nums; i++) {
Promise.resolve(promises[i]).then(
value => {
count++
result[i] = value
if (count == nums) {
return resolve(result)
}
}, error => {
reject(error)
}
)
}
})
}
// 7.手写promise.race
// 8.手写防抖
function debounce(fn, wait) {
let timer = null
return function () {
let context = this
let args = arguments
if (timer) {
clearTimeout(timer)
timer = null
}
timer = setTimeout(() => {
fn.apply(context, args)
}, wait)
}
}
// 9.手写节流
function throttle(fn, delay) {
let currTime = Date.now()
return function () {
let context = this
let args = arguments
let nowTime = Date.now()
if (nowTime - currTime > delay) {
fn.apply(context, args)
currTime = Date.now()
}
}
}
// 10.手写类型判断
// 11.手写call
// 使用方法:fn.call(obj,25,'男')
Function.prototype.myCall = function (context) {
// context若为空则挂载至window
context = context || window
// 获取参数:设置args为除了第一个参数外的所有参数
let args = [...arguments].splice(1);
const fn = Symbol('key')
context[fn] = this
const result = context[fn](...args)
delete context[fn]
return result
}
// 测试用例
const obj11 = {
name: 'OBJECT11'
}
function fn11(age, sex) {
console.log(this.name, age, sex)
}
fn11.myCall(obj11, 25, '男')
// 12.手写apply
// 使用方法:fn.apply(obj,[25,'男'])
Function.prototype.myApply = function (context) {
const context = context || window
const fn = Symbol('key')
context[fn] = this
let result
// 判断是否有第二个参数
if (arguments[1]) {
context[fn](...arguments[1])
} else {
context[fn]()
}
delete context[fn]
return result
}
// 测试用例
const obj12 = {
name: 'OBJECT12'
}
function fn12(age, sex) {
console.log(this.name, age, sex)
}
fn12.myApply(obj12, [25, '男'])
// 13.手写bind
// 使用方法:fn.bind(obj,25,'男')()
Function.prototype.myBind = function (context) {
let args = [...arguments].splice(1)
return function Fn() {
return this.apply(this instanceof Fn ? this : context, args.concat(...arguments))
}
}
// 测试用例
const obj13 = {
name: 'OBJECT13'
}
function fn13(age, sex) {
console.log(this.name, age, sex)
}
fn13.myBind(obj13, [25, '男'])()
// 14.柯里化函数
// 15.手写ajax
const _URL = "/server"
let xhr = new XMLHttpRequest()
xhr.open('GET', _URL, true)
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
if (this.status === 200) {
handle(this.response)
} else {
console.error(this.statusText)
}
}
xhr.onerror = function () {
console.error(this.statusText)
}
xhr.setRequestHeader("Accept", "application/json")
xhr.send()
// 16.Promise封装ajax
function getJSON(url) {
let promise = new Promise(function (resolve, reject) {
let xhr = new XMLHttpRequest()
xhr.open('GET', _URL, true)
xhr.onreadystatechange = function () {
if (this.readyState !== 4) return
if (this.status === 200) {
resolve(this.response)
} else {
reject(new Error(this.statusText))
}
}
xhr.onerror = function () {
reject(new Error(this.statusText))
}
xhr.setRequestHeader("Accept", "application/json")
xhr.send()
})
return promise
}
// 17.浅拷贝
Object.assign(target, source_1, source_2)
// 18. 深拷贝
JSON.parse(JSON.stringify(obj))
// 递归实现
function deepClone(obj) {
// 判空或不是对象
if (obj == null || typeof obj !== "object") { return }
// newObj isArray ?[]:{}
let newObj = Array.isArray(obj) ? [] : {}
// 遍历对象
for (let key in obj) {
// 判断key是否是object自身上的,而不是其原型链上
if (obj.hasOwnProperty(key)) {
newObj[key] = typeof obj[key] === "object" ? deepClone(obj[key]) : obj[key]
}
}
return newObj
}
// 测试用例
const person = {
name: 'hugh',
age: 25,
hobby: {
hName: '羽毛球'
}
}
console.log(deepClone(person))
// 12.实现sleep函数
function sleep(t) {
return new Promise(resolve => setTimeout(resolve, t))
}
// 测试用例
console.log('sleep函数')
sleep(1000).then(() => {
console.log('1秒后执行')
})
// 20.实现Object.assign()
// 21.实现日期格式化函数
// 22.交换a、b的值,不用临时变量
a = a + b
b = a - b
a = a - b
// 23.打乱数组
const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]
arr.sort(() => Math.random() - 0.5)
console.log(arr)
// 24.数组求和
// reduce求和
arr.reduce((prev, item) => prev += item, 0)
// 递归求和
function add(arr) {
if (arr.length === 1) return arr[0]
return arr[0] + add(arr.slice(1))
}
// 25.手写扁平化数组
// ES6中flat
arr.flat(Infinity)
// split和toString
arr.toString.split(",")
// 递归
function flatten(arr) {
let result = []
for (i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]))
} else {
result.push(arr[i])
}
}
}
// 26.数组去重
Array.from(new Set(arr))
// 27.
三、LeetCode
000.ACM模式输入
多行输⼊,每行两个整数
// 引⼊readline模块来读取标准输⼊
const readline = require('readline');
// 创建readline接⼝
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// 处理输⼊和输出
function processInput() {
rl.on('line', (input) => {
// 将输⼊按空格分割成a和b的数组
const [a, b] = input.split(' ').map(Number);
// 计算a和b的和并输出
const sum = a + b;
console.log(sum);
});
}
// 开始处理输⼊
processInput();
001.两数之和
https://leetcode.cn/problems/two-sum/description/
变量:map,complement
方法:map里是否有complement
1.创建map
2.遍历数组
3.定义差,target-nums[i]
4.如果map.has(差),返回[i, map.get(差)]
else,map.set(值,索引)
5.全不符,直接return[]
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
// 新建map
const map = new Map()
// 遍历num数组
for(let i = 0; i < nums.length; i++){
// 定义 差
const complement = target - nums[i]
// 如果map里面有nums[i]对应的差
if(map.has(complement)){
// 返回 差的索引and和数组索引i
return [map.get(complement),i]
// 如果map里面没nums[i]对应的差
}else{
// 将nums[i]和数组索引i放进map中
map.set(nums[i],i)
}
}
// 如果遍历完nums数组,仍然没有,即返回一个空数组
return []
};
002.两数相加
https://leetcode.cn/problems/add-two-numbers/description/
变量:dummy,curry,carry
方法:sum->curr.next->carry->curr
1.新建dummy节点为空,curr指针指向dummy,设置进位carry为0
2.只要l1和l2其中有一个链表不为空,定义两数之和sum
如果l1不为空,sum+l1的值,l1进一位;l2同理
最终sum是2个链表数之和**(sum)+进位(carry)**
curr指向新建一个节点,值为sum取模10;计算进位carry,除于10再floor向下舍
指针指向下一个节点
3.最后检查carry,如果carry>0,即需要新增一个节点来放carry的值
4.返回dummy.next
/**
* 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 addTwoNumbers = function(l1, l2) {
// 定义链表头,dummy为空节点
let dummy = new ListNode()
// 指针指向链表头
let curr = dummy
// 设置进位carry
let carry = 0
// 只要l1和l2其中有一个链表不为空,就一直执行
while(l1 !== null || l2 !== null){
// 两数相加之和
let sum = 0
// 如果l1不为空
if(l1 !== null) {
// sum加上l1的值
sum += l1.val
// l1要进一位
l1 = l1.next
}
// 如果l2不为空
if(l2 !== null) {
// sum加上l2的值
sum += l2.val
// l2要进一位
l2 = l2.next
}
// 最终sum是 2个链表数之和+进位
sum += carry
// curr指向新建一个节点,值为sum取模10,12 % 10 = 2
curr.next = new ListNode(sum % 10)
// 计算进位carry,除于10再floor向下舍
carry = Math.floor(sum / 10)
// 指针指向下一个节点
curr = curr.next
}
// 最后检查carry,如果carry>0,即需要新增一个节点来放carry的值
if(carry > 0){
curr.next = new ListNode(carry)
}
// 返回头节点,即dummy.next
return dummy.next
};
003.无重复字符的最长子串
https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/
变量:set,i,j,maxLength
方法:set是否有s[i]
1.创建set,定义双遍历指针 i 和 j 都为0
2.定义无重复字符串最大长度maxLength为0,遍历字符串
3.如果set还有s[i],
while循环确保set不存在s[i]*
4.*如果set里没有s[i],即没有重复过的字符
将s[i]添加进set**,并更新maxLength的值,取maxLength和set.size的max值
删除s[j],即第一次出现的重复值,并将j++指向下一个值
再将s[i]加入set里
5.返回maxLength
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
// 新建set
const set = new Set()
// 双指针,滑动窗口思想,i是遍历指针,j是指向字符串s的开头
let i = 0, j = 0, maxLength = 0
// 遍历字符串
for(i = 0; i < s.length; i++){
// 如果set里有s[i],即有重复过的字符
if(set.has(s[i])){
// 这里要用while,确保set里不存在s[j]
while(set.has(s[i])){
// 删除s[j],即第一次出现的重复值
set.delete(s[j])
// 将j指向下一个值
j++
}
// 再将s[i]加入set里
set.add(s[i])
}else{
// 如果set里没有s[i],即没有重复过的字符
// 添加进set
set.add(s[i])
// 更新无重复字符的最长子串的值
maxLength = Math.max(maxLength, set.size)
}
}
// 返回无重复字符的最长子串的值
return maxLength
};
005.最长回文子串
https://leetcode.cn/problems/longest-palindromic-substring/
变量:start,maxLength
方法:中心扩展算法expandAroundCenter
1.如果字符串长度小于2,直接返回自身
2.创建start为0,即最长回文子串的起始位置;创建最大长度maxLength为1
3.定义中心扩展算法(left,right)双指针
只要当左右指针都不越界,且左右字符都相等时
如果s内的长度(right - left + 1)> maxLength,即在s内找到了更大的回文子串
更新maxLength的值为s的长度,更新start = left
双指针变动,left-- right++
4.遍历字符串s,考虑到两种情况
i-1,i+1
i, i+1
5.返回s.substring(start, start + maxLength)
/**
* @param {string} s
* @return {string}
*/
var longestPalindrome = function(s) {
// 如果字符串长度小于2,即为a这种,直接返回自身即可
if(s.length < 2){
return s
}
// 设置start,即最长回文字符串的起始位置
let start = 0
// 设置最长回文字符串的长度
let maxLength = 1
// expandAroundCenter中心扩展算法
function expandAroundCenter(left, right){
// 只要当左右都不越界,且左右字符相等时
while(left >= 0 && right < s.length && s[left] === s[right]){
// 如果s内的长度大于maxLength,即在s内找到了更大的回文子串
if(right - left + 1 > maxLength){
// 更新maxLength长度
maxLength = right - left + 1
// 更新start起始位置
start = left
}
// 中心扩展
left--
right++
}
}
for(let i = 0; i < s.length; i++){
// abcba,c为中心
expandAroundCenter(i-1, i+1)
// abba,中心在b之间
expandAroundCenter(i, i+1)
}
// 返回最长回文字符串
return s.substring(start, start + maxLength)
};
015.三数之和
https://leetcode.cn/problems/3sum/description/
变量:i,start,end
方法:nums升序,确保数组不重复
1.定义result数组
2.给nums数组升序排序
3.遍历nums数组,从0至length-2,三指针注意边界
i是遍历指针,i是第一个元素时或新的数与之前的数不相等时,再进行判断,即跳过相同的数(不进行判断)
start = i + 1, end = length - 1,只要start < end
如果三数相加等于0,将三数push进result数组内,start++,end–
确保结果数组里不出现重复,只要start[i]===[i-1],start++
4.三数之和小于0,找更大的值,start++
5.三数之和大于0,找更小的值,end–
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
// 返回的结果
const result = []
// 给nums数组升序排序
nums.sort(function(a, b) {
return a - b
})
// 遍历nums数组,由于三指针,注意边界
for(let i = 0; i < nums.length-2; i++){
// i为第一个元素时或新的数与之前的数不相等时,再进行判断;
// 即跳过相同的数,不进行判断
if(i === 0 || nums[i] !== nums[i-1]){
// 三指针
let start = i + 1
let end = nums.length - 1
// 只要start不等于end
while(start < end){
// 如果三数相加等于0
if(nums[i] + nums[start] + nums[end] === 0){
// 将三数放进结果数组里
result.push([nums[i],nums[start],nums[end]])
start++
end--
// 确保结果数组里不出现重复
while(start < end && nums[start] === nums[start - 1]){
start++
}
// 三数之和小于0,找更大的值
}else if (nums[i] + nums[start] + nums[end] < 0){
start++
// 三数之和大于0,找更小的值
}else if (nums[i] + nums[start] + nums[end] > 0){
end--
}
}
}
}
return result
};
019.删除链表的倒数第N个节点
https://leetcode.cn/problems/remove-nth-node-from-end-of-list/
变量:dummy,start,end
方法:双指针滑动窗口,遍历n控制end位置
1.创建dummy空节点,dummy指向head
2.双指针start和end都指向dummy,遍历n控制end和start的身位,i<=n
3.只要end不为空,end.next;start.next
4.删除节点start.next=start.next.next
5.return dummy.next
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} n
* @return {ListNode}
*/
var removeNthFromEnd = function(head, n) {
// 定义链表头,dummy为空节点
let dummy = new ListNode()
// 让dummy指向head
dummy.next = head
// 设置双指针,利用滑动窗口,定位到倒数第n个节点
let start = dummy
let end = dummy
// 让end指针与start指针间隔n+1个位置
// i<=n,即end !== null
// i<n,即end.next !== null
for(let i = 0; i <= n; i++){
end = end.next
}
// 只要end节点不为空,就让双指针继续挪动
while(end !== null){
start = start.next
end = end.next
}
// end为空时,start节点在要删除的节点的前一个位置
// 直接将指针跳过要删除的节点,指向它的next
start.next = start.next.next
// 返回头节点
return dummy.next
};
020.有效的括号
https://leetcode.cn/problems/valid-parentheses/
变量:map,stack
方法:map存括号,stack模拟栈,左括号push,右括号pop
1.new Map存set括号,k是左括号,v是右括号
2.new Array当stack,遍历字符串s
map里有没有这个k,有的话把v push进stack;
map里没有这个k,比较s[i]和stack.pop,不一样false
3.如果map.has(s),即Map里有s[i]这个k,即左括号,将对应的右括号存入stack
4.如果Map里没有s这个k,即右括号,比较s[i]和stack.pop是不是一样。不一样return false
5.遍历完s,看stack是否为空,不为空false;
6.最后return true
/**
* @param {string} s
* @return {boolean}
*/
var isValid = function(s) {
// 定义一个map来放配对的括号
const mapping = new Map()
mapping.set('(',")")
mapping.set('[',"]")
mapping.set('{',"}")
// 定义一个数组作为栈
const stack = []
// 遍历字符串
for(let i = 0; i < s.length; i++){
// 如果Map里有s[i],即s[i]是左括号
if(mapping.has(s[i])){
// 将Map里s[i]对应的右括号push进栈
stack.push(mapping.get(s[i]))
// 如果Map里没有s[i],即s[i]是右括号
}else{
// 如果pop出栈的不是s[i]
if(stack.pop() !== s[i]){
// 不符合有效括号集定义
return false
}
}
}
// 如果栈不为空,即还有剩余的括号
if(stack.length !== 0){
// 不符合有效括号集定义
return false
}
// 排除万难,即符合定义
return true
};
021.合并两个有序链表
https://leetcode.cn/problems/merge-two-sorted-lists/description/
变量:dummy,curr
1.创建空链表dummy,curry指向dummy
2.只要l1和l2都不为空,while每次都curr.next
3.如果l1<l2,curr指向l1且l1.next;else curr指向l2且l2.next
4.如果剩下的l1不为空,全指向l1;l2同理
5.return dummy.next
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} list1
* @param {ListNode} list2
* @return {ListNode}
*/
var mergeTwoLists = function(list1, list2) {
// 创建一个空结点dummy作为新链表
let dummy = new ListNode()
// 让curr指向dummy
let curr = dummy
// 只要l1和l2都不为空
while(list1 !== null && list2 !== null){
// 如果l1的值<l2
if(list1.val < list2.val){
// curr指向l1
curr.next = list1
// l1进一步
list1 = list1.next
// 如果l2的值<l1
}else{
// curr指向l2
curr.next = list2
// l2进一步
list2 = list2.next
}
// curr进一步
curr = curr.next
}
// 如果l1还有剩余
if(list1 !== null){
// curr直接指向l1
curr.next = list1
}
// 如果l2还有剩余
if(list2 !== null){
// curr直接指向l2
curr.next = list2
}
// 返回链表dummy.next
return dummy.next
};
024.两两交换链表中的节点
https://leetcode.cn/problems/swap-nodes-in-pairs/description/
变量:dummy,curr
1.创建空节点dummy指向head,创建遍历指针curr指向dummy
2.只要curr后还有2个节点不为空
3.定义n1和n2,分别是curr的下一个节点和下下个节点
4.curr.next; n1.next ; n2.next
5.curr指向n1,此时curr下一个节点为n2,应跳过n2节点
6.return dummy.next
/**
* 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 swapPairs = function(head) {
// 创建空节点dummy
let dummy = new ListNode()
// 让dummy节点指向head
dummy.next = head
// 定义遍历指针curr
let curr = dummy
// 只要curr后还有两个节点不为空
while(curr.next !== null && curr.next.next !== null){
//定义n1,即curr的下一个节点
let n1 = curr.next
//定义n2,即curr的下下一个节点
let n2 = curr.next.next
// curr n1 n2,按照顺序找到next
curr.next = n2
n1.next = n2.next
n2.next = n1
// 将curr指向n1,此时curr下一个节点为n2,应跳过n2节点
curr = n1
}
// 返回交换后的链表头节点
return dummy.next
};
049.字母异位词分组
https://leetcode.cn/problems/group-anagrams/description/
变量:map,characters,ascii,key
方法:创建key,map有就追加,没有就添加
1.检查数组是否为空,若为空返回空数组
2.创建map,k为含26个字母的数组转换的字符串,v为数组(不同的字母异位词)
characters是含26个字母的空数组[0,…,0]; ascii是字母的索引(a为97);characters[ascii]是字母出现的次数
3.遍历strs数组,定义characters
4.遍历str字母,定义每个字母的ASCII - 97,让数组内字母出现的次数+1
5.定义key,将characters用’-'join成独一无二的字符串
6.如果map里面有k,展开运算符将str追加进map.get(k);没有k,直接将str加入v
7.遍历map,将map中的v返回给一个数组,并返回该数组作为结果
/**
* @param {string[]} strs
* @return {string[][]}
*/
var groupAnagrams = function(strs) {
// 判断字符串是否为空
if(strs.length === 0){
// 如果为空,返回空数组
return []
}
// 创建map,k为含26个字母的数组转换的字符串,v为数组(不同的字母异位词)
const map = new Map()
// 遍历字符串strs中的每个元素str
for(const str of strs){
// 定义含26个字母的空数组(作为map的key)
// ES6新方法,Array(数量).fill(填充内容)
const characters = Array(26).fill(0)
// 遍历每个str的字母
for(let i = 0; i < str.length; i++){
// 定义每个字母的ASCII码,并且a为97,使得从0开始,作为characters的索引
const ascii = str.charCodeAt(i) - 97
// 让数组内对应的索引的值+1,默认为全0,例如b出现一次,即characters[2] = 0+1
characters[ascii]++
}
// 将含26个字母的数组转换的字符串,且用-连接,确保独一无二
const key = characters.join('-')
// 如果map里面有k,如已有tea,又来eat
if(map.has(key)){
// 将str追加入对应k的v中
// ES6新方法,...展开运算符
map.set(key, [...map.get(key), str])
// 如果map里面没有k
}else{
// 将str加入对应k的v中
map.set(key, [str])
}
}
// 遍历map,将v返回给一个数组
const result = []
for(const arr of map){
// 将map里的v,push进result数组
// arr[0]代表k,arr[1]代表v
result.push(arr[1])
}
// 返回结果
return result
};
053.最大子数组和
https://leetcode.cn/problems/maximum-subarray/description/
变量:memo,max
方法:for循环从1开始,memo[i - 1] + nums[i], nums[i]
1.创建记忆数组memo,表示最大和的子数组
2.第一个值nums[0],直接赋给memo和max
3.遍历nums数组,从1开始,用Math.max()
比较旧数组+新元素单独成一个数组的和和新元素单独成一个数组的大小
memo[i - 1] + nums[i], nums[i]
比较max和memo[i]的大小
4.返回max
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
// 创建记忆数组
const memo = []
// 第一个值直接赋给数组
memo[0] = nums[0]
// 让最大值为第一个数
let max = nums[0]
// 遍历nums数组,从第一个开始
for(let i = 1; i < nums.length; i++){
// 判断保留旧数组,还是让新元素单独成一个数组
// 比较旧数组+新元素单独成一个数组和新元素单独成一个数组的大小
// nums[i]新元素单独成一个数组
// memo[i]最大数组
// memo[i - 1]最大旧数组
memo[i] = Math.max(memo[i - 1] + nums[i], nums[i])
// 判断max和memo[i]谁比较大
max = Math.max(max, memo[i])
}
// 返回最大值
return max
};
054.螺旋矩阵
https://leetcode.cn/problems/spiral-matrix/description/
变量:上下左右边界,direction
方法:只要不越界,右下左上,i是右左右左
1.判空,返回空数组
2.定义上下左右四个边界、方向(顺时针:右下左上)、结果数组
3.只要左 <= 右 且 上 <= 下
如果方向为右,遍历向右行,左到右,i++,push值matrix[top][i],top++,方向改为下
如果方向为下,遍历向下行,上到下,i++,push值matrix[i][right],right–,方向改为左
如果方向为左,遍历向左行,右到左,i–,push值matrix[bottom][i],bottom–,方向改为上
如果方向为上,遍历向上行,下到上,i–,push值matrix[i][left],left++,方向改为右
/**
* @param {number[][]} matrix
* @return {number[]}
*/
var spiralOrder = function(matrix) {
// 空数组直接返回空数组
if (matrix.length === 0) {
return []
}
// 定义上下左右四个边界
let top = 0
let bottom = matrix.length - 1
let left = 0
let right = matrix[0].length - 1
// 定义方向,顺时针,右下左上
let direction = "right"
// 定义结果数组
let result = []
// 只要左<=右且上<=下
while (left <= right && top <= bottom) {
if(direction === "right"){
// 遍历向右行,left到right
for(let i = left; i <= right; i++){
// 将值push进结果数组
result.push(matrix[top][i])
}
// 走完了top一层
top++
// 改变方向,向下
direction = "down"
}else if (direction === "down"){
// 遍历向下行,top到bottom
for(let i = top; i <= bottom; i++){
// 将值push进结果数组
result.push(matrix[i][right])
}
// 走完了right一层
right--
// 改变方向,向左
direction = "left"
}else if (direction === "left"){
// 遍历向左行,right到left
for(let i = right; i >= left; i--){
// 将值push进结果数组
result.push(matrix[bottom][i])
}
// 走完了bottom一层
bottom--
// 改变方向,向上
direction = "top"
}else if (direction === "top"){
// 遍历向上行,bottom到top
for(let i = bottom; i >= top; i--){
// 将值push进结果数组
result.push(matrix[i][left])
}
// 走完了left一层
left++
// 改变方向,向右
direction = "right"
}
}
// 返回结果
return result
};
055.跳跃游戏
https://leetcode.cn/problems/jump-game/description/
变量:maxJump
方法:Greedy贪心算法,length-2至0,i+nums[i]>=maxJump
1.定义最大跳数maxJump为 长度-1
2.从后往前遍历数组,且从倒数第二个开始至第一个
如果 索引+值 >= maxJump,是个通的点
更新最大跳数到通的点
3.如果maxJump为0,返回true
/**
* @param {number[]} nums
* @return {boolean}
*/
var canJump = function (nums) {
// Greedy贪心算法
// 定义最大跳数为长度-1
let maxJump = nums.length - 1
// 从后往前遍历数组,且从倒数第二个开始至第一个
for (let i = nums.length - 2; i >= 0; i--) {
// 如果索引+值大于等于最大跳数,是个通的点
if (i + nums[i] >= maxJump) {
// 更新最大跳数到通的点
maxJump = i
}
}
// 如果maxJump为0,返回true
return maxJump === 0
};
056.合并区间
https://leetcode.cn/problems/merge-intervals/description/
变量:curr,result,interval
方法:长度判断<2,数组升序,遍历数组curr1[1]和interval[0],判断curr的length为空
1.判空或1个元素,返回原数组
2.数组sort升序,按照元素第一位数
3.定义curr指针为第1个元素和结果数组
4.for of遍历数组,可以合并的情况,即curr的终止位置>interval的起始位置,将curr的终止位置更新取大值
不可以合并的情况,将前一个合并后的数组curr推进结果,指针指向interval继续遍历
5.如果最后一位是可以合并的情况,需要push
6.返回结果数组
/**
* @param {number[][]} intervals
* @return {number[][]}
*/
var merge = function (intervals) {
// 如果长度为0或只有1个
if (intervals.length < 2) {
return intervals
}
// 按照数组第一位数排序
intervals.sort(function (a, b) {
return a[0] - b[0]
})
// 定义指针
let curr = intervals[0]
// 定义结果数组
let result = []
// 遍历intervals数组
for (let interval of intervals) {
// 可以合并的情况,即curr的终止位置>interval的起始位置
if (curr[1] >= interval[0]) {
// 将curr的终止位置更新,取大值
curr[1] = Math.max(curr[1], interval[1])
// 不可以合并的情况
}else{
// 将前一个合并后的数组push进结果
result.push(curr)
// 指针指向interval继续遍历
curr = interval
}
}
// 如果最后一位是可以合并的情况,需要push
if(curr.length !== 0){
result.push(curr)
}
// 返回结果
return result
};
062.不同路径
https://leetcode.cn/problems/unique-paths/description/
遍历:memo二维数组
方法:第1行和第1列都为1,m为row,n为col,遍历二维数组,memo[row][col] = memo[row - 1][col] + memo[row][col - 1]
1.创建memo二维数组
2.第一列和第一行的所有格子的不同路径数都只有1条
填充第一列为1;填充第一行为1
4.遍历memo二维数组,新格子的不同路径数 = 左边格子和上边格子路径数之和
6.返回结果,注意索引要-1
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function (m, n) {
// 创建memo数组
const memo = []
// 二维memo数组
// m是行,n是列;按照行再创建数组
for (let i = 0; i < m; i++) {
memo.push([])
}
// 第一列和第一行的路径只有1条
// 填充第一列为1
for (let row = 0; row < m; row++) {
memo[row][0] = 1
}
// 填充第一行为1
for (let col = 0; col < n; col++) {
memo[0][col] = 1
}
// 遍历memo二维数组
for (let row = 1; row < m; row++) {
for (let col = 1; col < n; col++) {
// 新格子的不同路径数 = 左边格子和上边格子路径数之和
memo[row][col] = memo[row - 1][col] + memo[row][col - 1]
}
}
// 返回结果,索引要-1
return memo[m-1][n-1]
};
066.加一
https://leetcode.cn/problems/plus-one/description/
方法:右往左遍历,是不是9,进位处理[1,…digits]
1.从右往左遍历digits数组
2.如果元素不是9,首先是最右边的个位,加1,返回数组;
元素是9,即个位是9,仅需将个位设为0,遍历到十位会加1的
3.如果是99或999这种情况,则上述遍历不会返回结果
直接返回使用展开运算符将1放至第一位
/**
* @param {number[]} digits
* @return {number[]}
*/
var plusOne = function (digits) {
// 从右往左遍历digits数组
for (let i = digits.length - 1; i >= 0; i--) {
// 如果元素不是9,首先是最右边的个位
if (digits[i] !== 9) {
// 加1
digits[i]++
// 返回数组
return digits
// 元素是9,即个位是9
}else{
// 仅需将个位置为0,遍历到十位会加1的
digits[i] = 0
}
}
// 如果是99或999这种情况,则上述遍历不会返回结果
// 直接使用展开运算符将1放至第一位
return [1, ...digits]
};
070.爬楼梯
https://leetcode.cn/problems/climbing-stairs/description/
变量:memo数组
方法:DP,1 2 3<=n
1.创建记忆化数组memo,定义memo[1]和memo[2]
2.遍历n,memo[i] = memo[i - 2] + memo[i - 1]
3.返回memo[n]
/**
* @param {number} n
* @return {number}
*/
var climbStairs = function (n) {
// 创建记忆化数组memo
const memo = []
// 1楼只有1种方法,1
memo[1] = 1
// 2楼有2种方法,11,2
memo[2] = 2
// 3楼有3种方法,111,12,21
// 遍历数组n
for (let i = 3; i <= n; i++) {
// 到达i的方法数是memo[i-2]和memo[i-1]的和
memo[i] = memo[i - 2] + memo[i - 1]
}
// /返回结果
return memo[n]
};
073.矩阵置零
https://leetcode.cn/problems/set-matrix-zeroes/description/
变量:m,n,row,col
方法:遍历二维数组,通过元素为0设置row和col;通过row和col状态设置零
1.创建m、n,m是row,n是col
2.Array().fill(false)填充row和col
3.遍历二维数组,如果某一个元素是0,将其对应的row、col,设为true
4.遍历二维数组,如果row或col是true,将其对应的元素置为0
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var setZeroes = function(matrix) {
// m是行,n是列
const m = matrix.length, n = matrix[0].length;
// 两个标记数组row和col,填充false
const row = new Array(m).fill(false);
const col = new Array(n).fill(false);
// 遍历二维数组,将0所在行和列的标记数组置为true
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (matrix[i][j] === 0) {
row[i] = col[j] = true;
}
}
}
// 遍历二维数组,通过标记数组row和col的状态来置零
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (row[i] || col[j]) {
matrix[i][j] = 0;
}
}
}
};
078.子集(元素互不相同)
https://leetcode.cn/problems/subsets/description/
变量:result
方法:回溯 rpc cpi bic cp
1.创建结果空数组result
2.定义回溯函数backtrack,start是nums数组的开始查找的索引,curr是待处理的子集数组
把curr数组值传递push([…curr])进result
遍历nums数组,从start开始
把nums[i]push进curr
递归调用(i + 1,curr)
把curr数组pop出最后一个元素
3.backtrack(0, []),让回溯跑起来
4.返回result数组
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsets = function (nums) {
// 定义结果数组
const result = []
// 定义回溯函数,start是nums数组的开始查找的索引,curr是待处理的子集数组
function backtrack(start, curr) {
// 将curr数组值传递给result数组
result.push([...curr])
// 遍历nums数组,从start开始
for (let i = start; i < nums.length; i++) {
// 把nums[i]加入curr数组
curr.push(nums[i])
// 递归调用
backtrack(i + 1, curr)
// 把curr数组的最后一个元素移除
curr.pop()
}
}
// 让回溯跑起来,0,[]
backtrack(0, [])
// 返回结果数组
return result
};
090.子集Ⅱ(元素可以重复)
https://leetcode.cn/problems/subsets-ii/description/
变量:result
方法:升序,回溯,rpc cpi bic cp,i > start && nums[i] === nums[i - 1] 跳过
1.创建结果空数组result
2.定义回溯函数backtrack,start是nums数组的开始查找的索引,curr是待处理的子集数组
把curr数组值传递push([…curr])进result
遍历nums数组,从start开始
判断i > start且数组中的数是否与上一个相同,相同就continue跳过(i > start确保的是12和122这种情况,不误删122)
把nums[i]push进curr
递归调用(i + 1,curr)
把curr数组pop出最后一个元素
3.backtrack(0, []),让回溯跑起来
4.返回result数组
/**
* @param {number[]} nums
* @return {number[][]}
*/
var subsetsWithDup = function (nums) {
// 定义结果数组
const result = []
// 对nums数组升序排序
nums.sort((a, b) => {
return a - b
})
// 定义回溯函数,start是开始索引,curr是待处理的数组
function backtrack(start, curr) {
// 将curr数组值传递给result数组
result.push([...curr])
// 遍历nums数组,从start开始
for (let i = start; i < nums.length; i++) {
// 判断i > start且数组中的数是否与上一个相同,相同就跳过
// i>start确保的是12和122这种情况,不误删122
if (i > start && nums[i] === nums[i - 1]) {
continue
}
// 把nums[i]加入curr数组
curr.push(nums[i])
// 递归调用
backtrack(i + 1, curr)
// 把curr数组的最后一个元素移除
curr.pop()
}
}
// 让回溯跑起来,0,[]
backtrack(0, [])
// 返回结果数组
return result
};
083.删除排序链表中的重复元素
https://leetcode.cn/problems/remove-duplicates-from-sorted-list/description/
变量:curr
方法:不为空,比较值,跳过
1.创建curr遍历指针等于head
2.只要curr不为空,且curr的下一位不为空(即只剩最后一位情况)
如果curr的值等于下一位curr的值,删掉下一位节点
否则,指针正常遍历
3.返回head指针
/**
* 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 deleteDuplicates = function (head) {
// 定义遍历指针curr
let curr = head
// 只要curr不为空,且curr的下一位不为空(即只剩最后一位情况)
while(curr !== null && curr.next !== null){
// 如果curr的值等于下一位curr的值
if(curr.val === curr.next.val){
// 删掉下一位节点
curr.next = curr.next.next
// 如果curr的值不等于下一位curr的值
}else{
// 指针正常遍历
curr = curr.next
}
}
// 返回head指针
return head
}
206.反转链表(整个链表)
https://leetcode.cn/problems/reverse-linked-list/description/
变量:n p c
方法:n cn p c
1.定义后一个指针prev为空,遍历指针curr为head,下一个指针next为head
2.只要curr指针不为空,(next指针是为了占住下一位)
n cn p c
或 解构赋值curr.next -> prev, prev -> curr, curr -> curr.next
3.返回prev指针
/**
* 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) {
// 定义后一个指针prev为空
let prev = null
// 定义遍历指针curr为head
let curr = head
// 定义下一个指针next为head
let next = head
// 只要curr指针不为空
while(curr !== null){
// next指针是为了占住下一位
// n cn p c
next = curr.next
curr.next = prev
prev = curr
curr = next
// 解构赋值curr.next -> prev, prev -> curr, curr -> curr.next
// [curr.next,prev,curr] = [prev,curr,curr.next]
}
// 返回prev
return prev
};
092.反转链表Ⅱ(区间链表)
https://leetcode.cn/problems/reverse-linked-list-ii/description/
变量:n p c
方法:p和c移位置,p2,c2占位置,n cn p c,p2指向p,c2指向c
1.定义后一个指针prev为空,遍历指针curr为head,下一个指针next为head
2.将curr和prev移到left位,遍历从1至<left
prev = curr, curr = curr.next
3.定义prev2和curr2占住位置
4.遍历从left至=right,(next指针是为了占住下一位)
n cn p c
或 解构赋值curr.next -> prev, prev -> curr, curr -> curr.next
5.如果prev2不等于null,prev2指向prev;否则head指向prev
6.curr2指向curr
7.返回head指针
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} left
* @param {number} right
* @return {ListNode}
*/
var reverseBetween = function (head, left, right) {
// 定义后一个指针prev为空
let prev = null
// 定义遍历指针curr为head
let curr = head
// 定义下一个指针next为head
let next = head
// 将curr和prev移到left位
for (let i = 1; i < left; i++) {
prev = curr
curr = curr.next
}
// 定义prev2、curr2占住位置
let prev2 = prev
let curr2 = curr
// 从left至right
for (let i = left; i <= right; i++) {
// next指针是为了占住下一位
// n cn p c
next = curr.next
curr.next = prev
prev = curr
curr = next
// 解构赋值curr.next -> prev, prev -> curr, curr -> curr.next
// [curr.next,prev,curr] = [prev,curr,curr.next]
}
// 如果left不为1,即prev不为空
if(prev2 !== null){
prev2.next = prev
// prev为空,left为1
}else{
head = prev
}
// 让curr2指回占位的curr
curr2.next = curr
// 返回head
return head
};
121.买卖股票的最佳时机(只能交易一次)
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock/description/
变量:minPrice,maxProfit
方法:数组判空,最低价小更新,最大利润大更新
1.如果数组为空,返回0
2.初始化最低价格为数组第一位;初始化最大利润为0
3.遍历prices数组
如果值比最低价小,更新最低价格
如果产生了新的最大利润(当前值-最低值),更新最大利润
4.返回最大利润
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function (prices) {
// 如果数组为空,返回0
if (prices.length === 0) {
return 0
}
// 初始化最低价格为第一位
let minPrice = prices[0]
// 初始化最大利润为0
let maxProfit = 0
// 遍历prices数组
for (let i = 0; i < prices.length; i++) {
// 如果值比最低价小,更新最低价格
if (prices[i] < minPrice) {
minPrice = prices[i]
// 如果产生了新的最大利润
} else if (prices[i] - minPrice > maxProfit) {
maxProfit = prices[i] - minPrice
}
}
// 返回maxprofit
return maxProfit
};
122.买卖股票的最佳时机Ⅱ(可以交易多次)
https://leetcode.cn/problems/best-time-to-buy-and-sell-stock-ii/description/
变量:profit
方法:贪心算法,数组判空,今天小于明天即加上利润
1.如果数组为空,返回0
2.初始化利润为0
3.遍历prices数组,i<length-1,因为要和第二天比较
如果今天比明天小,更新利润,即加上两天的差
4.返回利润
/**
* @param {number[]} prices
* @return {number}
*/
var maxProfit = function (prices) {
// 数组判空
if (prices.length === 0) {
return 0
}
// 初始化利润为0
let profit = 0
// 遍历price数组,i < prices.length - 1,因为会和第二天比较
for (let i = 0; i < prices.length - 1; i++) {
// 如果第一天的值<第二天的值,即有利润
if (prices[i] < prices[i + 1]) {
// 更新利润
profit += prices[i + 1] - prices[i]
}
}
// 返回利润
return profit
};