1.两个链表的第一个公共子节点
这个问题第一反应就是可以使用map或者set来做,先遍历其中一条链表并存入map/set中,再遍历另外一条链表通过contains()来找到第一个公共子节点。
另外一个就是用双指针来解决,两个指针,一个指向A,一个指向B。两个指针同时移动,其中一个指针移动到最后时,将其指向另一条链表;另外一个指针同理。当两个指针都改变指向后,两个指针已经处于同一起点,往后遍历,第一个相等的就是需要的答案。
1.1.集合
public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
if (pHead1 == null || pHead2 == null) {
return null;
}
ListNode current1 = pHead1;
ListNode current2 = pHead2;
HashMap<ListNode, Integer> hashMap = new HashMap<ListNode, Integer>();
while (current1 != null) {
hashMap.put(current1, null);
current1 = current1.next;
}
while (current2 != null) {
if (hashMap.containsKey(current2))
return current2;
current2 = current2.next;
}
return null;
}
1.2.双指针
public static ListNode get(ListNode la, ListNode lb){
ListNode cur1 = la;
ListNode cur2 = lb;
while(cur1 != cur2){
cur1 = cur1 == null ? lb : cur1.next;
cur2 = cur2 == null ? la : cur2.next;
}
return cur1;
}
还可以使用差和双指针和栈来解决。
差和双指针:计算两条链表的长度差k,长的先移动k步。在往后找到第一个相等的节点。
2.判断链表是否为回文链表
这个题第一时间能想到的就是用list集合来做,把所有元素存入list集合中,再进行首尾比较,一但出现不等,那就不是回文序列。但是教官提示我们面试的时候不能这么干,所以使用另外的方法。
除此之外,可以使用栈和快慢双指针来解决。
2.1.全部入栈
public static boolean isPalindromeByAllStack(ListNode head) {
ListNode temp = head;
Stack<Integer> stack = new Stack();
//把链表节点的值存放到栈中
while (temp != null) {
stack.push(temp.val);
temp = temp.next;
}
//然后再出栈
while (head != null) {
if (head.val != stack.pop()) {
return false;
}
head = head.next;
}
return true;
}
2.2.快慢双指针
几个关键点:
-
slow每次移动一步,fast每次移动两步,当fast指针到达链表的末尾时,slow指针就会在链表的中间
-
在寻找中间节点的过程中顺便反转前半部分链表,pre指向slow的前一个节点,prepre指向slow的前一个节点。再改变完指向后不要忘记更新pre和prepre(后移)
-
如果链表的长度是奇数,fast指针会停在最后一个节点上,此时slow指针指向的是链表的中间节点,需要将slow指针向后移动一步,跳过中间节点。
-
最后只需要将反转后的前半部分与后半部分进行比较即可
public static boolean isPalindromeByTwoPoints(ListNode head) {
if (head == null || head.next == null) {
return true;
}
ListNode fast = head,slow = head;
ListNode pre = head, prepre = null;
// 找到中间节点并反转链表前半部分
while(fast != null && fast.next != null){
pre = slow; // slow的前驱
slow = slow.next;
fast = fast.next.next;
pre.next = prepre; // 反转
prepre = pre; //后移
}
// 节点数为奇数时,跳过中间节点,此时fast指向最后一个节点
if(fast != null) slow = slow.next;
//比较前半部分后后半部分是否相等
while (slow != null && pre != null){
if(slow.data != pre.data) return false;
slow = slow.next;
pre = pre.next;
}
return true;
}
3.合并有序链表
3.1.合并两个有序链表
3.1.1.直接求解
public static ListNode mergeTwoListsMoreSimple(ListNode l1, ListNode l2) {
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) {
prev.next = l1;
l1 = l1.next;
} else {
prev.next = l2;
l2 = l2.next;
}
prev = prev.next;
}
// 最多只有一个还未被合并完,直接接上去就行了,这是链表合并比数组合并方便的地方
prev.next = l1 == null ? l2 : l1;
return prehead.next;
}
3.1.2递归
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1 == null) return list2;
if(list2 == null) return list1;
if(list1.val < list2.val){
list1.next = mergeTwoLists(list1.next,list2);
return list1;
}
else{
list2.next = mergeTwoLists(list1,list2.next);
return list2;
}
}
3.2.合并k个链表
只要会合并两个链表,就只需要加个循环就可以合并k个链表了。
package com.ljw.javademo.day1;
import java.util.ArrayList;
import java.util.List;
/**
* Created with IntelliJ IDEA.
*
* @Auther: ljw
* @Date: 2023/07/18/18:58
* @Description:
*/
public class 合并k个链表 {
public static void main(String[] args) {
ListNode head1 = new ListNode(1);
head1.next = new ListNode(3);
ListNode head2 = new ListNode(1);
head2.next = new ListNode(2);
ListNode head3 = new ListNode(1);
head3.next = new ListNode(7);
List<ListNode> listNodes = new ArrayList<>();
listNodes.add(head1);
listNodes.add(head2);
listNodes.add(head3);
ListNode listNode = mergeKList(listNodes);
travelListNode(listNode);
}
public static ListNode mergeKList(List<ListNode> listNodes){
ListNode res = null;
for(ListNode listNode : listNodes){
res = mergeTwoLists(res,listNode);
}
return res;
}
public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1 == null) return list2;
if(list2 == null) return list1;
if(list1.data < list2.data){
list1.next = mergeTwoLists(list1.next,list2);
return list1;
}
else{
list2.next = mergeTwoLists(list1,list2.next);
return list2;
}
}
public static void travelListNode(ListNode head){
while(head != null){
System.out.print(head.data+" ");
head = head.next;
}
}
}
3.3.合并链表
关键点:找到a的前驱,b的后继,list2的尾节点
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
// 需要保留a的前驱和b的后继
ListNode pre = list1;
ListNode post1 = list1;
// list2的尾部节点
ListNode post2 = list2;
int i = 0,j = 0;
while(pre != null && post1 != null && j < b){
// 找到a的前驱
… post1 = post1.next;
// 找到list2的尾节点
while(post2.next != null){
post2 = post2.next;
}
pre.next = list2;
post2.next = post1;
return list1;
}
4.双指针专题
4.1.链表的中间节点
快慢双指针:慢指针移动一步,快指针移动两步,当快指针到尾部时,慢指针刚好到达中间节点。
public ListNode middleNode(ListNode head) {
ListNode slow = head,fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
return slow;
}
4.2.链表中的倒数第k个节点
首先想到了暴力求解,先遍历链表求得链表长度L,再根据k和L找到答案,但是这样需要遍历两次链表。因此我们使用快慢双指针来解决。fast先移动k步,slow指向头节点,然后同时移动,当fast为null时,slow刚好指向了需要的答案。
注意:链表的长度可能小于k,所以移动时要判断fast是否为空。
快慢双指针
public ListNode getKthFromEnd(ListNode head, int k) {
// 暴力解决需要遍历两次链表
// ListNode cur = head;
// int length=0;
// while(cur!=null){
// length++;
// cur=cur.next;
// }
// if(length==1 || length==k){
// return head;
// }
// cur=head;
// for(int i =1;i<length-k;i++){
// cur=cur.next;
// }
// ListNode res = cur.next;
// return res;
//双指针
ListNode fast=head;
ListNode slow=head;
//头指针前移k步
while(fast != null && k > 0){
fast = fast.next;
k--;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
4.3.旋转链表
通过观察,可以发现相当于将原链表断成两部分,我们只需要找到断开的节点,再修改指向就可以了。要找到这个断开点,就是跟上一题基本一样,区别点在于上一题是要找到这个断开点的后继,这个是要找到断开点。所以在fast那里会有所区别:到最后一个节点判断条件为fast.next != null
,到null为fast != null
。
还有一点k可能大于链表长度,所以需要求链表长度并取余进行判断。同时k等于链表长度时不需要旋转
public ListNode rotateRight(ListNode head, int k) {
if(head == null || k == 0) return head;
ListNode temp = head;
ListNode slow = head;
ListNode fast = head;
int length = 0;
// 求链表长度
while(head != null){
head = head.next;
length++;
}
// k等于链表长度时,不需要旋转
if((k % length == 0)) return temp;
// fast先移动k步
while((k % length) > 0){
fast = fast.next;
k--;
}
// 同时移动
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
// 此时已经找到了要断开的地方,修改指向即可
ListNode res = slow.next;
slow.next = null;
fast.next = temp;
return res;
}
5.删除链表元素专题
5.1.删除链表中的特定节点
对于链表要删除某一结点时,需要知道该节点的前驱pre和后继节点next。然后让pre.next = next就完成了删除。
**注意:**头节点需要做特殊处理,所以通过创建一个指向头节点的虚拟节点来简化操作
public ListNode removeElements(ListNode head, int val) {
ListNode res = new ListNode(0,head);
ListNode cur = res;
while(cur.next != null){
if(cur.next.val == val){
cur.next = cur.next.next;
}
else{
cur = cur.next;
}
}
return res.next;
}
5.2.删除链表的·倒数第K个节点
在双指针专题中已经学会了如何让找到倒数第k个节点:快慢指针,这个题不过是多了一步删除操作。
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode fast = head;
ListNode temp = new ListNode(0);
temp.next = head;
ListNode slow = temp;
for(int i = 0;i < n;i++){
fast = fast.next;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return temp.next;
}
5.3.删除链表M个节点后的第N个节点
力扣要会员。。
public static ListNode delete(ListNode head,int m,int n){
ListNode dummyHead = new ListNode(0,head);
ListNode cur = dummyHead;
int count = 0;
while(true){
// 移动m
count = m;
while (count > 0 && cur != null){
cur = cur.next;
count--;
}
if(cur == null) break;;
// 删除n个节点
// 走到这儿的时候cur是要删除的n个节点的前面那个节点
count = n;
ListNode forward = cur;
while (count > 0 && forward != null){
forward = forward.next;
count--;
}
if (forward == null) {
cur.next = null;
break;
}
cur.next = forward.next;
}
return dummyHead.next;
}
5.4.删除重复元素
5.4.1.重复元素保留一个
从头节点开始遍历,当前节点与后继相等时删除,不等则将当前节点后移。
cur和cur.next比较
public ListNode deleteDuplicates(ListNode head) {
if(head == null) return head;
ListNode cur = head;
while(cur.next != null){
if(cur.val == cur.next.val){
cur.next = cur.next.next;
}
else{
cur = cur.next;
}
}
return head;
}
5.4.2.重复元素一个不要
cur.next和cur.next.next比较,头节点可能会被删除,使用虚拟头节点
public ListNode deleteDuplicates(ListNode head) {
if (head == null) {
return head;
}
ListNode dummy = new ListNode(0, 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;
}
}
ListNode dummy = new ListNode(0, 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;
}