javaScript 结构算法刷题 数组题

学习笔记

  • 数组
      • 二分法
        • 二分搜索的常见问题,区间左右闭合,区间左闭右开
          • 相关题目
      • 移除元素
          • 相关题目
      • 有序数组的平方
      • 长度最小的子数组
          • 相关题目
      • 螺旋矩阵
      • 参考

数组

二分法

二分法,就是猜数字游戏,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
};

参考

代码随想录学习的笔记,自行百度

你可能感兴趣的:(算法,javascript,leetcode)