五种方法解决两个链表的公共节点的问题
方法一:利用 Set 集合解决
解决思路
利用Set
集合存储链表head1
,然后再遍历另一个链表head2
并且将其每一个元素都与Set
集合中的节点进行匹配,若匹配成功则说明找到两个链表的公共节点了,返回head2
public static ListNode findFirstNodeBySet(ListNode head1, ListNode head2) {
Set<ListNode> listNodes = new HashSet<>();
while (head1 != null) {
listNodes.add(head1);
head1 = head1.next;
}
while (head2 != null) {
if (listNodes.contains(head2)) {
return head2;
}
head2 = head2.next;
}
return null;
}
方法二:利用 Hash 查找
解题思路
和方法一类似
public static ListNode findFirstNodeByHashMap(ListNode head1, ListNode head2) {
if (head1 == null || head2 == null) {
return null;
}
HashMap<ListNode, Integer> hashMap = new HashMap<>();
while (head1 != null) {
hashMap.put(head1, null);
head1 = head1.next;
}
while (head2 != null) {
if (hashMap.containsKey(head2)) {
return head2;
}
head2 = head2.next;
}
return null;
}
方法三:利用栈寻找公共子节点
解题思路
创建两个栈stackHead1
和stackHead2
,分别将两个链表中的节点加入栈中,再同时遍历两个栈,当两个栈顶元素相同时,则两个栈同时进行出栈操作
public static ListNode findFirstNodeByStack(ListNode head1, ListNode head2) {
if (head1 == null || head2 == null) {
return null;
}
Stack<ListNode> stackHead1 = new Stack<>();
Stack<ListNode> stackHead2 = new Stack<>();
while (head1 != null) {
stackHead1.push(head1);
head1 = head1.next;
}
while (head2 != null) {
stackHead2.push(head2);
head2 = head2.next;
}
ListNode tempNode = null;
while (stackHead1.size() > 0 && stackHead2.size() > 0) {
if (stackHead1.peek() == stackHead2.peek()) {
tempNode = stackHead1.pop();
stackHead2.pop();
} else {
break;
}
}
return tempNode;
}
方法四:拼接两个字符串
解题思路
现有字符串A
和B
A:1-2-3-4-5-6
B:11-22-4-5-6
按照AB
或BA
的方式分别进行拼接可得到
AB:1-2-3-4-5-6-11-22-4-5-6
BA:11-22-4-5-6-1-2-3-4-5-6
定义两个指针,在遍历完一个链表之后,调整指针继续遍历另一个链表
public static ListNode findFirstNodeByCombine(ListNode head1, ListNode head2) {
if (head1 == null || head2 == null) {
return null;
}
ListNode p1 = head1;
ListNode p2 = head2;
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
if (p1 != p2) {
if (p1 == null) {
p1 = head2;
}
if (p2 == null) {
p2 = head1;
}
}
}
return p1;
}
方法五:差和双指针
解题思路
先定义两个指针current1
和current2
利用循环分别求出两个链表的长度size1
和size2
,求差得到sub
,然后调整两个指针让其重新指向链表头,将指向长度较长的链表指针先移动sub
长度,最后再同时移动两个指针寻找两个链表的共同节点
public static ListNode findFirstNodeBySub(ListNode head1, ListNode head2) {
int size1 = 0;
int size2 = 0;
ListNode current1 = head1;
ListNode current2 = head2;
while (current1 != null) {
size1++;
current1 = current1.next;
}
while (current2 != null) {
size2++;
current2 = current2.next;
}
int sub = size1 > size2 ? size1 - size2 : size2 - size1;
current1 = head1;
current2 = head2;
if (size1 > size2) {
while (size1 != size2) {
current1 = current1.next;
size1--;
}
} else {
while (size1 != size2) {
current2 = current2.next;
size2--;
}
}
while (current1 != current2) {
current1 = current1.next;
current2 = current2.next;
}
return current1;
}
判断链表是否回文
方法一:栈解决
解题思路
将链表中的每个元素存储到一个栈中,并记录入栈元素的个数。然后,从栈中依次出栈一半的元素。 在出栈的同时,从链表的头部开始向后遍历,并将栈中的元素与链表中的元素进行比较,这样操作的目的是比较栈中的元素与链表中的对应元素是否相等。依次可得到该链表是否为回文链表
public static boolean isBackStringByStack(ListNode head) {
if (head == null) {
return true;
}
Stack<ListNode> nodeStack = new Stack<>();
ListNode current = head;
int size = 0;
while (current != null) {
nodeStack.push(current);
size++;
current = current.next;
}
size >>= 1;
while (size-- >= 0) {
if (nodeStack.pop().val != head.val) {
return false;
}
head = head.next;
}
return true;
}
方法二:快慢指针
解题思路
先定义两个指针一个快指针fast
一个慢指针slow
,两个指针同时开始遍历链表head
,慢指针一次向后移动一位,快指针一次向后移动两位,在快指针遍历结束后,慢指针正好指向链表head
的中间位置,再利用prepre
指针可让pre
指向反转后的前半部分链表,最后让pre
和slow
(此时slow
指向后半段链表)同时向后遍历,当遇到元素不等时说明不是回文链表
public static boolean isBackStringByTwoPoints(ListNode head) {
if (head == null) {
return true;
}
ListNode slow = head, fast = head;
ListNode pre = head, prepre = null;
// 该 while 循环结束后 pre 将指向反转后的前一半 head 链表
while (fast != null && fast.next != null) {
pre = show;
slow = slow.next;
fast = fast.next.next;
pre.next = prepre;
prepre = pre;
}
// 针对待判断链表个数为奇数时,保证
if (fast != null) {
slow = slow.next;
}
while (pre != null && slow != null) {
if (pre.val != slow.val) {
return false;
}
pre = pre.next;
slow = slow.next;
}
return true;
}
方法三:递归解决
解题思路
利用递归让指向链表的头指针head
先指向链表的最后,指针temp
指向头部,然后依次比较temp.val
和head.val
(在递归中,head.val
表示从后往前遍历的节点值,temp.val
表示从前往后遍历的节点值)
static ListNode temp;
public static boolean isPalindromeByRe(ListNode head) {
if (head == null) {
return true;
}
temp = head;
return check(head);
}
private static boolean check(ListNode head) {
if (head == null) {
return true;
}
boolean res = check(head.next) && (temp.val == head.val);
temp = temp.next;
return true;
}
合并链表
合并两个有序链表
解题思路
创建新链表,同时遍历两个链表将两个链表中较小的元素优先接入新链表,直到某一个链表为空后,再将另一个非空链表中未遍历到的元素接入新链表中
public static ListNode createNewList(ListNode list1, ListNode list2) {
ListNode newListNode = new ListNode(-1);
ListNode pre = newListNode;
while (list1 != null && list2 != null) {
if (list1.val <= list2.val) {
newListNode.next = list1;
list1 = list1.next;
} else {
newListNode.next = list2;
list2 = list2.next;
}
newListNode = newListNode.next;
}
newListNode.next = list1 == null ? list2 : list1;
return pre.next;
}
合并 k 个有序链表
解题思路
通过遍历链表数组反复调用以上合并两个有序链表的方法,依次将多个链表进行合并
public static ListNode mergeLists(ListNode[] listNodes) {
ListNode res = null;
for (ListNode list : listNodes) {
res = createNewList(res, list);
}
return res;
}
合并两个链表
力扣 1669 题
解题思路
定义三个指针,其中利用遍历让两个指针一个指向待删除部分的前一位,一个指向待删除部分的后一位,令第三个指针指向待插入链表的最后一位,即可将待删除部分替换为待插入链表
public static ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
int count = 0;
ListNode preList1 = list1;
while (count < a - 1) {
preList1 = preList1.next;
count++;
}
ListNode tailList1 = preList1;
int sub = b - a + 1;
while (sub > 0) {
tailList1 = tailList1.next;
sub--;
}
preList1.next = tailList1.next; // 删除中间部分链表
tailList1 = tailList1.next;
// 拼接链表2
ListNode tailList2 = list2;
// 找到链表2的尾节点
while (tailList2.next != null) {
tailList2 = tailList2.next;
}
tailList2.next = tailList1;
preList1.next = list2;
return list1;
}
以上代码中的前两个while
循环的目的主要是为了将链表list1
的头指针preList1
指向待删除部分的前一位,将尾指针tailList1
指向待删除部分的后一位,两个指针都需要从头开始遍历,所以可以将两个while
合并为一个while
循环,代码如下:
public static ListNode mergeInBetweenBeGood(ListNode list1, int a, int b, ListNode list2) {
ListNode preList1 = list1, tailList1 = list1;
int i = 0, j = 0;
// 将指针 prelist1 和 tailList1 指向合适的位置
while (preList1 != null && tailList1 != null && j < b) {
if (i != a - 1) {
preList1 = preList1.next;
i++;
}
if (j != b) {
tailList1 = tailList1.next;
j++;
}
}
tailList1 = tailList1.next;
ListNode tailList2 = list2;
while (tailList2.next != null) {
tailList2 = tailList2.next;
}
preList1.next = list2;
tailList2.next = tailList1;
return list1;
}
双指针
寻找倒数第 k 个元素
解题思路
定义两个指针,一个快指针fast
一个慢指针slow
,令快指针先走k
步,然后快慢指针再同时向后遍历直到快指针遍历到链表的末尾时,则慢指针slow
正好指向的是链表list
中倒数第k
个节点
private static ListNode findEndForNum(ListNode list, int k) {
if (list == null) {
return null;
}
ListNode fast = list;
ListNode slow = list;
while (fast != null && k > 0) {
fast = fast.next;
k--;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
旋转链表
力扣 61 题
解题思路
先计算链表的长度,根据k
值找到新链表的头结点和尾节点,再调整节点的连接顺序,即可得到旋转后的链表
如下方法用到了快慢指针,先让快指针向后移动k
次,再让快慢指针同时向后移动,直到快指针移动到链表末尾为止,此时慢指针指向的正好就是需要旋转的位置
private static ListNode rotateList(ListNode list, int k) {
if (list == null) {
return null;
}
ListNode head = list;
ListNode fast = list;
ListNode slow = list;
int size = 0; // 表示链表的长度
while (list != null) {
list = list.next;
size++;
}
// k 的值和链表长度相等,与 k = 0 的情况是一样的
// 都链表内节点不做任何移动
if (k % size == 0) {
return head;
}
while ((k % size) > 0) {
fast = fast.next;
k--;
}
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
ListNode res = slow.next;
slow.next = null;
fast.next = head;
return res;
}
细节:
将以上链表长度size
对k
值取模操作,主要是为了避免k
值大于size
,且题目中k
值在等于size
的整数倍时,待旋转的链表顺序不变,所以取模不仅不影响解题,还可以减少快指针移动的次数
删除节点
删除特定节点
力扣 203 题
解题思路
在删除链表时候,需要注意头结点的删除方式和正常节点的删除方式不一样,需要定义一个头结点
preHead
,让其next
指向该链表preHead.next = head
,循环遍历链表head
找到目标节点进行删除
public ListNode removeElements(ListNode head, int val) {
ListNode preHead = new ListNode(0);
preHead.next = head;
ListNode cur = preHead;
while (cur.next != null) {
if (cur.next.val == val) {
cur.next = cur.next.next;
} else {
cur = cur.next;
}
}
return preHead.next;
}
删除倒数第 n 个节点
力扣 19 题
解题思路
利用快慢指针解决
public static ListNode removeNthFromEnd(ListNode head, int n) {
ListNode preHead = new ListNode(0);
preHead.next = head;
ListNode fast = preHead;
ListNode slow = preHead;
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 preHead.next;
}
删除所有重复元素
力扣 82 题
解题思路
先判断指针cur.next
指向的节点和后一位节点cur.next.next
的值是否相等,如果相等则进入内循环遍历将cur.next
一直向后移动,直到不再出现重复元素为止
public static ListNode deleteDuplicateNode(ListNode head) {
if (head == null) {
return head;
}
ListNode preHead = new ListNode(-1);
preHead.next = head;
ListNode cur = preHead;
while (cur.next != null && cur.next.next != null) {
if (cur.next.val == cur.next.next.val) {
int t = cur.next.val;
while (cur.next != null && cur.next.val == t) {
cur.next = cur.next.next;
}
} else {
cur = cur.next;
}
}
return preHead.next;
}
欢迎大佬批评指正😁😁😁