算法Day 1(数组篇)

291 阅读4分钟

LeetCode #35 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。 你可以假设数组中无重复元素。

示例1:输入: [1,3,5,6], 5 输出: 2

示例2:输入: [1,3,5,6], 2 输出: 1

示例3:输入: [1,3,5,6], 7 输出: 4

示例4:输入: [1,3,5,6], 0 输出: 0

方法一

从题目意思看,需要把一个元素按大小顺序进行有序插入。 观察示例,把数组记作arr, 目标值为val,插入的位置:index 示例1 存在目标值,得出关系arr[index] === val

示例2 不存在目标值,目标值处于数组中间。得出关系arr[index] > val

示例3 目标值不存在数组且大于数组最大值。得出关系index > arr[arr.length - 1],也即 index === arr.length

示例4 目标值不存在数组且小于数组最小值。得出关系arr[index] > val

上述四种其实就是所有可能的情况,不难发现,插入的位置和目标值 的关系: arr[index] >= val 或者 index === arr.length,所以有一种遍历的解法

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */
 // 遍历n次,时间复杂度O(n)
var searchInsert = function(nums, target) {
  for(var i=0; i<nums.length; i++) {
    if(nums[i] >= target) return i;
  }
  return nums.length;
};
上述意思,遍历数组,一旦当前元素值大于或等于目标值时,
当前位置便是要找的位置,如果数组中没有出现这个值,
说明目标值超过数组最大值,此时返回最大数组长度。

方法二

题目中提到数组是有序的,且目的是插入元素。相当于你找到数组中与目标值大小接近的元素位置,就能知道插在哪了。

// 二分法
var searchInsert = function(nums, target) {
  var left = 0, right = nums.length;
    while(left <= right) {
      var mid = Math.floor((left + right) / 2);
        if(nums[mid] === target) {
          return mid;
        } else if(nums[mid] < target) {
          left = mid + 1;
        } else if(nums[mid] > target) {
          right = mid - 1;
        }
    }
  return left;
};

大致思路:每次取数组中间值和目标值进行比较,
判断目标值处于数组的哪个范围,
并且不断对半切割数组,缩小范围,最终找到目标值的位置。

至于为什么最后返回left,因为要么返回mid,是中间值与目标值恰好相等的情况,
要么跳出循环,条件是left > right,
说明最后一个循环是判断nums[mid] < target,
然后left = mid + 1 超过了right,而target 的位置比mid大,就是mid后一个位置即left
可以辅助画图比较清楚。

LeetCode #27 移除元素

给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素

示例1:给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。

示例2:给定 nums = [0,1,2,2,3,0,4,2], val = 2,函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

方法一

思路:题目强调了不要使用额外的数组空间,原地修改,移除数组中值为val的元素返回剩余长度。可以想想 js 中移除原数组元素的方法,shift, pop, splice,由于不一定是首尾的元素,能想到的也就是splice,好的

// 时间复杂度O(n * n)
var removeElement = function(nums, val) {
  for(var i=0; i<nums.length; i++) {
    if(nums[i] === val) {
      // 找到相等的元素,`原地`删除
      nums.splice(i, 1);
      // 注意删除后后面的元素往前移一位,故i不变
      i -= 1;
    }
  }
  return nums.length;
};

方法二

/*
  双指针法, 一旦找到不为val值的元素,从头开始保存
  时间复杂度 O(n)
*/

var removeElement = function(nums, val) {
  var point = 0; // 创建指针
  for(var i=0; i<nums.length; i++) {
    if(nums[i] !== val) {
      // 当前元素不用移除
      nums[point] = nums[i]; // 从头保存
      point++; // 指针后移
    }
  }
  // point的长度便是新数组长度
  return point;
}
// 上面可能会有疑问,数组等于val的元素并没有被移除啊。
//   别忘了题目还有个条件:你不需要考虑数组中超出新长度后面的元素

方法三

/*
  双指针法
  好处是如果数组中需要移除的元素数量较少时,可以减少赋值次数,缩短时间
*/
var removeElement = function(nums, val) {
  var start = 0, end = nums.length;
  while(start < end) {
    if(nums[start] === val) {
      // 找到需要移除的值时,用最后一个元素来替换,同时数组缩短1
      nums[start] = nums[end - 1];
      end -= 1;
    } else {
      start += 1;
    }
  }
  return end;
}

欢迎评论区交流