1.两链表第一个公共子节点
1.1哈希集合
public ListNode findFirstCommonNodeBySet(ListNode headA,ListNode headB){
Set<ListNode> set = new HashSet<>();
while(headA != null){
set.add(headA);
headA = headA.next;
}
while(headB != null){
if(set.contain(headB)){
return headB;
}
headB = headB.next;
}
return null;
}
1.2栈
将两个链表的两个节点分别入两个栈,然后分别出栈,相等就继续出栈,最晚出栈的那一组即两个链表的第一个公共子节点。(花费2n(O)的空间复杂度)
Java的java.util.Stack类是java.util.Vector的子类,用于实现堆栈。下列常见操作
- push(E item): 把项压入栈顶。
- pop(): 移除栈顶项并作为此函数的值返回该项。
- peek(): 查看栈顶项而不移除它。
- empty(): 检查栈是否为空。
- search(Object o): 返回对象在堆栈中的位置,以 1 为基数。
- element(): 检索但不移除栈顶元素。
- elements(): 返回此堆栈中元素的迭代器。
- size(): 返回此集合中元素的数目。
- clear(): 移除此集合中的所有元素。
- equals(Object obj): 比较此对象与指定对象的等价性。
- hashCode(): 返回此对象的哈希码值。
import java.util.Stack;
public ListNode findFirstCommonNodeByStack(ListNode headA,ListNode headB){
Stack<ListNode> stackA = new Stack();
Stack<ListNode> stackB = new Stack();
while(stackA != null){
stackA.push(headA);
headA = headA.next;
}
while(headB != null){
stackB.push(headB);
headB = headB.next;
}
ListNode preNode = null;
while(stackA.size()>0&&stackB.size()>0){
if(stackA.peek() == stackB.peek()){
preNode = stackA.pop();
stackB.pop();
}else
{
break;
}
}
return preNode;
}
2.判断链表是否为回文序列
2.1基础全部压栈法
public boolean isPalindrome(ListNode head){
//拷贝原始链表并将其压栈
ListNode temp = head;
Stack<Integer> stack = new Stack();
while(temp != null){
stack.push(temp.value);
temp = temp.next;
}
//将复制的链表出栈并与原始链表比对,出栈是倒叙,恰好满足回文序列的检测要求
while(head != null){
if(head.value != stack.pop()){
return fasle;
}
//值相等则继续检查下一个节点。
//此处pop()出栈操作,会自动移除栈顶元素,故已经压栈的拷贝链表无需考虑顺延至下一个节点
head = head.next
}
return true;
}
tips:此基础操作有待优化
3.合并有序链表
3.1合并两个有序链表
例子:将两个升序链表合并为一个新的升序链表返回,新链表拼接两个链表所有节点组成。
思路:(1)新建一个链表,分别遍历两个原链表每次选最小节点接到新链表上。
(2)将一个链表节点拆下,合并至另一个链表上。
若采用(1)代码如下
public ListNode mergeTwoLists(ListNode list1,ListNode list2){
//新建链表头节点值为-1,不影响排序即可
ListNode newHead = new ListNode(-1);
//保留此新建链表头节点并命名为res,newHead更多的作为后续接入口使用
ListNode res = newHead;
while(list1 != null&&list2 != null){
//两链表同长
if(list1 != null&&list2 != null){
if(list1.val < list2.val){
newHead.next = list1;//list1原始表头节点接入newHead
list1 = list1.next;//失去原表头的list1顺延获得新表头
}else if(list1.val > list2.val){
newHead.next = list2;
list2 = list2.next;
}else if(list1.val == list2.val){
newHead.next = list2;
list2 = list2.next;
newHead = newHead.next;//此处暂时更新newHead以便具有相等值的list1头节点也接入进来
newHead.next = list1;
list1 = list1.next;
}newHead = newHead.next;//统一更新接入节点,供下次循环使用
}
//到此处本该结束,但考虑到两个要合并的链表有可能长短不一,有一个链表节点会先耗尽,剩下的链表仍要坚持完成接入任务
else if(list1 != null && list2 == null)//余list1
{
newHead.next = list1;
list1 = list1.next;
newHead = newHead.next;
}
else if(list2 != null && list1 == null)//余list2
{
newHead.next = list2;
list2 = list2.next;
newHead = newHead.next;
}
}
return res.next;//原来的那个值为-1的头节点就扔掉辣
}
写完发现有点臃肿,我们将一个while分解成三个比较好看,如下:
public ListNode mergeTwoLists(ListNode list1,ListNode list2){
//新建链表头节点值为-1,不影响排序即可
ListNode newHead = new ListNode(-1);
//保留此新建链表头节点并命名为res,newHead更多的作为后续接入口使用
ListNode res = newHead;
while(list1 != null&&list2 != null){
//两链表同长
if(list1.val < list2.val){
newHead.next = list1;//list1原始表头节点接入newHead
list1 = list1.next;//失去原表头的list1顺延获得新表头
}else if(list1.val > list2.val){
newHead.next = list2;
list2 = list2.next;
}else if(list1.val == list2.val){
newHead.next = list2;
list2 = list2.next;
newHead = newHead.next;//此处暂时更新newHead以便具有相等值的list1头节点也接入进来
newHead.next = list1;
list1 = list1.next;
}newHead = newHead.next;//统一更新接入节点,供下次循环使用
}
//到此处本该结束,但考虑到两个要合并的链表有可能长短不一,有一个链表节点会先耗尽,剩下的链表仍要坚持完成接入任务
while(list1 != null && list2 == null)//余list1
{
newHead.next = list1;
list1 = list1.next;
newHead = newHead.next;
}
while(list2 != null && list1 == null)//余list2
{
newHead.next = list2;
list2 = list2.next;
newHead = newHead.next;
}
return res.next;//原来的那个值为-1的头节点就扔掉辣
}
细看,仍觉不优雅,继续优化直到最简
public ListNode mergeTwoLists(ListNode head1,ListNode head2){
ListNode prehead = new ListNode(-1);
ListNode prev = prehead;
while(head1 != null && head2 != null){
//等于的情况可合并至不等的一方
if(head1.val <= head2.val){
prev.next = list1;
list1 = list1.next;
}else{
prev.next = list2;
list2 = list2.next;
}
prev = prev.next;
}
//还剩一个未被合并完,剩下的接上即可,原链表都是升序的这里不会乱序
prehead.next = list1 == null ? list2 : list1;
return prehead.next;
}
这里为化到最简使用了三目运算符——A ? B:C——若事件A为真则取B,不然取C。
3.2合并K个链表
先合并两个,剩下的再逐步与之合并
在mergeTwoLists的基础上构建函数 mergeKLists , 传参 (ListNodes[] lists)
public ListNode mergeKLists(ListNode[] lists){
ListNode res = null;
for(ListNode list: lists){
mergeTwoLists(res,list);
}
return res;
}
3.3链表の移花接木
链表list1,包含n个元素;链表list2,包含m个元素。请你将list1中下标 a 至 b 的节点删除(包括a,b),并在此接入list2。
class Solution {
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
ListNode preA = list1;
for(int i=0;i < a-1;i++){
preA = preA.next;
}
ListNode preB = preA;
for(int i=0;i < b-a+2;i++){
preB = preB.next;
}
//此处共删去b-a+1个节点,a-1 + b-a+1 + 1=b+1,preB即被切割的list1的后半段链表的头节点.
preA.next = list2;//list2头节点接list1前半部分的尾节点
while(list2.next != null){
list2 = list2.next;
}
list2.next = preB;//list2尾节点接list1后半部分的头节点
return list1;
}
}
4.双指针
4.1找中间节点
给单链表返回其中间节点(逢双则返回第二个中间节点)
我们使用快慢指针,slow一次走一步,fast一次走两步。fast到达末尾时Slow即中间节点。
public ListNode middleNode(ListNode head){
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next;
}
return slow;
}
4.2寻找倒数第k个元素
public ListNode getKthFromEnd(ListNode head,int k){
ListNode fast = head;
ListNode slow = head;
//本题从1开始计数,fast先走k步到第k+1位。然后和slow一起往后遍历,我们要fast走到末尾null节点,这时我们往前数k个数即找到倒数第k个节点,即slow.
while(fast != null && k > 0){
fast = fast.next;
k--;
}
while(fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
}
4.3旋转链表
给出链表的头节点,旋转链表,将每个节点向右移动k个位置。
两种思路:
1.整个链表反转,再将前k和n-k两部分分别反转,反转链表。
2.双指针找到倒数k的位置,再将两链表拼接:
public ListNode rotateRight(ListNode head,int k){
if(head == null || k == 0){
return head;
}
ListNode slow = head;
ListNode fast = head;
ListNode temp = head;//记录原链表
int len = 0;
//先统计链表元素个数
while(head != null){
head = head.next;
len++;
}
//向右移动k个单位,若k是len的整数倍,那么相当于没有旋转.取余而非除以是考虑到k>len
if(k % len == 0){
return temp;
}
while((k%len)>0){
k--;
fast = fast.next;
}
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
//此时fast为尾部节点,slow为要断开的地方,(此处指向右位移k位后的链表尾节点)
ListNode res = slow.next;//确定头
slow.next = null;//尾接null
fast.next = temp;//中间拼接
//以上三步是旋转后链表的拼接
return res;
}
5.链表删除专题
5.1删除特定节点
删除某特定值的节点
public ListNode removeElements(ListNode head,int val){
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode cur = dummyHead;
while(cur.next != null){
if(cur.next.val == val){
cur.next == cur.next.next;//跳过即可删除cur.next,cur还在的
}else{
cur = cur.next;//否则遍历
}
}
return dummyHead.next;
}
5.2删除倒数第N个节点
算链表长:
public ListNode removeNthFromEnd(ListNode head,int n){
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
int length = getLength(head);
ListNode cur = dummyHead;
//找到倒数第n个节点
for(int i;i<length-n+1;++i){
cur = cur.next;
}
//找到就删掉
cur.next = cur.next.next;
return dummyHead.next;
public int getLength(ListNode head){
int length = 0;
while(head != null){
++length;
head = head.next;
}
return length;
}
}
双指针:
public ListNode removeNthFromEnd(ListNode head,int n){
ListNode dummyHead = new ListNode(0);
dummyHead.next = head;
ListNode first = head;
ListNode second = dummyHead;
for(int i=0; i<n;++i){
first = first.next;
}
while(first != null){
first = first.next;
second = second.next;
}
second.next = second.next.next;
return dummyHead.next;
}
5.3删除重复元素
保留一个重复元素/删除所有重复元素(假设给定链表排好序的)
若保留:
public ListNode deleteDuplicates(ListNode head){
if(head == null){
return head;
}
ListNode cur = head;
while(cur.next != null){
if(cur.val == cur.next.var){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return head;
}
若不保留:
public ListNode deleteDuplicates(ListNode head){
if(head == null){
return head;
}
ListNode dummy = new ListNode(0,head);//参数里的head说明dummy节点将被连接到head,无需 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;
}
6.回顾第一个公共子节点问题
使用栈或者集合都需要开辟 O(n)空间,我们尝试只用一两个变量解决.
6.1拼接两个字符串
public ListNode findFirstCommonNode(ListNode pHead1,ListNode pHead2){
if(pHead1==null || pHead2==null){
return null
}
ListNode p1=pHead1;
ListNode p2=pHead2;
while(p1!=p2){
p1=p1.next;
p2=p2.next;
if(p1!=p2){//避免序列不存在交集时陷入死循环
if(p1==null){
p1=pHead2;
}
if(p2==null){
p2=pHead1;
}
}
}
return p1;
}
6.2差和双指针
长的链表先走 一个链表长度差值,然后两个链表同时往前走,节点一样时即找到公共节点。
public ListNode findFirstCommonNode(ListNode pHead1,ListNode pHead2){
if(pHead1==null || pHead2==null){
return null
}
ListNode current1=pHead1;
ListNode current2=pHead2;
int l1=0,l2=0;
while(current1!=null){
current1=current1.next;
l1++;
}
while(current2!=null){
current2=current2.next;
l2++;
}
current1=pHead1;
current2=pHead2;
int sub=l1>l2?l1-l2:l2-l1 ;
if(l1>l2){
int a=0;
while(a<sub){
current1=current1.next;
a++;
}
}
if(l1<l2){
int a=0;
while(a<sub){
current2=current2.next;
a++;
}
}
while(current1!=current2){
current1=current1.next;
current2=current2.next;
}
return current1;
}