本文会持续更新,总结学习过的算法
一、前缀和数组
1、应用场景
频繁的计算数组某个区间的和,例如:计算nums[i,j]这个区间和就是nums[i,j] = presum[j+1] - presum[i]
2、代码实现
// 构造前缀和数组
function createPreSum(nums){
let presum = new Array(nums.length + 1)
presum[0] = 0
for(let i = 0;i<nums.length;i++){
presum[i+1] = presum[i] + nums[i]
}
return presum
}
二、差分数组
1、应用场景
频繁的对数组某个区间进行增加和减少
2、代码实现
// 构造差分数组
function createDiffArr(nums){
let diffarr = new Array(nums.length)
diffarr[0] = nums[0]
for(let i = 1;i<nums.length;i++){
diffarr[i] = nums[i] - nums[i-1]
}
return diffarr
}
// 构造原数组
function createNums(diffarr){
let nums = new Array(diffarr.length)
nums[0] = diffarr[0]
for(let i = 1;i<nums.length;i++){
nums[i] = nums[i-1] + diffarr[i]
}
return diffarr
}
// 区间的增加和减少
function increaseNums(i,j,number,diffarr){ // number可以为正数也可以是负数
diffarr[i] = diffarr[i] + number
if(j < diffarr.length - 1){
diffarr[j+1] = diffarr[j+1] - number
}
}
三、双指针技巧秒杀七道数组题目
双指针分类:
- 快慢指针
- 左右指针
- 中间向两端
- 两端向中间
1、原地去重数组(必须是排序数组)
function removeDuplicates(nums){
if(nums.length === 0) return []
let slow=0,fast=0
while(fast<nums.length){
if(nums[slow] !== nums[fast]){
slow++
nums[slow] = nums[fast]
}
fast++
}
return nums.slice(0,slow+1)
}
2、原地删除数组的某个元素
function removeElement(nums,val){
let slow=0,fast=0
while(fast<nums.length){
if(nums[fast] !== val){
nums[slow] = nums[fast]
slow++
}
fast++
}
}
3、判断是否为回文串
function isPalindrome(str){
let left=0,right=str.length-1
while(left < right){
if(str.charAt(left) !== str.charAt(right)){
return false
}
left++
right--
}
return true
}
4、最长的回文子串
function Palindrome(str,left,right){
while(0<=left && right<=str.length-1){
if(str.charAt(left) === str.charAt(right)){
left--
right++
}
}
return str.slice(left+1,right)
}
function longestPalindrome(nums){
let res = ''
for(let i=0;i<nums.length;i++){
let str1 = Palindrome(str,i,i)
let str2 = Palindrome(str,i,i+1)
res = res.length>str1.length?res:str1
res = res.length>str2.length?res:str2
}
return res
}
四、双指针技巧之链表
五、滑动窗口
1、应用场景
处理子串问题方面
2、代码实现
function silidingWindow(str){
let left=0,right=0
while(right<str.length){
window.push(str.charAt(right))
right++ // 扩大窗口
// 更新数据操作
// 收缩窗口的条件
while(){
window.splice(0,1)
left++
// 更新数据操作
}
}
}
六、二分查找
1、应用场景
查询某一个数的位置
2、代码实现
1、普通二分查找
function binary_search(nums,target){
let left=0,right=nums.length-1
while(left<=right){
let mid = left+(right-left)/2
if(nums[mid]===target) return mid
else if(nums[mid]<target) left=mide+1
else right=mid-1
}
return -1
}
2、左边界二分查找
function left_bound(nums,target){
let left=0,right=nums.length-1
while(left<=right){
let mid = left+(right-left)/2
if(nums[mid] === target) right=mid-1
else if(nums[mid]<target) left=mid+1
else right=mid-1
}
if(left >= nums.length || nums[left]!== target) return -1
return left
}
3、右边界二分查找
function right_bound(nums,target){
let left=0,right=nums.length-1
while(left<=right){
let mid= left+(right-left)/2
if(nums[mid]===target) left=mid+1
else if(nums[mid]<target) left=mid+1
else right=mid-1
}
if(right<0||nums[right]!==target) return -1
return right
}
七、括号问题
八、单调栈问题
归并排序
1、应用场景
归并排序可以获取到某个元素它后面有多少个比它自己小的
2、代码实现
function mergeSort(nums){
const merge = (nums,lo,mid,hi)=>{
let temp = nums.slice(lo,hi+1)
let i=lo,hi=mid+1
for(let p=lo;p<=hi;p++)
{
if(i===mid+1) nums[p]=temp[j++]
else if(j===hi+1) nums[p]=temp[i++]
else if(temp[i]>temp[j]) nums[p]=temp[j++]
else nums[p]=temp[i++]
}
}
const sort = (nums,lo,hi)=>{
if(lo === hi) return
let mid = Math.floor(lo+(hi-lo)/2)
sort(nums,lo,mid)
sort(nums,mid+1,hi)
merge(nums,lo,mid,hi)
}
sort(nums,0,nums.length-1)
}
快速排序
nsum问题
1、实现思路
nsum问题可以看做一个单独的数和其他n-1sum的问题的搭配,例如3sum问题可以拆成一个1数字+2sum和的问题
2、代码实现
function nsum(nums,target,start,n){
let size = nums.length
let res = []
if(n<2||size<n) return res
if(n===2){
let lo = start,hi = size-1
while(lo<hi){
let sum = nums[lo]+nums[hi]
let left = nums[lo],right = nums[hi]
if(sum > target){
while(lo<hi&&nums[hi] === right)hi--
}else if(sum <target){
while(lo<hi&&nums[lo]===left)lo++
}else{
res.push([left,right])
while(lo<hi&&nums[lo]===left)lo++
while(lo<hi&&nums[hi]===left)hi--
}
}
}else{
for(let i=start;i<size;i++){
let sub = nsum(nums,target-nums[i],i+1,n-1)
sub.forEach(item=>{
item.push(nums[i])
res.push(item)
})
while(i<size-1&&nums[i] === nums[i+1])i++
}
}
return res
}
反转链表集合
// 后序遍历
var reverseList = function(head) {
if(head === null || head.next===null) return head
let last = reverseList(head.next)
head.next.next = head
head.next = null
return last
};
const reverseN = (head,n)=>{
if(n===1){
// 存下一个节点
nextNode = head.next
return head
}
let last = reverseN(head.next,n-1)
head.next.next = head
head.next = nextNode
return last
}
var reverseBetween = function(head, left, right) {
let nextNode = ''
const reverseN = (head,n)=>{
if(n===1){
// 存下一个节点
nextNode = head.next
return head
}
let last = reverseN(head.next,n-1)
head.next.next = head
head.next = nextNode
return last
}
if(left===1){
return reverseN(head,right)
}
head.next = reverseBetween(head.next,left-1,right-1)
return head
};
LeetCode刷题
一、前缀和数组
1、209.长度最小的子数组
var minSubArrayLen = function(target, nums) {
// 构造前缀和数组
const createPreSum = (nums)=>{
let presum = new Array(nums.length + 1)
presum[0] = 0
for(let i = 0;i<nums.length;i++){
presum[i + 1] = presum[i] + nums[i]
}
return presum
}
let presum = createPreSum(nums)
let minLength = nums.length + 1
// 1、双重for循环 时间复杂度为O(n*n)
/*for(let i = 0;i<nums.length;i++){
for(let j = 0;j<nums.length;j++){
// 计算nums[i,j]区间和
if(presum[j+1]-presum[i] >= target){
let length = j - i + 1
minLength = Math.min(length,minLength)
}
}
}*/
// 2、快慢指针,时间复杂度为O(n)
/*let right = 0,left = 0
while(right < presum.length){
if(presum[right] - presum[left] < target){
right ++
}else{
let length = right - left
minLength = Math.min(minLength,length)
left ++
}
}*/
return minLength === nums.length+1?0:minLength
};
2、724.寻找数组的中心下标
var pivotIndex = function(nums) {
// 构造前缀和数组
const createPreSum = (nums)=>{
let presum = new Array(nums.length + 1)
presum[0] = 0
for(let i=0;i<nums.length;i++){
presum[i+1] = presum[i] + nums[i]
}
return presum
}
// 创建中心下标指针
let center = -1
let leftsum = 0,rightsum = 0
// 创建前缀和数组
let presum = createPreSum(nums)
// 寻找中心下标
for(let i=0;i<nums.length;i++){
if(i === 0){
leftsum = 0
rightsum = presum[nums.length] - presum[i + 1]
}else if(i === nums.length - 1){
rightsum = 0
leftsum = presum[nums.length - 1]
}else{
rightsum = presum[nums.length] - presum[i + 1]
leftsum = presum[i]
}
if(rightsum === leftsum){
center = i
break
}
}
return center
};
二、差分数组
1、1109. 航班预订统计
var corpFlightBookings = function(bookings, n) {
// 构造原数组
const createNums = (diffarr)=>{
let nums = new Array(n)
nums[0] = diffarr[0]
for(let i=1;i<diffarr.length;i++){
nums[i] = nums[i-1] + diffarr[i]
}
return nums
}
// 区间的增加或者减少
const increaseNum = (i,j,number,diffarr)=>{
diffarr[i] += number
if(j < diffarr.length - 1){
diffarr[j+1] -= number
}
}
// 创建差分数组
let diffarr = new Array(n).fill(0)
// 遍历对区间进行增加或者减少
bookings.forEach(item=>{
increaseNum(item[0]-1,item[1]-1,item[2],diffarr)
})
// 创建原数组
return createNums(diffarr)
};
2、1094.拼车
var carPooling = function(trips, capacity) {
// 构造原数组
const createNums = (diffarr)=>{
const nums = new Array(diffarr.length)
nums[0] = diffarr[0]
for(let i=1;i<diffarr.length;i++){
nums[i] = nums[i-1] + diffarr[i]
}
return nums
}
// 区间的增加或者减少
const increaseNum = (i,j,number,diffarr)=>{
diffarr[i] += number
if(j<diffarr.length - 1){
diffarr[j+1] -= number
}
}
// 创建差分数组
let diffarr = new Array(1001).fill(0)
// 遍历
trips.forEach(item=>{
increaseNum(item[1],item[2]-1,item[0],diffarr)
})
// 创建原数组
let nums = createNums(diffarr)
return !nums.some(item=>item>capacity)
};
五、滑动窗口
对于滑动窗口来说,存储字符时,考虑用对象来进行存储。
1、76. 最小覆盖子串
var minWindow = function(s, t) {
let need = {}
for(let i=0;i<t.length;i++){
need[t.charAt(i)] = (need[t.charAt(i)] || 0) + 1
}
let start=0,minlength=s.length+1,res=''
let window = {}
let valid = 0
let left=0,right=0
while(right<s.length){
let char1 = s.charAt(right)
right++
// 更新数据
if(need[char1]){
window[char1] = (window[char1] || 0) + 1
if(window[char1] === need[char1]){
valid++
}
}
while(valid === t.length){
let length = right - left
start = left
if(length < minlength){
res = s.slice(start,start+length)
console.log(res)
minlength = length
}
let char2 = s.charAt(left)
left++
if(need[char2]){
if(window[char2] === need[char2]){
valid--
}
window[char2] --
}
}
}
return res
};
剑指offer
1、剪绳子14-1
方法一:暴力递归+回溯
// let maxVal = 1
// let track = []
// const traverse = (n)=>{
// if(n===0){
// let sum = 1
// if(track.length !== 1){
// track.forEach(item=>{
// sum = sum*item
// })
// }
// maxVal = Math.max(maxVal,sum)
// return
// }
// for(let i=1;i<=n;i++){
// track.push(i)
// traverse(n-i)
// track.pop()
// }
// }
// traverse(n)
// return maxVal
方法二:后序遍历
const traverse = (n,step)=>{
if(step === 1&&n === 0){
return 0
}else if(step >1 &&n===0){
return 1
}
let maxVal = 1
for(let i=1;i<=n;i++){
let sum = i * traverse(n-i,step+1)
maxVal = Math.max(sum,maxVal)
}
return maxVal
}
return traverse(n,0)
方法三:后序遍历+备忘录
let visited = []
const traverse = (n,step)=>{
if(step === 1&&n === 0){
return 0
}else if(step >1 &&n===0){
return 1
}
let maxVal = 1
for(let i=1;i<=n;i++){
let sum = 1
if(visited[n-i]){
sum = i * visited[n-i]
}else{
sum = i * traverse(n-i,step+1)
}
maxVal = Math.max(sum,maxVal)
}
visited[n] = maxVal
return maxVal
}
return traverse(n,0)
方法四:动态规划
let dp = new Array(n+1).fill(0)
dp[2] = 1
// dp定义:长度为n,最大值为dp[n]
for(let i=3;i<=n;i++){
for(let j=2;j<=i;j++){
dp[i] = Math.max(dp[i],Math.max(j*dp[i-j],j*(i-j)))
}
}
return dp[n]