Leetcode上刷到的三个单链表算法题
1.给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
//单链表结构
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
- 首先进行分析,最简单且容易想到的方法就是由一个指针temp从单链表表头head遍历到最后一个,如果temp.next= =null || temp.next.next==null,则表示已经遍历到末尾,则返回head,遍历过程中每两个交换一下位置
//长度不足两个时直接返回head
if(head==null || head.next==null){
return head;
}
//定义三个指针,pre,temp,after,temp用于遍历,after用于记录temp.next的位置
//pre用于记录temp前面的位置
ListNode pre;
ListNode temp=head;
ListNode after=temp.next;
//第一次交换之后第一位与第二位交换,head指向第二位
head=after;
temp.next=after.next;
after.next=temp;
//判断temp后两位是否为空,若为空则不进行交换操作
while(temp.next!=null && temp.next.next!=null){
//移位
pre=temp;
temp=temp.next;
after=temp.next;
//交换位置
temp.next=after.next;
after.next=temp;
pre.next=after;
}
return head;
执行结果如下
- 上述方法想起来比较简单,但代码过于冗余,若要简化代码量,则需要用到递归,具体思路为:开始判断head || head.next是否为空,若为空则不进行交换,直接返回head,判断之后则进行交换位置操作,这里我们至于要定义一个指针temp=head,head=head.next将head与temp进行交换,交换完之后则用递归进行下一轮交换,缺点
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null || head.next==null){
return head;
}
//temp指针
ListNode temp=head;
//交换
head=head.next;
temp.next=head.next;
head.next=temp;
//递归
temp.next=swapPairs(temp.next);
return head;
}
}
执行结果如下,该方法优点就是代码简洁,缺点就是每次递归都会新建一个List Node temp,内存损耗稍微大一些
2.给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5
- 这个题是上一个题的强化,还是用递归的方法来解。首先分析题,实际上为长度为k的单链表的逆序问题,逆序操作之后在进行递归。
class Solution {
public ListNode reverseKGroup(ListNode head, int k) {
//k==1则直接返回
if(k==1){
return head;
}
//定义t指针遍历head后面k个节点,若长度不足k,则不进行操作直接返回
ListNode t=head;
for(int i=0;i<k;i++){
if(t==null){
return head;
}
t=t.next;
}
//定义三个指针,pre,temp,after
ListNode pre=head;
ListNode temp=head.next;
ListNode after=temp.next;
//逆序
temp.next=pre;
for(int i=0;i<k-2;i++){
pre=temp;
temp=after;
after=after.next;
//指针反向实现逆序
temp.next=pre;
}
//递归
head.next=reverseKGroup(after,k);
//因为交换位置,temp变为头指针
return temp;
}
}
3.对链表进行插入排序。
插入排序的动画演示如上。从第一个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5
- 正常插入排序,时间复杂度较大,理解起来也比较绕,定义四个指针,now,pre,temp,last。now指向当前需要排序的节点,last指向已经排好序的部分的最后一个节点,即now的前一个节点。temp用于排序链表遍历,pre指向temp的前一个节点,方便进行插入操作。
class Solution {
public ListNode insertionSortList(ListNode head) {
//head为空则返回
if(head==null){
return head;
}
//定义四个指针
ListNode now=head.next;
ListNode pre;
ListNode temp;
ListNode last=head;
//now遍历
while(now!=null){
pre=head;
temp=head.next;
//如果now的值大于head的值,则进行插入排序
if(now.val < pre.val){
last.next=now.next;
now.next=head;
head=now;
now=last.next;
}else {
//temp遍历进行插入排序
while(temp!=now){
if(temp.val>now.val){
last.next=now.next;
pre.next=now;
now.next=temp;
break;
}
//temp后移
pre=temp;
temp=temp.next;
}
//遍历完之后last指针后移,不需要插入排序
if(temp==now){
last=last.next;
}
}
//now指针指向last.next
now=last.next;
}
return head;
}
}
- 上述方法时间复杂度稍大,如果需要对时间进行优化,则可以新建一个单链表用于存储排序之后的链表,但内存稍有损耗
public ListNode insertionSortList(ListNode head) {
if(head==null||head.next==null){
return head;
}
//定义node指针指向新建节点,新建节点的数值为Integer的最小值
ListNode node=new ListNode(Integer.MIN_VALUE);
//node.next指向head
node.next=head;
//op指向待排序链表第一个节点进行遍历插入排序
//right指向op前一个节点
//temp指向已排序链表进行遍历然后与待插入节点right进行比较
ListNode right=node,op,temp;
while(right.next!=null){
op=right.next;
//如果op比right大,则不需要排序
if(op.val>=right.val){
//right移位
right=right.next;
}
else{
temp=node;
//temp遍历找到待插入位置
while(temp.next.val<op.val){
temp=temp.next;
}
//插入
right.next=op.next;
op.next=temp.next;
temp.next=op;
}
}
return node.next;
}
执行结果如下,明显时间缩短