(1)力扣21 合并两个有序链表
/**
* 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 mergeTwoLists(ListNode head1, ListNode head2)
{
//创建一个新链表,将两个要合并的链表都插入到这个新的链表里面
ListNode dummy=new ListNode(0);
ListNode cur=dummy;
while(head1!=null&&head2!=null)
{
//谁小谁插入
if(head1.val<=head2.val)
{
cur.next=head1;
head1=head1.next;
cur=cur.next;
cur.next=null;
}
else
{
cur.next=head2;
head2=head2.next;
cur=cur.next;
cur.next=null;
}
}
//跳出这个循环之后,有可能某一个链表还不为空
if(head1!=null) cur.next=head1;
if(head2!=null) cur.next=head2;
return dummy.next;
}
}
(2)力扣86 分隔链表
把原链表分成两个小链表,一个链表中的元素大小都小于 x,另一个链表中的元素都大于等于 x,然后再将这两个链表连接起来
class Solution
{
public ListNode partition(ListNode head, int x)
{
//链表1,用来装比x小的元素
ListNode dummy1=new ListNode(-1);
ListNode p1=dummy1;
//链表2,用来装大于等于x的元素
ListNode dummy2=new ListNode(-1);
ListNode p2=dummy2;
ListNode p=head;
while(p!=null)
{
//大的加到链表2里面去
if(p.val>=x)
{
p2.next=p;
p2=p2.next;
p=p.next;
p2.next=null;
}
else
{
p1.next=p;
p1=p1.next;
p=p.next;
p1.next=null;
}
}
p1.next=dummy2.next;
dummy2.next=null;
return dummy1.next;
}
}
(3)力扣23 合并k个升序链表
利用最小堆找出k个链表第一个节点中,值最小的节点
每个链表把各自的第一个节点送进最小堆里面,最小的节点才能添加进新链表里面
//传入的是k个链表的头节点组成的数组:[head1,head2,head3.head4,head5......]
class Solution
{
public ListNode mergeKLists(ListNode[] lists)
{
if(lists.length==0) return null;
PriorityQueue<ListNode> pq=new PriorityQueue<ListNode>((a,b)->(a.val-b.val) );
//将装满头节点的lists数组里面的头节点全部入最小堆
for(ListNode head:lists)
{
if(head!=null)//加这个条件就是为了处理边界条件[[],[],[]],头结点全部都是空结点的情况
{
pq.add(head);
}
}
ListNode dummy=new ListNode(-1);
ListNode current=dummy;
while(pq.size()!=0)
{
//将堆顶的结点出堆
ListNode temp=pq.poll();
//如果这个结点后面还有结点,那么出堆的同时还要把它的next结点加入到堆里面
if(temp.next!=null)
{
pq.add(temp.next);
}
尾插法形成新链表
current.next=temp;
current=current.next;
current.next=null;
}
return dummy.next;
}
}
(4)力扣19 删除链表的倒数第k个结点
遍历两次肯定是可以得到结果的,如果想只遍历一次就得到结果:双指针
总结:两个指针保持距离为k-1,一前一后,这样前面的指针跑到最后一个位置,后面的指针指向的就是倒数第k个结点
两个指针起始时,同时指向dummy结点,然后其中一个指针向后走k步,另一个指针向前走1步,这样两个指针之间就相差k-1步(也就是慢指针前进k-1步就能到达快指针的位置),两个指针一前一后,然后两个指针一起向后走,当后面的结点跑到最后一个位置的时候,前面的指针指向的就是倒数第k个节点
class Solution
{
public ListNode removeNthFromEnd(ListNode head, int k)
{
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode slow = dummy;
ListNode fast = dummy;
//fast指针先前进k步
for(int i=0;i<k;i++)
{
fast=fast.next;
}
//slow指针前进1步
slow=slow.next;
//prev指针指向slow指针指向节点的上一个结点,便于删除
ListNode prev = dummy;
while (fast.next != null)
{
fast = fast.next;
prev = slow;
slow = slow.next;
}
//删除slow指针指向的结点
prev.next = slow.next;
// 释放 待删除节点slow 的next指针, 这句删掉也能AC
slow.next = null;
return dummy.next;
}
}
(5)876. 链表的中间结点 - 力扣(LeetCode)
起始时两个指针 slow
和 fast
都指向链表头结点 head,然后slow指针一次走一步,fast指针一次走两步,
这样,当 fast
走到链表末尾时,slow
就指向了链表中点
值得注意的是要按链表结点个数为奇数或者偶数进行排序
class Solution
{
public ListNode middleNode(ListNode head)
{
ListNode fast=head,slow=head;
while(fast.next!=null&&fast.next.next!=null)
{
fast=fast.next.next;
slow=slow.next;
}
//结点个数为偶数个的时候
if(fast.next!=null&&fast.next.next==null)
{
slow=slow.next;
fast=fast.next;
}
//结点个数为奇数个的时候 什么都不做
return slow;
}
}
另一种写法:
并不是初始时,fast和slow都是位于head
而是fast=head.next,slow=head
class Solution
{
public ListNode middleNode(ListNode head)
{
ListNode fast = head.next,slow = head;
while (fast != null && fast.next != null)
{
slow = slow.next;
fast = fast.next.next;
}
if(fast!=null)
{
slow=slow.next;//偶数个
}
return slow;
}
}
中间结点有关的题目:
力扣2095 删除链表的中间结点
class Solution
{
public ListNode deleteMiddle(ListNode head)
{
ListNode middle=findMedian(head);//findMedian函数的功能是找出;链表的中间结点,返回这个中间结点
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode p=dummy;
while(p.next!=middle)
{
p=p.next;
}
p.next=p.next.next;
return dummy.next;
}
public ListNode findMedian(ListNode head)
{
ListNode slow=head;
ListNode fast=head;
while(fast.next!=null)
{
fast=fast.next;
//通过[1,2,3,4,5,6]这个测试用例可以发现存在下面这种情况:
//有可能只前进一步fast就到达了最后一个结点了,此时不能再前进了,再前进就会报越界异常了
if(fast.next==null)
{
slow=slow.next;
break;
}
else
{
fast=fast.next;
slow=slow.next;
}
}
return slow;
}
}
(6)力扣237 删除链表中的结点
注意这道题目中没有告诉你这个链表的头结点,传入的node就是我们要删除的结点
我们创建两个指针,一个指针p指向传入的结点,另一个指针q指向传入结点的下一个结点,
然后不断执行:
p.val=q.val
p=q;
q=q.next;
循环终止的条件是:q.next=null,因为这样q就指向了最后一个节点,此时p.val=q.val,然后p.next=null即可
最后跳出循环的时候:
class Solution
{
public void deleteNode(ListNode node)
{
ListNode p=node;
ListNode q=node.next;
while(q.next!=null)
{
p.val=q.val;
p=q;
q=q.next;
}
p.val=q.val;
p.next=null;
}
}
(7)力扣141 环形链表,判断链表是否有环
方法一:
public class Solution
{
public boolean hasCycle(ListNode head)
{
if(head==null) return false;
HashSet set=new HashSet();
while(head!=null)
{
if(set.contains(head)) return true;
else{
set.add(head);
head=head.next;
}
}
return false;
}
}
方法二:
每当慢指针 slow
前进一步,快指针 fast
就前进两步。
如果 fast
最终遇到空指针,说明链表中没有环;如果 fast
最终和 slow
相遇,那肯定是 fast
超过了 slow
一圈,说明链表中含有环
public class Solution
{
public boolean hasCycle(ListNode head)
{
ListNode slow=head,fast=head;
//一直往前移,直到fast=null
while(fast!=null)
{
//slow移动一步,fast移动两步
slow=slow.next;
fast=fast.next;
//进入这个循环,fast是不为null的,但是fast=fast.next后不能保证fast不为null
//如果只走一步就为null了,直接返回false
//第一步不为null才能走第二步
if(fast==null)
{
return false;
}
else
{
fast=fast.next;
}
if(slow==fast) return true;
}
return false;
}
}
(8)力扣链表中环的入口
上一个问题的进阶版:如果链表有环,返回这个环的起点
方法一:
public class Solution
{
public ListNode detectCycle(ListNode head)
{
if(head==null) return null;
HashSet set=new HashSet();
while(head!=null)
{
if(set.contains(head)) return head;
else
{
set.add(head);
head=head.next;
}
}
return null;
}
}
方法二:
当快慢指针相遇时,让其中任一个指针指向头节点(不是dummy结点,而是第一个有效的结点),然后让它俩以相同速度前进,再次相遇时所在的节点位置就是环开始的位置
public class Solution
{
public ListNode detectCycle(ListNode head)
{
ListNode slow=head,fast=head;
while(fast!=null)
{
slow=slow.next;
fast=fast.next;
if(fast!=null)
{
fast=fast.next;
}
else
{
return null;
}
//有环了,slow等于head,然后slow,fast速度一致往前走,再次相遇的位置就是环的入口
if(slow==fast)
{
slow=head;
while(slow!=fast)
{
slow=slow.next;
fast=fast.next;
}
return slow;
}
}
return null;
}
}
(9)160. 相交链表 - 力扣(LeetCode)两个链表是否相交
需要返回两个链表相交的起始结点
方法一:
public class Solution
{
public ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
HashSet set=new HashSet();
while(headA!=null)
{
set.add(headA);
headA=headA.next;
}
while(headB!=null)
{
if(set.contains(headB)) return headB;
headB=headB.next;
}
return null;
}
}
方法二:
用两个指针 p1
和 p2
分别在两条链表上前进
p1
遍历完链表 A
之后开始遍历链表 B
,让 p2
遍历完链表 B
之后开始遍历链表 A
最终p1走过的距离是m+k+n,而p2走过的距离是n+k+m
这道题存在的一个坑在于:
这种情况,两个链表没有相交的部分时,容易陷入死循环,超时
解决方法:定义一个计数器count,p1指针遍历完A链表再跳到B链表,count++
p2指针遍历完B链表再跳到A链表,count++
这样count=2了,如果没有相交部分,那count还要++,所以如果count>2了,那就说明这两个链表没有相交的部分,返回null
public class Solution
{
public ListNode getIntersectionNode(ListNode headA, ListNode headB)
{
ListNode p1=headA,p2=headB;
int count=0;
if(p1==null||p2==null) return null;
//确保两个链表都不是空链表
while(p1!=p2)
{
if(count>2) return null;//count超过2,那这两个链表就没有相交部分
//p1指针下一个到底指向哪一个结点,是直接p1.next,还是headB
if(p1.next==null)
{
p1=headB;
count++;
}
else
{
p1=p1.next;
}
if(p2.next==null)
{
p2=headA;
count++;
}
else
{
p2=p2.next;
}
}
return p1;
}
}
(10)力扣707 设计链表
①解释题目:就是实现五个功能:获取链表中第index个结点的值,在表头添加元素,在表尾添加元素,在第index个结点之前添加结点,删除第index个结点
②这里定义了一个size,初始等于0,用来记录链表的长度,每次向链表中添加一个结点就size++,删除一个结点就size--
③由于index从0开始计数,所以size=最大的index+1
④跳出循环的时候current指向哪个结点
初始的时候current=dummy
while(int i=0;i<index;i++)
{
current=current.next;
}
有了这张表就很清晰(从第0轮开始)每i轮最终current会到达第i个结点
所以可以判断:跳出这个循环,current=index-1,因为i=index的时候进不去循环
class MyLinkedList
{
//dummy结点
ListNode dummy;
//用size来记录链表的长度,执行添加结点操作时size++
int size;
// 构造方法.初始化size和summy两个参数
public MyLinkedList()
{
size=0;
dummy=new ListNode(0);
}
//获取第index个结点的数值
public int get(int index)
{
if(index<0||index>=size) return -1;
ListNode current=dummy;
for(int i=0;i<=index;i++)
{
current=current.next;
}
return current.val;
}
//链头添加一个结点
public void addAtHead(int val)
{
ListNode new_node=new ListNode(val);
new_node.next=dummy.next;
dummy.next=new_node;
size++;
}
//链尾添加一个结点
public void addAtTail(int val)
{
ListNode new_node=new ListNode(val);
ListNode current=dummy;
for(int i=0;i<size;i++)
{
current=current.next;
}
current.next=new_node;
size++;
}
//在第 index 个节点之前添加节点
public void addAtIndex(int index, int val)
{
//如果index小于0,在头部插入节点(直接调用前面写好的addAtHead函数)
if(index<0) addAtHead(val);
//如果 index 等于链表的长度,在链表末尾添加元素,可以直接调用前面写好的addAtTail函数
if(index==size) addAtTail(val);
//如果 index 大于链表长度,则不会插入节点
if(index>size) return;
ListNode new_node=new ListNode(val);
ListNode current=dummy;
for(int i=0;i<index;i++)
{
current=current.next;
}
//跳出循环,此时current指向的是第index-1个结点
new_node.next=current.next;
current.next=new_node;
size++;
}
public void deleteAtIndex(int index)
{
if(index<0||index>=size) return;
ListNode current=dummy;
for(int i=0;i<index;i++)
{
current=current.next;
}
//跳出循环,此时current指向的是第index-1个结点
current.next=current.next.next;
size--;
}
}
(11)剑指 Offer 24. 反转链表 - 力扣(LeetCode)反转链表
力扣206也是一样的题目
其实就是头插法创建一个新链表
总共三个指针,dummy和head这两个指针是肯定是不用说的,无非是多了一个指向head指针下一个结点的temp指针
核心就是下面这两行代码:
head.next=dummy.next;
dummy.next=head;
class Solution
{
public ListNode reverseList(ListNode head)
{
if(head==null) return null;
ListNode dummy=new ListNode(-1);
while(head!=null)
{
//temp用来标记这一轮要插入结点的下一个结点(也就是下一轮要插入的结点,要不然下一轮找不到位置)
ListNode temp=head.next;
//这两行就是头插法插入结点到dummy结点的后面
head.next=dummy.next;
dummy.next=head;
head=temp;
}
return dummy.next;
}
}
(12) 92. 反转链表 II - 力扣(LeetCode)
反转链表的某个区间
核心代码:
temp.next=q.next;
q.next=temp
class Solution
{
public ListNode reverseBetween(ListNode head, int m, int n)
{
ListNode dummy = new ListNode(0);
dummy.next = head;
// 初始化指针,一前一后,p在前,q在后,先p后q
ListNode q = dummy;
ListNode p = dummy.next;
// p移到第一个要反转的结点位置,q移动到第一个要反转的结点的前一个结点
for(int i= 0; i < m - 1; i++)
{
p = p.next;
q = q.next;
}
// 头插法插入节点
for (int i = 0; i < n - m; i++)
{
//存下来p指针后面的结点,将它用头插法插入到q指针后面去
ListNode temp = p.next;
//从链表中删除刚刚的temp结点
p.next = p.next.next;
//下面两行代码就是头插法,插入temp结点到g指针指向结点的
temp.next = q.next;
q.next = temp;
}
return dummy.next;
}
}
(13)24. 两两交换链表中的节点 - 力扣(LeetCode)
class Solution
{
public ListNode swapPairs(ListNode head)
{
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode current=dummy;
//交换的是current的后面两个节点
while(current.next!=null&¤t.next.next!=null)
{
ListNode p=current.next;
ListNode q=current.next.next;
ListNode temp=current.next.next.next;
current.next=q;
q.next=p;
p.next=temp;
current=p;
}
return dummy.next;
}
}
(14)力扣 1669 合并两个链表
class Solution
{
public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2)
{
ListNode dummy=new ListNode(0);
dummy.next=list1;
ListNode temp1=dummy;
ListNode temp2=dummy;
//temp1指针指到第a-1个结点
for(int i=0;i<a;i++)
{
temp1=temp1.next;
}
//temp2指针指到第b+1个结点
for(int j=0;j<b+2;j++)
{
temp2=temp2.next;
}
temp1.next=list2;
ListNode temp3=list2;
while(temp3.next!=null)
{
temp3=temp3.next;
}
temp3.next=temp2;
return dummy.next;
}
}
(15)力扣234回文链表
将链表的值加入到数组里面,转化为判断是否为回文数组的问题
class Solution
{
public boolean isPalindrome(ListNode head)
{
ArrayList a=new ArrayList();
if(head==null) return true;
while(head!=null)
{
a.add(head.val);
head=head.next;
}
int i=0,j=a.size()-1;
while(i<j)
{
if(a.get(i)!=a.get(j))
{
return false;
}
i++;
j--;
}
return true;
}
}
如果要求用O(1)的空间复杂度,思路:先找到链表中点,然后将链表的前半段进行反转,最后比较反转后的前半段和后半段是否所有元素相等
(16)力扣328 奇偶链表
第一个结点的索引是1,第二个结点的索引是2......
将索引为奇数的结点全部放到前面来,索引为偶数的结点都放到后面去
思路:整两个链表,一个链表是用来存储索引为奇数的结点,另一个链表用来存储索引为偶数的结点
class Solution
{
public ListNode oddEvenList(ListNode head)
{
ListNode jishu_dummy=new ListNode(-1);//奇数链表的虚拟头节点
ListNode jishu_tail=jishu_dummy;//奇数链表的尾结点
ListNode oushu_dummy=new ListNode(-1);//偶数链表的虚拟头节点
ListNode oushu_tail=oushu_dummy;//偶数链表的尾结点
int num=1;
//遍历原链表
while(head!=null)
{
//偶数的话,就将这个结点添加到偶数链表里面去
if(num%2==0)
{
oushu_tail.next=head;
oushu_tail=oushu_tail.next;
head=head.next;
oushu_tail.next=null;
num++;
}
else
{
jishu_tail.next=head;
jishu_tail=jishu_tail.next;
head=head.next;
jishu_tail.next=null;
num++;
}
}
jishu_tail.next=oushu_dummy.next;
return jishu_dummy.next;
}
}
(17)力扣61 旋转链表
尾结点指向头节点,构造首尾相连的链表
在原链表中找到新链表的头节点p,并且找出这个头节点的前一个结点q
q.next=null
return p;即可
class Solution
{
public ListNode rotateRight(ListNode head, int k)
{
if(head==null) return null;
ListNode dummy=new ListNode(-1);
dummy.next=head;
//求出链表的长度
int length=1;
while(head.next!=null)
{
head=head.next;
length++;
}
//构建环形链表
head.next=dummy.next;
k=k%length;
int result=length-k;
ListNode temp=dummy;
//temp指针跑到新链表的尾结点的位置,也就是新链表的头节点在旧链表中的前一个位置
for(int i=0;i<result;i++)
{
temp=temp.next;
}
//新链表的头节点
ListNode new_head=temp.next;
temp.next=null;
dummy.next=null;
return new_head;
}
}
18. 力扣382 链表随机结点
Random test=new Random();
int num=random.nextInt(n);
//获得一个[0.n)的一个随机数
class Solution
{
ArrayList<Integer> arraylist=new ArrayList();
Random random=new Random();
//构造函数,负责将链表中的所有值添加进arraylist里面
public Solution(ListNode head)
{
while(head!=null)
{
arraylist.add(head.val);
head=head.next;
}
}
public int getRandom()
{
int num=random.nextInt(arraylist.size());
return arraylist.get(num);
}
}
接下来是两道链表相加的题目
每一位相加,比如a,b相加,sum=a+b+flag
(1)进位flag=sum%10
(2)这一位变成:sum=sum/10
然后进位flag送到下一轮去
19 力扣2 两数之和
题目意思如下:
要注意下面这种情况:最高位还要向前进1
核心代码:
sum=num1+num2+flag
currentNode.val=sum%10
flag=sum/10
class Solution
{
public ListNode addTwoNumbers(ListNode l1, ListNode l2)
{
//构造一个头结点
ListNode dummy=new ListNode(-1);
//再整一个当前节点,初始时,当前节点等于dummy节点
ListNode current=dummy;
//进位标志
int flag=0;
//只要还有一个链表有数字,就要继续相加
while(l1!=null||l2!=null)
{
int num1=0,num2=0,sum=0;
if(l1!=null)
{
num1=l1.val;
}
if(l2!=null)
{
num2=l2.val;
}
sum=num1+num2+flag;
//进位为flag/10
flag=sum/10;
//新节点的值为sum%10
ListNode new_node=new ListNode(sum%10);
current.next=new_node;
//接下来就是将三个指针往后移l1.next,l2.next,current.next
if(l1!=null)
{
l1=l1.next;
}
if(l2!=null)
{
l2=l2.next;
}
current=current.next;
}
//跳出循环,此时l1,l2都为空了,此时判断一下是否还有进位
//如果有进位,就还要再new一个新节点
if(flag==1)
{
ListNode new_node=new ListNode(1);
current.next=new_node;
}
return dummy.next;
}
}
19.力扣445 两数相加||
两数相加|和两数相加||之间的区别:
即两数相加|第一个链表节点是低位
而两数相加||第一个链表节点是高位
方法一:写一个反转链表的函数,将两个链表分别反转,然后就是两个链表相加,最后的结果再进行一次反转
方法二: 将两个链表的元素进栈,然后栈顶元素依次相加,采用头插法创建新链表
class Solution
{
public ListNode addTwoNumbers(ListNode l1, ListNode l2)
{
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
while (l1 != null)
{
stack1.push(l1.val);
l1 = l1.next;
}
while (l2 != null)
{
stack2.push(l2.val);
l2 = l2.next;
}
int flag = 0;
ListNode head = null;
while (!stack1.isEmpty() || !stack2.isEmpty() || carry > 0)
{
int sum = carry;
sum += stack1.isEmpty()? 0: stack1.pop();
sum += stack2.isEmpty()? 0: stack2.pop();
ListNode node = new ListNode(sum % 10);
node.next = head;
head = node;
carry = sum / 10;
}
return head;
}
}
20.链表的归并排序:力扣148 排序链表
和数组的归并排序也是一样的,
(1) 找中点
(2)左右子链表(左右子数组)分别排好序
(3)将两个有序链表进行合并(两个链表合并)
class Solution
{
public ListNode sortList(ListNode head)
{
if(head==null||head.next==null) return head;
//step1:找中间结点
ListNode fast = head.next, slow = head;
while (fast != null && fast.next != null)
{
slow = slow.next;
fast = fast.next.next;
}
ListNode temp=slow.next;//中点的下一个结点
slow.next=null;//将链表一分为二
ListNode left= sortList(head);//将前半段链表进行排序
ListNode right=sortList(temp);//将后半段链表进行排序
//接下来合并两个有序链表
ListNode result=merge(left,right);
return result;
}
public ListNode merge(ListNode left,ListNode right)
{
ListNode dummy=new ListNode(0);
ListNode current=dummy;
while(left!=null&&right!=null)
{
if(left.val<=right.val)
{
current.next=left;
left=left.next;
current=current.next;
current.next=null;
}
else
{
current.next=right;
right=right.next;
current=current.next;
current.next=null;
}
}
//跳出循环,一定有一个链表还没有比较完
if(left!=null)//这个if-else结构可以用三元表达式来代替:current.next=left!=null?left:right;
{
current.next=left;
}
else
{
current.next=right;
}
return dummy.next;
}
}