数据结构专项-算法第一周(链表上)

121 阅读2分钟
  1. 141. 环形链表
  2. 142. 环形链表 II
  3. 202. 快乐数
  4. 206. 反转链表
  5. 92. 反转链表 II

141. 环形链表

给你一个链表的头节点 head ,判断链表中是否有环。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。 如果链表中存在环 ,则返回 true 。 否则,返回 false 。

提示: 链表中节点的数目范围是 [0, 104] -105 <= Node.val <= 105 pos 为 -1 或者链表中的一个 有效索引 。

题意理解: 判断一个链表是否存在环,及是否可以无限的循环访问

采用快慢指针法,就像操场跑步一样,两个人同时出发,一个人跑的快,一个人跑的慢,跑的快的那个一定会追上那个慢的人

  1. 假定快的是慢的的速度的二倍,及慢的走一步,快的走两步slow存慢的,quickly存快的
  2. 如果链表可以循环完,则快已经重点,慢的还在路上,返回false
  3. 当快的追上慢的时,则存在环,返回true
  4. 最后可以整合一下,返回快和慢是否相等即可
var hasCycle = function (head) {
    if (!head) return false;
    let quickly = head?.next?.next, slow = head;
    while (quickly && quickly.next && slow !== quickly) {
        quickly = quickly.next?.next;
        slow = slow.next;
    }
    return slow === quickly
};

142. 环形链表 II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。 不允许修改 链表。

提示: 链表中节点的数目范围在范围 [0, 104] 内 -105 <= Node.val <= 105 pos 的值为 -1 或者链表中的一个有效索引

题意理解: 返回链表中第一个环节点,不存在环则返回null

  1. 快慢指针法,假定快的是慢的的速度的二倍,及慢的走一步,快的走两步slow存慢的,quickly存快的
  2. 先找到相遇的节点,及slow=quickly
  3. 根据推断可知,相遇节点到第入环第一个节点的距里等于首节点到入环的距里

推理过程

  1. 先画图更好理解。
  2. 设首节点到第一个入环节点的距里为a,入环到相遇节点的距里为c,相遇节点到入环的距里为c,快指针是满指针的两倍
  3. 慢指针的距里 a+b;快指针的距里a+n(b+c)+b,n为b在环中的圈数
  4. 快指针是满指针的两倍 2(a+b) = a+n(b+c)+b,约一下可得 a= (n-1)b + (n-1+1)c =(n-1)(b+c) +c (b+c为一圈)
var detectCycle = function (head) {
    if (!head) return null;
    let quick = head, slow = head;
    while (true) {
        if (!slow.next || !quick.next?.next) return null;
        slow = slow.next;
        quick = quick.next?.next;
        if (slow === quick) {
            break
        }
    }
    while (quick && head && quick !== head) {
        quick = quick.next
        head = head.next
    }
    return quick
};

使用Set,及hash法 遍历链表,将当前节点存入set中,如果set存在则返回该节点否则存入set中 遍历结束,还不存在则返回null。

var detectCycle = function (head) {
    if (!head) return null;
    const set = new Set();
    while(head){
        if(set.has(head)){
            return head
        }
        set.add(head)
        head=head.next;
    }
    return null;
};

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。 如果这个过程 结果为 1,那么这个数就是快乐数。 如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1: 输入:n = 19 输出:true 解释: 12 + 92 = 82 82 + 22 = 68 62 + 82 = 100 12 + 02 + 02 = 1

提示: 1 <= n <= 231 - 1

题意理解: 判断一个数是否符合上述条件,是就返回true,否则返回false。

  1. 利用快慢指针原理,计算下一个数
  2. 如果是快乐数一定存在一个环就是为1的环,继续下去也为1
  3. 声明两个变量slow,quickly用来存储快慢数,循环slow,quickly
  4. 当两束相等且为1时则范湖true,否则返回false
var isHappy = function(n) {
    function getNextNum(x){
        let num=0;
        while(x){
            num+=Math.pow(x%10,2);
            x=Math.floor(x/10)
        }
        return num;
    }
    let slow=n,quickly=n;
    do{
        slow=getNextNum(slow);
        quickly=getNextNum(getNextNum(quickly));
    }while(slow!==quickly && slow!==1)
    return slow===1;
};

hash法及使用set原理 循环遍历计算结果,存入set中 如果当计算结果为1则返回true,否则存入当前数,set中存在当前数返回false

var isHappy = function(n) {
    let set=new Set();
    let num=n;
    while(true){
        let currentNum=0;
        while(num){
            currentNum+=Math.pow(num%10,2) ;
            num=Math.floor(num/10)
        }
        if(set.has(currentNum)) return false;
        set.add(currentNum);
        num=currentNum;
        if(currentNum===1){
            return true;
        }
    }
};

206. 反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

提示: 链表中节点的数目范围是 [0, 5000] -5000 <= Node.val <= 5000

提议理解: 将一个链表进行反转

  1. 递归反转
  2. 终止条件,当前节点不存在或当前节点的下个节点不存在,就返回当前节点
  3. 递归当前节点的下个节点
  4. 进行反转
var reverseList = function(head) {
    if(!head || !head.next) return head;
    let tail = head?.next;
    let ret = reverseList(head?.next)
    head.next = tail?.next;
    tail.next = head;
    return ret;
};

暴力法,借助变量进行反转

  1. 声明一个变量newHead用来存储新链表
  2. 遍历就链表,每次生诚新节点,新节点.next=newHead,newHead=新节点
  3. 返回newHead
var reverseList = function(head) {
    if(!head) return null;
    let newHead=null;
    while(head){
        let node = new ListNode(head.val)
        node.next=newHead;
        newHead=node;
        head=head.next;
    }
    return newHead;
};

92. 反转链表 II

给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

提示: 链表中节点数目为 n 1 <= n <= 500 -500 <= Node.val <= 500 1 <= left <= right <= n

题意理解: 反转位置left到right中间的链表,其余不变。数据不是从1开始,所以start.val === left获取反转开始位置不可取

  1. 先声明变量newHead并初始化一个新节点,以为可能从1就开始反转,就无法暂定到根节点
  2. 设置新节点的值newHead.next = head,以后操作新节点,设start用来存储开始位置,迭代寻找start = newHead
  3. 找到开始位置start,初始化一个变量pre等于开始位置start,将pre.next设为null,不影响后续使用,并将并进局部反转,使用递归
  4. 递归结束条件,找到反转结束位置,或者当前节点不存才或子节点不存在,返回当前节点
  5. pre.next指向递归的返回值
  6. 最后返回newHead.next
var reverseBetween = function (head, left, right) {
    if(left>=right) return head;
    function reverse(head, i, k) {
        if (i >= k || !head || !head.next) return head;
        let ne = head.next;
        let res = reverse(head.next, ++i, k)
        head.next = ne.next;
        ne.next = head;
        return res;
    }
    let newHead = new ListNode(-1);
     newHead.next = head;
    let start = newHead;
    let j=left;
    while (start && j>1) {
        start = start.next
        j--;
    }
    let pre = start.next;
    start.next=null;
    let currentReverse = reverse(pre, 1, right - left + 1)
    start.next = currentReverse
    return newHead.next;
};