目录
1.合并两个有序链表
2.删除链表中的所有重复元素,不保留
3.删除链表中的重复元素,保留一个
4.分隔链表
5.环形链表
6.返回环形链表的入口节点
7.相交链表
8.移除链表元素
9.反转链表
10.回文链表
11.链表的中间节点
要求:将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
(1)基础做法
主要思想:边界条件:一个链表为空,则拼接后一定是那个非空链表;若两个都为空,则返回null;若两个链表都不为空:创建新的虚拟节点,遍历两个链表,将较小的那个链表拼接到这个虚拟节点后 ;若当两个链表中一旦有一个链表遍历完成为null后,则将另一个链表直接拼接到虚拟节点后即可。
/*//情况1:两个链表都为空:可以省略了,下面的情况2都包含了
if(list1==null && list2==null){
return null;
}
//情况2:有一个链表为null,则另一个链表就是合并后的链表
if(list1 == null ){
return list2;
}
if(list2 == null ){
return list1;
}
//情况3:两个链表都不为null
//(1)创建虚拟头结点
ListNode dummyHead = new ListNode();
ListNode tail = dummyHead;
//(2)遍历两个链表,双指针
while (list1.next != null && list2.next !=null){
if(list1.val > list2.val){
tail.next = list2;//--------注意拼接的是整个链表,并不是链表中的单独节点
tail = list2;
list2 = list2.next;
} else {
tail.next = list1;
tail = list1;
list1 = list1.next;
}
}
//(3)此时说明已经将其中一个链表遍历完,则直接将另一个链表的值全部加到合并链表中
if(list1 == null){
tail.next = list2;
}
if(list2 == null){
tail.next = list1;
}
//(4)返回最终的头结点
return dummyHead.next;
}*/
(2)递归法
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//边界条件
//情况2:有一个链表为null,则另一个链表就是合并后的链表
if(list1==null ){
return list2;
}
if(list2==null ){
return list1;
}
//递归的使用
if(list1.val <= list2.val){
list1.next = mergeTwoLists(list1.next,list2);//此时的list1中的第一个值是最小值,采用递归方法将List1的第一个节点后的所有节点与list2的所有节点合并
return list1;//list1保存的就是最小值头结点
} else {
list2.next = mergeTwoLists(list1.next,list2);//list2中的第一个值是最小值
return list2;
}
}
public ListNode deleteDuplicates(ListNode head) {
// 1.base case
if(head == null || head.next == null) {
return head;
}
if(head.val != head.next.val) {
head.next = deleteDuplicates(head.next);
return head;
}else {
// 头节点就是重复的节点,先处理完头节点的情况
ListNode newHead = head.next;
while(newHead != null && newHead.val == head.val) {
newHead = newHead.next;
}
// 此时newHead一定不是待删除的结点,最终整个传入函数,返回更新后的值即可
return deleteDuplicates(newHead);
}
}
(1)基本做法
主要思路:双指针法:创建一个虚拟头结点指向链表的头部head,第一个指针从dummyHead开始记作prev,第二个指针从虚拟头结点的下一个位置开始记作cur。 情况1:如果prev和cur的值不相等,说明不是重复的元素,就往后各移动一位; 情况2:如果prev和cur的值相等,说明两者保存的是重复元素,就将prev记录的值进行保留,跳过cur保存的重复值,更改prev的指向为:直接指向cur的下一个节点。同时再更新cur的指向:往后移动一位 。终止条件:cur比prev快一步,所以当cur指向的值为null时,说明遍历结束。
public ListNode deleteDuplicates(ListNode head) {
//(1)边界条件:只存在一个头结点或者不存在节点时,肯定不存在重复元素,直接返回头结点即可
if(head==null || head.next == null){
return head;
}
//(2)走到这里最少有两个节点存在
//创建一个虚拟头结点
ListNode dummyHead = new ListNode(-101);
//虚拟头结点添加到当前链表的最开始的头结点
dummyHead.next = head;
//创建一个头结点prev代替虚拟头结点从头开始
ListNode prev = dummyHead;//prev作为第一个指针
ListNode cur = prev.next;//cur作为第二个指针
//判断指针的值
while (cur!=null){
if(prev.val == cur.val){
prev = cur.next;
}else {
prev = prev.next;
}
cur = cur.next;
}
//返回链表的头结点
return dummyHead.next;
}
(2)递归法:先把以head为头结点的子链表的重复元素删除,保留一次(调用递归函数), 再判断删除后的子链表的头结点与头结点head是否是重复元素,是的话删除head,返回子链表的头部,不是则返回head。
public ListNode deleteDuplicates(ListNode head) {
//(1)边界条件
if(head==null || head.next == null){
return head;
}
//先把以head为头结点的子链表的重复元素删除,保留一次
head.next = deleteDuplicates(head.next);
//现在剩下头结点
return head.val == head.next.val ? head.next : head;
}
要求:给你一个链表的头节点head和一个特定值 x ,请你对链表进行分隔,使得所有小于x的节点都出现在大于或等于x的节点之前。 你应当保留两个分区中每个节点的初始相对位置。
主要思路:遍历原链表,将所有小于x节点的放在一个子链表l1,大于x的节点放在子链表l2,最后将l2与l1进行拼接。注意:l2的尾部一定要置为null。
public ListNode partition(ListNode head, int x) {
//边界:如果头结点不存在或者只存在一个节点
if(head ==null || head.next == null){
return head;
}
//产生两个虚拟头结点,并给出节点尾部
ListNode smallHead = new ListNode();
ListNode bigHead = new ListNode();
ListNode smallTial = smallHead;
ListNode bigTail = bigHead;
//遍历原链表,将所有小于x的节点尾插到小链表的尾部;相反的尾插到大链表的尾部
//当head为空时结束遍历
while (head!=null){
if(head.val < x){
smallTial.next = head;
smallTial = head;
}else{
bigTail.next = head;
bigTail = head;
}
head = head.next;
}
//结束遍历,将大链表的尾部断开,拼接大小链表
bigTail.next = null;
smallTial.next = bigHead.next;
return smallHead.next;
}
要求:给定一个链表判断该链表中是否有环。
主要思想:快慢指针:如果存在环,则快指针一定在某一个时刻追上慢指针。如果快指针为null时还没追上,则一定没有环。
public boolean hasCycle(ListNode head) {
//定义两个快慢指针
ListNode low = head;
ListNode fast = head;
//当快指针为null时还没相遇,说明没有环
while (fast !=null && fast.next != null){
//让快指针每次走两步,慢指针每次走一步
low = low.next;
fast = fast.next.next;
//如果快指针与慢指针相遇了,说明有环
if(low == fast){
return true;
}
}
return false;
}
要求:给定一个具有环的链表,要求返回该环形链表的入口节点。
主要思想:借助一个结论实现:当快慢指针相遇的时候,引入一个新的节点从链表的头结点开始,最终这个新的节点一定会与慢指针在入口位置相遇。
public ListNode detectCycle(ListNode head) {
//定义两个快慢指针
ListNode low = head;
ListNode fast = head;
//在快指针没走完的时候
while (fast!=null && fast.next != null){
fast = fast.next.next;
low = low.next;
if(low == fast){//快慢指针相遇
ListNode newNode = head;//引入一个新节点
while (newNode != low){//当慢指针与新节点不相等的时候,让两者一直走
low = low.next;
newNode = newNode.next;
}
//说明慢指针与新节点相遇,此时相遇的位置就是入口
return newNode;
}
}
return null;
}
要求:给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回null。
思想:使用两个引用listA和listB,同时开始遍历。 如果listA走完了,就走listB,listB也是一样,先走自身,走完了再走listA。如果两者有相交位置,一定在在相交位置相遇;否则当两者都走完还没相遇,就是没有相交节点。
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode l1 = headA;
ListNode l2 = headB;
while (l1 != l2){
l1 = (l1 == null? headB : l1.next);
l2 = (l2 == null? headA : l2.next);
}
return l1;
}
要求:给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回新的头节点。
(1)虚拟头结点法:遍历链表,走到被删除节点的前驱,与要删除的值比较,更改指向。
public ListNode removeElements(ListNode head, int val) {
//判断边界条件:链表中不存在节点,则直接返回Null
if(head == null){
return null;
}
//产生一个虚拟头结点
ListNode dummyNode = new ListNode();
//将虚拟头结点与原链表建立联系
dummyNode.next = head;
//让prev作为临时节点代替head
ListNode prev = dummyNode;
while (prev.next != null){
if(prev.next.val == val){
prev.next = prev.next.next;
}else{
prev = prev.next;//防止出现连续的被删除的节点,只有当prev.next不是被删除的节点时才移动
}
}
return dummyNode.next;
}
(2)递归法
public ListNode removeElements(ListNode head, int val) {
//边界条件
if(head == null){
return null;
}
//正常情况
//先把以head.next为头结点的子链表中的所有为val中的值删除完毕!
head.next = removeElements(head.next,val);
//最后判断头结点的情况:是否需要删除
return head.val == val ? head.next:head;
}
要求:输入链表:[1,2,3,4,5] 输出:[5,4,3,2,1]
(1)头插法
主要思路:先产生一个虚拟头接节点,从head开始不断遍历原链表,每遍历到一个节点,就产生一个与该值相等的新节点, 将该新节点不断头插到虚拟头结点后,最终dummyHead.next就是反转后链表的头结点。
public ListNode reverseList(ListNode head) {
//边界条件:
if (head == null || head.next == null) {
return head;
}
//产生虚拟头节点
ListNode dummyHead = new ListNode();
//遍历原链表,不断产生新节点,将该新节点不断头插到虚拟头结点之后
while (head != null) {
ListNode newNode = new ListNode(head.val);
newNode.next = dummyHead.next;//将新节点与原先的节点相连接
dummyHead.next = newNode;//将新节点头插到虚拟头结点后
head = head.next;
}
return dummyHead.next;
}
(2)在原链表移动
public ListNode reverseList(ListNode head) {
//边界条件
if (head == null || head.next == null) {
return head;
}
//让第一个指针指向空,第二个指针指向链表的头,当第二个指针为空的时候结束遍历
ListNode prev = null;
ListNode cur = head;
while (cur!=null){
//保存第二个指针的下一个指向(因为要对第二个指针进行操作,防止后面的值丢失。)
ListNode next = cur.next;
cur.next = prev;//让第二个指针指向第一个指针
//移动两个指针的位置
prev = cur;
cur = next;
}
//最终第一个指针指向的值就是头结点
return prev;
}
(3)递归法
public ListNode reverseList(ListNode head) {
//边界条件
if (head == null || head.next == null) {
return head;
}
//处理头节点之后的子链表:进行反转
ListNode next = head.next;
ListNode newHead = reverseList(head.next);
//拼接当前头结点和转后的子链表
head.next = null;//先断开原先链表头结点之后的节点
next.next = head;//连接反转后链表的尾部与原先链表的头部
return newHead;
}
要求:回文链表:1221是,12321是,123312不是回文链表。
主要思路:比如原链表为12321,将原链表看作两个子链表,以中间节点为其分开:L1: 12, L2: 321——>将L2进行转置结果为123,遍历L1和L2判断两者的值是否相等。在两个子链表为null之前,如果找到任意一个不相等的就返回false。
public boolean isPalindrome(ListNode head) {
//边界条件:链表为null或者只有一个节点都是true
if(head == null || head.next == null){
return true;
}
//找出链表的中间节点记作middleNode
ListNode middleNode = middleNode(head);
//将从middleNode为头结点的链表进行反转后的头结点记作l2
ListNode l2 = reverseList(middleNode);
//将以head为头结点的子链表与l2子链表进行遍历判断
while (head != null & l2!= null){
//找反例:判断两个子链表的头结点是否相同
if(head.val != l2.val){
return false;
}
//移动两个子链表头结点的位置
head = head.next;
l2 = l2.next;
}
return true;
}
// 函数目标:找中间节点
public ListNode middleNode(ListNode head) {
// 快慢指针法
ListNode low = head,fast = head;
while (fast != null && fast.next != null) {
low = low.next;
fast = fast.next.next;
}
return low;
}
// 函数目标:反转链表
public ListNode reverseList(ListNode head) {
// 1.base case
if(head == null || head.next == null) {
return head;
}
ListNode next = head.next;
ListNode newHead = reverseList(head.next);
// 拼接当前头结点和转后的子链表
head.next = null;
next.next = head;
return newHead;
}
要求:给你单链表的头结点 head ,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
思路:快慢指针法。 结论:让第一个和第二个指针都指向头结点,让第一个指针每次走一步,第二指针每次都两步, 当第二个指针走向null的时候,第一个指针所指的位置就是中间节点的位置。
public ListNode middleNode(ListNode head) {
ListNode first = head;
ListNode second = head;
while (second!=null && second.next!=null){
first = first.next;
second = second.next.next;
}
//说明此时第二个指针指向的是Null
return first;
}