leetcode刷题(javaScript)——链表相关场景题总结

 链表中的元素在内存中不是顺序存储的,而是通过next指针联系在一起的。常见的链表有单向链表、双向链表、环形链表等

在 JavaScript 刷题中涉及链表的算法有很多,常见的包括:

1. 遍历链表:从头到尾遍历链表,处理每个节点的值或指针。
2. 反转链表:将链表的指针方向反转,使链表的尾部变为头部。
3. 合并两个有序链表:将两个有序链表合并成一个有序链表。
4. 删除链表中的指定节点:删除链表中特定值或位置的节点。
5. 检测链表中是否有环:判断链表中是否存在环形结构。

对于新手刷题链表,一些需要注意的方法和建议包括:

1. 熟悉链表的基本操作:了解链表的基本结构和操作,包括节点的定义、指针操作等。
2. 注意边界情况:在处理链表时,要考虑边界情况,如空链表、只有一个节点的链表等。
3. 使用递归:递归是解决链表问题的常见方法,可以简化问题的复杂度。
4. 双指针技巧:在解决链表问题时,双指针技巧经常会派上用场,例如快慢指针用于检测环。
5. 利用辅助数据结构:有时候可以借助辅助数据结构如哈希表来解决链表问题

206. 反转链表

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

思路:新创建一个链表,链表头指针pre,新的链表用pre指向,最后返回这个头指针。

1——》2——》3——》4——》5——》null

可以转换为

null《——1《——2《——3《——4《——5

pre初始是空对象pre = null

难点来了

怎么实现null《——1 让1的next指向null呢?

如果1的next改变指向,那么2以及之后的数据将丢失,所以需要提前将1的next暂存给一个变量;

然后这个时候可以改变1的next指向为pre了。

那原始链表还需要遍历呀,这个时候将暂存的next赋给current。同时pre也从null后移指向1.所以第二次遍历结束

2——》3——》4——》5——》null

^

current指向2

null《——1

                ^

                pre指向

第三次遍历结束

3——》4——》5——》null

^

current指向3

null《——1《——2

                             ^

                        pre指向

这道题可能当时做会了过段时间就忘了,所以要牢记改变指向的时候数据连接会丢失,需要考虑丢掉还是要保留,如果要保留数据,一定在改变指向前处理。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function (head) {
    let prev = null;
    let current = head;
    while (current !== null) {
        let nextNode = current.next;
        current.next = prev;
        prev = current;
        current = nextNode;
    }
    return prev;
};

 使用迭代的方式遍历链表,将每个节点的指针方向反转。使用 prev 指针来保存已经反转部分的链表,current 指针来遍历原始链表。在每一步中,我们将 current.next 指向 prev,然后更新 prev 和 current 指针继续向后移动,直到遍历完整个链表。

最后,返回反转后的链表的头节点,即 prev 指针所指向的节点,这个节点就是原始链表的尾节点,也就是反转后链表的头节点。

237. 删除链表中的节点

有一个单链表的 head,我们想删除它其中的一个节点 node

给你一个需要删除的节点 node 。你将 无法访问 第一个节点  head

链表的所有值都是 唯一的,并且保证给定的节点 node 不是链表中的最后一个节点。

删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:

  • 给定节点的值不应该存在于链表中。
  • 链表中的节点数应该减少 1。
  • node 前面的所有值顺序相同。
  • node 后面的所有值顺序相同。

思路:不删除当前node节点,又没有上一个元素的信息,不能更改上一个元素指向node.next。从node出发,让node变成node.next,跳过node.next这个节点。我变成了他,同时,让他消失。。

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} node
 * @return {void} Do not return anything, modify node in-place instead.
 */
var deleteNode = function(node) {
    node.val = node.next.val;
    node.next = node.next.next;
};

 node.val = node.next.val让node替换成node.next元素

node.next = node.next.next 干掉node.next

node替代node.next结束

83. 删除排序链表中的重复元素

给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var deleteDuplicates = function (head) {
    if (!head) return head;
    let cur = head;
    while (cur.next) {
        if (cur.val === cur.next.val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return head;
};

 思路:比较简单的链表删除,题目已经保证是排序好的链表,只用考虑相邻元素重复的情况。当前元素和下一个元素重复的话,将当前元素的next指向,下下一个元素。跳过重复的。

如果没有重复,指针往后移动一个。注意需要返回排序后的数组,所以头指针的指向不能变,最后返回head指针。过程用cur指针替代head进行向后移动。

 203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

示例 1:

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

示例 2:

输入:head = [], val = 1
输出:[]

示例 3:

输入:head = [7,7,7,7], val = 7
输出:[]                  

思路:这道题诈看很简单,用指针遍历,当前元素和val相等时,用下一个元素替换当前元素。这种方法在最后一个元素时会失效,因为无法杀死最后一个元素了,最后一个元素和它前一个连接无法断开。

那如果在头节点之前新增一个节点呢,用cur的next节点参与比较。当遇到最后一个元素时,其实是示例中的5.next ===6,让5.next=null即可,杀死了最后一个元素6

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function (head, val) {
    let newNode = new ListNode(0, head);
    let cur = newNode;
    while (cur) {
        if (cur.next && cur.next.val === val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return newNode.next;
};

 这里用newNode指向新创建的链表,最后返回的是newNode.next,因为头加入了一个无用的节点。

用next节点参与比较,当前元素改变它的next指向即可

二刷:可以使用一个虚拟头结点,将head作为虚拟头结点的next节点,这样就可以处理head.val==val的情况了

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} val
 * @return {ListNode}
 */
var removeElements = function (head, val) {
    let dummyNode = new ListNode(0, head);//新建一个虚拟头结点删除链表元素
    let cur = dummyNode;
    while (cur.next) {
        if (cur.next.val == val) {
            cur.next = cur.next.next;
        } else {
            cur = cur.next;
        }
    }
    return dummyNode.next;
};

 

 876. 链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点。

方法一:首先遍历一遍单链表,记录链表的长度len,计算中间节点的位置。 用空间换时间:

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var middleNode = function (head) {
    let cur = head;
    let setp = Math.floor(getLength(head) / 2);
    while (setp--) {
        cur = cur.next;
    }
    return cur;
};

function getLength(head) {
    let cur = head;
    let length = 0;
    while (cur) {
        length++;
        cur = cur.next;
    }
    return length;
}

 当我用这种方法写完,时间复杂度n+n/2感觉没那么简单,一看评论区,果然大神就是多,双指针,时间复杂度为n,一趟排序就找到中间位置了。

方法二:利用快慢指针,快指针每次走两步,慢指针每次走一步,所以快指针走的距离为慢指针的两倍,故当快指针遍历到链表末尾时,慢指针指向记为中间节点 

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var middleNode = function (head) {
    let f = head;
    let s = head;
    while (f && f.next) {
        f = f.next.next;
        s = s.next;
    }
    return s;
};

 简直了,只要思想简单,代码就简单。这里判断f.next.next最后一步是等于null所以f.next不能为null,f也不能为null。

 1290. 二进制链表转整数

给你一个单链表的引用结点 head。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。

请你返回该链表所表示数字的 十进制值

 思路:我原称这是一道数学题

让2进制转10进制,头节点是2进制的高位,尾节点是2进制的低位,一般可以将链表翻转,从低位开始用x*2的length次方逐步求和。但是也可以从高位到低位逐步求和。怎么从高位到低位呢

示例:1011 =11

第一个元素 1=result

到第二个元素,知道长度为2 将上一个结果*2+当前元素0=2=result

到第三个元素,知道长度为3,前两个都少个2,由于2是公共乘数,可以将2提出来,2*result+1=5=result

到第四个元素,知道长度为4,同理2*result+1=11=result

结束,每个缺失的2都补上了

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {number}
 */
var getDecimalValue = function (head) {
    let result = 0;
    let cur = head;
    while (cur) {
        result =( result << 1) + cur.val;
        cur = cur.next;
    }
    return result;

};

 result<<1可以用result*2表示。

这里要将位运算用括号包裹起来,在 JavaScript 中,位运算符(如左移运算符 <<)的优先级比加法运算符低。因此,如果在一个表达式中同时使用位运算符和加法运算符,为了确保运算顺序正确,建议使用括号来明确指定运算的顺序。

61. 旋转链表

给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。

思路:将尾部向前数第K个元素作为头,原来的尾接到原来的头上 。整个过程就变成了找四个节点。旧的尾节点end,从前往后数第len-k是新的尾节点newEnd,新的尾结点后面一个是新的头节点newHead。然后将end->head。将newEnd=null。然后返回newHead就好了

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var rotateRight = function (head, k) {
    //边界处理
    if (!head || !head.next || k == 0) return head;
    let kk;
    let cur = head;
    let end;
    let newEnd, newHead;
    let len = 0;
    //让cur往后走,找到最后一个节点end
    while (cur.next) {
        len++;
        cur = cur.next;
    }
    end = cur;
    len++;
    kk = k % len;
    if (kk == 0) return head;
    //cur归位到head,找到第len-k个节点,这是新的尾结点
    cur = head;
    for (let i = 1; i < len - kk; i++) {
        cur = cur.next;
    }
    newEnd = cur;
    //尾结点的下一个是新的头结点
    newHead = cur.next;
    //让end指向head,连接旧的尾和旧的头
    end.next = head;
    //断开newEnd,让newEnd指向null
    newEnd.next = null;
    return newHead;
};

快慢指针

 141. 环形链表

如果链表中存在环,快指针和慢指针最终会在环中相遇;如果链表中不存在环,快指针会先到达链表尾部。

给你一个链表的头节点 head ,判断链表中是否有环。

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

如果链表中存在环 ,则返回 true 。 否则,返回 false

使用快慢指针的技巧来检测链表中是否存在环。具体来说,定义了两个指针 f 和 s,初始时都指向链表的头部。然后,使用一个循环来遍历链表,其中快指针 f 每次移动两步,慢指针 s 每次移动一步。如果链表中存在环,快指针 f 会在某个时刻追上慢指针 s,此时就可以判断链表中存在环,返回 true;如果快指针 f 到达链表尾部(即 f 或 f.next 为 null),则说明链表中不存在环,返回 false

这种算法的时间复杂度为 O(n),其中 n 是链表的长度。

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {boolean}
 */
var hasCycle = function (head) {
    let f = head, s = head;
    while (f != null && f.next != null) {
        s = s.next;
        f = f.next.next;
        if (s == f) return true;
    }
    return false;
};

 使用双等号 == 运算符来比较两个对象并不会直接比较它们的内容,而是比较它们的引用。

s==f则快指针和慢指针指向了同一个对象

142. 环形链表 II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null

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

不允许修改 链表。

思路:这题题目既不希望你通过哈希表存储已遍历节点,也不希望你更改链表本身的val值。而是希望你只根据快慢指针以及数学思维解决问题。

就是一道脑筋急转弯了。如果做题不看题解根本算不明白。

看下代码随想录对这段的题解吧:

假设从头结点到环形入口节点 的节点数为x。 环形入口节点到 fast指针与slow指针相遇节点 节点数为y。 从相遇节点 再到环形入口节点节点数为 z。 如图所示:

相遇的时候slow指针行走的步数=x+y+(y+z)*m圈,假设慢指针在环里走了m圈。

fast指针行走的步数=x+y+(y+z)*n圈,假设快指针在环里走了n圈。

fast比slow多走一倍,因此2(x+y)+2(y+z)*m=x+y+(y+z)*n

x+y=(n-2m)(y+z)

x=(n-2m-1)(y+z)+z

看x和z的关系。其中y+z是环的周长,可以看到,x长度等于慢指针绕了环n-2m-1圈后,在走z个单位就可以与x重合。因此,可以让slow和头指针同步走,两者一定会相交,如果环很小,slow会绕环n-2m-1圈。

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var detectCycle = function (head) {
    if (!head || !head.next) return null;
    //快慢指针,如果相交即有环
    let slow = head, quick = head;
    while (quick && quick.next) {
        slow = slow.next;
        quick = quick.next.next;
        if (slow === quick) {//有环
            let cur = head;
            while (cur != slow) {//相加的时候,慢指针和头指针同时走c个节点一定能相遇
                cur = cur.next;
                slow = slow.next;
            }
            return cur;
        }
    }
    return null;
};

 19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。

示例 1:

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

思路:快慢指针,快指针先走n步,然后快慢一起走,直到快指针走到最后。

要进行边界值的判断,题目说了n一定大于1,因此,如果节点数小与等于1返回的一定是null。同时,如果快指针初始化走n步就已经走完了,说明删除的是头节点,此时返回头节点下一个即可。否则删除的是中间的元素。n是小与等于链表长度sz的,所以初始化快指针一定能顺利走完链表

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function (head, n) {
    if (!head || !head.next) return null;
    //让快指针先走n+1步
    let slow = head, quick = head;
    for (let i = 0; i < n; i++) {//slow记录要删除的节点的前一个节点
        quick = quick.next;
    }
    if (!quick) {//如果快指针走n步的时候就已经到结束位置了,删除的是头节点
        return head.next;
    }
    while (quick.next) {
        slow = slow.next;
        quick = quick.next;
    }
    slow.next = slow.next.next;
    return head;
};

当然,这个题还可以维护一个n+1长度的队列,如果队列长度等于n说明删除的是头节点,否则,队列的头维护的是要删除节点的前一个节点。

链表之间比较

21. 合并两个有序链表

 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} list1
 * @param {ListNode} list2
 * @return {ListNode}
 */
var mergeTwoLists = function (list1, list2) {
    if (!list1) return list2;
    if (!list2) return list1;
    
    let head = new ListNode(0, null);
    let cur = head;
    while (list1 && list2) {
        let val;
        if (list1.val < list2.val) {
            val = list1.val;
            list1 = list1.next;
        } else {
            val = list2.val;
            list2 = list2.next;
        }
        let node = new ListNode(val, null);
        cur.next = node;
        cur = node;
    }
    
    cur.next = list1 || list2;
    
    return head.next;
};

 为了正确地创建 ListNode 的实例,应该使用 new ListNode(val, null)而不是ListNode(val,null)这是因为创建的是节点的实例而不是单纯的调用函数。这里的ListNode是构造函数。

怎么区分普通函数和构造函数?

  • 看大小写:一般构造函数函数名的首字母会大写,普通函数是小写。
  • 函数中的this指向不同:普通函数的this在严格模式下指向undefined,非严格模式指向window对象。构造函数的this指向它创建的对象实例。

构造函数的执行流程

  •     立即创建一个新的对象
  •     将新建的对象设置给函数中的this,在构造函数中可使用this来引用新建的对象
  •     逐行执行函数中的代码
  •     将新建的对象作为返回值返回

 160. 相交链表

给你两个单链表的头节点 headAheadB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null

图示两个链表在节点 c1 开始相交

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构

这道题,如果不看题解,第一次做肯定没有思路

首先比较的不是节点的值相等,而是指向同一个节点。

其次,一般会考虑n^2时间复杂度,穷举A,对A的每项在穷举B

其实只要找到A、B的起始位,让A和B可以同步向后 移动。

例如,上图中,如果B一开始指向b2,那么比较b2和a1,不相等,均向后移动一位,比较a2和b3是否相等。不等,在往后移动,比较c1和c1是否相等,相等则返回A或B当前指向的节点

 要找出两个单链表相交的起始节点,可以使用双指针法。具体步骤如下:

  1. 分别遍历两个链表,计算它们的长度。
  2. 计算两个链表的长度差,然后让较长的链表的指针先移动长度差个节点,使得两个链表剩余的长度相等。
  3. 同时遍历两个链表,比较节点是否相同,找到相交的起始节点。
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */

/**
 * @param {ListNode} headA
 * @param {ListNode} headB
 * @return {ListNode}
 */
var getIntersectionNode = function (headA, headB) {
    let curA = headA;
    let curB = headB;
    let lengthA = getLength(curA);
    let lengthB = getLength(curB);
    if (lengthB > lengthA) {
        let step = lengthB - lengthA;
        while (step--) {
            curB = curB.next;
        }
    } else if (lengthB < lengthA) {
        let step = lengthA - lengthB;
        while (step--) {
            curA = curA.next;
        }
    }
    while (curA) {
        if (curA == curB) {
            return curA;
        } else {
            curA = curA.next;
            curB = curB.next;
        }
    }
    return null;
};

function getLength(head) {
    let length = 0;
    let current = head;
    while (current) {
        length++;
        current = current.next;
    }
    return length;
}

 注意在计算链表长度时不要破坏传入的head指针指向,要创建新的变量指向head指针。

拿到lengthA和lengthB后可以判断哪个更长,移动长的链表的cur指针。

A和B的cur指针同步移动时无论判断while(curA)还是while(curB)都可以,因为剩余要比较的链表长度相等呀,比较次数也相等

当A和B的cur指向一样,同理,返回curA或curB都行,结束判断

 2. 两数相加

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

思路:考察两个链表进行相加,比较经典的题目。在比较的时候要考虑两种情况,两个链表都存在,以及剩余了一个链表,其中一个比较长。并且要考虑最后一个进位,要单独处理。由于创建一个新的链表返回,所以新创建的头结点不能动,要用额外的地址指向新的头结点,并移动额外的变量。最后返回头结点。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var addTwoNumbers = function (l1, l2) {
    //创建一个头节点,最后返回它
    let head = new ListNode();
    let cur = head;//让cur初始时指向head,过程中只移动cur
    let carry = 0;//进位值
    let sum = 0;//sum值
    while (l1 && l2) {//l1和l2都存在
        sum = l1.val + l2.val + carry;//三者相加
        carry = Math.floor(sum / 10);//拿到进位值
        const node = new ListNode(sum % 10);//生成新的节点
        cur.next = node;//将新的节点插入
        cur = cur.next;//cur指向最后一个节点
        l1 = l1.next;
        l2 = l2.next;
    }
    let link = l1 || l2;//上面结束时,要么l1多,要么l2多出来
    while (link) {//用link计算多出来的链表
        sum = link.val + carry;//不要忘了进位
        const node = new ListNode(sum % 10);
        carry = Math.floor(sum / 10);
        cur.next = node;
        cur = cur.next;
        link = link.next;
    }
    if (carry) {//不要忘了进位
        const node = new ListNode(1);
        cur.next = node;
        cur = cur.next;

    }
    return head.next;//由于第一个head是空,所以返回head.next
};

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

思路:这题为什么是中等题,这是简单题吧,就是两个指针一个在后,一个在前,两两交换val值。往前走的时候每次都两步就好啦。

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var swapPairs = function (head) {
    if (!head || !head.next) return head;
    //初始快慢指针
    let fast = head.next;
    let slow = head;
    //如果快指针还没到头
    while (fast) {
        [fast.val, slow.val] = [slow.val, fast.val];//交换快慢指针的val
        slow = fast.next;//慢指针指向fast后面
        fast = fast.next ? fast.next.next : null;//先判断fast.next是不是存在,fast往前走两步
    }
    return head;//交换后返回head
};

86. 分隔链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

 思路:猛一看我还以为二分查找呢,然后看了一下,保留节点出现顺序。那么就想到了重建链表。根据x,遍历一次链表,将比x小的构建一条链表,大于等于x的构建一个链表,采用尾插法插入。然后将两个链表拼接,小的在前,大的在后。这里除了新建的两个链表创建节点外。其他节点用原有链表里的,重点在改变链表之间的指向关系

 由于头节点是一个空节点,所以返回的是头节点的next

/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} x
 * @return {ListNode}
 */
var partition = function (head, x) {
    if (!head || !head.next) return head;
    //比x大的串成一个链表l2
    //比x小的串成一个链表l1
    //这两个链表合并l1->l2就是要得到的结果
    let smallHead = new ListNode();
    let curS = smallHead;
    let largeHead = new ListNode();
    let curL = largeHead;
    let cur = head;
    while (cur) {
        if (cur.val < x) {//找到比x小的节点
            curS.next = cur;
            curS = curS.next;
        } else {
            curL.next = cur;
            curL = curL.next;
        }
        cur = cur.next;//让原链表cur向后走
    }
    curL.next = null;
    curS.next = largeHead.next;
    return smallHead.next;
};

 链表结合其他数据类型

 705. 设计哈希集合

不使用任何内建的哈希表库设计一个哈希集合(HashSet)。

实现 MyHashSet 类:

  • void add(key) 向哈希集合中插入值 key
  • bool contains(key) 返回哈希集合中是否存在这个值 key
  • void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。

思路:这道题需要有哈希表的前置储备知识,不然你都不知道它想考你什么

实现哈希表(也称为哈希映射或散列表)的方式有很多种

  • 数组+线性探测实现:将哈希值转换为数组的索引,将元素存储在数组中。当发生哈希冲突时,尝试使用下一个索引来存储元素,直到找到一个空闲的位置。这种实现方式简单,但会发生聚集现象,从而导致哈希表的性能下降。
  • 数组+链表:用数组来存储哈希表中的元素,但当发生哈希冲突时,不将元素存储在数组中,而是将元素存储在链表中。具体来说,将哈希值转换为数组的索引,并将元素存储在该索引对应的链表中。但如果链表的长度过长,则在查找、插入和删除元素时的性能会下降。
  • 双散列实现:双散列实现是一种开地址实现的扩展。在这种实现方式中,我们使用两个不同的哈希函数来计算元素的索引。当发生哈希冲突时,我们会尝试使用第二个哈希函数来计算索引,从而避免了使用相同的索引来存储不同的元素。

在本题的实现里,采用数组+链表的方式存储key。找数组下标的方法就用key%数组的长度。找到数组index后,将key放在index对应的链表里。于是对哈希表的判断元素是否存在,添加元素、删除元素,就转换成了在单链表里查找、删除、新增了。只不过多了找头结点的过程

// 额外定义链表节点类
class Node {
    constructor(val, next) {
        this.val = val !== undefined ? val : -1;
        this.next = next != undefined ? next : null;
    }
}

var MyHashSet = function () {
    //设定哈希表容量
    capacity = 100;
    this.data = [];//存储key对应的单链表
    //初始化每个index的节点
    for (let i = 0; i < capacity; i++) {
        this.data[i] = new Node();
    }
    //将key转换为index,获取index存储链表的头节点
    this.getHead = key => {
        let index = key % capacity;
        return this.data[index];
    }
};

/** 
 * @param {number} key
 * @return {void}
 */
MyHashSet.prototype.add = function (key) {
    if (this.contains(key)) return;
    //找到key通过hash生成的index里的链表头节点
    let head = this.getHead(key);
    //插在头结点的后面
    let temp = new Node(key, head.next);
    head.next = temp;
};

/** 
 * @param {number} key
 * @return {void}
 */
MyHashSet.prototype.remove = function (key) {
    //先判断集合里有没有key
    if (!this.contains(key)) return;
    //有key,找到对应index的单链表头节点
    let head = this.getHead(key);
    //单链表中删除指定key,都会吧
    while (head.next && head.next.val != key) {
        head = head.next;
    }
    head.next = head.next.next;
};

/** 
 * @param {number} key
 * @return {boolean}
 */
MyHashSet.prototype.contains = function (key) {//先写contains方法,因为新增和删除都会用到
    //根据key去找对应数组下标的链表头节点
    let head = this.getHead(key);
    //在单链表里查找key,都会吧
    while (head && head.val != key) {
        head = head.next;
    }
    return head != null;
};

/**
 * Your MyHashSet object will be instantiated and called as such:
 * var obj = new MyHashSet()
 * obj.add(key)
 * obj.remove(key)
 * var param_3 = obj.contains(key)
 */

706. 设计哈希映射

不使用任何内建的哈希表库设计一个哈希映射(HashMap)。

实现 MyHashMap 类:

  • MyHashMap() 用空映射初始化对象
  • void put(int key, int value) 向 HashMap 插入一个键值对 (key, value) 。如果 key 已经存在于映射中,则更新其对应的值 value
  • int get(int key) 返回特定的 key 所映射的 value ;如果映射中不包含 key 的映射,返回 -1
  • void remove(key) 如果映射中存在 key 的映射,则移除 key 和它所对应的 value

思路:与705一样,节点多了一个属性而已

使用数组+单链表的方式构建哈希表。通过key%数组得到哈希索引,在根据索引找到单链表的头节点。这样get、put和remove就转换为在单链表里进行查找、新增和删除

//定义单链表节点类,key,val,next
class Node {
    constructor(key, val, next) {
        this.key = key != undefined ? key : -1;
        this.val = val != undefined ? val : -1;
        this.next = next != undefined ? next : null;
    }
}
var MyHashMap = function () {
    let capactity = 100;//取哈希的数组长度
    this.data = [];//存单链表节点的数组
    for (let i = 0; i < capactity; i++) {
        this.data[i] = new Node();
    }
    //获取key所在单链表头节点
    this.getHead = key => {
        let index = key % capactity;
        return this.data[index];
    }
};

/** 
 * @param {number} key 
 * @param {number} value
 * @return {void}
 */
MyHashMap.prototype.put = function (key, value) {
    //先调get方法,判断是否存在
    let head = this.getHead(key);
    if (this.get(key) == -1) {//不存在,头插法插入这个节点
        let node = new Node(key, value, head.next);
        head.next = node;
    } else {//存在,单链表遍历,修改节点val
        while (head && head.key != key) {
            head = head.next;
        }
        head.val = value;
    }

};

/** 
 * @param {number} key
 * @return {number}
 */
MyHashMap.prototype.get = function (key) {//最先实现get方法,因为put和remove都会用到
    let head = this.getHead(key);//获取单链表头节点
    while (head && head.key != key) {//单链表里查找元素
        head = head.next;
    }
    return head ? head.val : -1;
};

/** 
 * @param {number} key
 * @return {void}
 */
MyHashMap.prototype.remove = function (key) {
    if (this.get(key) == -1) return;//调get方法,看有没有key
    let head = this.getHead(key);//有的话,找到head节点
    while (head.next && head.next.key != key) {//单链表节点删除
        head = head.next;
    }
    head.next = head.next.next;
};

/**
 * Your MyHashMap object will be instantiated and called as such:
 * var obj = new MyHashMap()
 * obj.put(key,value)
 * var param_2 = obj.get(key)
 * obj.remove(key)
 */

  • 24
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三月的一天

你的鼓励将是我前进的动力。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值