数据结构与算法之链表

有更好的方法或者是有问题想讨论的同学可以评论or私信我,大家一起学习一起进步~

2022/4/2
I.链表环相关
牛客网-在线编程-算法篇-面试必刷top101-链表bm6
1.判断链表中是否有环
-方法1. 使用快慢指针 time: O(n) space: O(1)
定义慢指针slow和快指针fast, slow一次走一步,fast一次走两步。若链表无环,则slow和fast不可能相遇。若slow和fast相遇了,则链表一定有环。具体代码如下,
function hasCycle( head ) {
  //corner case, empty linked list has no cycle
  if (!head) return false;
  //Traverse linked list using two pointers.
  let slow = head, fast = head;
  //Move one pointer(slow_p) by one and another pointer(fast_p) by two.
  while (fast != null && fast.next != null) {
      slow = slow.next;
      fast = fast.next.next;
      //If these pointers meet at the same node then there is a loop. 
      if (slow == fast) return true;
  }
  //If pointers do not meet then linked list doesn’t have a loop.
  return false;
}
-方法2.使用哈希表 time: O(n) space: O(n)
定义一个哈希表,每次访问过一个结点我们都把这个结点加入哈希表中,如果新访问的结点已经存在于该哈希表中就说明链表有环。具体代码如下,
function hasCycle( head ) {
  // use hashtable, everytime we visited a node, add it into the hashtable
  let set = new Set();
  while (head != null) {
  if (set.has(head)) return true;
      set.add(head);
      head = head.next;
  }
  return false;
}
牛客网-在线编程-算法篇-面试必刷top101-链表bm7
2.链表中环的入口结点
-方法1.使用哈希表 time: O(n) space: O(n)
定义一个哈希表,每次访问过一个结点我们都把这个结点加入哈希表中,如果新访问的结点已经存在于该哈希表中就返回该结点。如果遍历完成也没有重复访问的节点就无环,返回null. 具体代码如下,
function EntryNodeOfLoop(pHead)
{
  //corner case
  if (!pHead) return null;
  //hashtable time: O(n) space: O(n)
  let set = new Set();
  while (pHead != null) {
  if (set.has(pHead)) return pHead;
      set.add(pHead);
      pHead = pHead.next;
  }
  return null;
}
-方法2.使用双指针 time: O(n) space: O(1)
定义一个慢指针一次走一步,一个快指针一次走两步,快指针和慢指针将会在环中某点相遇。设从链表头结点到环入口点到距离为a, 环入口点到相遇点到距离为b, 相遇点到环入口点到距离为c, 如下图所示(图片来源网络,侵删)

慢指针走的距离长度等于——a+b
快指针走的距离长度等于——a+k(b+c)+b, 其中k(k > 0)为快指针走的圈数。就像在环形操场跑步,快指针同学的速度是慢指针同学速度的两倍,那么快指针同学总会在某个点追上慢指针同学,并且此时快指针同学已经比慢指针同学多跑了一圈以上了。
我们知道快指针的速度是慢指针的两倍,相同时间下快指针走过的路程也该是慢指针的两倍,于是可以得到等式:
2(a+b) = a+k(b+c)+b  --> a = k(b+c) - b --> a = (k-1)(b+c) + c, k-1 > 0.
所以如果我们让一个指针从起点出发,一个指针从相遇点出发,最后它们一定会相遇在环入口。
代码如下,
function EntryNodeOfLoop(pHead)
{
  //corner case
  if (!pHead) return null;
  let slow = pHead, fast = pHead;
  while (fast != null && fast.next != null) {
      slow = slow.next;
      fast = fast.next.next;
      if (slow == fast) {
          slow = pHead;
          while (slow != fast) {
              slow = slow.next;
              fast = fast.next;
          }
      return slow;
      }
  }
  return null;
}

II.链表其他题目(剑指offer)

牛客网-剑指offer-知识分类篇-数据结构-链表jz6
3.从尾到头打印链表 time: O(n) space: O(n) 太简单不说了
牛客网-剑指offer-知识分类篇-数据结构-链表jz24
4.反转链表
-方法:先对原链表作头结点删除操作,再对新链表作头结点插入操作。代码如下,
function ReverseList(pHead) {
    let prev = null, curr = pHead;
    while (curr != null) {
        let next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
}
牛客网-剑指offer-知识分类篇-数据结构-链表jz52
5.两个链表的第一个公共结点
-方法:两个链表若是有公共结点且链表长度不相等,那么让长链表先把差值走了,短链表再开始走,就会碰到公共结点。代码如下,
function FindFirstCommonNode(pHead1, pHead2)
{
  //corner case
  if (pHead1 == null || pHead2 == null) return null;
  let p1 = pHead1, p2 = pHead2;
  while (p1 != p2) {
      p1 = p1.next;
      p2 = p2.next;

      if (p1 == p2) break;
      //pHead2 is longer, let it to walk first
      if (p1 == null) p1 = pHead2;
      //pHead2 is longer, let it to walk first
      if (p2 == null) p2 = pHead1;
  }
  return p1;
}

2022/4/3
牛客网-剑指offer-知识分类篇-数据结构-链表jz52
6.链表中倒数最后k个结点
     -方法1:先遍历一遍链表,计算出它的长度存入length变量。于是现在倒数第k个结点就是正数第(length - k)个结点。这里k可能大于length, 可能等于length, 也可能小于length. 当k大于length时,链表中不存在该结点,返回null; 当k等于length时,倒数第k个结点恰好是链表第一个结点,返回pHead; 当k小于length时,结点存在于链表中的(length - k)位置,遍历(length - k)次链表,然后返回剩余链表元素。代码如下,
function FindKthToTail( pHead , k ) {
  // conrner case
  if (pHead === null || k === 0) return null;

  // count the length of the linked list
  let length = 0, p = pHead;
  while (p) {
      length++;
      p = p.next;
  }

  // traverse the linked list by (length - k) times
  let times = length - k;
  if (times < 0) {
      return null;
  } else if (times === 0) {
      return pHead;
  } else {
      while (times > 0) {
          pHead = pHead.next;
          times--;
      }
      return pHead;
    }
}
     -方法2:思路与方法1相同,不过是不计算链表长度,直接遍历k次,剩余元素数量即为正序遍历次数。复制链表pHead, 然后遍历(k - 1)次该复制链表。最后(复制链表的剩余元素数量 - 1)则为正序遍历次数。代码如下,
function FindKthToTail( pHead , k ) {
  //corner case
  if (pHead == null || k == 0) return null;

  let node = pHead;
  //traverse the linked list by k times, and the length of the left linked list will be (length - k)
  for(let i = 1; i < k; i++){
      pHead = pHead.next;
      if (pHead == null) return null;
  }

  //traverse the linked list by (length - k) times, and we'll get the result
  while (pHead.next){
      node = node.next;
      pHead = pHead.next;
  }
  return node;
}

2022/4/4
牛客网-剑指offer-知识分类篇-数据结构-链表jz18
7.删除链表的结点
     -方法:找到待删结点的前一个结点,改变改结点指向。代码如下,
function deleteNode( head , val ) {
  // corner case
  if (!head) return null;
  if (head.val === val) return head.next;

  // find the element before the toBeDelete element
  let res = head;
  while (head !== null && head.next !== null && head.next.val !== val) head = head.next;

  // delete the toBeDelete element
  head.next = head.next.next;

  return res;
}
#数据结构与算法面试常考题##春招##实习##笔试题目##前端##校招#
全部评论

相关推荐

11-08 13:58
门头沟学院 Java
程序员小白条:竟然是蓝桥杯人才doge,还要花钱申领的offer,这么好的公司哪里去找
点赞 评论 收藏
分享
爱看电影的杨桃allin春招:我感觉你在炫耀
点赞 评论 收藏
分享
4 3 评论
分享
牛客网
牛客企业服务