day01| 704. 二分查找、27. 移除元素、977.有序数组的平方、209.长度最小的子数组、59.螺旋矩阵II

115 阅读3分钟

704.二分查找

704. 二分查找 - 力扣(LeetCode)

二分查找适用于数组有序排列且没有重复元素的数组,写法取决于对数组区间的定义,一共有两种写法,第一种是左闭右闭,第二种是左闭右开

左闭右闭写法

左闭右闭是指左指针为数组第一个元素所在位置,右指针为数组最后一个元素所在位置

这种写法的关键点在于:

  • 循环的条件是left<=right,因为在左闭右闭的条件下,left=right是有效的,比如只有一个元素的情况下

  • middle的值大于指定值时,right值更新为middle - 1; 当middle的值小于指定值时,left值更新为middle + 1

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let left = 0,right = nums.length - 1,mid = Math.floor((left + right) / 2);
    while(left <= right){
        if(nums[mid] === target) return mid;
        else if(nums[mid] < target) left = mid + 1;
        else if(nums[mid] > target) right = mid - 1;
        mid = Math.floor((left + right) / 2)
    }
    return -1;
};

左闭右开写法

左闭右开是指左指针为数组第一个元素所在位置,右指针为索引为数组长度的位置,即不指向任何一个元素

这种写法的关键点在于:

  • 循环的条件是left<right,因为在左闭右开的条件下,left=right是无效的

  • middle的值大于指定值时,right值更新为middle; 当middle的值小于指定值时,left值更新为middle + 1

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
var search = function(nums, target) {
    let left = 0,right = nums.length,mid = Math.floor((left + right) / 2);
    while(left < right){
        if(nums[mid] === target) return mid;
        else if(nums[mid] < target) left = mid + 1;
        else if(nums[mid] > target) right = mid;
        mid = Math.floor((left + right) / 2)
    }
    return -1;
};

27.移除元素

27. 移除元素 - 力扣(LeetCode)

该题有两种写法,一种是通过双循环,当找到某个位置是目标元素时,把后面所有的元素都向前移动一位;一种是通过双指针

暴力for循环

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    for(let i = 0; i < nums.length;i++){
        if(nums[fastIndex] === val){
           for(let j = i + 1 ; j < nums.length;j++){
               nums[j - 1] = nums[j]
           }
        }
    }
    return slowIndex;
};

双指针

双指针也叫快慢指针,利用两个指针来完成双层for循环

在这一题里面,快指针是去找新数组的元素,因此快指针会遍历所有的元素,可以利用这个指针来做一个for循环,而慢指针则是指向更新新数组下标的位置;即当快指针遍历时,若找到与目标元素相等的元素时,慢指针则不动,快指针仍向前移动一位,目的是让慢指针所在位置的元素被快指针所找到的不是目标元素的元素覆盖掉,因为当快指针所指元素不是目标元素时,会将所指元素赋给慢指针所在索引

/**
 * @param {number[]} nums
 * @param {number} val
 * @return {number}
 */
var removeElement = function(nums, val) {
    let slowIndex = 0; //慢指针
    for(let fastIndex = 0; fastIndex < nums.length;fastIndex++){ 
    //利用快指针做一个for循环
        if(nums[fastIndex] !== val){
            nums[slowIndex] = nums[fastIndex];
            slowIndex++;
        }
    }
    return slowIndex;
};

977.有序数组的平方

977. 有序数组的平方 - 力扣(LeetCode)

使用JS内置的map函数和sort函数

map函数将数组内所有值平方后得到新数组,而后通过sort函数将新数组重新排序

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortedSquares = function (nums) {
  return nums.map(ele => ele * ele).sort((a, b) => a - b)
}

使用双指针法

由于题目所给数组为非递减顺序的整数数组,正整数部分平方后的顺序不变,不确定的是负整数数字平方后所处的顺序,因此可以将两个指针定于收尾,从数组的最后一位开始填写

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var sortedSquares = function (nums) {
  let arrLength = nums.length,
    left = 0, //用于判断比较的左指针
    right = arrLength - 1, //用于判断比较的右指针
    index = arrLength - 1, //用于在新数组中填写平方后数字的索引指针
    result = new Array(arrLength).fill(0) //创建一个和所给数组相同长度的新数组
  while (left <= right) {
    let leftNum = nums[left] * nums[left]
    let rightNum = nums[right] * nums[right]
    if (rightNum >= leftNum) {
      //若右指针平方大于等于左指针平方,新数组中填入右指针平方
      result[index--] = rightNum
      right--
    } else if (rightNum < leftNum) {
      //若左指针平方大于左指针平方,新数组中填入左指针平方
      result[index--] = leftNum
      left++
    }
  }
  return result
}

209.长度最小的子数组

209. 长度最小的子数组 - 力扣(LeetCode)

暴力解法

两次for循环找到所有区间情况,再比较,时间复杂度为O(n方),容易超时

滑动窗口

由于数组中没有负整数,所以每加一个数,总和一定增加,每去掉一个数,总和一定减小,便可联想到滑动窗口的特性,通过增加项、减少项来控制窗口的大小,题目要求的是连续的最小的子数组,滑动窗口中元素既是连续的,又可以得到窗口的大小,该方法的时间复杂度为O(n)

/**
 * @param {number[]} nums
 * @return {number[]}
 */
var minSubArrayLen = function (target, nums) {
  let slow = 0,
    fast = slow,
    sum = 0,
    result = nums.length + 1,
    temp = 0
  //这个fast指针代表的是滑动窗口的终止位置,确定滑动窗口的终止位置后
  //再去移动起始位置来减小滑动窗口的大小,当移动后窗口内元素相加小于
  //target时,又移动fast指针来增加滑动窗口的大小,通过不断地移动
  //让窗口大小不断变化,里面的元素不断变化,直到包含最后一个元素
  //每个元素每操作两次,时间复杂度为O(2n),即O(n)
  for (fast; fast < nums.length; fast++) {
    sum += nums[fast]
    while (sum >= target) {
      temp = fast - slow + 1
      if (result > temp) result = temp
      sum -= nums[slow++]
    }
  }
  return result === nums.length + 1 ? 0 : result
}

59.螺旋矩阵II

59. 螺旋矩阵 II - 力扣(LeetCode)

是一个模拟画圈的一个过程,没有涉及什么算法的问题 注意循环不变量的问题,在该算法中,循环不变量就是对每一条边的处理方法都是只处理一条边的开头结点,结尾结点由下一条边处理

/**
 * @param {number} n
 * @return {number[][]}
 */
var generateMatrix = function (n) {
  let startX = 0, //二维数组的横排第一个索引值
    startY = 0, //二维数组的纵排第一个索引值
    circle = Math.floor(n / 2), //圈数,用来判断循环的次数
    mid = Math.floor(n / 2), //矩阵最中间的位置,用于n为奇数时设置阵中心位置值
    count = 1, // 用于计数
    offset = 1, // 使用左闭右开区间,处理一条边时,把结尾处的节点留给下一条边处理,所以需要偏移量为1
    result = new Array(n).fill(0).map(() => new Array(n).fill(0))
  while (circle--) {
    let row = startX,
      col = startY
    // 上行从左到右,变的是col的值
    for (; col < n - offset; col++) {
      result[row][col] = count++
    }
    // 右行从上到下,变的是row的值
    for (; row < n - offset; row++) {
      result[row][col] = count++
    }
    // 下行从右到左,变的是col的值
    for (; col > offset - 1; col--) {
      result[row][col] = count++
    }
    // 左行从下到上,变的是row的值
    for (; row > offset - 1; row--) {
      result[row][col] = count++
    }
    startX++
    startY++
    offset += 1 //每转完一圈,偏移量需要+1
  }
  if (n % 2 === 1) result[mid][mid] = count 
  return result
}