排序
冒泡排序
搜索插入位置
记住找得到的时候插入位置就是mid 找不到的时候 right就是你的左边界 插入right+1处即可
二分查找
一个容易犯的小错误是数组最后一个元素的下标是length-1
二分法:数组有序、数组元素不重复
二分法的循环条件是left<=right
当一直找不到跳出循环时 right在左left在右 right指左边界 left指右边界
(均不包括target)(target在right和left指的元素的范围之间)
元素在right和left之间
最后四种情况:(其实也就两种:找到和找不到)
- target在数组左边 left指向0 right一直往左移到指向-1跳出循环
- target在数组右边 right指向length-1 left一直往右移到length跳出循环
- target在数组中且找到 mid指向target位置 此时left和right是正常的
- target在数组中且找不到 跳出时 right在前 left在后 target在两个指的范围之间
简单说就是:
找到了:left在左 right 在右 mid在中间指向找到的元素
找不到:right在左是右边界 left在右是左边界 target在right和left数值范围之间(且不包含)
排序数组中查找元素的第一个和最后一个位置
在mid就算已经找到的情况下 持续让左指针往右逼近/右指针往左逼近 直到跳出循环
此时right为左边界 left为右边界 target范围在左右边界之间(且不包含)
var searchRange = function(nums, target) {
let left=searchLeft(nums,target)
let right=searchRight(nums,target)
// 说明找到了
if(nums[left+1]===target || nums[right-1]===target) return [left+1,right-1]
return [-1,-1]
};
// 跳出循环时 right就是左边界 left是右边界
// 找左边界
var searchLeft = function(nums,target){
let left = 0,right=nums.length-1
// 闭闭区间 等于号有效
// 尽可能向左逼近 就算找到了也向左逼近
while(left<=right){
let mid = left + ((right-left) >> 1)
if(nums[mid] < target){
left = mid+1
}else{
right = mid-1
}
}
return right
}
var searchRight = function(nums,target){
let left = 0,right=nums.length-1
// 尽可能向右逼近
while(left<=right){
let mid = left + ((right-left) >> 1)
if(nums[mid] <= target){
left=mid+1
}else{
right=mid-1
}
}
return left
}
移除元素
双指针法 思路是快指针遍历原数组 慢指针指向新数组的下标
比较含退格的字符串(难)
String用split('')转成Array在处理
返回的时候k指向新数组的length位 用slice切片之后再join回字符串
slice含左不含右
退格慢指针最多退到0
var backspaceCompare = function(s, t) {
return (backspaceString(s) == backspaceString(t))
};
var backspaceString = function(s){
// 注意先把字符串转为数组才能进行操作
const arr = s.split('')
let k = 0 //k指新数组元素 i遍历当前数组的下标
for(let i=0;i<arr.length;i++){
if(arr[i]!=='#'){
arr[k]=arr[i]
k++
}
// 碰到# 慢指针退格
if(arr[i]==='#'){
k>=1 ? k-- : k = 0 //最多退到0
}
}
return arr.slice(0,k).join('')
}
两数之和
哈希:Map 什么时候使用哈希法,当我们需要查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法
两数之和:一个for循环来遍历元素 一个map来存遍历过的元素 每次循环时查map里是否有符合条件的元素
var twoSum = function(nums, target) {
const map = new Map()
// map存已经遍历过的元素和对应下标
for(let i=0;i<nums.length;i++){
// 先从map里查找是否有符合条件的元素
if(map.has(target-nums[i])) return [i,map.get(target-nums[i])]
// 没有的话 存入该元素
map.set(nums[i],i)
}
};
三数之和
双指针 先排序 然后遍历 当前i指target 然后双指针二分查
var threeSum = function(nums) {
// 升序排序
nums.sort((a,b)=>a-b)
let result = []
// 遍历时 i指target
// -4 -1 -1 0 1 2
for(let i=0;i<nums.length;i++){
// 定义左右两个指针
let left=i+1,right=nums.length-1
// 对target元素去重
if(nums[i] === nums[i-1]) continue
while(left<right){
let sum = nums[i]+nums[left]+nums[right]
if(sum>0) right--
else if(sum<0) left++
else{
result.push([nums[i],nums[left],nums[right]])
// 找到一个满足条件的三元组后 对left和right进行去重
while(left<right && nums[left]===nums[left+1]) left++
while(left<right && nums[right]===nums[right-1]) right--
left++
right--
}
}
}
return result
};
盛最多水的容器
leetcode.cn/problems/co… 双指针 向内移动指针
var maxArea = function(height) {
let left=0,right = height.length-1
let max = 0
while(left<right){
// 容量=短板*宽度 宽度肯定会变小
// 移动长板 短板变短或不变 体积肯定变小
// 移动短板 短板可能变大 体积可能变大
// 所以移动短板
if(height[left]<height[right]){
let curr = height[left]*(right-left)
max = Math.max(max,curr)
left++
}else{
let curr = height[right]*(right-left)
max = Math.max(max,curr)
right--
}
}
return max
};
动态规划
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
01背包:物品只有一个 选or不选 完全背包:物品有无数个 选几个or不选
爬楼梯
不同路径
js生成二维数组
const m = 4;
const n = 5;
let arr = Array.from(new Array(m), () => new Array(n));
const m = 4;
const n = 5;
let arr = new Array(m).fill().map(() => new Array(n));
Array(m) 和 new Array(m)效果是一样的
分割等和子集
leetcode.cn/problems/pa… 01背包问题 weight和value是相同的 dp[target]=target代表刚好装满 可以分割