算法训练--链表
链表基本概念
-
链表的定义:链表是一种递归的数据结构,他或者为空(null),或者是指向一个结点(node)的引用,该结点含有一个泛型的元素和一个指向另一条链表的引用
链表是一种常见且基础的数据结构,是一种线性表,但是他不是按线性顺序存取数据,而是在每一个节点里存到下一个节点的地址。我们也可以这样理解,链表是通过指针串联在一起的线性结构,每一个链表结点由两部分组成,数据域及指针域,链表的最后一个结点指向null。也就是我们所说的空指针
-
链表常用的有 3 类: 单链表、双向链表、循环链表
-
单链表:单链表 [Linked List]:由各个内存结构通过一个
Next指针链接在一起组成,每一个内存结构都存在后继内存结构【链尾除外】,内存结构由数据域和 Next 指针域组成 -
双向链表:双向链表 [Double Linked List]:,由各个内存结构通过指针
Next和指针Prev链接在一起组成,每一个内存结构都存在前驱内存结构和后继内存结构【链头没有前驱,链尾没有后继】,内存结构由数据域、Prev 指针域和 Next 指针域组成 -
循环链表:由各个内存结构通过一个指针
Next链接在一起组成,每一个内存结构都存在后继内存结构,内存结构由数据域和 Next 指针域组成链表首尾相连
-
-
链表数组性能分析
相关题目练习
206. 反转链表
-
题目描述
-
题解
/** * 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 reverseList(ListNode head) { if(head==null){ return head; } //前指针节点 ListNode pre=null; //当前指针节点 ListNode curr=head; while(curr!=null){ ListNode temp=curr.next;//临时节点,存放当前节点的下一个节点 curr.next=pre;//将当前节点的指针指向前面的节点 pre=curr;//前指针后移 curr=temp;//当前指针后移 } return pre; } }
24. 两两交换链表中的节点
-
题目描述
-
题解
// 虚拟头结点 class Solution { public ListNode swapPairs(ListNode head) { ListNode dummy=new ListNode(0); dummy.next=head; ListNode pre=dummy; while(pre.next!=null && pre.next.next!=null){ ListNode temp=head.next.next;//缓存head.next.next为temp pre.next=head.next;//步骤一:pre的next指向head的next head.next.next=head;//步骤二:head.next的next指向head head.next=temp;//步骤三:head的next指向temp pre=head;//后移 head=head.next;//后移 } return dummy.next;//输出排除虚拟头节点 } }
141. 环形链表
-
题目描述
-
题解
/** 快慢指针,快指针每次移动两位,慢指针移动一位 */ public class Solution { public boolean hasCycle(ListNode head) { ListNode fast=head; ListNode slow=head; while(fast!=null && fast.next!=null){ fast=fast.next.next; slow=slow.next; if(fast==slow){ return true; } } return false; } }
142. 环形链表 II
-
题目描述
-
题解
从头结点出发一个指针,从相遇节点 也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
/** 两个步骤 1.通过快慢指针的方法判断链表是否有环 2.如果有环,则寻找入环的第一个节点: 一个从起点开始的新指针start和从节点B开始的慢指针slow同步走,相遇的地方必然是入环的第一个节点A */ public class Solution { public ListNode detectCycle(ListNode head) { ListNode fast=head; ListNode slow=head; while(fast!=null && fast.next!=null){ slow=slow.next; fast=fast.next.next; if(slow==fast){ //有环 ListNode index1=fast; ListNode index2=head; //两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口 while(index1!=index2){ index2=index2.next; index1=index1.next; } return index1; } } return null; } }
25. K 个一组翻转链表
-
题目描述
-
题解
class Solution { public ListNode reverseKGroup(ListNode head, int k) { if(head==null || head.next==null) return head; ListNode tail=head; for(int i=0;i<k;i++){ if(tail==null)return head; tail=tail.next; } //反转前k个元素,剩余数量小于k的话则不需要反转 ListNode pre=reverse(head,tail); //下一轮开始的地方就是tail head.next=reverseKGroup(tail,k); return pre; } public ListNode reverse(ListNode head,ListNode tail){ ListNode curr=head; ListNode pre=null; while(curr!=tail){ ListNode temp=curr.next; curr.next=pre; pre=curr; curr=temp; } return pre; } }
21. 合并两个有序链表
-
题目描述
-
题解
class Solution { public ListNode mergeTwoLists(ListNode list1, ListNode list2) { if(list1==null){ return list2; } if(list2==null){ return list1; } if(list1.val>list2.val){ //当前节点谁小,就让这个较小的节点的next和另一个链表继续递归合并 list2.next=mergeTwoLists(list1,list2.next); return list2; }else{ list1.next=mergeTwoLists(list2,list1.next); return list1; } } }
203. 移除链表元素
-
题目描述
-
题解
链表操作的两种方式:
- 直接使用原来的链表来进行删除操作
- 设置一个虚拟头结点在进行删除操作
移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点,所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点
其实可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了
class Solution {
public ListNode removeElements(ListNode head, int val) {
if(head==null){
return head;
}
//构建一个虚拟的头结点
ListNode dummy=new ListNode(-1,head);
ListNode pre=dummy;
ListNode cur=head;
while(cur!=null){
if(cur.val==val){
//删除节点,pre的next指向cur的下一个节点
pre.next=cur.next;
}else{
//pre节点后移
pre=cur;
}
//cur节点后移
cur=cur.next;
}
//输出时,排除虚拟头节点
return dummy.next;
}
}
707. 设计链表
-
题目描述
-
题解
// 定义链表节点结构体(单链表) class ListNode{ int val; ListNode next; ListNode(){}; ListNode(int val){ this.val=val; } } class MyLinkedList { //链表元素个数 size=0; // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点 ListNode dummy; public MyLinkedList() { size=0; dummy=new ListNode(0); } public int get(int index) { //如果index非法,返回-1 if(index<0 || index>=size){ return -1; } ListNode cur=dummy; //包含一个虚拟头节点,所以查找第 index+1 个节点 for(int i=0;i<=index;i++){ cur=cur.next; } return cur.val; } public void addAtHead(int val) { addAtIndex(0,val); } public void addAtTail(int val) { addAtIndex(size,val); } public void addAtIndex(int index, int val) { if(index>size){ return; } if(index<0){ index=0; } size++; ListNode pre=dummy; //找到要插入节点的前驱 for(int i=0;i<index;i++){ pre=pre.next; } ListNode addNode=new ListNode(val); addNode.next=pre.next; pre.next=addNode; } public void deleteAtIndex(int index) { if(index<0 || index>=size){ return; } size--; ListNode pre=dummy; //找到要删除节点的前驱 for(int i=0;i<index;i++){ pre=pre.next; } pre.next=pre.next.next; } }
19. 删除链表的倒数第 N 个结点
-
题目描述
-
题解
- 双指针的经典应用,如果要删除倒数第n个节点,让fast移动n步,然后让fast和slow同时移动,直到fast指向链表末尾。删掉slow所指向的节点就可以了
/** * 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 removeNthFromEnd(ListNode head, int n) { ListNode dummy=new ListNode(-1); dummy.next=head; ListNode fast=dummy; ListNode slow=dummy; while(n>0){ fast=fast.next; n--; } //找到待删除节点slow的前驱节点 ListNode pre=dummy; while(fast!=null){ pre=slow; slow=slow.next; fast=fast.next; } //绕过待删除节点slow,前驱的next指针直接指向slow.next节点 pre.next=slow.next; return dummy.next; } }
面试题 02.07. 链表相交
-
题目描述
-
题解
简单来说,就是求两个链表交点节点的指针。 这里要注意,交点不是数值相等,而是指针相等
目前curA指向链表A的头结点,curB指向链表B的头结点;我们求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点,否则循环退出返回空指针
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode(int x) { * val = x; * next = null; * } * } */ public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode curA=headA; ListNode curB=headB; int lenA=0,lenB=0; while(curA!=null){ lenA++; curA=curA.next; } while(curB!=null){ lenB++; curB=curB.next; } curA=headA; curB=headB; //让curA为最长链表的头,lenA为其长度 if(lenB>lenA){ //swap lenA lenB int temp=lenB; lenB=lenA; lenA=temp; //swap curA curB ListNode tempNode=curB; curB=curA; curA=tempNode; } int gap=lenA-lenB; //让curA和curB在同一起点上(末尾位置对齐) while(gap>0){ curA=curA.next; gap--; } // 遍历curA 和 curB,遇到相同则直接返回 while(curA!=null){ if(curA==curB){ return curA; } curA=curA.next; curB=curB.next; } return null; } }
翻转链表二
-
题目描述
-
题解
class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if(head==null) return head; ListNode dummy=new ListNode(-1); dummy.next=head; ListNode pre=dummy; //找到入口节点 cur 注意是left-1 for(int i=0;i<left-1;i++){ pre=pre.next; } ListNode cur=pre.next; //翻转 for(int i=0;i<right-left;i++){ ListNode temp=cur.next; cur.next=temp.next; temp.next=pre.next; pre.next=temp; } return dummy.next; } }
CodeTop系列
25. K 个一组翻转链表
-
题目描述
-
题解
class Solution { public ListNode reverseKGroup(ListNode head, int k) { if(head==null || head.next==null) return head; ListNode tail=head; for(int i=0;i<k;i++){ if(tail==null) return head; tail=tail.next; } ListNode pre=reverse(head,tail); head.next=reverseKGroup(tail,k); return pre; } public ListNode reverse(ListNode head,ListNode tail){ ListNode pre=null; ListNode cur=head; while(cur!=tail){ ListNode temp=cur.next; cur.next=pre; pre=cur; cur=temp; } return pre; } }
24. 两两交换链表中的节点
-
题目描述
-
题解
class Solution { public ListNode swapPairs(ListNode head) { if(head==null) return head; ListNode dummy=new ListNode(-1); dummy.next=head; ListNode cur=head; ListNode pre=dummy; while(cur!=null && cur.next!=null){ ListNode temp=cur.next.next; pre.next=cur.next; cur.next.next=cur; cur.next=temp; pre=cur; cur=cur.next; } return dummy.next; } }
面试题 02.05. 链表求和
-
题目描述
-
题解
class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { int carry=0; ListNode dummy=new ListNode(-1); ListNode cur=dummy; while(l1!=null || l2!=null || carry!=0){ int num1=l1==null?0:l1.val; int num2=l2==null?0:l2.val; int sum=num1+num2+carry; ListNode temp=new ListNode(sum%10); cur.next=temp; cur=cur.next; carry=sum/10; l1=l1==null?null:l1.next; l2=l2==null?null:l2.next; } return dummy.next; } }
2. 两数相加
-
题目描述
-
题解
class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummy=new ListNode(-1); ListNode pre=dummy; int t=0; while(l1!=null || l2!=null || t!=0){ if(l1!=null){ t+=l1.val; l1=l1.next; } if(l2!=null){ t+=l2.val; l2=l2.next; } pre.next=new ListNode(t%10); pre=pre.next; t/=10; } return dummy.next; } }
445. 两数相加 II
-
题目描述
-
题解
class Solution { public ListNode addTwoNumbers(ListNode l1, ListNode l2) { Stack<Integer> s1=buildStack(l1); Stack<Integer> s2=buildStack(l2); int carry=0; ListNode dummy=new ListNode(-1); ListNode cur=dummy; while(!s1.isEmpty() || !s2.isEmpty() || carry!=0){ int num1=s1.isEmpty()?0:s1.pop(); int num2=s2.isEmpty()?0:s2.pop(); int sum=num1+num2+carry; ListNode temp=new ListNode(sum%10); temp.next=cur.next; cur.next=temp; carry=sum/10; } return dummy.next; } public Stack<Integer> buildStack(ListNode node){ Stack<Integer> stack=new Stack<>(); while(node!=null){ stack.push(node.val); node=node.next; } return stack; } }
148. 排序链表
-
题目描述
-
题解
class Solution { /** * 参考:Sort List——经典(链表中的归并排序) https://www.cnblogs.com/qiaozhoulin/p/4585401.html * * 归并排序法:在动手之前一直觉得空间复杂度为常量不太可能,因为原来使用归并时,都是 O(N)的, * 需要复制出相等的空间来进行赋值归并。对于链表,实际上是可以实现常数空间占用的(链表的归并 * 排序不需要额外的空间)。利用归并的思想,递归地将当前链表分为两段,然后merge,分两段的方 * 法是使用 fast-slow 法,用两个指针,一个每次走两步,一个走一步,知道快的走到了末尾,然后 * 慢的所在位置就是中间位置,这样就分成了两段。merge时,把两段头部节点值比较,用一个 p 指向 * 较小的,且记录第一个节点,然后 两段的头一步一步向后走,p也一直向后走,总是指向较小节点, * 直至其中一个头为NULL,处理剩下的元素。最后返回记录的头即可。 * * 主要考察3个知识点, * 知识点1:归并排序的整体思想 * 知识点2:找到一个链表的中间节点的方法 * 知识点3:合并两个已排好序的链表为一个新的有序链表 */ public ListNode sortList(ListNode head) { return head == null ? null : mergeSort(head); } private ListNode mergeSort(ListNode head) { if (head.next == null) { return head; } ListNode p = head, q = head, pre = null; while (q != null && q.next != null) { pre = p; p = p.next; q = q.next.next; } pre.next = null; ListNode l = mergeSort(head); ListNode r = mergeSort(p); return merge(l, r); } ListNode merge(ListNode l, ListNode r) { ListNode dummyHead = new ListNode(0); ListNode cur = dummyHead; while (l != null && r != null) { if (l.val <= r.val) { cur.next = l; cur = cur.next; l = l.next; } else { cur.next = r; cur = cur.next; r = r.next; } } if (l != null) { cur.next = l; } if (r != null) { cur.next = r; } return dummyHead.next; } }
143. 重排链表
-
题目描述
-
题解
class Solution { public void reorderList(ListNode head) { if(head==null) return; ListNode l1 = head; //找链表中点 ListNode mid = middleNode(head); ListNode l2 = mid.next; //断开中点 mid.next = null; //反转右半段 l2 = reverse(l2); //合并两个链表 merge(l1, l2); } public ListNode middleNode(ListNode head) { ListNode fast = head; ListNode slow = head; while (fast != null && fast.next != null) { fast=fast.next.next; slow = slow.next; } return slow; } public ListNode reverse(ListNode head) { ListNode pre = null; ListNode cur = head; while (cur != null) { ListNode temp=cur.next; cur.next=pre; pre=cur; cur = temp; } return pre; } public void merge(ListNode l1, ListNode l2) { while (l1 != null && l2 != null) { ListNode temp1 = l1.next; ListNode temp2 = l2.next; l1.next=l2; l1 = temp1; l2.next=l1; l2 = temp2; } } }
138. 复制带随机指针的链表
-
题目描述
-
题解
class Solution { Map<Node, Node> map = new HashMap<>(); public Node copyRandomList(Node head) { if(head==null) return head; if(!map.containsKey(head)){ Node node=new Node(head.val); map.put(head, node); node.next=copyRandomList(head.next); node.random=copyRandomList(head.random); } return map.get(head); } }
328. 奇偶链表
-
题目描述
-
题解
class Solution { public ListNode oddEvenList(ListNode head) { if(head==null || head.next==null) return head; ListNode cur1=head; ListNode head2=head.next; ListNode cur2=head2; while(cur1.next!=null && cur2.next!=null){ cur1.next=cur2.next; cur1=cur1.next; cur2.next=cur1.next; cur2=cur2.next; } cur1.next=head2; return head; } }
23. 合并K个升序链表
-
题目描述
-
题解
class Solution { public ListNode mergeKLists(ListNode[] lists) { if(lists.length==0) return null; else if(lists.length==1) return lists[0]; else if(lists.length==2) return mergeTwoList(lists[0],lists[1]); else{ int mid=lists.length/2; ListNode[] l1=new ListNode[mid]; for(int i=0;i<mid;i++){ l1[i]=lists[i]; } ListNode[] l2=new ListNode[lists.length-mid]; for(int i=mid,j=0;i<lists.length;i++,j++){ l2[j]=lists[i]; } return mergeTwoList(mergeKLists(l1),mergeKLists(l2)); } } public ListNode mergeTwoList(ListNode l1,ListNode l2){ if(l1==null) return l2; if(l2==null) return l1; if(l1.val>l2.val){ l2.next=mergeTwoList(l1,l2.next); return l2; }else{ l1.next=mergeTwoList(l1.next,l2); return l1; } } }
234. 回文链表
-
题目描述
-
题解
class Solution { public boolean isPalindrome(ListNode head) { //1、快慢指针找出链表中点 if(head==null || head.next==null) return true; ListNode fast=head; ListNode slow=fast; while(fast!=null && fast.next!=null){ fast=fast.next.next; slow=slow.next; } ListNode pre=null; if(fast!=null) slow=slow.next; //偶数节点 //2、反转后半部分 while(slow!=null){ ListNode temp=slow.next; slow.next=pre; pre=slow; slow=temp; } //3、比较从头结点到中间节点是否相同 while(head!=null && pre !=null){ if(head.val!=pre.val) return false; head=head.next; pre=pre.next; } return true; } }
83. 删除排序链表中的重复元素
-
题目描述
-
题解
class Solution { public ListNode deleteDuplicates(ListNode head) { ListNode cur=head; while(cur!=null && cur.next!=null){ if(cur.val==cur.next.val){ cur.next=cur.next.next; }else{ cur=cur.next; } } return head; } }
82. 删除排序链表中的重复元素 II
-
题目描述
-
题解
class Solution { public ListNode deleteDuplicates(ListNode head) { if (head == null || head.next == null) return head; ListNode dummy = new ListNode(-1); dummy.next=head; ListNode cur = dummy; while (cur.next != null && cur.next.next != null) { if (cur.next.val == cur.next.next.val) { int x = cur.next.val; while (cur.next != null && cur.next.val == x) { cur.next = cur.next.next; } } else { cur = cur.next; } } return dummy.next; } }
19. 删除链表的倒数第 N 个结点
-
题目描述
-
题解
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { ListNode dummy=new ListNode(-1); dummy.next=head; ListNode fast=dummy; ListNode slow=dummy; while(n>=0){ fast=fast.next; n--; } while(fast!=null){ fast=fast.next; slow=slow.next; } slow.next=slow.next.next; return dummy.next; } }
剑指22. 链表中倒数第k个节点
-
题目描述
-
题解
class Solution { public ListNode getKthFromEnd(ListNode head, int k) { ListNode dummy = new ListNode(-1); dummy.next = head; ListNode fast = dummy; ListNode slow = dummy; while (k-- >= 0) { fast = fast.next; } while (fast != null) { fast = fast.next; slow = slow.next; } return slow.next; } }
160. 相交链表
-
题目描述
-
题解
public class Solution { public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode l1 = headA; ListNode l2 = headB; int len1 = 0, len2 = 0; while (l1 != null) { len1++; l1 = l1.next; } while (l2 != null) { len2++; l2 = l2.next; } l1 = headA; l2 = headB; if (len2 > len1) { int temp=len2; len2 = len1; len1 = temp; ListNode tempNode = l2; l2 = l1; l1 = tempNode; } int gap = len1 - len2; while (gap-- > 0) { l1 = l1.next; } while (l1 != l2) { l1 = l1.next; l2 = l2.next; } return l1; } }
92. 反转链表 II
-
题目描述
-
题解
class Solution { public ListNode reverseBetween(ListNode head, int left, int right) { if(head==null) return head; ListNode dummy=new ListNode(-1); dummy.next=head; ListNode pre=dummy; //找到反转的入口节点的前驱节点 for(int i=0;i<left-1;i++){ pre=pre.next; } ListNode cur=pre.next; for(int i=0;i<right-left;i++){ ListNode temp=cur.next; cur.next=temp.next; temp.next=pre.next; pre.next=temp; } return dummy.next; } }
142. 环形链表 II
-
题目描述
-
题解
public class Solution { public ListNode detectCycle(ListNode head) { if(head==null) return null; ListNode fast=head; ListNode slow=head; while(fast!=null && fast.next!=null){ fast=fast.next.next; slow=slow.next; if(fast==slow){ ListNode index1=fast; ListNode index2=head; while(index1!=index2){ index1=index1.next; index2=index2.next; } return index1; } } return null; } }