K个一组翻转链表
在JavaScript中,处理链表问题时,通常需要手动实现链表节点的数据结构,并编写相应的函数来操作链表。对于“K个一组翻转链表”的问题,你可以按照以下步骤来实现:
思路
-
定义一个哑节点(Dummy Node):哑节点作为链表的头部,其
next
指针指向原始链表的头节点。哑节点的存在可以简化边界条件的处理,特别是在翻转第一组节点时。 -
遍历链表:使用指针(例如
prev
和head
)遍历链表,其中prev
指针指向当前要翻转的链表段的前一个节点,head
指针指向当前要翻转的链表段的头节点。 -
检查剩余节点数:在每次翻转之前,通过遍历来检查剩余节点数是否足够K个。如果不足K个,则无需翻转,直接返回当前链表。
-
翻转链表段:使用辅助函数翻转从
head
到tail.prev
(不包括tail
)的链表段,并返回翻转后的新头节点和新尾节点。注意,这里的tail
是原始链表中第K个节点的下一个节点,即翻转后链表段的下一个节点。 -
连接翻转后的链表段:将翻转后的链表段通过
prev.next
连接到原链表上,并更新prev
和head
为下一组要翻转的链表段的起始位置。 -
重复步骤2-5,直到遍历完整个链表。
-
返回结果:最终返回哑节点的
next
指针,即翻转后的链表头节点(如果原始链表为空,则返回null
)。
下面是一个可能的实现:
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
function reverseKGroup(head, k) {
if (!head || k === 1) return head;
let dummy = new ListNode(0); // 创建一个哑节点,方便处理头节点
dummy.next = head;
let prev = dummy; // prev用于连接翻转后的链表段
while (head) {
let tail = prev; // tail用于记录当前需要翻转链表的最后一个节点的前一个节点
// 检查剩余节点是否足够k个
for (let i = 0; i < k; i++) {
tail = tail.next;
if (!tail) {
// 如果不足k个,则直接返回结果
return dummy.next;
}
}
// 翻转前k个节点
let nextGroup = tail.next; // 记录下一组的头节点
let [newHead, newTail] = reverse(head, tail); // 翻转并返回新的头尾节点
// 将翻转后的链表段连接到原链表上
prev.next = newHead;
newTail.next = nextGroup;
// 更新prev和head为下一组的开始
prev = newTail;
head = nextGroup;
}
return dummy.next;
}
// 翻转链表从head到tail.prev
function reverse(head, tail) {
let prev = tail.next;
let curr = head;
while (prev !== tail) {
let nextTemp = curr.next;
curr.next = prev;
prev = curr;
curr = nextTemp;
}
return [prev, head];
}
// 示例用法
let head = new ListNode(1);
head.next = new ListNode(2);
head.next.next = new ListNode(3);
head.next.next.next = new ListNode(4);
head.next.next.next.next = new ListNode(5);
let result = reverseKGroup(head, 2);
// 打印翻转后的链表值
let curr = result;
while (curr) {
console.log(curr.val);
curr = curr.next;
}
// 输出应为: 2 1 4 3 5
删除排序数组中的重复项
在原数组改:
要在原地删除排序数组中的重复项,并且只使用O(1)的额外空间,你可以使用双指针的方法。这种方法中,一个指针(我们称之为slow
)用于追踪不重复元素应该放置的位置,另一个指针(我们称之为fast
)用于遍历数组。
以下是具体的算法步骤:
- 初始化两个指针
slow
和fast
,都指向数组的第一个元素。 - 遍历数组(
fast
指针从第二个元素开始),比较arr[slow]
和arr[fast]
的值。 - 如果
arr[slow]
不等于arr[fast]
,说明找到了一个新的不重复的元素,将arr[fast]
的值复制到arr[slow+1]
(因为slow
指向的是当前不重复序列的最后一个元素),然后slow
和fast
都向前移动一位。 - 如果
arr[slow]
等于arr[fast]
,说明遇到了重复元素,只移动fast
指针。 - 重复步骤2-4,直到
fast
指针到达数组的末尾。 slow + 1
就是移除重复项后数组的新长度(因为slow
是从0开始的索引,所以实际长度需要加1)。
下面是相应的JavaScript代码实现:
function removeDuplicates(nums) {
if (nums.length === 0) {
return 0;
}
let slow = 0;
for (let fast = 1; fast < nums.length; fast++) {
if (nums[slow] !== nums[fast]) {
slow++;
nums[slow] = nums[fast];
}
}
return slow + 1;
}
// 示例
let nums = [1,1,2,2,3];
let newLength = removeDuplicates(nums);
console.log(newLength); // 输出: 3
console.log(nums); // 输出: [1, 2, 3]
新的数组(内置API)
1. 使用Set
和扩展运算符(...
)
Set
是ES6引入的一种新的数据结构,它类似于数组,但是成员的值都是唯一的,没有重复的值。因此,我们可以利用Set
的这个特性来去除数组中的重复项。
let arr = [1, 2, 2, 3, 4, 4, 5];
let uniqueArr = [...new Set(arr)];
console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
2. 使用filter()
方法
filter()
方法创建一个新数组,其包含通过所提供函数实现的测试的所有元素。我们可以利用这个特性,结合indexOf()
方法来检查元素是否首次出现。
let arr = [1, 2, 2, 3, 4, 4, 5];
let uniqueArr = arr.filter((item, index, self) => {
return self.indexOf(item) === index;
});
console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
3. 使用Map
对象
类似于Set
,Map
对象也可以用来存储键值对,其中键是唯一的。我们可以利用这个特性,将数组的值作为键存储到Map
中,然后提取出Map
的键来形成一个没有重复元素的数组。
let arr = [1, 2, 2, 3, 4, 4, 5];
let map = new Map();
arr.forEach(item => map.set(item, true));
let uniqueArr = Array.from(map.keys());
console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]
4. 使用reduce()
方法
reduce()
方法对数组中的每个元素执行一个由您提供的reducer函数(升序执行),将其结果汇总为单个返回值。我们可以利用这个特性来累加不重复的元素。
let arr = [1, 2, 2, 3, 4, 4, 5];
let uniqueArr = arr.reduce((acc, current) => {
if (acc.indexOf(current) === -1) acc.push(current);
return acc;
}, []);
console.log(uniqueArr); // 输出: [1, 2, 3, 4, 5]