数组概念及拓展
数组概念
数组是存放在连续内存空间上的相同类型数据的集合。
数组可以方便的通过下标索引的方式获取到下标下对应的数据。
在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
};