collection与数据结构 链表与linkedlist(二):链表精选oj例题(上)-爱代码爱编程
1. 删除链表中所有值为val结点
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head == null){
return head;
}
ListNode pre = head;
ListNode cur = head.next;
while(cur != null){
if(cur.val == val){
pre.next = cur.next;
}else{
pre = pre.next;
}
cur = cur.next;
}
if(head.val == val){
head = head.next;
}
return head;
}
}
这个题我们在上一篇博客中实现单向链表中展示过,这里不再赘述.
2. 反转一个单链表
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null){
return head;//列表为空的情况
}
if(head.next == null){
return head;//结点只有一个的情况
}
ListNode cur = head.next;
ListNode curNext = cur.next;//防止下一个结点找不到,所以定义curNext
head.next = null;//先把头结点的next置为null,否者返回时会越界
while(cur != null){
cur.next = head;
head = cur;
cur = curNext;//头插法
if(curNext !=null){
curNext = curNext.next;//防止走到最后的时候空指针异常
}
}
return head;
}
}
动态演示:
翻转链表
[注意]
- 需要定义一个nodeNext结点来找到下一个结点,**,cur想要往后走的时候,不是我们想要找的下一个结点,因为在链表头插法修改了next的地址,**为了防止下一个结点的地址丢失,所以我们引入curNext.
- 在一开始的时候,需要把head.next置为空,因为翻转链表之后,一开始的头结点将是新链表的最后一个结点,最后一个结点的next为null.
- 在循环体内部要判断curNext是否为null,若没有,cur走到了最后一个结点的时候,curNext就位null了,在执行
curNext = curNext.next
的时候,就会出现空指针异常.
3. 链表的中间结点
class Solution {
public ListNode middleNode(ListNode head) {
if(head == null){
return head;
}
ListNode slow = head;
ListNode fast = head;//定义双指针
while (fast != null && fast.next != null){//顺序不可以反,否则会nullException,
//第一个是奇数的终止情况,第二个是偶数
fast = fast.next.next;//快指针走两步
slow = slow.next;//慢指针走一步
}
return slow;//最后慢指针指向的就是中间结点
}
}
动态演示
寻找中间结点
[注意]
- 这个题大多数人想到的是通过计数的方法来遍历链表,之后再/2,这种方法固然没错,但是它的时间复杂度较高,它遍历了两次链表,为了只遍历一次就通过,我们引入了快慢指针,快指针的速度是慢指针的2倍.
- 这个题分为奇数项和偶数项,针对不同的项数,限制条件也不同.
while (fast != null && fast.next != null)
- 限制条件
while (fast != null && fast.next != null)
的两个条件不可以反,如果fast==null,后面的条件就会报空指针异常.
4. 返回倒数第k个节点
这道题我们加大一点难度,把说明的条件删去.
class Solution {
public int kthToLast(ListNode head, int k) {
if (head == null) {
return head.val;
}
if(k < 0){//负数返回null
return -1;
}
ListNode slow = head;
ListNode fast = head;
int count = 0;
while (count != k - 1) {//先让fast走k-1步
if (fast != null) {//防止k太大导致fast越界
fast = fast.next;
count++;
} else {
return -1;//如果越界返回null
}
}
while (fast.next != null) {//fast指向最后一个结点就结束,所以加上next
fast = fast.next;
slow = slow.next;
}
return slow.val;
}
}
动态演示
寻找倒数第k个结点
[注意]
- 这道题和上一道题一样,许多人首先想到的就是遍历链表,得到size,减去k之后再遍历,这种做法可通过,但是效率欠缺.所以我们又引入了快慢指针.
- 既然删掉了限制条件,那么k就有可能是无效的,其中一种就是k小于0,通过
if(k < 0)
来限制.越界的情况,很多人又想,这不是还得遍历数组去得到size吗?其实不用,只要限制快指针在第一次走的时候不越界即可.if (fast != null)
- fast指向最后一个节结点的时候,slow就是中间结点,所以避免在
while (fast.next != null)
把next去掉,否者slow就会多走一格.
5. 合并两个有序链表
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode node = new ListNode(-1);//引入傀儡结点
ListNode heada = list1;
ListNode headb = list2;
ListNode tmp = node;//定义临时结点用于遍历
while(heada != null && headb != null){//遍历两个链表
if(heada.val < headb.val){//比较对应位置的大小
tmp.next = heada;
heada = heada.next;
tmp = tmp.next;//连接对应结点
}else{
tmp.next = headb;
headb = headb.next;
tmp = tmp.next;
}
}
if(headb == null){//判断最后谁先走完,没走完的链表后面的值一定比已经走完那个链表后面的值大,所以直接连上即可
tmp.next = heada;
}else{
tmp.next = headb;
}
return node.next;
}
}
动态演示
合并两个有序链表
[注意]
- 这里我们为了把为了把两个链表串起来,我们需要引入傀儡结点,就像想要用一根线串起零散的珠子需要给这根线的头按上一根针一样.
- 两个链表总会有其中一个会先走完,其实走完之后,未走完的链表后面的结点其实都已经比走完链表的尾结点大了,所以直接串上去即可.