目录
- 一、二分查找相关应用
- 704.二分查找
- 35.搜索插入位置
- 方法一:二分法
- 暴力解法
- 34.在排序数组中查找元素的开始位置和最后一个位置
- 方法一:暴力解法
- 方法二:二分法,确定左右两侧的边界
- 69.x的平方根
- 方法一:二分法
- 方法二:暴力解法
- 错解:多次判断,超时了
- 367.有效完全平方数
- 方法一:暴力解法
- 方法二:二分法
- 二、移除元素相关
- 27.移除元素
- 方法一:暴力解法
- 暴力解法2
- 快慢指针方法
- 双向指针
- 26.删除有序数组中的重复项
- 暴力解法
- 快慢指针
- 283.移动零
- 错解
- 快慢指针
- 暴力解法
- 844.比较含退格的字符串
- 方法一:使用栈 栈是用来解决括号匹配等问题的一大利器
- 方法二:使用指针,从右往左
- 977.有序数组的平方
- 方法一:暴力解法
- 方法二:双指针
- 三、滑动窗口相关问题
- 209.长度最小的子数组
- 方法一:暴力解法
- 暴力解法修改
- 滑动窗口 最小滑动窗口
- 904.水果成篮
- 暴力解法
- 滑动窗口 这里是最大滑动窗口
- 76.最小覆盖子串
一、二分查找相关应用
704.二分查找
var search = function(nums, target) {
let low = 0,high = nums.length - 1
let mid = Math.ceil(low + (high - low) /2)
while(low<=high){
mid = Math.ceil(low + (high - low) /2)
if(target==nums[mid]){
return mid
}else if(nums[mid]<target){
//往右走
low = mid + 1
}else{
//往左走
high = mid - 1
}
}
return -1
};
35.搜索插入位置
方法一:二分法
var searchInsert = function(nums, target) {
//二分查找 找到该插入到的位置
let low = 0,high = nums.length - 1
let mid = 0
while(low<=high){
mid = Math.ceil(low + (high - low)/2)
if(nums[mid]==target){
return mid
}else if(target<nums[mid]){
//往左走
high = mid - 1
}else{
//往右走
low = mid + 1
}
}
//当low==high的时候,mid = low = high 还未找到target,if(target<nums[mid]) => high = mid - 1,这时候mid左侧都比target小 故target应该放到mid的位置,if(target>nums[mid]) => low = mid + 1,包括mid和他左侧的都比他小,target应该放入到mid+1的位置 总之返回位置总是high + 1
return nums[mid]>target?mid:mid+1
};
暴力解法
var searchInsert = function(nums, target) {
//暴力解法
let len = nums.length
for(let i = 0;i<len;i++){
if(nums[i]==target){
//找到target了
return i
}else if(nums[i]>target){
//将target放入到nums[i]
return i
}
}
return len
};
34.在排序数组中查找元素的开始位置和最后一个位置
方法一:暴力解法
var searchRange = function(nums, target) {
if(nums==[]) return [-1,-1]
let start = null,end = null
for(let i=0;i<nums.length;i++){
if(nums[i]==target){
start = i
break //结束循环
}
}
//倒着来 去找target
for(let j = nums.length - 1;j>=0;j--){
if(nums[j]==target){
end = j
break
}
}
if(start==null&&end==null){
return [-1,-1]
}
return [start,end]
};
方法二:二分法,确定左右两侧的边界
var searchRange = function(nums, target) {
//由于数组是递增的 那么如果target有多个的话,一定是相邻的 可以使用二分法来分别找到左右边界
const getLeftBorder = (nums,target) =>{
let low = 0,high = nums.length - 1
let mid = Math.ceil((low+high)/2) //找到中间的下标
let leftBorder = null//用来记录左边界
while(low<=high){
mid = Math.ceil((low+high)/2)
if(nums[mid]>=target){
//注意 这里等于的时候也要移动
high = mid - 1
leftBorder = high
}else{
low = mid + 1
}
}
return leftBorder //根据循环里面的判断条件,最后跳出循环的时候,high位于范围的左侧
}
const getRightBorder = (nums,target) =>{
let low = 0,high = nums.length - 1
let mid = Math.ceil((low+high)/2) //找到中间的下标
let rightBorder = null
while(low<=high){
mid = Math.ceil((low+high)/2)
if(nums[mid]<=target){ //这样才能移动到右侧边界
low = mid + 1
rightBorder = low
}else{
high = mid - 1
}
}
return rightBorder
}
const left = getLeftBorder(nums,target)
const right = getRightBorder(nums,target)
//情况一:nums为空 或者 target不再这个范围内
if(nums==[]||left==null||right==null){
return [-1,-1]
}
//要保证left与right之间有一个结点
if(right-left>1){
return [left+1,right-1]
}
return [-1,-1]
};
69.x的平方根
方法一:二分法
var mySqrt = function(x) {
//计算并返回x的算术平方根 这里给出的是非负整数
//除了0之外 别的非负整数的算术平方根的范围是1 到 x
//里用二分法 从 1 到 x 之间进行判断
let low = 1,high = x
let mid = null
while(low<=high){
mid = Math.ceil((low+high)/2)
if(mid*mid<=x){
if((mid+1)*(mid+1)>x){
return mid
}
low = mid + 1
}else{
if((mid-1)*(mid-1)<=x){
return mid - 1
}
high = mid - 1
}
}
//如果在x = 0 那么返回0
return 0
};
方法二:暴力解法
var mySqrt = function(x) {
if(x==0) return 0
//如果x = 1 那么这个遍历中只有一项 没有i+1 就会出错
if(x==1) return 1
for(let i=1;i<x;i++){
if(i*i<=x&&(i+1)*(i+1)>x){
return i
}
}
};
错解:多次判断,超时了
var mySqrt = function(x) {
//计算并返回x的算术平方根 这里给出的是非负整数
//除了0之外 别的非负整数的算术平方根的范围是1 到 x
//里用二分法 从 1 到 x 之间进行判断
let low = 1,high = x
let mid = null
while(low<=high){
mid = Math.ceil((low+high)/2)
if(mid*mid<=x&&(mid+1)*(mid+1)>x){
return mid
}
if(mid*mid<x&&(mid+1)*(mid+1)<x){
//继续往右侧移动
low = mid + 1
continue
}
if(mid*mid>x&&(mid-1)*(mid-1)<=x){
return mid - 1
}
if(mid*mid>x&&(mid-1)*(mid-1)>x){
//想左侧移动
high = mid - 1
continue
}
}
//如果在x = 0 那么返回0
return 0
};
367.有效完全平方数
方法一:暴力解法
var isPerfectSquare = function(num) {
//正整数num 那么它的平方根一定在 1 到 num之间 这里注意1的平方还是1
for(let i = 1;i<=num;i++){
if(i*i==num){
return true
}
}
return false
};
方法二:二分法
var isPerfectSquare = function(num) {
//二分法 num的平方根一定在 1 到num之间
if(num==1) return true
let low = 1,high = num
while(low<=high){
mid = Math.ceil((low+high)/2)
if(mid*mid==num){
return true
}else if(mid*mid<num){
//要往右走
low = mid + 1
}else{
//往左走
high = mid - 1
}
}
//未找到
return false
};
二、移除元素相关
27.移除元素
方法一:暴力解法
var removeElement = function(nums, val) {
//题目说了 数组的顺序可以改变
//对数组进行排序
nums.sort((a,b)=>a-b)
//那么val值相同的元素都是相邻的
let count = 0 //用来记录val的个数
let start = null //开始的索引位置
//找到val的开始位置
for(let i = 0;i<nums.length;i++){
if(nums[i]==val){
start = i
break//结束循环
}
}
for(let i=0;i<nums.length;i++){
if(nums[i]==val){
count++
}
}
nums.splice(start,count)
};
暴力解法2
var removeElement = function(nums, val) {
//暴力解法二
let len = nums.length
//两层for循环 外边的一层是用来找val 里面的for循环用来从后往前移动val
for(let i=0;i<len;i++){
if(nums[i]==val){
//从后往前去移动元素 将当前的nums[i]给替换掉
for(let j=i;j<len-1;j++){
nums[j] = nums[j+1]
}
//移动元素结束之后
len--
i--//刚刚移动过来的这个元素没有判断,需要判断一下
}
}
return len
};
快慢指针方法
var removeElement = function(nums, val) {
//快慢指针
let slow = 0,fast = 0
//这样就可以只用一个for循环即可
for(fast;fast<nums.length;fast++){
if(nums[fast]!==val){ //当遍历到的值不是val时,slow和fast同时移动
nums[slow++] = nums[fast] //当遇到的是val,slow会停留在这个val值这里,然后fast向后移动,if fast遇到的这个值不是val,那么slow 和 fast 进行交换 ,else fast遇到的还是val,那么slow还是不动,fast继续移动;由于交换完成之后,slow自动加一,所以最后slow指向数组的末尾的后一个位置
}
}
return slow
};
双向指针
var removeElement = function(nums, val) {
//从左右两侧进行比较
let left = 0,right = nums.length - 1
while(left<=right){
//左侧指针要找val值
while(left<=right&&nums[left]!==val){
++left
}
//右侧指针要找不是val的值
while(left<=right&&nums[right]==val){
--right
}
//这里曾经有一个错误理解,右侧指针移动的过程中会将等于val的值给忽略过去,最会返回数组长度的时候,要看left 所以这样不会造成影响
//以上两个while循环结束之后,left指针指向一个等于val的值,right指向一个不等于val的值
if(left<right){ //if left==right 就没有必要交换了
nums[left++] = nums[right--]
}
}
//每次结束之后,left都会进行加一,故最后left指向新数组的末尾的下一项
return left
};
26.删除有序数组中的重复项
暴力解法
var removeDuplicates = function(nums) {
//暴力解法
let len = nums.length
for(let i = 0;i<len;i++){
let target = nums[i]
for(let j = i+1;j<len;j++){
//从这里开始去找target
if(nums[j]==target){
//后面的全部数组向前进行移动
for(let k=j+1;k<len;k++){
nums[k-1] = nums[k]
}
//移动一次之后,len--
len--
j--
}
}
}
return len
};
快慢指针
var removeDuplicates = function(nums) {
//千万要注意,nums是升序的 因为是升序的 所以不会出现 3 4 3的情况
//使用快慢指针
let slow = 0,fast = 1
for(fast;fast<nums.length;fast++){
if(nums[slow]!==nums[fast]){
slow++
nums[slow] = nums[fast]
}
}
//因为slow先加一 然后才会将新的值放入到slow这里 故最后结束的时候 slow指向的是数组的最后的位置
return slow + 1
};
283.移动零
错解
使用左右两侧指针会导致元素的顺序发生变化
var moveZeroes = function(nums) {
//使用双指针 左右两侧指针
let left = 0,right = nums.length - 1
while(left<=right){
//左指针去找0
while(left<=right&&nums[left]!==0){
++left
}
//右指针将0忽略
while(left<=right&&nums[right]==0){
--right
}
//执行完成上面两个while循环之后,left指向0 right不指向0 然后将left与right所指向的值进行交换
if(left<right){
nums[left] = nums[right]
nums[right] = 0
}
}
};
快慢指针
var moveZeroes = function(nums) {
//快慢指针 先将非零元素移动到前面 然后再将后面的元素填补为0
let slow = 0,fast = 0
for(fast;fast<nums.length;fast++){
if(nums[fast]!==0){
nums[slow++] = nums[fast]
}
}
//上面的for循环完成之后,前面都是非零值 此时slow指向非零部分末尾的下一个位置
for(let i=slow;i<nums.length;i++){
//将后面全都填充成0
nums[i] = 0
}
};
暴力解法
var moveZeroes = function(nums) {
//暴力解法
let len = nums.length
//外层循环判断每一项是否是0 里面的for循环来移动位置
for(let i=0;i<len;i++){
if(nums[i]==0){
for(let j=i+1;j<len;j++){
nums[j-1] = nums[j]
}
len--
i--
}
}
let j = len
while(j<nums.length){
nums[j] = 0
j++
}
};
844.比较含退格的字符串
题解参考
方法一:使用栈 栈是用来解决括号匹配等问题的一大利器
var backspaceCompare = function(s, t) {
//使用栈
const helper = str =>{
let stack = []
for(let i=0;i<str.length;i++){
if(str.charAt(i)!=='#'){
stack.push(str.charAt(i))
}else{
stack.pop()
}
}
//结束之后,转换成字符串
return stack.join()
}
const str1 = helper(s)
const str2 = helper(t)
if(str1==str2){
return true
}else{
return false
}
};
方法二:使用指针,从右往左
var backspaceCompare = function(s, t) {
//使用指针
let i = s.length - 1
let j = t.length - 1 //指向字符串的最后一个位置
let skipS = 0
let skipT = 0 //用来记录从右往左进行遍历过程中遇到的#号
//主要思路:对两个字符串同时从右侧往左侧进行遍历,如果遇到#,那么skip加一,如果遇到的不是#,且skip是正数,那么skip减一 直到两个字符串都比较完成
while(i>=0||j>=0){
//对字符串s进行统计
while(i>=0){
if(s[i]=='#'){
skipS++ //统计遇到的#个数
i-- //继续往左侧移动
}else if(skipS>0){
skipS-- //用来消除前面的字符串
i--
}else{
break
}
}
//对字符串t进行统计
while(j>=0){
if(t[j]=='#'){
skipT++
j--
}else if(skipT>0){
skipT--
j--
}else{
break
}
}
//如果处理退格之后的字符不相等,返回false
if(s[i]!==t[j]) return false
i--
j--
}
return true
};
977.有序数组的平方
方法一:暴力解法
var sortedSquares = function(nums) {
//方法:先对每个元素进行平方放入到原来的位置,然后对数组进行排序
for(let i=0;i<nums.length;i++){
nums[i] = nums[i]*nums[i]
}
nums.sort((a,b)=>a-b)
return nums
};
方法二:双指针
var sortedSquares = function(nums) {
//思路:由于数组是递增的,数组平方之后的大小 要以 0 为中心,向两侧逐渐变大
//故只需要数组首尾两端 然后将大的放到新数组的最后一个位置 这里使用双指针,分别指向数组的首尾
let i = 0, j = nums.length - 1, last = nums.length - 1
let arr = new Array(nums.length)
while(i<=j){
let leftVal = nums[i] * nums[i]
let rightVal = nums[j] * nums[j]
arr[last--] = leftVal<rightVal?(j--,rightVal):(i++,leftVal)
}
return arr
};
三、滑动窗口相关问题
209.长度最小的子数组
方法一:暴力解法
var minSubArrayLen = function(target, nums) {
//暴力解法
let res = [] //用来记录从当前i开始连续有几个元素
for(let i=0;i<nums.length;i++){
let sum = nums[i] //用来保存当前的累加和
if(sum>=target){ //当前值已经满足条件的了,后面就不用再遍历了
return 1
}
for(let j=i+1;j<nums.length;j++){
sum+=nums[j]
if(sum>=target){
res.push(j-i+1)
break //结束当前的循环
}
}
}
if(res.length==0) return 0
//找出res中最小的
res.sort((a,b)=>a-b)
return res[0]
};
暴力解法修改
var minSubArrayLen = function(target, nums) {
//暴力解法
let min = null //用来记录最小值
for(let i=0;i<nums.length;i++){
let sum = nums[i] //用来保存当前的累加和
if(sum>=target){ //当前值已经满足条件的了,后面就不用再遍历了
return 1
}
for(let j=i+1;j<nums.length;j++){
sum+=nums[j]
if(sum>=target){
min?min = Math.min(min,j-i+1):min = j-i+1
break //结束当前的循环
}
}
}
return min?min:0
};
滑动窗口 最小滑动窗口
var minSubArrayLen = function(target, nums) {
//滑动窗口 i是窗口的最左侧 j是窗口的最右侧
let res = null //用来记录最小值
let sum = 0 //用来记录窗口所有元素的和
let i = 0, j = 0
for(j;j<nums.length;j++){
sum+=nums[j]
while(sum>=target){
res?res=Math.min(res,j-i+1):res=j-i+1
//左侧指针i向右侧移动
sum-=nums[i++]
}
}
return res?res:0
};
904.水果成篮
暴力解法
var totalFruit = function(fruits) {
//暴力解法
let max = 0
//使用两层循环 外层循环是从下标i开始,j是从i往后开始几个
for(let i=0;i<fruits.length;i++){
let type1 = null
let type2 = null
type1 = fruits[i]
let sum = 1//当前篮子中的水果数量
for(let j=i+1;j<fruits.length;j++){
if(fruits[j]==type1){
sum++
}else if(type2==null){
type2 = fruits[j]
sum++
}else if(fruits[j]==type2){
sum++
}else{
break
}
}
max = Math.max(max,sum)
}
return max
};
滑动窗口 这里是最大滑动窗口
var totalFruit = function(fruits) {
let max = 0//用来记录最大值
let map = new Map() //用来记录某个种类在滑动窗口中出现的次数,注意窗口中只能由两个键值
let left = 0 //窗口左侧指针
//下面right是窗口的右侧指针 只要map中只有两个键值,那么窗口就可以一直扩大直到数组结束
for(let right=0;right<fruits.length;right++){
//设置键值
map.set(fruits[right],map.get(fruits[right])+1||1)
//只要map对象中的键值个数超过两个,那么左侧指针开始往右侧移动
while(map.size>2){
map.set(fruits[left],map.get(fruits[left])-1)
//一直在删除原来窗口中的键的val值,删除过程中,看那两个键 哪个先删除为0
if(map.get(fruits[left])==0){
map.delete(fruits[left])
}
//将某个键值给删除之后进行新的窗口 新的窗口中有一个原先的老的键值还有一个新的键值
++left
}
max = Math.max(max,right-left+1)
}
return max
};
76.最小覆盖子串
var minWindow = function(s, t) {
//t中也会有重复的元素,但是t不用去重,因为题目中要求要找的字串中必须和t的字符的数量都是一样的
let map = new Map() //保存t中的所有的字符 及其数量
for(let i=0;i<t.length;i++){
map.set(t.charAt(i),map.get(t.charAt(i))+1||1)
}
let left = 0, right = 0
let minStr = ''
let size = map.size//保存所需要的字符种类
for(right;right<s.length;right++){
if(map.has(s[right])){
map.set(s[right],map.get(s[right])-1)
}
if(map.get(s[right])==0) size-- //需求的字符个数减一
//只有需求的元素都有了,才会执行while
while(!size){
let curStr = s.substring(left,right+1)
minStr?(minStr = minStr.length<curStr.length?minStr:curStr):minStr = curStr
if(map.has(s[left])){
map.set(s[left],map.get(s[left])+1)
if(map.get(s[left])==1) size++//需求数量加一
}
left++
}
}
return minStr
};