数组
二分法
二分法,就是猜数字游戏,1-100,你说个数80,我告诉你小了,你就会在81-100之间继续猜一个数,之道猜到我心中的答案数。
二分搜索的常见问题,区间左右闭合,区间左闭右开
- 区间左右闭合实现( 即nums=[a,b,c,…,z] )leetcode704
var search = function(nums, target) {
let left = 0
let right = nums.length - 1
while (left<=right){
let mid = Math.floor((left+right)/2)
if(nums[mid]>target){
right = mid -1
}else if(nums[mid]<target){
left = mid + 1
}else{
return mid
}
}
return -1 // 在第35题中 return left 返回应该插入的位置
};
分析:
目的是返回target的下标值,数组中存在相同的数值就返回该值的下标,目标值不存在就返回-1。代码就是单纯的写一个左闭右闭的二叉搜索。
有一个相似的题目是leetcode35,可以暴力遍历搜索数组,但是也可以用这个二分法实现,不同的是数组中不包含目标值需要返回目标值插入的位置,解决方法同理。把二分法输入进去测试,会提示nums=[1,3,4,5] target=2 时报错,带入一遍就知道return应该是left值
- 左闭右开
var search = function(nums, target) {
// right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间
let left = 0, right = nums.length;
// 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况
while (left < right) {
let mid = left + Math.floor((right - left)/2);
// 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在;
// 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围
if (nums[mid] > target) {
right = mid; // 去左区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右区间寻找
} else {
return mid;
}
}
return -1;
};
题目还没刷到过,先空着
相关题目
leetcode 34
给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
官方思路:
寻找target在数组里的左右边界,有如下三种情况:
情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
这三种情况都考虑到,说明就想的很清楚了。
接下来,在去寻找左边界,和右边界了。
采用二分法来去寻找左右边界,为了让代码清晰,我分别写两个二分来寻找左边界和右边界。
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var searchRange = function(nums, target) {
const getLeftBorder = (nums, target) => {
let left = 0, right = nums.length - 1;
let leftBorder = -2;// 记录一下leftBorder没有被赋值的情况
while(left <= right){
let middle = left + Math.floor((right - left)/2);
if(nums[middle] >= target){ // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
const getRightBorder = (nums, target) => {
let left = 0, right = nums.length - 1;
let rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) {
let middle = left + Math.floor((right - left)/2);
if (nums[middle] > target) {
right = middle - 1;
} else { // 寻找右边界,nums[middle] == target的时候更新left
left = middle + 1;
rightBorder = left;
}
}
return rightBorder;
}
let leftBorder = getLeftBorder(nums, target);
let rightBorder = getRightBorder(nums, target);
// 情况一
if(leftBorder === -2 || rightBorder === -2) return [-1,-1];
// 情况三
if (rightBorder - leftBorder > 1) return [leftBorder + 1, rightBorder - 1];
// 情况二
return [-1, -1]
};
leetcode 69
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留整数部分 ,小数部分将被舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
var mySqrt = function(x) {
let left = 0
let right = x/2 +1
const target = x
while(left<=right){
let mid = Math.floor((left+right)/2)
let res = mid*mid
if( res > target ){
right = mid - 1
}else if( res < target ){
left = mid + 1
}else if( res == target){
return mid
}
}
return left-1
};
leetcode 367
给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
var isPerfectSquare = function(num) {
let left = 0
let right = num
while(left<=right){
let mid = Math.floor((left+right)/2)
let res = mid * mid
if( res > num ){
right = mid - 1
}else if( res < num ){
left = mid + 1
}else if( res == num ){
return true
}
}
return false
};
移除元素
数组中删除元素操作其实是覆盖不是删除,如nums=[1,2,3],删除val=2,其中底层空间变化是nums=[1,3,3]返回出来是[1,3],实现方法是把后面的3往前移一位覆盖掉2,所以就有了新的nums=[1,3],且nums[2] = 3
相关题目
leetcode 27
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
var removeElement = function(nums, val) {
let slow = 0
for(let fast = 0;fast<nums.length;fast++){
if(nums[fast] !== val){
nums[slow] = nums[fast]
slow++
}
}
return slow
};
官方解答:
快慢指针(双指针)
leetcode 283
var moveZeroes = function(nums) {
let slow = 0
let fast = 0
for( fast; fast < nums.length; fast++){
if(nums[fast] !== 0){
nums[slow] = nums[fast]
slow++
}
}
for( slow; slow < nums.length; slow++){
nums[slow] = 0
}
return nums
};
leetcode 844
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,如果两者相等,返回 true 。# 代表退格字符。
输入:s = “ab#c”, t = “ad#c”
输出:true
解释:s 和 t 都会变成 “ac”。
var backspaceCompare = function(s, t) {
function delback (k){
let kArr = []
for(let i = 0; i < k.length; i++){
if(k[i] !== '#'){
kArr.push(k[i])
}
else{
kArr.pop()
}
}
return kArr
}
let sArr = delback(s)
let tArr = delback(t)
return (sArr.join('') == tArr.join(''))
};
tips
1.数组不能直接 == 比较,直接报false,必须转成字符串,比如toString、join等转一下
2.push\pop是数组操作方法,直接拿输入字符用不行
有序数组的平方
leetcode 977
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序
var sortedSquares = function(nums) {
let arr = []
for(let i = 0; i<nums.length; i++){
let p = nums[i]*nums[i]
arr.push(p)
}
return arr.sort(function(a,b){return a-b})
};
tips
sort(function(a,b){return b-a})降序
双指针解
var sortedSquares = function(nums) {
let res = []
let j = nums.length - 1
for(let i = 0;i <= j;){
let left = nums[i] * nums[i]
let right = nums[j] * nums[j]
if( left > right){
res.unshift(left)
i++
}else{
res.unshift(right)
j--
}
}
return res
};
tips
重要的数学理念是,非递减数组,两头的平方肯定比中间大
长度最小的子数组
leetcode 209
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, …, numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
var minSubArrayLen = function(target, nums) {
let i = 0
let sum = 0
let res = nums.length + 1
for(let j = 0;j <= nums.length; j++){
sum += nums[j]
while( sum >= target){
let subl = j - i + 1
res = Math.min(res,subl)
sum = sum - nums[i]
i++
}
}
return res > nums.length? 0 : res
};
tips
最后return res改成判断是因为有一个特别狗的输入是nums=[1,1,…,1]八个1,target是11, 不经过循环
官方解答:
双指针 滑动窗口
相关题目
leetcode904
你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类 。
你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。
输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。
tips
以上全是废话,给一个数组,找出数组中,只含有两个不同值且连续的子数组,求子数组的最大长度
var totalFruit = function(fruits) {
let slow = 0, fast = 0
let bucket1 = -1, bucket2 = -1
// 加入count 计水果的类型
let count = 0, max = 0, res = 0
for(fast; fast < fruits.length; fast++){
// 过第一个第二个框
if(count < 2){
if(count == 0){
// 放下第一个框
bucket1 = fruits[fast]
count++
}else if(fruits[fast] !== bucket1){
// 当往后摸遇到不等于第一个框装的类型时,装第二个框
bucket2 = fruits[fast]
count++
}
}
// 当出现第三种类型的果子时
else if((fruits[fast] !== bucket1) && (fruits[fast] !== bucket2)){
bucket1 = fruits[fast-1]
bucket2 = fruits[fast]
// 更新慢指针位置
slow = fast - 1
// slow更新后要考虑左边界发生的改变,需要重新找左边界
while(fruits[slow] == bucket1){
slow--
}
// 多减了一个,因为最左的slow也等于bucket1,执行了slow--
// 按道理讲应该判断if fruits[slow] !== fruits[slow-1] 但是内存爆炸了
slow++
}
max = fast - slow + 1
// 更新res,防止出现中间长,两头短的情况
res = Math.max(max,res)
}
return res
};
leetcode 76
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
输入:s = “ADOBECODEBANC”, t = “ABC”
输出:“BANC”
输入: s = “a”, t = “aa”
输出: “”
解释: t 中两个字符 ‘a’ 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串
var minWindow = function(s, t) {
let slow = 0, fast = 0
let res = ''
let map = new Map()
// 先展开t,注意相同值计数
for(let i=0; i<t.length; i++){
map.set(t[i],(map.has(t[i]) ? map.get(t[i]) + 1 : 1))
}
let mapl = map.size
for(fast; fast < s.length; fast++){
if(map.has(s[fast])){
map.set(s[fast],map.get(s[fast]) - 1)
if(map.get(s[fast]) == 0 ){
mapl--
}
}
while(mapl == 0){
let newRes = s.substring(slow,fast+1)
// 更新结果
if(!res || newRes.length < res.length){
res = newRes
}
// 开始移动慢指针
if(map.has(s[slow])){
map.set(s[slow],map.get(s[slow]) + 1)
if(map.get(s[slow]) == 1){
mapl++
}
}
slow++
}
}
return res
};
tips
以后补充吧
挺搞事的这题
螺旋矩阵
tips
就是二分法中讲区间左闭右开的情况,实现首位不互掐
leetcode59
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
var generateMatrix = function(n) {
let x = 0, y = 0 // 矩阵位置索引
let loop = Math.floor(n/2) // 旋转圈数,如果n是记奇数,单独给最后的中心赋值
let center = Math.floor(n/2) // 中心位置
let offset = 1 // 每层填充元素个数需要减去的值,左闭右开
let count = 1 // 更新填充数字
let res = new Array(n).fill(0).map(() => new Array(n).fill(0))
while(loop--) {
let row = x, col = y
// 上
for(col ; col < x + n - offset; col++){
res[row][col] = count++
}
// 右
for(row ; row < y + n - offset; row++){
res[row][col] = count++
}
// 下
for(col ; col > y; col--){
res[row][col] = count++
}
// 左
for(row ; row > x; row--){
res[row][col] = count++
}
x++
y++
offset+=2
}
if(n % 2 == 1){
res[center][center] = count
}
return res
};
参考
代码随想录学习的笔记,自行百度