用JavaScript写704.二分查找 和 27. 移除元素 | 二分法和双指针法 | 代码随想录训练营Day1

209 阅读4分钟

数组概念及拓展

数组概念

数组是存放在连续内存空间上的相同类型数据的集合。

数组可以方便的通过下标索引的方式获取到下标下对应的数据。

在C++中,不论是一维数组,还是二维数组在内存的空间地址都是连续的,但在 JavaScript 的Array中,则是通过哈希映射或者字典的方式来实现,所以不是连续的。

但随着语言的发展, JavaScript 引擎已经在为同种数据类型的数组分配连续的存储空间了。

TC39已决定在JavaScript 中引入类型化数组ArrayBuffer ,它能够提供一大块连续的存储位置。

详情可参考这篇文章:JS数组的演进和性能

面试题:数组和链表的区别

数组:数组是顺序存放的相同类型数据结构的集合。数组中提供了下标来访问数组中的元素,数组中的元素总个数叫做数组的长度。

链表:链表在物理存储单元上是一种非连续、非顺序的存储结构,链表中有一系列结点,各个节点通过指针间的相互链接来实现它的逻辑顺序。单向链表中的每一个元素都要保存一个指向下一个元素的指针,双向链表中的每个元素既要保存指向下一个元素的指针,又要保存指向上一个元素的指针,且链表中的最后一个元素保存一个指向第一个元素的指针。

区别:①数组从栈上分配内存,使用方便,但是自由度小;链表从堆上分配内存,自由度大,但是要注意内存泄漏。

②数组在内存中顺序存储,可通过下标访问,访问效率高;链表访问效率低,如果想要访问某个元素,需要从头遍历。

③(1)数组在内存中连续; (2)使用数组之前,必须事先固定数组长度,不支持动态改变数组大小;(3) 数组元素增加时,有可能会数组越界;(4) 数组元素减少时,会造成内存浪费;(5)数组增删时需要移动其它元素;(1)链表采用动态内存分配的方式,在内存中不连续 (2)支持动态增加或者删除元素 (3)需要时可以使用malloc或者new来申请内存,不用时使用free或者delete来释放内存。

面试题:堆和栈的区别

对操作场景:

(1)内存操作场景下,堆与栈表示两种内存的管理方式。

(2)数据结构场景下,堆与栈表示两种常用的数据结构。

对数据结构:

JavaScript存在栈和队列概念,通过数组的方式,模仿实现堆栈。

栈:栈是一种运算受限的线性表,其限制是指只仅允许在表的一端进行插入和删除操作,这一端被称为栈顶(Top),相对地,把另一端称为栈底(Bottom)。把新元素放到栈顶元素的上面,使之成为新的栈顶元素称作进栈、入栈或压栈(Push);把栈顶元素删除,使其相邻的元素成为新的栈顶元素称作出栈或退栈(Pop)。通过数组的push()、pop()方法实现栈。

堆:堆其实是一种优先队列,也就是说队列中存在优先级,比如队列中有很多待执行任务,执行时会根据优先级找优先度最高的先执行。

704二分查找

要点

需要注意区分左闭右闭区间和左闭右开区间两种写法,我个人对left = right这个条件的意义的理解是:在左闭右闭中,给左右边界的数是可达到的数,允许left = right进入循环,之后去掉不能达到的边界值;在左闭右开中,仅在更大区间内middle+1确保左侧区间闭合,另一侧的middle不做处理,因为while条件中定义的边界本身就是不能到达的。

左闭右闭:right = length - 1 left <= right middle±1

左闭右开:right = length left < right 仅在更大区间内确保左侧区间闭合 middle+1

代码实现

左闭右闭区间

let search = function(nums, target) {
  // 左闭右闭
  let left = 0, right = nums.length - 1, mid = 0;
  while(right >= left){
    // 位运算防止大数溢出的操作?(右移一位相当于去1后除二)
    mid = left + ((right - left) >> 1);
    if(nums[mid] > target){
      right = mid - 1;
    } else if (nums[mid] < target){
      left = mid + 1;
    }else return mid;
  }
  return -1;
};

左闭右开区间

let search = function(nums, target) {
  // 左闭右开
  let left = 0, right = nums.length, mid = 0;
  while(right > left){
    // 位运算防止大数溢出的操作?(右移一位相当于去1后除二)
    mid = left + ((right - left) >> 1);
    if(nums[mid] > target){
      right = mid;
    } else if (nums[mid] < target){
      left = mid + 1;
    }else return mid;
  }
  return -1;
};

27移除元素

要点

将原数组修改后,根据返回的长度进行分割,返回一个新的数组作为结果。

快慢指针中,快指针寻找目标val,慢指针慢n步后指向了val原来所在的位置,快指针此时指向新的下一个非目标的数,此时可以进行改变数组的操作。相当于把原来是val的位置都空了出来,其他元素都对应向前移动。

代码实现

暴力求解

var removeElement = function(nums, val) {
  // 暴力法(双重for循环)
  let len = nums.length
  for(let i = 0; i < nums.length; i++){
    if(nums[i] === val){
      for(let j = i; j < nums.length; j++){
        nums[j] = nums[j+1]
      }
      i--
      // 移除了一个元素,所以i需要回退
      len--
    }
  }
  return len
};

双指针法

for循环:

var removeElement = function(nums, val) {
  let fast = 0, slow = 0;
  for(fast = 0; fast < nums.length; fast++){
    if(nums[fast] != val){
      nums[slow++] = nums[fast]
    }
  }
  return slow
};

while循环:

var removeElement = function(nums, val) {
  let fast = 0, slow = 0;
  while(fast < nums.length){
    if(nums[fast] !== val){
      nums[slow++] = nums[fast]
    }
    fast++
  }
  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--
    }
    if(left < right){
      nums[left++] = nums[right--]
    }
  }
  return left
};

更多练习

二分法练习

35.搜索插入位置

var searchInsert = function(nums, target) {
  let left = 0,right = nums.length, mid = 0
  while(left <= right){
    mid = left + ((right - left) >> 1)
    if(nums[mid] < target){
      left = mid + 1 
    } else if(nums[mid] > target){
      right = mid - 1
    } else return mid
  }
  return right + 1
};