链表系列专题

19. 删除链表的倒数第 N 个结点

给定一个链表,删除链表的倒数第 个节点,并且返回链表的头结点。

非递归解决

这题让删除链表的倒数第n个节点,首先最容易想到的就是先求出链表的长度length,然后就可以找到要删除链表的前一个结点,让他的前一个结点指向要删除结点的下一个结点即可,这里就以示例为例画个图看一下

public ListNode removeNthFromEnd(ListNode head, int n) {
     ListNode pre = head;
	 // 获取要删除的倒数第N个节点
     int last = length(head) - n;
     //如果last等于0表示删除的是头结点
     if (last == 0)
         return head.next;
     //这里首先要找到要删除链表的前一个结点
     for (int i = 0; i < last - 1; i++) {
         pre = pre.next;
    }
    //然后让前一个结点的next指向要删除节点的next
    pre.next = pre.next.next;
    return head;
}

//求链表的长度
private int length(ListNode head) {
    int len = 0;
    while (head != null) {
        len++;
        head = head.next;
    }
    return len;
}

141. Linked List Cycle

快慢指针解决

判断链表是否有环应该是老生常谈的一个话题了,最简单的一种方式就是快慢指针,慢指针针每次走一步,快指针每次走两步,如果相遇就说明有环,如果有一个为空说明没有环。代码比较简单。

到这里问题好像并没有结束,为什么快慢指针就一定能判断是否有环。我们可以这样来思考一下,假如有环,那么快慢指针最终都会走到环上,假如环的长度是m,快慢指针最近的间距是n,如下图中所示

图片

快指针每次走两步,慢指针每次走一步,所以每走一次快慢指针的间距就要缩小一步,在图一中当走n次的时候就会相遇,在图二中当走m-n次的时候就会相遇。

/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        // 1、定义快慢指针
        ListNode fast,slow;
        // 2、定义快慢指针的初始位置
        fast=slow=head;
        // 3、定义快指针移动规则
        while(fast!=null&&fast.next!=null){
            // 快指针走两步
            fast=fast.next.next;
            // 慢指针走一步
            slow=slow.next;
            // 判断结果
            if(fast==slow){
                return true;
            }
        }
        return false;
    }
}

面试题 02.02. 返回倒数第 k 个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。

例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

双指针求解

这题要求链表的倒数第k个节点,最简单的方式就是使用两个指针,第一个指针先移动k步,然后第二个指针再从头开始,这个时候这两个指针同时移动,当第一个指针到链表的末尾的时候,返回第二个指针即可。

 public ListNode getKthFromEnd(ListNode head, int k) {
     ListNode first = head;
     ListNode second = head;
     //第一个指针先走k步
     while (k-- > 0) {
         first = first.next;
     }
     //然后两个指针在同时前进
     while (first != null) {
       first = first.next;
        second = second.next;
    }
    return second;
}

LCR 136. 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

他表示的是删除链表中值等于val的结点,那么递归的终止条件就是当head等于空的时候,我们直接返回head,因为一个空的链表我们是没法删除的,也就是下面这样

if (head == null)    return head;

如果head结点不等于空,并且head结点的值等于val,我们直接返回head结点的下一个结点

if (head.val == val)    return head.next;

否则也就是说头结点是删不掉的,我们就递归调用,从头结点的下一个开始继续上面的操作,直到删除为止。

head.next = deleteNode(head.next, val);return head;

 递归实现如下: 

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode deleteNode(ListNode head, int val) {
        // 单链表的头结点
         if(head==null){
             return null;
         }
         if(head.val==val){
            return head.next; 
         }
         // 其他情况就递归删除,从头结点的下一个节点开始,
         head.next=deleteNode(head.next, val);
         return head;
    }
}

  迭代实现如下:边界条件判断.

 public ListNode deleteNode(ListNode head, int val) {
     //边界条件判断
     if (head == null)
         return head;
     //如果要删除的是头结点,直接返回头结点的下一个结点即可
     if (head.val == val)
         return head.next;
     ListNode cur = head;
     //找到要删除结点的上一个结点
    while (cur.next != null && cur.next.val != val) {
        cur = cur.next;
    }
    //删除结点
    cur.next = cur.next.next;
    return head;
}

328. 奇偶链表

给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。

第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。

请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。

你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。

输入: head = [1,2,3,4,5]
输出: [1,3,5,2,4]

输入: head = [2,1,3,5,6,4,7]
输出: [2,3,6,7,1,5,4]

编码如下:注意这里需要使用几个变量,分别记录奇数链表的头节点和尾节点,偶数链表的头节点 和尾节点。

public ListNode oddEvenList(ListNode head) { 
   if (head == null || head.next == null) 
    {
	return head;
    } 	
    //奇数链表的头节点 
    ListNode oddHead = head; 
    //奇数链表的当前节点 
    ListNode oddCur = oddHead; 
    //偶数链表的头节点 
    ListNode evenHead = head.next; 
    //偶数链表的当前节点 
    ListNode evenCur = evenHead; 
    while (evenCur != null && evenCur.next != null) { 
        //奇数节点串一起 
        oddCur.next = oddCur.next.next; 
        //偶数节点串一起 
        evenCur.next = evenCur.next.next; 
        //奇偶指针往后移 
        oddCur = oddCur.next; 
        evenCur = evenCur.next; 
    } 
    //最后偶数链表和奇数链表需要串在一起 
    oddCur.next = evenHead; 
    return oddHead; 
}

876. 链表的中间结点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

输入:head = [1,2,3,4,5]
输出:[3,4,5]
解释:链表只有一个中间结点,值为 3 。

输入:head = [1,2,3,4,5,6]
输出:[4,5,6]
解释:该链表有两个中间结点,值分别为 3 和 4 ,返回第二个结点。
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode middleNode(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        // 定义快慢指针,一个快指针和一个慢指针,两个指针同时开始走,fast指针每次走两步,慢指针每次走一步
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        } 
        return slow;
    }
}

462. 找出两个链表的第一个公共节点

方法一:通过Set集合,第一个链表全部放入Set中,然后第二个集合进行判断是否存在.

public ListNode getIntersectionNode(ListNode headA, ListNode headB) { 
     //创建集合set 
     Set<ListNode> set = new HashSet<>(); 
     //先把链表A的结点全部存放到集合set中 
     while (headA != null) { 
         set.add(headA); 
         headA = headA.next; 
     } 
  
    //然后访问链表B的结点,判断集合中是否包含链表B的结点,如果包含就直接返回 
    while (headB != null) { 
        if (set.contains(headB)) 
            return headB; 
        headB = headB.next; 
    } 
    //如果集合set不包含链表B的任何一个结点,说明他们没有交点,直接返回null 
    return null; 
}

方法二:我们还可以使用两个指针,最开始的时候一个指向链表A,一个指向链表B,然后他们每
次都要往后移动一位,顺便查看节点是否相等。如果链表A和链表B不相交,基本上没啥
可说的,我们这里假设链表A和链表B相交。那么就会有两种情况,
一种是链表A的长度和链表B的长度相等,他们每次都走一步,最终在相交点肯定会相
遇。

public ListNode getIntersectionNode(ListNode headA, ListNode headB) { 
  //tempA和tempB我们可以认为是A,B两个指针 
  ListNode tempA = headA; 
  ListNode tempB = headB; 
  while (tempA != tempB) { 
  //如果指针tempA不为空,tempA就往后移一步。 
  //如果指针tempA为空,就让指针tempA指向headB(注意这里是headB不是tempB) 
  tempA = tempA == null ? headB : tempA.next; 
  //指针tempB同上 
  tempB = tempB == null ? headA : tempB.next; 
  } 
  //tempA要么是空,要么是两链表的交点 
  return tempA; 
} 

24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

输入:head = [1,2,3,4]
输出:[2,1,4,3]
输入:head = []
输出:[]
输入:head = [1]
输出:[1]

 假如使用递归从第3个节点往后的节点全部两两交换了,这个时候我们可以把链表分为3 部分,第一个节点,第二个节点和后面交换完成的链表,就是1→2→3,这种形式,我 们只要再把1和2位置交换了。

class Solution {
    public ListNode swapPairs(ListNode head) {
         if(head==null||head.next==null){
            return head;
         }
        // 保存第三个节点
        //从第3个链表往后都交换完了,我们只需要交换前两个链表即可
         ListNode third=swapPairs(head.next.next);
        ListNode second=head.next;
        //这里我们把链表分为3组,分别是第1个节点,第2个节点,后面 
        head.next=third;
        second.next=head;
        return second;
    }
}

206. 反转链表

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
//每次访问的原链表节点都会成为新链表的头结点
public ListNode reverseList(ListNode head) { 
//新链表 
ListNode newHead = null; 
while (head != null) { 
//先保存访问的节点的下一个节点,保存起来 
//留着下一步访问的 
ListNode temp = head.next;
 //每次访问的原链表节点都会成为新链表的头结点, 
//其实就是把新链表挂到访问的原链表节点的 
//后面就行了 
head.next = newHead; 
//更新新链表 
newHead = head; 
//重新赋值,继续访问 
head = temp; 
} 
//返回新链表 
return newHead; 
} 

​​​​​​1. 合并两个有序链表

 边界条件判断,任何一个为空就返回另外一个,然后就是递归合并了。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
        // 边界条件判定
        if (l1 == null) {
            return l2;
        }
        if (l2 == null) {
            return l1;
        }
        // 按照单链表的方式,递归合并链表即可
        if (l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
}

234. 回文链表

通过双指针实现.

这题是让判断链表是否是回文链表,所谓的回文链表就是以链表中间为中心点两边对 称。我们常见的有判断一个字符串是否是回文字符串,这个比较简单,可以使用两个指 针,一个最左边一个最右边,两个指针同时往中间靠,判断所指的字符是否相等。反转上半个链表,再进行对比值。

 给你一个单链表的头节点 head ,请你判断该链表是否为

回文链表

。如果是,返回 true ;否则,返回 false 。

输入:head = [1,2,2,1]
输出:true
import java.util.*;
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals = new ArrayList<Integer>();
        // 将链表的值复制到数组中
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }
        // 使用双指针判断是否回文
        int front = 0;
        int back = vals.size() - 1;
        while (front < back) {
            if (!vals.get(front).equals(vals.get(back))) {
                return false;
            }
            front++;
            back--;
        }
        return true;
    }
}
  • 16
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大道之简

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值