From c2c64470b09c7b8d886d86ee1cf07cb981aa8d38 Mon Sep 17 00:00:00 2001 From: LouisFonda Date: Sat, 19 Apr 2025 18:10:05 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=93=BE=E8=A1=A8=E6=93=8D=E4=BD=9C=20?= =?UTF-8?q?2,19,21,25,61,82,86,92,138,141?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../link/138随机链表的复制.js | 148 +++++++++++++++++ top-interview-leetcode150/link/141环形链表.js | 49 ++++++ .../link/19删除链表的倒数第n个节点.js | 118 +++++++++++++ .../link/21合并两个有序链表.js | 97 +++++++++++ .../link/25k个一组反转链表.js | 85 ++++++++++ top-interview-leetcode150/link/2两数相加.js | 92 ++++++++++ top-interview-leetcode150/link/61旋转链表.js | 48 ++++++ .../link/82删除排序列表中的重复元素.js | 48 ++++++ top-interview-leetcode150/link/86分隔链表.js | 46 +++++ .../link/92反转链表II.js | 157 ++++++++++++++++++ .../link/linked-list-tools.js | 27 +++ 11 files changed, 915 insertions(+) create mode 100644 top-interview-leetcode150/link/138随机链表的复制.js create mode 100644 top-interview-leetcode150/link/141环形链表.js create mode 100644 top-interview-leetcode150/link/19删除链表的倒数第n个节点.js create mode 100644 top-interview-leetcode150/link/21合并两个有序链表.js create mode 100644 top-interview-leetcode150/link/25k个一组反转链表.js create mode 100644 top-interview-leetcode150/link/2两数相加.js create mode 100644 top-interview-leetcode150/link/61旋转链表.js create mode 100644 top-interview-leetcode150/link/82删除排序列表中的重复元素.js create mode 100644 top-interview-leetcode150/link/86分隔链表.js create mode 100644 top-interview-leetcode150/link/92反转链表II.js create mode 100644 top-interview-leetcode150/link/linked-list-tools.js diff --git a/top-interview-leetcode150/link/138随机链表的复制.js b/top-interview-leetcode150/link/138随机链表的复制.js new file mode 100644 index 0000000..d116bbb --- /dev/null +++ b/top-interview-leetcode150/link/138随机链表的复制.js @@ -0,0 +1,148 @@ +/** + * https://leetcode.cn/problems/copy-list-with-random-pointer/?envType=study-plan-v2&envId=top-interview-150 + * // Definition for a _Node. + * function _Node(val, next, random) { + * this.val = val; + * this.next = next; + * this.random = random; + * }; + */ + +/** + * @param {_Node} head + * @return {_Node} + */ +const copyRandomList = function (head) { + +}; + +// Definition for a _Node. +// eslint-disable-next-line no-underscore-dangle +function _Node(val, next, random) { + this.val = val; + this.next = next; + this.random = random; +} + +/* +遍历整个链表,创建每一个节点的克隆节点,并且使用map将它们直接联系起来,原节点作为key,新节点作为value, +遍历完毕之后,再遍历一一遍原链表,根据random_index和map来为可能列表添加random_index +*/ +function f1(head) { + const map = new Map(); // 储存原链表和克隆链表直接的关系 + const dummyHead = new _Node(); // 哑节点,用于构建克隆链表 + let curClone = dummyHead; + let cur = head; + + while (cur) { + // 创建克隆节点 + curClone.next = new _Node(cur.val); + curClone = curClone.next; + // 将原节点和克隆节点映射 + map.set(cur, curClone); + cur = cur.next; + } + + // 遍历原节点,根据原节点的信息为克隆节点添加random指针 + cur = head; + curClone = dummyHead.next; + while (cur) { + if (cur.random) { + curClone.random = map.get(cur.random); + } + cur = cur.next; + curClone = curClone.next; + } + return dummyHead.next; +} + +/* +f1 的优化版本,首先创建克隆节点和映射,之后再拼接克隆链表 +*/ +function f2(head) { + if (!head) return null; + + const map = new Map(); // 储存原节点 => 克隆节点的映射 + let cur = head; + + // 第一次遍历:创建所有新节点,并放入 map 中 + while (cur) { + map.set(cur, new _Node(cur.val)); + cur = cur.next; + } + + cur = head; + + // 第二次遍历:设置新节点的 next 和 random 指针 + while (cur) { + const cloneNode = map.get(cur); + cloneNode.next = map.get(cur.next) || null; + cloneNode.random = map.get(cur.random) || null; + cur = cur.next; + } + + return map.get(head); +} + +/* +直接再原节点中插入克隆节点,那么原节点和克隆节点就存在了引用关系,之后根据引用关系来设置random,最后 +分离原链表和克隆链表即可 +*/ + +function f3(head) { + if (!head) return null; + + // 创建克隆节点,并且把克隆节点放在原节点后面 + let cur = head; + while (cur) { + // 将原节点之后的所有节点移动到克隆节点的后面,再将克隆节点作为原节点的next + const clone = new _Node(cur.val); + clone.next = cur.next; + cur.next = clone; + cur = clone.next; // 遍历下一个原节点 + } + + // 更具原节点的random来设置克隆节点的random + + cur = head; + while (cur) { + if (cur.random) { + cur.next.random = cur.random.next; + } + cur = cur.next.next; // 下一个原节点 + } + + // 分离节点 + cur = head; + const cloneHead = head.next; + while (cur) { + const clone = cur.next; + cur.next = clone.next; // 原节点指向原本下一个节点 + if (clone.next) { + clone.next = clone.next.next; // 克隆节点指向克隆节点的下一个节点 + } + cur = cur.next; + } + return cloneHead; +} + +/* +回溯加哈希表 +*/ + +function f4(head, cacheMap = new Map()) { + if (!head) return null; + + // 如果没有这个节点的克隆映射 + if (!cacheMap.has(head)) { + const clone = new _Node(head.val); + cacheMap.set(head, clone); + // 克隆节点的下一个节点为当前节点的下一个节点的克隆节点,递归调用即可 + clone.next = f4(head.next, cacheMap); + // random同理 + clone.random = f4(head.random, cacheMap); + } + + // 如果有克隆节点,直接返回 + return cacheMap.get(head); +} diff --git a/top-interview-leetcode150/link/141环形链表.js b/top-interview-leetcode150/link/141环形链表.js new file mode 100644 index 0000000..4d1e36a --- /dev/null +++ b/top-interview-leetcode150/link/141环形链表.js @@ -0,0 +1,49 @@ +/** + * https://leetcode.cn/problems/linked-list-cycle/?envType=study-plan-v2&envId=top-interview-150 + * Definition for singly-linked list. + * function ListNode(val) { + * this.val = val; + * this.next = null; + * } + */ + +/** + * @param {ListNode} head + * @return {boolean} + */ +const hasCycle = function (head) { + +}; + +/* +利用set来检测在遍历的过程中是否发生了重复,如果发生了重复说明有环,如果没有表示无环 +*/ + +function f1(head) { + const seenNodes = new Set(); + + let curNote = head; + + while (curNote !== null) { + if (seenNodes.has(curNote)) return true; // 之前遍历出现过,直接判定有环 + seenNodes.add(curNote); + curNote = curNote.next; + } + return false; // 遍历了整个链表也没有发生重复,表示无环 +} + +/* +通过快慢指针来判断是否有环 +*/ + +function f2(head) { + let slow = head; + let fast = head; + + while (fast !== null && fast.next !== null) { + slow = slow.next; + fast = fast.next.next; + if (slow === fast) return true; // 如果快慢指针相遇,表示有环 + } + return false; // 如果快指针走到了链表末尾,表示没有环 +} diff --git a/top-interview-leetcode150/link/19删除链表的倒数第n个节点.js b/top-interview-leetcode150/link/19删除链表的倒数第n个节点.js new file mode 100644 index 0000000..0200ddd --- /dev/null +++ b/top-interview-leetcode150/link/19删除链表的倒数第n个节点.js @@ -0,0 +1,118 @@ +/** + * 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} + */ +const removeNthFromEnd = function (head, n) { + +}; + +function ListNode(val, next) { + this.val = (val === undefined ? 0 : val); + this.next = (next === undefined ? null : next); +} + +/* +思路:首先统计列表的长度,根据长度信息找到倒数第n个节点的前一个节点,把它删掉即可 +*/ +function f1(head, n) { + // 1.获取列表的长度 + const len = getListLength(head); + // 2.创建哑节点,减少处理逻辑 + const dummyHead = new ListNode(-1); + dummyHead.next = head; + // 3.找到要删除节点的前一个节点 + let pre = dummyHead; + for (let i = 0; i < len - n; i++) { + pre = pre.next; + } + // 4. 删除这个节点 + pre.next = pre.next.next; + return dummyHead.next; +} + +/** + * 获取链表的节点个数 + * @param {ListNode} head - 链表的头节点 + * @return {number} - 链表的节点个数 + */ +const getListLength = function (head) { + let count = 0; // 初始化计数器 + let current = head; // 初始化当前节点为链表头 + + // 遍历整个链表,直到链表末尾 + while (current !== null) { + count++; // 每遍历一个节点,计数器加一 + current = current.next; // 移动到下一个节点 + } + + return count; // 返回链表的长度 +}; + +/* +思路:只需要找到要去除节点的前一个节点就能去掉这个节点,所以核心问题就变成了 +如何寻找这个节点,直接利用快慢指针,首先初始化两个指针在同一位置,然后让快指针 +往后走n步,之后一同走,直到快指针到达最后一个节点,此时的慢指针指向的就是要删除 +节点的前一个节点。 +*/ + +function f2(head, n) { + const dummyHead = new ListNode(-1); // 建立哑节点,便于处理head + dummyHead.next = head; + let slow = dummyHead; + let fast = dummyHead; + + // 1.快指针先走n+1步(夺走一部是因为从哑节点开始走) + for (let i = 0; i <= n; i++) { + fast = fast.next; + } + + // 2.两个指针同时往后走,如果fast已经是最后一个节点, + while (fast) { + slow = slow.next; + fast = fast.next; + } + + // 3.删除slow后面的那个节点 + slow.next = slow.next.next; + + // 4.返回处理后的链表 + return dummyHead.next; +} + +/* +利用栈,f1中我们统计了链表的长度,之后根据长度和n来找到要删除节点的前一个节点,这样实际上遍历 +了两遍,可以直接将所有节点按照顺序压入栈中,之后弹出栈顶的n个元素,剩下的栈顶元素就是我们要操作 +的pre节点 +*/ + +function f3(head, n) { + const dummyHead = new ListNode(-1); // 建立哑节点,便于处理head + dummyHead.next = head; + + // 1.将所有节点压入栈中 + const stack = []; + let cur = dummyHead; + while (cur) { + stack.push(cur); + cur = cur.next; + } + + // 2.弹出栈顶的n个元素 + for (let i = 0; i < n; i++) { + stack.pop(); + } + // 3.操作栈顶元素,这个元素就是要删除元素的前一个节点 + const prev = stack[stack.length - 1]; + prev.next = prev.next.next; + + // 4.返回修改后的链表 + return dummyHead.next; +} diff --git a/top-interview-leetcode150/link/21合并两个有序链表.js b/top-interview-leetcode150/link/21合并两个有序链表.js new file mode 100644 index 0000000..a356417 --- /dev/null +++ b/top-interview-leetcode150/link/21合并两个有序链表.js @@ -0,0 +1,97 @@ +/** + * 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} + */ +const mergeTwoLists = function (list1, list2) { + +}; + +function ListNode(val, next) { + this.val = (val === undefined ? 0 : val); + this.next = (next === undefined ? null : next); +} + +/* +遍历两个链表,如果l1的值小于l2的值,就创建一个新的节点,把l1的值放入这个新节点中,之后把这个新节点 +拼接到l3中,之后返回代表l3的头节点即可 +*/ +function f1(list1, list2) { + const dummyHead = new ListNode(0); // 哑节点,用于拼接链表 + let current = dummyHead; + + while (list1 !== null || list2 !== null) { + if (list1 && list2) { + if (list1.val <= list2.val) { + current.next = new ListNode(list1.val); + list1 = list1.next; + } else { + current.next = new ListNode(list2.val); + list2 = list2.next; + } + current = current.next; + } else if (list1 === null) { // 当list1为空时直接将list2剩余部分拼接即可 + current.next = list2; + break; + } else if (list2 === null) { // 当list2为空时直接将list1剩余部分拼接即可 + current.next = list1; + break; + } + } + return dummyHead.next; +} + +/* +思路和上面的一致,上面的效率之所以慢是因为花费了大量的时间在创建新节点上,这个操作虽然直观,但是 +显得多余。 +*/ +function f2(list1, list2) { + const dummyHead = new ListNode(0); // 哑节点,用于拼接链表 + let current = dummyHead; + + while (list1 !== null && list2 !== null) { + if (list1.val <= list2.val) { + current.next = list1; + list1 = list1.next; + } else { + current.next = list2; + list2 = list2.next; + } + current = current.next; + } + + // 如果 list1 或者 list2 还有剩余节点,直接拼接到结果链表 + if (list1 !== null) { + current.next = list1; + } else if (list2 !== null) { + current.next = list2; + } + return dummyHead.next; +} + +/* +利用递归,思考递归过程,首先思考f3的作用,f3的作用非常单一,就是合并两个有序链表,合并完成之后,使其 +仍然有序,假设list1[0]比list2[0]小,那么list1[0]一定是合并后链表的头节点,我们只需把list1[1:]和list2 +继续做交给f3,把合并后的头节点作为list1[0]的next,不就完成整个递归过程了嘛! +*/ +function f3(l1, l2) { + if (l1 === null) { + return l2; // 如果 l1 空了,返回 l2 + } if (l2 === null) { + return l1; // 如果 l2 空了,返回 l1 + } if (l1.val < l2.val) { + // 如果 l1 的值小于 l2,则 l1 当前节点连接到后面的结果 + l1.next = mergeTwoLists(l1.next, l2); + return l1; // 返回 l1,表示 l1 的节点被连接到了合并后的链表中 + } + // 否则,l2 当前节点连接到后面的结果 + l2.next = mergeTwoLists(l1, l2.next); + return l2; // 返回 l2,表示 l2 的节点被连接到了合并后的链表中 +} diff --git a/top-interview-leetcode150/link/25k个一组反转链表.js b/top-interview-leetcode150/link/25k个一组反转链表.js new file mode 100644 index 0000000..5804fef --- /dev/null +++ b/top-interview-leetcode150/link/25k个一组反转链表.js @@ -0,0 +1,85 @@ +/** + * 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} + */ +const reverseKGroup = function (head, k) { + +}; + +function ListNode(val, next) { + this.val = (val === undefined ? 0 : val); + this.next = (next === undefined ? null : next); +} + +/* +这个问题可以拆解成n个leetcode的子问题,直接利用k把区间划分出来,调用reverseBetween即可 +*/ +function f1(head, k) { + // 利用哑节点来处理头节点问题 + const dummyHead = new ListNode(-1); + dummyHead.next = head; + let left = 2; + let right = left + k - 1; + const n = getListLength(dummyHead); + while (right <= n) { + reverseBetween(, left, right); + left = right + 1; + right = left + k - 1; + } + return dummyHead.next; +} + +/** + * + * @param {*} head 头节点 + * @param {*} left 要反转的区域的第一个节点 + * @param {*} right 要反转的区域的最后一个节点 + * @returns 返回区域反转后的节点 + */ +const reverseBetween = function (head, left, right) { + const dummyHead = new ListNode(-1); // 创建一个哑节点,简化边界情况 + dummyHead.next = head; // 哑节点的next指向链表头 + + let pre = dummyHead; // `pre` 用于指向 `left` 前一个节点 + // 寻找 `left` 的前一个节点 + for (let i = 0; i < left - 1; i++) { + pre = pre.next; + } + + const cur = pre.next; // `cur` 指向要反转部分的第一个节点 + // 从 `left` 到 `right` 反转链表部分 + for (let i = 0; i < right - left; i++) { + const next = cur.next; // `next` 指向当前节点的下一个节点 + cur.next = next.next; // 跳过 `next` 节点 + next.next = pre.next; // 将 `next` 插入到 `cur` 前面 + pre.next = next; // 更新 `pre.next` 为 `next` + } + + return dummyHead.next; // 返回反转后的链表 +}; + +/** + * 获取链表的节点个数 + * @param {ListNode} head - 链表的头节点 + * @return {number} - 链表的节点个数 + */ +let getListLength = function (head) { + let count = 0; // 初始化计数器 + let current = head; // 初始化当前节点为链表头 + + // 遍历整个链表,直到链表末尾 + while (current !== null) { + count++; // 每遍历一个节点,计数器加一 + current = current.next; // 移动到下一个节点 + } + + return count; // 返回链表的长度 +}; diff --git a/top-interview-leetcode150/link/2两数相加.js b/top-interview-leetcode150/link/2两数相加.js new file mode 100644 index 0000000..6012a80 --- /dev/null +++ b/top-interview-leetcode150/link/2两数相加.js @@ -0,0 +1,92 @@ +/** + * 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} + */ +const addTwoNumbers = function (l1, l2) { + +}; + +/* +直接遍历两个链表,列表的头节点代表的是各位,后一个节点代表的是十位,以此类推,所以只需定义一个 +变量表示进位即可,把计算好的结果拼接成l3即可 +*/ + +function ListNode(val, next) { + this.val = (val === undefined ? 0 : val); + this.next = (next === undefined ? null : next); +} + +function f1(l1, l2) { + let carry = 0; // 进位 + const dummyHead = new ListNode(0); // 哑节点,用于返回结果 + let current = dummyHead; // 当前节点,用于构建求和之和的链表 + + // 遍历两个链表,直到,链表都为空,并且没有进位(只要有节点或者进位不等于0就需要创建新的求和节点) + + while (l1 !== null || l2 !== null || carry !== 0) { + let sum = carry; + + if (l1) { + sum += l1.val; + l1 = l1.next; + } + + if (l2) { + sum += l2.val; + l2 = l2.next; + } + + // 根据sum,计算出current节点的值和进位值 + current.next = new ListNode(sum % 10); + current = current.next; + carry = Math.floor(sum / 10); + } + return dummyHead.next; +} + +/* +将链表转换成字符串数字,之后把字符串数字相加,最后处理成一个新的列表 +*/ +function f2(l1, l2) { + let numStr1 = ''; + let numStr2 = ''; + + // 将第一个链表反转并拼接为字符串 + let cur1 = l1; + while (cur1 !== null) { + numStr1 = cur1.val + numStr1; + cur1 = cur1.next; + } + + // 将第二个链表反转并拼接为字符串 + let cur2 = l2; + while (cur2 !== null) { + numStr2 = cur2.val + numStr2; + cur2 = cur2.next; + } + + // 将拼接的字符串转为数字并进行加法运算 + const sum = BigInt(numStr1) + BigInt(numStr2); // 使用 BigInt 处理大数字 + + // 将求和结果转换为字符串 + const sumStr = sum.toString(); + + // 将求和结果转换为链表 + const dummyHead = new ListNode(0); + let current = dummyHead; + + for (let i = sumStr.length - 1; i >= 0; i--) { + current.next = new ListNode(Number(sumStr[i])); + current = current.next; + } + + return dummyHead.next; // 返回结果链表的头节点 +} diff --git a/top-interview-leetcode150/link/61旋转链表.js b/top-interview-leetcode150/link/61旋转链表.js new file mode 100644 index 0000000..5334ef9 --- /dev/null +++ b/top-interview-leetcode150/link/61旋转链表.js @@ -0,0 +1,48 @@ +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * https://leetcode.cn/problems/rotate-list/?envType=study-plan-v2&envId=top-interview-150 + * @param {ListNode} head + * @param {number} k + * @return {ListNode} + */ +/* +思路:观察发现,旋转1,就等于将链表首位相连,从原头节点出发,走 n-2 步,之后断开这个节点后后面 +的节点连接即可,例如1->2->3,roate1就是3->1->2,来一个长一点的例子1->2->3->4->5->6,我们假设 +要roate4,想象6->1是首位相连的,从原头节点1开始往后走n-4-1也就是1步,到达2的位置,这个时候记录 +2.next作为返回的头节点,并且把它断开,就变成了3->4->5->5->1->2,这就是我们要的结果 +*/ + +const rotateRight = function (head, k) { + +}; + +function f1(head, k) { + if (head === null || head.next === null) return head; // 没有节点,或者只有一个节点,直接返回 + // 1.遍历链表获取尾节点,和节点个数 + let tail = head; + let len = 1; + while (tail.next) { + len++; + tail = tail.next; + } + + // 2.把链表首尾相连 + tail.next = head; + + // 3.从head往后走n-k-1步,找到要断开的位置 + tail = head; + k %= len; // 当k比链表长度还长时需取模操作,比如len=3,要旋转4,那么效果和旋转1是一样的 + for (let i = 0, setp = len - k - 1; i < setp; i++) { + tail = tail.next; + } + // 4.保留tail的next作为新节点的头节点,然后断开 + const rHead = tail.next; + tail.next = null; + return rHead; +} diff --git a/top-interview-leetcode150/link/82删除排序列表中的重复元素.js b/top-interview-leetcode150/link/82删除排序列表中的重复元素.js new file mode 100644 index 0000000..965e1af --- /dev/null +++ b/top-interview-leetcode150/link/82删除排序列表中的重复元素.js @@ -0,0 +1,48 @@ +/** + * 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} + */ +const deleteDuplicates = function (head) { + +}; + +function ListNode(val, next) { + this.val = (val === undefined ? 0 : val); + this.next = (next === undefined ? null : next); +} + +/* +思路:先建立哑节点,用于处理头部重复的情况,用cur来遍历整个链表,如果cur.next和cur.next.next 的值 +相等,那么表示从cur.next开始发生了重复,我们记录这个val,从cur.next开始删除所有值等于val的节点;如果 +cur.next和cur.next.next不相等,那么就没有重复,将cur移动到下一个节点cur = cur.next +*/ + +function f1(head) { + if (!head) { + return head; + } + const dummyHead = new ListNode(-1); + dummyHead.next = head; + let cur = dummyHead; + let curVal; // 保存重复节点的val + // 从哑节点开始,查看next和next.next是否相等 + while (cur.next && cur.next.next) { + if (cur.next.val === cur.next.next.val) { + curVal = cur.next.val; + // 检测cur.next的值是否等于curVal,如果等于就删除这个节点 + while (cur.next && cur.next.val === curVal) { + cur.next = cur.next.next; + } + } else { + cur = cur.next; + } + } + return dummyHead.next; +} diff --git a/top-interview-leetcode150/link/86分隔链表.js b/top-interview-leetcode150/link/86分隔链表.js new file mode 100644 index 0000000..ff4d8cd --- /dev/null +++ b/top-interview-leetcode150/link/86分隔链表.js @@ -0,0 +1,46 @@ +/** + * 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} + */ +import { ListNode } from './linked-list-tools'; + +const partition = function (head, x) { + +}; + +/* +思路:遍历整个链表,把小于x的值连接到一个叫smallHead的哑节点上,把大于等于x的值连接到一个叫largeHead的哑节点上 +之后把smallHead整个链表,和largeHead整个链表连接起来就行了 +*/ +function f1(head, x) { + let small = new ListNode(0); + let large = new ListNode(0); + const smallHead = small; + const largeHead = large; + + // 遍历整个head表示的列表 + while (head) { + if (head.val < x) { + small.next = head; + small = small.next; + } else { + large.next = head; + large = large.next; + } + head = head.next; + } + + // 连接两个链表 + small.next = largeHead.next; + large.next = null; // 思考1->2->3->1, x=2 这种情况 samllHead:1->1, largeHead:2->3->1,这个时候3->1是不应该存在的 + + return smallHead.next; +} diff --git a/top-interview-leetcode150/link/92反转链表II.js b/top-interview-leetcode150/link/92反转链表II.js new file mode 100644 index 0000000..4c9b9ed --- /dev/null +++ b/top-interview-leetcode150/link/92反转链表II.js @@ -0,0 +1,157 @@ +/** + * Definition for singly-linked list. + * function ListNode(val, next) { + * this.val = (val===undefined ? 0 : val) + * this.next = (next===undefined ? null : next) + * } + */ +/** + * https://leetcode.cn/problems/reverse-linked-list-ii/?envType=study-plan-v2&envId=top-interview-150 + * @param {ListNode} head + * @param {number} left + * @param {number} right + * @return {ListNode} + */ +const reverseBetween = function (head, left, right) { + +}; + +/* +思路: 首先找到left指向的节点和right指向的节点,之后把left的前一个节点和right的后一个节点保存起来 +,之后反转left到right的所有节点,再把原先得前后两部分拼接上即可。 +*/ + +function f1(head, left, right) { + if (left === right) return head; // 无需反转 + let cur = head; + let i = 1; + let pre = null; + let leftNode = null; + let rightNode = null; + let next = null; + while (cur) { + if (i === left - 1) { // 寻找left的前一个节点 + pre = cur; + } else if (i === left) { // 寻找left节点 + leftNode = cur; + } else if (i === right) { // 寻找right 节点 + rightNode = cur; + next = cur.next; + rightNode.next = null; // 将链表截断 + break; // 退出循环 + } + cur = cur.next; + i++; + } + + // 从left到right开始反转 + const rHead = reverseLink(leftNode); // rHead和RightNode指向了同一个节点 + if (pre) { + pre.next = rHead; + } else { + head = rightNode; + } + leftNode.next = next; + return head; +} + +/* + 反转一个链表,使用递归 + */ +function reverseLink(head) { + // 基本情况:如果链表为空或只有一个节点,直接返回 + if (!head || !head.next) return head; + + // 递归反转后续链表 + const rHead = reverseLink(head.next); + + // 当前节点的下一个节点的 next 指向当前节点,反转链表 + head.next.next = head; + + // 断开当前节点与下一个节点的连接,防止形成环 + head.next = null; + + return rHead; +} + +/* +反转一个链表使用迭代 +*/ +const reverseLinkedList = (head) => { + let pre = null; + let cur = head; + + while (cur) { + const next = cur.next; + cur.next = pre; + pre = cur; + cur = next; + } +}; + +function ListNode(val, next) { + this.val = (val === undefined ? 0 : val); + this.next = (next === undefined ? null : next); +} + +/* +优化:思路和上面是一样的,利用哑节点来优化,减少边界判断情况 +*/ +function f2(head, left, right) { + // left在等于1时和大于1时处理方式不同,利用哑节点来减少判断情况 + const dummyHead = new ListNode(-1); + dummyHead.next = head; + + // 1.寻找left节点的前一个节点pre + // 这里直接使用for循环,从dummyHead的位置走left-1步即可 + let pre = dummyHead; + for (let i = 0; i < left - 1; i++) { + pre = pre.next; + } + + // 2.从pre的位置往后走 right - Left + 1步,即可找到right指向的节点 + let rightNode = pre; + for (let i = 0; i < right - left + 1; i++) { + rightNode = rightNode.next; + } + + // 3.将链表从left到right的位置切断 + const leftNode = pre.next; + const surr = rightNode.next; + pre.next = null; + rightNode.next = null; + + // 4.反转切断的这一部分 + reverseLinkedList(leftNode); + + // 5.接回原来的链表中 + pre.next = rightNode; + leftNode.next = surr; + return dummyHead.next; +} + +/* +直接在链表上面操作,把要反转的区域 +*/ + +function f3(head, left, right) { + const dummyHead = new ListNode(-1); // 创建一个哑节点,简化边界情况 + dummyHead.next = head; // 哑节点的next指向链表头 + + let pre = dummyHead; // `pre` 用于指向 `left` 前一个节点 + // 寻找 `left` 的前一个节点 + for (let i = 0; i < left - 1; i++) { + pre = pre.next; + } + + const cur = pre.next; // `cur` 指向要反转部分的第一个节点 + // 从 `left` 到 `right` 反转链表部分 + for (let i = 0; i < right - left; i++) { + const next = cur.next; // `next` 指向当前节点的下一个节点 + cur.next = next.next; // 跳过 `next` 节点 + next.next = pre.next; // 将 `next` 插入到 `cur` 前面 + pre.next = next; // 更新 `pre.next` 为 `next` + } + + return dummyHead.next; // 返回反转后的链表 +} diff --git a/top-interview-leetcode150/link/linked-list-tools.js b/top-interview-leetcode150/link/linked-list-tools.js new file mode 100644 index 0000000..7df73ea --- /dev/null +++ b/top-interview-leetcode150/link/linked-list-tools.js @@ -0,0 +1,27 @@ +/** + * 获取链表的节点个数 + * @param {ListNode} head - 链表的头节点 + * @return {number} - 链表的节点个数 + */ +export const getListLength = function (head) { + let count = 0; // 初始化计数器 + let current = head; // 初始化当前节点为链表头 + + // 遍历整个链表,直到链表末尾 + while (current !== null) { + count++; // 每遍历一个节点,计数器加一 + current = current.next; // 移动到下一个节点 + } + + return count; // 返回链表的长度 +}; + +/** + * 链表节点 + * @param {*} val + * @param {*} next + */ +export function ListNode(val, next) { + this.val = (val === undefined ? 0 : val); + this.next = (next === undefined ? null : next); +}