19. 删除链表的倒数第 N 个结点
给定一个链表,删除链表的倒数第 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;
}
}