例题:两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
使用四个指针,node1
和node2
分别指向要交换的两个结点,pre
指向node1前一个结点,next
指向node2后一个结点。
交换node1和node2的操作:
- node2.next = node1;
- node1.next = next;
- pre.next = node2;
然后,pre = node1,再根据p重新定位其余三个指针。
这个题指针这么多,一定要严防空指针
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null || head.next==null) return head;
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pre = dummyHead;
ListNode node1 = pre.next;
ListNode node2 = node1.next;
ListNode next = node2.next;
while(node1!=null && node2!=null){
node2.next = node1;
node1.next = next;
pre.next = node2;
pre = node1;
node1 = pre.next;
node2 = node1==null?null:node1.next; //链表问题要严防空指针
next = node2==null?null:node2.next;
}
return dummyHead.next;
}
}
1.K 个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
思路:
- 使用递归,
reverseKGroup(ListNode head, int k)
的含义是:返回k个一组的翻转链表头结点 - 所以,在
reverseKGroup(ListNode head, int k)
函数中,要做的就是翻转前k个结点,然后将k个结点之后的链表进行k个一组翻转(即调用everseKGroup),接到已经反转好的链表后面,返回新链表的头结点。
尤其要注意链表最后部分的空指针问题
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
ListNode cur = head;
for (int i = 0; i < k; i++) {
if(cur == null) return head;
cur = cur.next;
}
cur = head;
ListNode pre = null;
ListNode next = cur.next;
for (int i = 0; i < k; i++) {
cur.next = pre;
pre = cur;
cur = next;
if(next!=null) next = next.next;
}
head.next = reverseKGroup(cur,k);
return pre;
}
}
2. 对链表进行插入排序
插入排序算法:
1.插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
2.每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
3.重复直到所有输入数据插入完为止。
思路:
- 使用一个
cur
来遍历链表,需要一个pre
指向cur前面的一个结点。 - 每一个cur都比较从head到cur所有结点的结点值,遇到值大于
cur.val
的结点,便把cur插在它的前面。
class Solution {
public ListNode insertionSortList(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pre = dummyHead;
ListNode cur = head;
while(cur!=null){
ListNode node = dummyHead;
while(node.next!=cur && node.next.val<cur.val){
node = node.next;
}
if(node.next==cur){ //当node==cur说明cur是目前为止最大的
pre = pre.next;
cur = cur.next;
}else{
pre.next = cur.next;
cur.next = node.next;
node.next = cur;
cur = pre.next;
}
}
return dummyHead.next;
}
}
优化:
- 每次不一定从dummyHead开始扫描,可以通过和上一次插入的结点值进行比较。如果值比上一次插入的大,那么就从上一次插入的位置开始扫描。否则从头扫描。
class Solution {
public ListNode insertionSortList(ListNode head) {
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode pre = dummyHead;
ListNode cur = head;
ListNode lastInsert = head;
while(cur!=null){
ListNode node = null;
if(cur.val>lastInsert.val) node = lastInsert;
else node = dummyHead;
while(node.next!=cur && node.next.val<cur.val){
node = node.next;
}
if(node.next==cur){
pre = pre.next;
cur = cur.next;
}else{
pre.next = cur.next;
cur.next = node.next;
node.next = cur;
lastInsert = cur;
cur = pre.next;
}
}
return dummyHead.next;
}
}
3.排序链表
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
使用归并排序可以完美的解决问题,但是,不能用自顶向下的方式,也就是不可以使用递归,因为空间复杂度不允许。所以要采用自底向上,这里先给出自顶向下的代码:
class Solution {
public ListNode sortList(ListNode head) {
if(head==null || head.next==null) return head;
//第一步,找到中点,断开链表
ListNode fast = head;
ListNode slow = head;
while(fast.next!=null && fast.next.next!=null){
slow = slow.next;
fast = fast.next.next;
}
//此时slow就是左半段末尾
//第二步归并排序好的链表
ListNode right = sortList(slow.next);
slow.next = null;
ListNode left = sortList(head);
ListNode newList = new ListNode(0);
ListNode cur = newList;
while(left!=null && right!=null){
if(left.val<right.val){
cur.next = left;
left = left.next;
}else{
cur.next = right;
right = right.next;
}
cur = cur.next;
}
cur.next = left==null?right:left;
return newList.next;
}
}