leetcode-master/problems/0143.重排链表.md

696 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<p align="center">
<a href="https://www.programmercarl.com/xunlian/xunlianying.html" target="_blank">
<img src="../pics/训练营.png" width="1000"/>
</a>
<p align="center"><strong><a href="./qita/join.md">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们受益!</strong></p>
# 143.重排链表
[力扣题目链接](https://leetcode.cn/problems/reorder-list/submissions/)
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20210726160122.png)
## 思路
本篇将给出三种C++实现的方法
* 数组模拟
* 双向队列模拟
* 直接分割链表
### 方法一
把链表放进数组中,然后通过双指针法,一前一后,来遍历数组,构造链表。
代码如下:
```CPP
class Solution {
public:
void reorderList(ListNode* head) {
vector<ListNode*> vec;
ListNode* cur = head;
if (cur == nullptr) return;
while(cur != nullptr) {
vec.push_back(cur);
cur = cur->next;
}
cur = head;
int i = 1;
int j = vec.size() - 1; // i j为之前前后的双指针
int count = 0; // 计数,偶数取后面,奇数取前面
while (i <= j) {
if (count % 2 == 0) {
cur->next = vec[j];
j--;
} else {
cur->next = vec[i];
i++;
}
cur = cur->next;
count++;
}
cur->next = nullptr; // 注意结尾
}
};
```
### 方法二
把链表放进双向队列,然后通过双向队列一前一后弹出数据,来构造新的链表。这种方法比操作数组容易一些,不用双指针模拟一前一后了
```CPP
class Solution {
public:
void reorderList(ListNode* head) {
deque<ListNode*> que;
ListNode* cur = head;
if (cur == nullptr) return;
while(cur->next != nullptr) {
que.push_back(cur->next);
cur = cur->next;
}
cur = head;
int count = 0; // 计数,偶数取后面,奇数取前面
ListNode* node;
while(que.size()) {
if (count % 2 == 0) {
node = que.back();
que.pop_back();
} else {
node = que.front();
que.pop_front();
}
count++;
cur->next = node;
cur = cur->next;
}
cur->next = nullptr; // 注意结尾
}
};
```
### 方法三
将链表分割成两个链表,然后把第二个链表反转,之后在通过两个链表拼接成新的链表。
如图:
<img src='https://code-thinking.cdn.bcebos.com/pics/143.重排链表.png' width=600> </img></div>
这种方法,比较难,平均切割链表,看上去很简单,真正代码写的时候有很多细节,同时两个链表最后拼装整一个新的链表也有一些细节需要注意!
代码如下:
```CPP
class Solution {
private:
// 反转链表
ListNode* reverseList(ListNode* head) {
ListNode* temp; // 保存cur的下一个节点
ListNode* cur = head;
ListNode* pre = NULL;
while(cur) {
temp = cur->next; // 保存一下 cur的下一个节点因为接下来要改变cur->next
cur->next = pre; // 翻转操作
// 更新pre 和 cur指针
pre = cur;
cur = temp;
}
return pre;
}
public:
void reorderList(ListNode* head) {
if (head == nullptr) return;
// 使用快慢指针法将链表分成长度均等的两个链表head1和head2
// 如果总链表长度为奇数则head1相对head2多一个节点
ListNode* fast = head;
ListNode* slow = head;
while (fast && fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
}
ListNode* head1 = head;
ListNode* head2;
head2 = slow->next;
slow->next = nullptr;
// 对head2进行翻转
head2 = reverseList(head2);
// 将head1和head2交替生成新的链表head
ListNode* cur1 = head1;
ListNode* cur2 = head2;
ListNode* cur = head;
cur1 = cur1->next;
int count = 0; // 偶数取head2的元素奇数取head1的元素
while (cur1 && cur2) {
if (count % 2 == 0) {
cur->next = cur2;
cur2 = cur2->next;
} else {
cur->next = cur1;
cur1 = cur1->next;
}
count++;
cur = cur->next;
}
if (cur2 != nullptr) { // 处理结尾
cur->next = cur2;
}
if (cur1 != nullptr) {
cur->next = cur1;
}
}
};
```
## 其他语言版本
### Java
```java
// 方法一 Java实现使用数组存储节点
class Solution {
public void reorderList(ListNode head) {
// 双指针的做法
ListNode cur = head;
// ArrayList底层是数组可以使用下标随机访问
List<ListNode> list = new ArrayList<>();
while (cur != null){
list.add(cur);
cur = cur.next;
}
cur = head; // 重新回到头部
int l = 1, r = list.size() - 1; // 注意左边是从1开始
int count = 0;
while (l <= r){
if (count % 2 == 0){
// 偶数
cur.next = list.get(r);
r--;
}else {
// 奇数
cur.next = list.get(l);
l++;
}
// 每一次指针都需要移动
cur = cur.next;
count++;
}
// 注意结尾要结束一波
cur.next = null;
}
}
// 方法二:使用双端队列,简化了数组的操作,代码相对于前者更简洁(避免一些边界条件)
class Solution {
public void reorderList(ListNode head) {
// 使用双端队列的方法来解决
Deque<ListNode> de = new LinkedList<>();
// 这里是取head的下一个节点head不需要再入队了避免造成重复
ListNode cur = head.next;
while (cur != null){
de.offer(cur);
cur = cur.next;
}
cur = head; // 回到头部
int count = 0;
while (!de.isEmpty()){
if (count % 2 == 0){
// 偶数,取出队列右边尾部的值
cur.next = de.pollLast();
}else {
// 奇数,取出队列左边头部的值
cur.next = de.poll();
}
cur = cur.next;
count++;
}
cur.next = null;
}
}
// 方法三
public class ReorderList {
public void reorderList(ListNode head) {
ListNode fast = head, slow = head;
//求出中点
while (fast.next != null && fast.next.next != null) {
slow = slow.next;
fast = fast.next.next;
}
//right就是右半部分 12345 就是45 1234 就是34
ListNode right = slow.next;
//断开左部分和右部分
slow.next = null;
//反转右部分 right就是反转后右部分的起点
right = reverseList(right);
//左部分的起点
ListNode left = head;
//进行左右部分来回连接
//这里左部分的节点个数一定大于等于右部分的节点个数 因此只判断right即可
while (right != null) {
ListNode curLeft = left.next;
left.next = right;
left = curLeft;
ListNode curRight = right.next;
right.next = left;
right = curRight;
}
}
public ListNode reverseList(ListNode head) {
ListNode headNode = new ListNode(0);
ListNode cur = head;
ListNode next = null;
while (cur != null) {
next = cur.next;
cur.next = headNode.next;
headNode.next = cur;
cur = next;
}
return headNode.next;
}
}
```
### Python
```python
# 方法二 双向队列
class Solution:
def reorderList(self, head: ListNode) -> None:
"""
Do not return anything, modify head in-place instead.
"""
d = collections.deque()
tmp = head
while tmp.next: # 链表除了首元素全部加入双向队列
d.append(tmp.next)
tmp = tmp.next
tmp = head
while len(d): # 一后一前加入链表
tmp.next = d.pop()
tmp = tmp.next
if len(d):
tmp.next = d.popleft()
tmp = tmp.next
tmp.next = None # 尾部置空
# 方法三 反转链表
class Solution:
def reorderList(self, head: ListNode) -> None:
if head == None or head.next == None:
return True
slow, fast = head, head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
right = slow.next # 分割右半边
slow.next = None # 切断
right = self.reverseList(right) #反转右半边
left = head
# 左半边一定比右半边长, 因此判断右半边即可
while right:
curLeft = left.next
left.next = right
left = curLeft
curRight = right.next
right.next = left
right = curRight
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while(cur!=None):
temp = cur.next # 保存一下cur的下一个节点
cur.next = pre # 反转
pre = cur
cur = temp
return pre
```
### Go
```go
// 方法一 数组模拟
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reorderList(head *ListNode) {
vec := make([]*ListNode, 0)
cur := head
if cur == nil {
return
}
for cur != nil {
vec = append(vec, cur)
cur = cur.Next
}
cur = head
i := 1
j := len(vec) - 1 // i j为前后的双指针
count := 0 // 计数,偶数取后面,奇数取前面
for i <= j {
if count % 2 == 0 {
cur.Next = vec[j]
j--
} else {
cur.Next = vec[i]
i++
}
cur = cur.Next
count++
}
cur.Next = nil // 注意结尾
}
```
```go
// 方法二 双向队列模拟
/**
* Definition for singly-linked list.
* type ListNode struct {
* Val int
* Next *ListNode
* }
*/
func reorderList(head *ListNode) {
que := make([]*ListNode, 0)
cur := head
if cur == nil {
return
}
for cur.Next != nil {
que = append(que, cur.Next)
cur = cur.Next
}
cur = head
count := 0 // 计数,偶数取后面,奇数取前面
for len(que) > 0 {
if count % 2 == 0 {
cur.Next = que[len(que)-1]
que = que[:len(que)-1]
} else {
cur.Next = que[0]
que = que[1:]
}
count++
cur = cur.Next
}
cur.Next = nil // 注意结尾
}
```
```go
// 方法三 分割链表
func reorderList(head *ListNode) {
var slow=head
var fast=head
for fast!=nil&&fast.Next!=nil{
slow=slow.Next
fast=fast.Next.Next
} //双指针将链表分为左右两部分
var right =new(ListNode)
for slow!=nil{
temp:=slow.Next
slow.Next=right.Next
right.Next=slow
slow=temp
} //翻转链表右半部分
right=right.Next //right为反转后得右半部分
h:=head
for right.Next!=nil{
temp:=right.Next
right.Next=h.Next
h.Next=right
h=h.Next.Next
right=temp
} //将左右两部分重新组合
}
```
### JavaScript
```javascript
// 方法一 使用数组存储节点
var reorderList = function(head, s = [], tmp) {
let cur = head;
// list是数组可以使用下标随机访问
const list = [];
while(cur != null){
list.push(cur);
cur = cur.next;
}
cur = head; // 重新回到头部
let l = 1, r = list.length - 1; // 注意左边是从1开始
let count = 0;
while(l <= r){
if(count % 2 == 0){
// even
cur.next = list[r];
r--;
} else {
// odd
cur.next = list[l];
l++;
}
// 每一次指针都需要移动
cur = cur.next;
count++;
}
// 注意结尾要结束一波
cur.next = null;
}
// 方法二 使用双端队列的方法来解决 js中运行很慢
var reorderList = function(head, s = [], tmp) {
// js数组作为双端队列
const deque = [];
// 这里是取head的下一个节点head不需要再入队了避免造成重复
let cur = head.next;
while(cur != null){
deque.push(cur);
cur = cur.next;
}
cur = head; // 回到头部
let count = 0;
while(deque.length !== 0){
if(count % 2 == 0){
// even取出队列右边尾部的值
cur.next = deque.pop();
} else {
// odd, 取出队列左边头部的值
cur.next = deque.shift();
}
cur = cur.next;
count++;
}
cur.next = null;
}
//方法三 将链表分割成两个链表,然后把第二个链表反转,之后在通过两个链表拼接成新的链表
var reorderList = function(head, s = [], tmp) {
const reverseList = head => {
let headNode = new ListNode(0);
let cur = head;
let next = null;
while(cur != null){
next = cur.next;
cur.next = headNode.next;
headNode.next = cur;
cur = next;
}
return headNode.next;
}
let fast = head, slow = head;
//求出中点
while(fast.next != null && fast.next.next != null){
slow = slow.next;
fast = fast.next.next;
}
//right就是右半部分 12345 就是45 1234 就是34
let right = slow.next;
//断开左部分和右部分
slow.next = null;
//反转右部分 right就是反转后右部分的起点
right = reverseList(right);
//左部分的起点
let left = head;
//进行左右部分来回连接
//这里左部分的节点个数一定大于等于右部分的节点个数 因此只判断right即可
while (right != null) {
let curLeft = left.next;
left.next = right;
left = curLeft;
let curRight = right.next;
right.next = left;
right = curRight;
}
}
```
### TypeScript
> 辅助数组法:
```typescript
function reorderList(head: ListNode | null): void {
if (head === null) return;
const helperArr: ListNode[] = [];
let curNode: ListNode | null = head;
while (curNode !== null) {
helperArr.push(curNode);
curNode = curNode.next;
}
let node: ListNode = head;
let left: number = 1,
right: number = helperArr.length - 1;
let count: number = 0;
while (left <= right) {
if (count % 2 === 0) {
node.next = helperArr[right--];
} else {
node.next = helperArr[left++];
}
count++;
node = node.next;
}
node.next = null;
};
```
> 分割链表法:
```typescript
function reorderList(head: ListNode | null): void {
if (head === null || head.next === null) return;
let fastNode: ListNode = head,
slowNode: ListNode = head;
while (fastNode.next !== null && fastNode.next.next !== null) {
slowNode = slowNode.next!;
fastNode = fastNode.next.next;
}
let head1: ListNode | null = head;
// 反转后半部分链表
let head2: ListNode | null = reverseList(slowNode.next);
// 分割链表
slowNode.next = null;
/**
直接在head1链表上进行插入
head1 链表长度一定大于或等于head2,
因此在下面的循环中只要head2不为null, head1 一定不为null
*/
while (head2 !== null) {
const tempNode1: ListNode | null = head1!.next,
tempNode2: ListNode | null = head2.next;
head1!.next = head2;
head2.next = tempNode1;
head1 = tempNode1;
head2 = tempNode2;
}
};
function reverseList(head: ListNode | null): ListNode | null {
let curNode: ListNode | null = head,
preNode: ListNode | null = null;
while (curNode !== null) {
const tempNode: ListNode | null = curNode.next;
curNode.next = preNode;
preNode = curNode;
curNode = tempNode;
}
return preNode;
}
```
### C
方法三:反转链表
```c
//翻转链表
struct ListNode *reverseList(struct ListNode *head) {
if(!head)
return NULL;
struct ListNode *preNode = NULL, *curNode = head;
while(curNode) {
//创建tempNode记录curNode->next即将被更新
struct ListNode* tempNode = curNode->next;
//将curNode->next指向preNode
curNode->next = preNode;
//更新preNode为curNode
preNode = curNode;
//curNode更新为原链表中下一个元素
curNode = tempNode;
}
return preNode;
}
void reorderList(struct ListNode* head){
//slow用来截取到链表的中间节点(第一个链表的最后节点)每次循环跳一个节点。fast用来辅助每次循环跳两个节点
struct ListNode *fast = head, *slow = head;
while(fast && fast->next && fast->next->next) {
//fast每次跳两个节点
fast = fast->next->next;
//slow每次跳一个节点
slow = slow->next;
}
//将slow->next后的节点翻转
struct ListNode *sndLst = reverseList(slow->next);
//将第一个链表与第二个链表断开
slow->next = NULL;
//因为插入从curNode->next开始curNode刚开始已经head。所以fstList要从head->next开始
struct ListNode *fstLst = head->next;
struct ListNode *curNode = head;
int count = 0;
//当第一个链表和第二个链表中都有节点时循环
while(sndLst && fstLst) {
//count为奇数插入fstLst中的节点
if(count % 2) {
curNode->next = fstLst;
fstLst = fstLst->next;
}
//count为偶数插入sndList的节点
else {
curNode->next = sndLst;
sndLst = sndLst->next;
}
//设置下一个节点
curNode = curNode->next;
//更新count
++count;
}
//若两个链表fstList和sndLst中还有节点将其放入链表
if(fstLst) {
curNode->next = fstLst;
}
if(sndLst) {
curNode->next = sndLst;
}
//返回链表
return head;
}
```
<p align="center">
<a href="https://programmercarl.com/other/kstar.html" target="_blank">
<img src="../pics/网站星球宣传海报.jpg" width="1000"/>
</a>