刷算法的第一天,先抄下来

350 阅读3分钟

是这样的,偶然看到AlgoCasts这个视频讲解算法的网站,它的试看资源里唯一一个简单的算法题,是leetcode的141题环形链表。视频所讲的解法应该是比较好理解的,但是本身就链表这个概念的理解,以及实际的应用场景,我简单google了一下javaScript 链表这个关键字,Emm...,说实话,我没理解。我又搜了一下链表的应用场景,Emm...,告辞。所以,假期刷算法的第一天,应该不能叫刷,叫抄吧,没理解,简单记录下概念和实现,卷吧都卷吧。

概念

大致说下对我看到的概念的一些感性理解吧,不是准确的定义,表述可能不是很准确。

  • 链表是由一个个节点组成的,每个节点有一个val和一个指针nextnext指向下一个节点。
  • 链表有头head和尾tail,单向链表tail指向null
  • 链表分为单向链表、双向链表和循环链表

下面是来自灵魂画手的草图:

再来说下和数组的区别:

  • 数组在内存中是连续存储,链表是随机存储
  • 数组插入和删除的效率比较低,链表的插入删除效率比较高
  • 随机读取数组某个索引的值效率很高,因为连续存储,内存地址是固定的。链表的数据查找效率就很低,需要从头开始遍历。

实现

class Node {
  constructor (value) {
    this.value = value
    this.next = null
  }
}

class LinkedList {
  constructor () {
    this.head = null
    this.tail = this.head
    this.length = 0
  }
  append (value) {
    const newNode = new Node(value)
    if (!this.head) {
      this.head = newNode
      this.tail = newNode
    } else {
      this.tail.next = newNode
      this.tail = newNode
    }
    this.length++
  }

  prepend (value) {
    const node = new Node(value)
    node.next = this.head
    this.head = node
    this.length++
  }

  insert (value, index) {
    if (index >= this.length) {
      this.append(value)
    }
    const node = new Node(value)
    const { prevNode, nextNode } = thisg.getPrevNextNodes(index)
    prevNode.next = node
    node.next = nextNode
    this.length++
  }

  lookup (index) {
    let counter = 0;
    let currentNode = this.head;
    while(counter < index){
      currentNode = currentNode.next;
      counter++;
    }
    return currentNode;
  }

  remove (index) {
    let {previousNode,currentNode} = this.getNodes(index)
    previousNode.next = currentNode.next
    this.length--
  }

  reverse () {
    let previousNode = null
    let currentNode = this.head
    while(currentNode !== null) {
      let nextNode = currentNode.next
      currentNode.next = previousNode
      previousNode = currentNode
      currentNode = nextNode
    }
    this.head = previousNode
  }
}

回到算法题

说回题目,同样把几种解法都抄下来:

set法

遍历链表,创建一个临时的空set(用空数组也行),每遍历一个节点,就往set里塞一个,然后判断有没有重复的,有重复的说明链表有环。感觉和数组去重里的一个解法的思路类似。

const hasCycle = function(head) {
    // set
    const set = new Set()
    let cur = head
    while (cur) {
        if (set.has(cur)) return true
        set.add(cur)
        cur = cur.next
    }
    return false
}

快慢指针法

把两个指针想象成两个在跑步的人,一个人的速度是另一个的2倍,如果是直线跑,两人永远都追不上,但如果绕着操场跑,快的肯定能追到慢的。这个方法没有用到额外的变量,所以空间复杂度是O(1),也是几种解法里最优的。

const hasCycle = function(head) {
    if(head === null || head.next === null) {
        return false;
    }
    let slow = head;
    let fast = head.next;
    while (slow) {
        if(slow === fast) {
        return true
        }
        slow = slow?.next || null;
        fast = fast?.next?.next || null;
    }
    return false;
};

打标签法

const hasCycle = function(head) {
  while (head) {
    if (head.tag) {
      return true;
    }
    head.tag = true;
    head = head.next;
  }
  return false;
}

骚操作,利用javascript语言特性

const hasCycle = function(head) {
  try {
    JSON.stringify(head);
    return false;
  } catch {
    return true;
  }
}

由于循环列表存在对象循环引用,而JSON.stringify在面对循环引用的对象时会报错,所以利用这个API的特性,也算是一种解法吧,因为提交也通过了。

很好,今天就抄到这吧,溜了。