链表
- 1.链表的基本操作
- (1)反转链表(206)
- (2) 合并两个有序链表(21)
- (3)两两交换链表中的节点(24)
- 2.其它链表技巧
- (1)相交链表(160)
- (2)回文链表(234)
- 3.练习
- (1)删除排序链表中的重复元素(83)
- (2)奇偶链表(328)
- (3)删除链表的倒数第 N 个结点
- (4)排序链表(148)
1.链表的基本操作
(1)反转链表(206)
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
输入:head = []
输出:[]
//迭代
public class Solution {
public static void main(String[] args) {
ListNode listNode=new ListNode(1,new ListNode(2,new ListNode(3)));
Solution solution=new Solution();
ListNode list = solution.reverseList(listNode);
while (list!=null){
System.out.print(list.val+" ");
list=list.next;
}
}
public ListNode reverseList(ListNode head) {
if (head==null){
return null;
}
ListNode cur=head;//当前节点
ListNode pre=null;//当前节点前一个节点 初始化为null
while (cur!=null){//遍历整个链表
ListNode next=cur.next;//保存下一个节点 作为下一次的当前节点
cur.next=pre;//当前节点的下一个节点指向之前的节点 实现反转
pre=cur;//将当前节点作为下一次的前一个节点
cur=next;
}
return pre;
}
}
//递归
public class Solution {
public static void main(String[] args) {
ListNode listNode=new ListNode(1,new ListNode(2,new ListNode(3)));
Solution solution=new Solution();
ListNode list = solution.reverseList(listNode);
while (list!=null){
System.out.print(list.val+" ");
list=list.next;
}
}
public ListNode reverseList(ListNode head) {
if (head==null||head.next==null){
return head;
}
ListNode newHead=reverseList(head.next);
head.next.next=head;
head.next=null;
return newHead;
}
}
(2) 合并两个有序链表(21)
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
输入:l1 = [1,2,4], l2 = [1,3,4]
输出:[1,1,2,3,4,4]
输入:l1 = [], l2 = []
输出:[]
输入:l1 = [], l2 = [0]
输出:[0]
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(2,new ListNode(3)));
ListNode list2=new ListNode(0,new ListNode(3,new ListNode(4)));
Solution solution=new Solution();
ListNode list = solution.mergeTwoLists(list1,list2);
while (list!=null){
System.out.print(list.val+" ");
list=list.next;
}
}
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode pre=new ListNode(0);//设置一个头节点
ListNode head=pre;
while (list1!=null&&list2!=null){
if (list1.val<= list2.val){
head.next=list1;
list1=list1.next;
}else {
head.next=list2;
list2=list2.next;
}
head=head.next;
}
if (list1!=null){
head.next=list1;
}else {
head.next=list2;
}
return pre.next;
}
}
(3)两两交换链表中的节点(24)
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
输入:head = [1,2,3,4]
输出:[2,1,4,3]
输入:head = []
输出:[]
输入:head = [1]
输出:[1]
//迭代
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4))));
Solution solution=new Solution();
ListNode list = solution.swapPairs(list1);
while (list!=null){
System.out.print(list.val+" ");
list=list.next;
}
}
public ListNode swapPairs(ListNode head) {
ListNode listNode=new ListNode(-1);//设置一个首部节点 首部节点的下一个节点指向head的头节点
listNode.next=head;
ListNode temp=listNode;//标志节点 指向当前要交换的节点的前一个结点
//当temp后边只有一个节点或者没有节点时 交换结束
while (temp.next!=null&&temp.next.next!=null){
//需要交换的两个节点
ListNode list1=temp.next;
ListNode list2=temp.next.next;
//交换两个节点
temp.next=list2;//标志节点的下一个节点是list2
list1.next=list2.next;
list2.next=list1;
temp=list1;//更换标志节点
}
return listNode.next;
}
}
2.其它链表技巧
(1)相交链表(160)
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,6,1,8,4,5], skipA = 2, skipB = 3
输出:Intersected at '8'
解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。
从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,6,1,8,4,5]。
在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
— 请注意相交节点的值不为 1,因为在链表 A 和链表 B 之中值为 1 的节点 (A 中第二个节点和 B 中第三个节点)
是不同的节点。换句话说,它们在内存中指向两个不同的位置,而链表 A 和链表 B 中值为 8 的节点 (A 中第三
个节点,B 中第四个节点) 在内存中指向相同的位置。
//集合
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4))));
ListNode list2=new ListNode(3,new ListNode(2,new ListNode(3,new ListNode(4))));
Solution solution=new Solution();
ListNode list = solution.getIntersectionNode(list1,list2);
while (list!=null){
System.out.print(list.val+" ");
list=list.next;
}
}
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//设置一个节点指向headA的头节点 因为题目要求返回后链表的原始结构不变 所以设置
ListNode tempA=headA;
ListNode tempB=headB;
Set<ListNode> set=new HashSet<>();//存储节点
//将headA的所有节点加入集合
while (tempA!=null){
set.add(tempA);
tempA=tempA.next;
}
//在headB中如果找到第一个相等的节点 那么这个节点之后的其他节点都相等 返回这个节点
while (tempB!=null){
if (set.contains(tempB)){
return tempB;
}
tempB=tempB.next;
}
return null;
}
}
//双指针
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode tempA=headA;
ListNode tempB=headB;
while (tempA!=tempB){
tempA=tempA==null?headB:tempA.next;
tempB=tempB==null?headA:tempB.next;
}
return tempA;
}
(2)回文链表(234)
给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。
输入:head = [1,2,2,1]
输出:true
输入:head = [1,2]
输出:false
//将值复制到数组后用双指针法
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(1,new ListNode(2,new ListNode(1))));
Solution solution=new Solution();
System.out.println(solution.isPalindrome(list1));
}
public boolean isPalindrome(ListNode head) {
ListNode cur=head;//当前节点
List<Integer> list=new ArrayList<>();
//遍历链表 将元素加入list集合
while (cur!=null){
list.add(cur.val);
cur=cur.next;
}
//使用两个指针 分别从头和尾开始遍历 判断是否相等
int left=0;
int right=list.size()-1;
while (left<=right){
//如果不相等 返回false
if (!list.get(left).equals(list.get(right))){
return false;
}
left++;
right--;
}
return true;
}
}
//反转后半部分链表然后比较 链表全部反转然后和原链表比较是不可行的
//因为这样的话原链表会只存在一个头节点 后边节点会消失
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(1,new ListNode(2,new ListNode(1))));
Solution solution=new Solution();
System.out.println(solution.isPalindrome(list1));
}
public boolean isPalindrome(ListNode head) {
if (head.next==null){
return true;
}
//得到中心位置
ListNode half = getHalf(head);
//反转中心位置之后的链表
ListNode reversalList = getReversalList(half.next);
ListNode left=head;
ListNode mid=reversalList;
//设置result变量是为了之后有机会将反转的链表复原 否则在判断到不是回文的时候就可以直接返回false
boolean result=true;
while (mid!=null){
if (left.val!= mid.val){
result=false;
break;//退出这个while循环
}
left=left.next;
mid=mid.next;
}
//还原链表
half.next=getReversalList(reversalList);
return result;
}
//得到反转链表
private ListNode getReversalList(ListNode head) {
ListNode cur=head;//当前节点
ListNode pre=null;//当前节点的前一个节点
while (cur!=null){
ListNode next=cur.next;//保存当前节点的下一个节点
cur.next=pre;//当前节点的next节点指向前一个节点
pre=cur;//更新前一个节点
cur=next;//更新当前节点
}
return pre;
}
//使用快慢指针得到链表的中心位置
private ListNode getHalf(ListNode head){
ListNode slow=head;//慢指针 一次移动一个节点
ListNode fast=head;//快指针 一次移动两个节点
while (fast.next!=null&&fast.next.next!=null){
fast=fast.next.next;
slow=slow.next;
}
//当遍历完链表后 slow指向的位置就是链表的中心位置
return slow;
}
}
3.练习
(1)删除排序链表中的重复元素(83)
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
输入:head = [1,1,2]
输出:[1,2]
输入:head = [1,1,2,3,3]
输出:[1,2,3]
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(1,new ListNode(2,new ListNode(3))));
Solution solution=new Solution();
ListNode listNode = solution.deleteDuplicates(list1);
while (listNode!=null){
System.out.print(listNode.val+" ");
listNode=listNode.next;
}
}
public ListNode deleteDuplicates(ListNode head) {
if (head==null||head.next==null){
return head;
}
ListNode cur=head;//当前节点
while (cur!=null&&cur.next!=null){
if (cur.val==cur.next.val){
cur.next=cur.next.next;//当前节点的next节点指向next的next节点
}else {//当当前节点的值不等于下一个节点的值时 移动当前节点
cur=cur.next;
}
}
return head;
}
}
(2)奇偶链表(328)
给定单链表的头节点 head ,将所有索引为奇数的节点和索引为偶数的节点分别组合在一起,然后返回重新排序的列表。
第一个节点的索引被认为是 奇数 , 第二个节点的索引为 偶数 ,以此类推。
请注意,偶数组和奇数组内部的相对顺序应该与输入时保持一致。
你必须在 O(1) 的额外空间复杂度和 O(n) 的时间复杂度下解决这个问题。
输入: head = [1,2,3,4,5]
输出: [1,3,5,2,4]
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4,new ListNode(5)))));
Solution solution=new Solution();
ListNode listNode = solution.oddEvenList(list1);
while (listNode!=null){
System.out.print(listNode.val+" ");
listNode=listNode.next;
}
}
public ListNode oddEvenList(ListNode head) {
if (head==null||head.next==null||head.next.next==null){
return head;
}
ListNode even=head.next;//偶数链表
ListNode curOdd=head;//奇数链表的当前节点
ListNode curEven=even;//偶数链表得当前节点
while (curEven!=null&&curEven.next!=null){
curOdd.next=curEven.next;
curOdd=curOdd.next;
curEven.next=curOdd.next;
curEven=curEven.next;
}
//奇数链表的最后一个节点的next节点指向偶数链表的头节点
curOdd.next=even;
return head;
}
}
(3)删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
输入:head = [1], n = 1
输出:[]
输入:head = [1,2], n = 1
输出:[1]
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4,new ListNode(5)))));
Solution solution=new Solution();
ListNode listNode = solution.removeNthFromEnd(list1,2);
while (listNode!=null){
System.out.print(listNode.val+" ");
listNode=listNode.next;
}
}
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head.next==null){
return null;
}
ListNode cur=head;//当前节点
int count=0;//节点个数
while (cur!=null){
cur=cur.next;
count++;
}
int res=count-n+1;//顺数删除的节点位置
ListNode pre=new ListNode(0,head);
cur=pre;
for (int i=1;i<res;i++){
cur=cur.next;
}
//当到需要被删除的节点的前一个节点时 next指向下一个节点的next节点
cur.next=cur.next.next;
return pre.next;
}
}
//栈
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4,new ListNode(5)))));
Solution solution=new Solution();
ListNode listNode = solution.removeNthFromEnd(list1,2);
while (listNode!=null){
System.out.print(listNode.val+" ");
listNode=listNode.next;
}
}
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head.next==null){
return null;
}
ListNode pre=new ListNode(0,head);
Deque<ListNode> stack=new LinkedList<>();
ListNode cur=pre;
//元素入栈
while (cur!=null){
stack.push(cur);
cur=cur.next;
}
//弹出栈顶的n个元素
for (int i=1;i<=n;i++){
stack.poll();
}
//当前栈顶元素是需要删除元素的前一个元素 则将其的next指向需要删除元素的下一个元素
stack.peek().next=stack.peek().next.next;
return pre.next;
}
}
//双指针
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(1,new ListNode(2,new ListNode(3,new ListNode(4,new ListNode(5)))));
Solution solution=new Solution();
ListNode listNode = solution.removeNthFromEnd(list1,2);
while (listNode!=null){
System.out.print(listNode.val+" ");
listNode=listNode.next;
}
}
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head.next==null){
return null;
}
ListNode pre=new ListNode(0,head);
ListNode first=head;//第一个指针 比第二个指针提前n个节点 当它到null时 第二个指针刚好是需要删除的节点
ListNode second=pre;
for (int i=1;i<=n;i++){
first=first.next;
}
//遍历直到第一个节点指向空
while (first!=null){
first=first.next;
second=second.next;
}
second.next=second.next.next;
return pre.next;
}
}
(4)排序链表(148)
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表 。
输入:head = [4,2,1,3]
输出:[1,2,3,4]
输入:head = []
输出:[]
//自底向上归并排序 空间复杂度O(1)
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(2,new ListNode(1,new ListNode(4,new ListNode(3,new ListNode(0)))));
Solution solution=new Solution();
ListNode listNode = solution.sortList(list1);
while (listNode!=null){
System.out.print(listNode.val+" ");
listNode=listNode.next;
}
}
public ListNode sortList(ListNode head) {
if (head==null){
return null;
}
//计算链表的长度
int length=getLength(head);
//加一个前置节点 是为了避免head.next=null的特殊情况的判断
ListNode preHead=new ListNode(0,head);
for (int subLength=1;subLength<length;subLength<<=1){
ListNode pre=preHead;//用于记录每一次不同subLength的已排序的链表的尾位置
ListNode cur=preHead.next;//用于记录拆分链表的位置
while (cur!=null){
//需要排序的第一个子链表 长度为subLength
ListNode head1=cur;
for (int i=1;i<subLength&&cur.next!=null;i++){
cur=cur.next;
}
//需要排序的第二个子链表 长度为subLength
ListNode head2=cur.next;
cur.next=null;//将前一个子链表与后边的断开
cur=head2;//第二个链表头重新赋值给cur
for (int i=1;i<subLength&&cur!=null&&cur.next!=null;i++){
cur=cur.next;
}
//断开第二个子链表与后边链表的连接
ListNode next=null;//记录已经排好序的子链表的结束位置
if (cur!=null){
next = cur.next;//下一个需要排序的子链表的初始位置
cur.next=null;//断开连接
}
//给两个子链表排序 将每一次排好序的链表合并到pre后边
pre.next=getMergeList(head1,head2);
while (pre.next!=null){
pre=pre.next;//将指针移动到已排序的链表末尾
}
cur=next;//下一个需要排序的子链表的前一个位置
}
}
return preHead.next;
}
//合并两个有序链表
private ListNode getMergeList(ListNode head1, ListNode head2) {
ListNode preHead=new ListNode(0);
ListNode cur=preHead;//当前合并链表的尾节点
while (head1!=null&&head2!=null){
if (head1.val<= head2.val){
cur.next=head1;
head1=head1.next;
}else {
cur.next=head2;
head2=head2.next;
}
cur=cur.next;//移动尾节点
}
if (head1!=null){
cur.next=head1;
}else if (head2!=null){
cur.next=head2;
}
return preHead.next;
}
//得到链表长度
private int getLength(ListNode head){
int length=0;
while (head!=null){
head=head.next;
length++;
}
return length;
}
}
//自顶向下归并排序 空间复杂度log(n)
public class Solution {
public static void main(String[] args) {
ListNode list1=new ListNode(2,new ListNode(1,new ListNode(4,new ListNode(3,new ListNode(0)))));
Solution solution=new Solution();
ListNode listNode = solution.sortList(list1);
while (listNode!=null){
System.out.print(listNode.val+" ");
listNode=listNode.next;
}
}
public ListNode sortList(ListNode head) {
return rec(head,null);
}
private ListNode rec(ListNode head, ListNode tail) {
if (head==null){
return null;
}
if (head.next==tail){
head.next=null;
return head;
}
//得到当前链表的中心位置的节点
ListNode mid=getHalf(head,tail);
ListNode list1=rec(head,mid);//递归将链表前半部分排好序
ListNode list2=rec(mid,tail);//递归将链表前后部分排好序
ListNode list=getMergeList(list1,list2);
return list;
}
//得到当前链表的中心位置
private ListNode getHalf(ListNode head,ListNode tail) {
ListNode slow=head;//慢指针 一次走一个节点
ListNode fast=head;//快指针 一次走两个节点
while (fast!=tail){
slow=slow.next;
fast=fast.next;
if (fast!=tail){
fast=fast.next;
}
}
return slow;//链表的中心位置
}
//合并两个有序链表
private ListNode getMergeList(ListNode head1, ListNode head2) {
ListNode preHead=new ListNode(0);
ListNode cur=preHead;//当前合并链表的尾节点
while (head1!=null&&head2!=null){
if (head1.val<= head2.val){
cur.next=head1;
head1=head1.next;
}else {
cur.next=head2;
head2=head2.next;
}
cur=cur.next;//移动尾节点
}
if (head1!=null){
cur.next=head1;
}else if (head2!=null){
cur.next=head2;
}
return preHead.next;
}
}