数组和链表面试篇


写在前面,本文是本人在面试中,面试官问到的一些关于数组和链表的问题,问题的答案是自己总结出来的,可能存在一些偏差,欢迎大家指正
后续如果有碰到更多问题,会继续补充

1. js数组和c++/java的数组有什么不同

js数组: 数组中可包含不同的数据类型,每个元素占据的空间不同,不需要声明数组的长度
c++数组:数组中的数据类型应该保持一致,每个元素占据的空间相同,需要声明数组的固定长度

2. 为什么c++/java的数组要设置固定长度

因为数组空间数连续的,所以这就意味着内存中需要有一整块的空间用来存放数组。如果长度不固定,那么内存中位于数组之后的区域没法继续往下分配了!内存不知道当前数组还要不要继续存放,要多少空间了。毕竟数组后面的空间得要留出来给别人去用,不可能让你(数组)一直霸占着对吧。

3. 为什么c++/java的数组要设置相同的数据类型

如果数组允许各种类型的数据,那么每存入一个元素都要进行装箱操作,每读取一个元素又要进行拆箱操作。统一数据类型就可以省略装箱和拆箱的步骤了,这样能提高存储和读取的效率。

4. 为什么js数组中可以包含不同的数据类型和不声明数组的长度

(1)它不是维基百科中严格定义的数组。在js中,数组在底层中实际是一个对象。这个就可以解释为什么可以存储不同数据类型的数据了
(2)其次,js数组的实现不是基础的数据结构实现的,js数组就是一个map,它有key,有value,而key就是数组的索引,value就是数组中的元素。
(3) js数组中包括两种数组,分别是快数组和慢数组
快数组:快数组是一种线性存储,其长度是可变的,可以动态调整存储空间。其内部有扩容和收缩机制

  • 扩容:新容量 = 旧容量 + 50% + 16,因为JS数组是动态可变的,所以这样安排的固定空间势必会造成内存空间的损耗。然后扩容后会将数组拷贝到新的内存空间
  • 收缩:当前容量是否大于等于当前数组长度的2倍+16,此外的都填入Holes(空洞,就是数组分配了空间但没有存入元素)对象
  • V8用快数组来实现内存空间的连续(增加内存来提升性能,空间换时间),但由于JS是弱类型语言,空间无法固定,所以使用数组的length来作为依据,在底层进行内存的重新分配。

慢数组:慢数组底层实现使用的是 HashTable 哈希表,相比于快数组,[它不用开辟大块的连续空间],从而节省内存,但无疑执行效率是比快数组要低的(时间换空间)。
自动切换快数组和慢数组:JS中长度不固定,类型不固定,所以我们在适合的适合会做相应的转换,以期望它能以最适合当前数组的方式去提升性能。当快数组节省仅为50%的空间时,就采用慢数组。
拓展:类似的,ArrayBuffer 对象用来表示通用的、[固定长度]的原始二进制数据缓冲区,它是一个字节数组。使用ArrayBuffer能在操作系统内存中得到一块连续的二进制区域。然后这块区域供JS去使用。但需要注意的是ArrayBuffer本身不具备操作字节能力,需要通过视图去操作

5. 数组为什么查找元素效率比较高?(以js的角度看)

(1)数组中存储的每个元素在空间存储上,内存地址是连续状态的。
(2)其次,通常首元素的内存地址作为整个数组对象的内存地址,可见我们是知道首元素内存地址的。
(3)再加上数组中的元素是有下标的,有下标就可以计算出被查找的元素和首元素的偏移量。
综上所述,实际上在数组中查找元素是可以通过数学表达式计算被查找元素的内存地址的,通过内存地址可以直接定位该元素。也就是说数组中有100个元素和有100万个元素,实际上在查找方面效率是一样的。

6. 数组和链表,谁的查找速度更快,时间复杂度分别是多少

数组更快,时间复杂度是O(1),链表需要O(n)

7. 数组和链表,谁的插入删除效率更高,时间复杂度分别是多少

  • 链表更高,时间复杂度O(1),不过这里指的是找到节点之后纯粹的插入或删除动作所需的时间复杂度。如果当前还未定位到指定的节点,只是拿到链表的Head,这个时候要去删除此链表中某个固定内容的节点,则需要先查找到那个节点,这个查找的动作又是一个遍历动作了,这个遍历查找的时间复杂度却是O(n),两者加起来总的时间复杂度其实是O(n)的。
  • 数组的是O(n)

8. 为什么数组可以通过下标查找其值

因为数组是一段连续的存储空间

9. js如何查找元素的下标

利用indexOf实现。indexOf 返回值为数值类型,返回值为查找到的元素的下标,如果没找到返回-1
拓展问题
(1)如何判断等于?是== 还是 ===

 [1,'1'].indexOf(1);  

indexOf 使用strict equality (无论是 ===, 还是 triple-equals操作符都基于同样的方法)进行判断 searchElement与数组中包含的元素之间的关系。
(2)indexOf有没有找不到的值类型?对象?
有, NaN。因为NaN是唯一一个自己不等于自己的
(3)还有其他方案实现数组可以通过下标查找其值吗
find、findIndex、 filter、map、forEach、every、some、for循环、includes
(4)查找符合条件的元素下标值怎么办
findIndex

10. 反转一个单链表

public class ListNode {
  int val;
  ListNode next;
  ListNode(int x) { val = x; }
}
class Solution {
  public ListNode reverseList(ListNode head) {
    ListNode pre = null;
    ListNode curr = head;
    while(curr != null){
      ListNode temp = curr.next;
      curr.next = pre;
      pre = curr;
      curr = temp;
    }
    return pre;
  }
}
<!-- js实现 递归加闭包-->
var reverseList = function (head) {
    if (head === undefined || head === null) return null
    var originalHead = head
    var reverseHead
    var reverse = function (head) {
        if (head.next === null) {
            reverseHead = head
            return head
        } else {
            var node = reverse(head.next)
            node.next = head
            if (originalHead === head) {
              head.next = null
              return reverseHead
            } else {
              return head
            }
        }
    }
    return reverse(head)
};

11. 用js实现一个链表

    class Node {
      constructor(v, next) {
        this.value = v
        this.next = next
      }
    }
    class LinkList {
      constructor() {
        this.size = 0
        // 虚拟头部
        this.dummyNode = new Node(null, null)
      }
      find(header, index, currentIndex) {
        if (index === currentIndex) return header
        return this.find(header.next, index, currentIndex + 1)
      }
      // v --> value  index --> 要插入的位置
      addNode(v, index) {
        this.checkIndex(index)
        // 查找节点要从首元结点开始
        let prev = this.find(this.dummyNode, index, 0)    
        // prev.next变成当前新节点的next
        prev.next = new Node(v, prev.next)
        this.size++
        return prev.next
      }
      insertNode(v, index) {
        return this.addNode(v, index)
      }
      addToFirst(v) {
        return this.addNode(v, 0)
      }
      addToLast(v) {
        return this.addNode(v, this.size)
      }
      // isLast --> 是否是最后一个节点
      removeNode(index, isLast) {
        this.checkIndex(index)
        index = isLast ? index - 1 : index
        let prev = this.find(this.dummyNode, index, 0)
        let node = prev.next
        prev.next = node.next
        node.next = null
        this.size--
        return node
      }
      removeFirstNode() {
        return this.removeNode(0)
      }
      removeLastNode() {
        return this.removeNode(this.size, true)
      }
      // 检查当前节点的地址是否存在
      checkIndex(index) {
        if (index < 0 || index > this.size) throw Error('Index error')
      }
      // 查询节点
      getNode(index) {
        this.checkIndex(index)
        if (this.isEmpty()) return
        return this.find(this.dummyNode, index, 0).next
      }
      isEmpty() {
        return this.size === 0
      }
      getSize() {
        return this.size
      }
    }

参考链接

js数组和c++/java数组的区别
数组和链表的区别
indexOf拓展面试题
反转单链表
链表算法题拓展-翻转链表对和判断链表是否有环(快慢指针

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值