牛客网刷题(链表)
1.从头到尾打印链表
输入一个链表的头节点,按链表从尾到头的顺序返回每个节点的值,返回值保存在数组中。
举一反三:反转链表
方法一:递归。
递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化成与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能将原本的问题分解为更小的问题,只是使用递归的关键。
思路:链表无法逆序访问,无法直接遍历链表得到从尾到头的逆序结果。但是我们都知道递归是到达低层之后才会往上回溯,因此考虑递归遍历链表,三段式如下:
终止条件:递归进入链尾,及节点为空时结束递归。
返回值:每次返回子问题时的全部输出。
本级任务:每级子任务递归地进入下一级,等下一级的子问题输出数组返回时,将自己的节点值添加至数组末尾。
具体做法:
step1:从表头开始递归的进入每一个节点。
step 2:遇到尾结点开始返回。
step3:直到递归返回表头。
import java.util.ArrayList;
public class Solution {
public void recursion(ListNode head,ArrayList<Integer> res){
if(head!=null){
recursion(head.next,res);
res.add(head.val);
}
}
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> res = new ArrayList<Integer>();
recursion(listNode,res);
return res;
}
}
方法二:栈
栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素上面,使之成为新的栈顶元素;元素出栈是指从一个栈删除元素又称出栈或退栈,它是把栈顶元素删除掉,使之相邻的元素成为新的栈顶元素。
思路:递归也可以用栈实现,因为本身栈就是先进后出,符合逆序的特点,递归本质上是用栈实现的。
具体做法:
step1:顺序遍历链表,将链表的值push到栈中。
step2:然后依次弹出栈中元素,加入到数组中,即可实现链表逆序。
import java.util.*;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer> res = new ArrayList<Integer>();
Stack<Integer> s = new Stack<Integer>();
while(listNode!=null){
s.push(listNode.val);
listNode=listNode.next;
}
while(!s.isEmpty()){
res.add(s.pop());
}
return res;
}
}
2.反转链表
给定一个单链表的头节点,pHead(该节点是有值的,它的val为1),长度为n,反转该链表后返回其新表头。
解题思路:利用栈先入后出的特性来解决,与上一个从头到尾打印链表的问题,区别在于。本题需要返回的是节点,而非对应的值。
代码:
import java.util.*;
public class Solution {
public ListNode ReverseList(ListNode head) {
//ArrayList<Integer> res = new ArrayList<Integer>();
Stack<ListNode> s = new Stack<ListNode>();
while(head!=null){
s.push(head);
head=head.next;
}
if(s.isEmpty()){
return null;
}
ListNode node =s.pop();
ListNode dummy=node;
while(!s.isEmpty()){
ListNode tempNode=s.pop();
node.next=tempNode;
node=node.next;
}
node.next=null;
return dummy;
}
}
3.合并排序链表
输入两个递增链表,单个链表的长度为n,合并这两个链表并使新链表的节点仍然是增序的。数据范围:0<=n<=1000,-1000<=节点值<=1000。
方法一:递归
思路:特殊情况如果有一个数组为空则返回另一个数组。如果pHead1的值比pHead2小,那么下个值应该是pHead1,那么应该返回pHead1。return前指定pHead1的下一个节点应该是pHead1.next和pHead2俩链表合并后的头结点。
如果phead1比pHead2大那么返回的应该是pHead2,return之前指定pHead2的下一个节点应该是pHead1和pHead2.next俩链表合并后的头结点。
代码:
import java.util.*;
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null||list2==null){
return list1 !=null?list1:list2;
}
if(list1.val<=list2.val){
list1.next=Merge(list1.next,list2);
return list1;
}else{
list2.next=Merge(list2.next,list1);
return list2;
}
}
}
方法二:利用额外数组
思路:(1)创建新数组nums;
(2)依次循环遍历pHead1和pHead2,将链表中的元素存储到nums中,再对nums进行排序;
(3)依次对排序后的数组nums取数并构建合并后的链表。
代码:
import java.util.*;
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null) return list2;
if(list2==null) return list1;
if(list1==null&&list2==null) return null;
ArrayList<Integer> list=new ArrayList<>();
while(list1!=null){
list.add(list1.val);
list1=list1.next;
}
while(list2!=null){
list.add(list2.val);
list2=list2.next;
}
Collections.sort(list);
ListNode newHead = new ListNode(list.get(0));
ListNode cur=newHead;
for(int i=1;i<list.size();i++){
cur.next=new ListNode(list.get(i));
cur=cur.next;
}
return newHead;
}
}
4.链表内指定区间反转
讲一个节点数为size的链表m位置到n位置之间的区间反转,要求时间复杂度为O(n),空间复杂度为O(1)。
例:链表为1-2-3-4-5-null,m=2,n=4,
返回1-4-3-2-5-null。
数据范围:链表长度0<size<=1000,0<m<=n<=size,链表中每个节点的值满足|val|<=1000 。
方法一:双指针(遍历两遍)
思路:(1)反转局部链表,将局部链表提取出来当做完整的链表做反转
(2)再将反转后的链表与其他节点建立连接,重构链表
(3)建议使用虚拟头节点避免对头节点进行复杂的分类考虑,简化操作
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
public ListNode reverseBetween (ListNode head, int m, int n) {
// write code here
if(head==null) return null;
//设置虚拟头节点
ListNode dummyNode = new ListNode(-1);
dummyNode.next=head;
ListNode pre=dummyNode;
//1.走left-1步到left的前一个节点
for(int i=0;i<m-1;i++){
pre=pre.next;
}
ListNode rightNode=pre;
//走right-left+1步到right节点
for(int i=0;i<n-m+1;i++){
rightNode=rightNode.next;
}
//截取出子链表
ListNode leftNode=pre.next;
ListNode cur=rightNode.next;
//切断链接
pre.next=null;
rightNode.next=null;
//反转局部链表
reverseLinkedList(leftNode);
//连回之前的链表
pre.next=rightNode;
leftNode.next=cur;
return dummyNode.next;
}
private void reverseLinkedList(ListNode head){
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode Cur_next=cur.next;
cur.next=pre;
pre=cur;
cur=Cur_next;
}
}
}
方法二:一次遍历(方法一优化版)
思路:方法一不足之处:当子区间[m,n]范围过大时(刚好为头尾节点),遍历成本变大。
因此本方法的思路在于固定子区间外的节点。
在需要反转的区间里,每遍历到一个节点就让这个节点来到反转区间的起始位置。
curr:指向待反转区域的第一个节点left;
Cur_next: 永远指向curr的下一个节点,循环过程中随之curr的变化而变;
pre:永远指向带反转区域的第一个节点left前的一个节点,循环过程中不变。
代码:
public class Solution {
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
public ListNode reverseBetween (ListNode head, int m, int n) {
ListNode dummyNode=new ListNode(-1);
dummyNode.next=head;
ListNode pre=dummyNode;
for(int i=0;i<m-1;i++){
pre=pre.next;
}
ListNode cur=pre.next;
ListNode Cur_next;
for(int i=0;i<n-m;i++){
Cur_next=cur.next;
cur.next=Cur_next.next;
Cur_next.next=pre.next;
pre.next=Cur_next;
}
return dummyNode.next;
}
}
5.链表中的节点每k个一组翻转
将给出的链表中的节点每k个一组翻转,返回翻转后的链表,如果链表中的节点数不是k的倍数,将剩下的节点保持原样 。不能改变节点中的值,只能改变节点本身。
数据范围:0<=n<=2000,1<=k<=2000,链表中的每个元素满足0<=k<=1000
例:给定1-2-3-4-5
k=2,返回2-1-4-3-5
k=3,返回3-2-1-4-5
官方题解:
方法:递归
思路:如果要对链表做分组翻转,首先拿到应该先考虑分段。分成一组一组之后进行组内翻转,
代码:
public class Solution {
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode reverseKGroup (ListNode head, int k) {
// write code here
// if(head==null) return null;
// ListNode dummyNode=new ListNode(-1);
// dummyNode.next=head;
//找到每次反转的尾部
ListNode tail=head;
//遍历k次到尾部
for(int i=0;i<k;i++){
//如果不足k到了链表尾,直接返回,不翻转
if(tail==null)
return head;
tail=tail.next;
}
//翻转时需要的前序和当前节点
ListNode pre=null;
ListNode cur=head;
//在到达当前段尾节点前
while(cur!=tail){
//翻转
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
//当前尾指向下一段要翻转的链表
head.next=reverseKGroup(tail,k);
return pre;
}
}
6.判断给定的链表是否存在环
判断给定的链表是否有环,有环返回TRUE,无环返回FALSE。
数据范围:0<=n<=10000,链表中任意值满足|val|<=10000。
官方题解:
方法一:双指针,双指针是指在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至多个),两个指针或是同方向访问两个链表,或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到目的。
思路:
链表不像二叉树,每个节点只有一个val值和next指针,一个节点只能有一个指针指向下一个节点,不能有两个指针,那这时就可以说一个性质:环形链表的环一定是在末尾,末尾没有NULL。如果是普通的线性链表末尾一定有null,那么可以根据链表是否有null判断是否有环。环形链表遍历过程不断循环,线性链表到null结束时,环形一直循环无法遍历结束。所以利用双指针,速度一快一慢,只要有环就一直循环,因为速度一快一慢,二者一定会相遇。
具体做法:
1:设置快慢两个指针,初始都指向链表头。
2:遍历链表,快指针每次走两步,慢指针每次走一步。
3:如果快指针到了链表尾,说明没有环,因为它每次走两步,所以要验证连续两步是否为null。
4:如果链表有环,那么快慢指针会在环内循环,因为快指针每次走两步,因为快指针会在环内追到慢指针,二者相遇代表有环。
代码:
public class Solution {
public boolean hasCycle(ListNode head) {
if(head==null) return false;
ListNode fast=head;
ListNode slow=head;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow)
return true;
}
return false;
}
}
7.链表中倒数最后k个结点
输入一个长度为n的链表,设链表中的元素的值为ai,返回该链表中倒数第k个结点。如果该链表长度小于k,请返回一个长度为0的链表。
数据范围:
0<=n<=10^5
0<=ai<=10^9
0<=k<=10^9.
官方题解:
方法一:快慢双指针
思路:因为无法逆序遍历链表,很难按照题目得到倒数第k个元素,可以试着反过来考虑,如果当期处于倒数第k的位置上,即距离链表尾的距离是k,那我们假设双指针指向这两个位置,二者同步向前移动,当前面的指针到了链表头时,两个指针的距离仍是k。虽然没办法让指针逆向移动,但这个思路是可以正向实施的。
具体做法:
1:准备一个快指针,从链表头开始先走k步;
2:准备慢指针,初始指向为原始链表头,代表当前元素,则快慢指针之间的距离一直是k;
3:快慢指针同步移动,当快指针到达链表尾时,慢指针刚好到达倒数第k个元素的位置。
代码 :
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
ListNode fast=pHead;
ListNode slow=pHead;
for(int i=0;i<k;i++){
if(fast!=null){
fast=fast.next;
}else return slow=null;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
方法二:先得到链表长度,再去找倒数第k个节点元素
思路:链表无法逆序遍历,但如果知道链表长度,对于倒数第k个位置,可以利用链表长度和k得到从正向遍历得到。
做法:
1:遍历一遍链表得到链表长度;
2:比较链表长度是否小于k,小于k直接返回空节点;
3:否则,从正向遍历n-k即可得到倒数第k个元素。
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
ListNode cur=pHead;
int count=0;
while(cur!=null){
cur=cur.next;
count++;
}
if(count<k){
return null;
}else{
cur=pHead;
for(int i=0;i<count-k;i++){
cur=cur.next;
}
}
return cur;
}
}
8.长度为n的链表,若其中包含环,请找出该链表环的入口节点,否则返回null;
数据范围:0<=10000,1<=节点值<=10000。
官方解法:双指针
思路:根据题目,本题需要解决两个问题:1.判断链表是否有环;2.在有环的链表中找到环的入口节点。
对于第一个任务利用快慢指针的方法判断链表是否有环。对于判断有环的链表,慢指针在进入环之前,快指针已经进入环且在里面循环。这样才能在慢指针进入后快指针追到了慢指针。假设快指针在环内走了n圈,慢指针在环内走了m圈,它们才相遇。进入环之前的距离为x,环入口到相遇点的距离为y,相遇点到环入口的距离为z。快指针走了x+n(y+z)+y,慢指针走了x+m(y+z)+y。快指针走的距离时慢指针的两倍,则x+n(y+z)+y=2(x+(m(y+z)+y),整理可得x+y=(n-2m)(y+z),环的大小为y+zy+z,说明从链表头经过环入口到相遇点的距离等于整数倍的环的大小:那么从头开始遍历到相遇的位置,和从相遇的位置开始在环中遍历会使用相同的步数,而双方最后都会经过入口到相遇位置这y个节点,说明这y个节点它们时重叠遍历的,那它们从入口位置就相遇了,利用这个规律就找到了环的入口位置。
1:利用判断链表中是否有环的方法来判断当前链表是否有环,并找到相遇节点;
2:慢指针在相遇点,快指针回到链表头,两个指针同步逐个元素逐个元素开始遍历链表;
3:再次相遇的地方就说环的入口节点。
代码:
public class Solution {
public ListNode HasCycle(ListNode pHead) {
if(pHead==null) return null;
ListNode fast=pHead;
ListNode slow=pHead;
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow)
return slow;
}
return null;
}
public ListNode EntryNodeOfLoop(ListNode head){
ListNode slow = HasCycle(head);
if(slow==null)
return null;
ListNode fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}
9.删除链表的倒数第n个节点
给定一个链表,删除链表的倒数第n个节点,并返头指针。给定链表1-2-3-4;n=2。删除倒数第n个节点后变为1-2-3-5.数据范围:链表长度0<=n<=1000,链表中任意节点的值满足0<=val<=1000;保证n一定是有效的。
官方解答方法一:双指针
思路:链表无法逆序遍历,但如果我们当前节点处于倒数第n的位置,即距离链表的位置是n,那么假设双指针分别位于这两个位置同步移动,当前面一个指针遍历至链表尾时,两个指针的距离依然是n。
具体方法:
1:给链表添加表头,方便处理第一个元素;
2:准备一个快指针,链表上先走n步;
3:准备慢指针指向表头,代表当前元素,准备前序指针指向添加的表头,这样快慢两个指针,距离永远是n;
4:快慢指针同步移动,快指针到达链表尾时,慢指针到达倒数第n的位置;
5:最后将该节点的前序节点指针指向该节点的后一个节点,删除该节点。
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
public ListNode removeNthFromEnd (ListNode head, int n) {
// write code here
if(head==null)
return null;
ListNode res=new ListNode(-1);
res.next=head;
ListNode fast=head;
ListNode cur=head;
ListNode pre=res;
for(int i=0;i<n;i++){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
pre=cur;
cur=cur.next;
}
pre.next=cur.next;
return res.next;
}
}
方法二:长度统计
思路:先统计链表长度,得到链表长度length,利用length-n得到下标,再根据下标访问。链表是没法直接统计长度的,需要利用遍历链表的方式来统计长度。
具体方法:
1.给链表添加一个表头,处理删除第一个元素时方便;
2.遍历整个链表,统计链表长度;
3.准备两个指针,一个指向原始链表头,表示当前节点,另一个指向添加的节点,表示前序节点。两个节点同时遍历L-n次,找到倒数第n个位置;
4.前序节点的next指向当前节点的next,代表越过当前节点的链接,即删除当前节点;
5.返回去掉添加的表头。
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
public ListNode removeNthFromEnd (ListNode head, int n) {
// write code here
if(head==null)
return null;
ListNode res=new ListNode(-1);
res.next=head;
ListNode cur=head;
ListNode pre=res;
int count=0;
while(cur!=null){
cur=cur.next;
count++;
}
cur=head;
for(int i=0;i<count-n;i++){
pre=cur;
cur=cur.next;
}
pre.next=cur.next;
return res.next;
}
}
10.两个链表的第一个公共结点
描述:输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共结点返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)数据范围:n<=1000.
解法:
思路:用两个指针N1、N2,N1从链表1的头节点开始遍历,N2从链表2的头节点开始遍历。N1和N2一起开始遍历,当N1走完链表1的(为null)尽头的时候,继续从链表2的头节点开始遍历,同样N2走完链表2 继续从链表1的头节点开始遍历。也即N1和N2都会遍历链表1和链表2。
两个指针同样的速度,走完链表1+链表2,那么无论是否有公共节点,他们肯定会同时到达终点。N2 到达链表1的终点,N1到达链表2的终点;
如何得到公共节点:有公共节点时,N1和N2必会相遇,所以当两者相等时,则到达第一个公共节点。
无公共节点时,N1和N2都会走到终点,此时他们都是NULL,也是相等的。
代码:
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
ListNode cur1=pHead1;
ListNode cur2=pHead2;
while(cur1!=cur2){
cur1=(cur1==null)?pHead2:cur1.next;
cur2=(cur2==null)?pHead1:cur2.next;
}
return cur1;
}
}
11.判断一个链表是否为回文结构
题目描述:给定一个链表,请判断该链表是否为回文结构。回文结构是指该字符串正序逆序完全一致。数据范围:链表节点数0≤n≤10^5.
每个节点值满足|val|≤10^7.
方法一:双指针法
利用快慢指针找到链表的中间节点,将链表的后半段反转。反转后的子链表与前半段子链表比较,若存在值不相等的情况就返回FALSE,否则返回TRUE。
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类 the head
* @return bool布尔型
*/
ListNode Reverse(ListNode pHead){
if(pHead==null) return null;
ListNode cur=pHead;
ListNode pre=null;
while(cur!=null){
ListNode temp=cur.next;
cur.next=pre;
pre=cur;
cur=temp;
}
return pre;
}
public boolean isPail (ListNode head) {
// write code here
//if(head==null) return false;
//ListNode head1=Reverse(head);
ListNode q=head;
ListNode p=head;
while(q!=null&&q.next!=null){
q=q.next.next;
p=p.next;
}
if(q!=null){
p=p.next;
}
p=Reverse(p);
q=head;
while(p!=null){
if(q.val!=p.val) return false;
q=q.next;
p=p.next;
}
return true;
}
}
方法二:数组复制反转法
回文结构正序与逆序的遍历结果应该是一致的,可以尝试将正序与逆序遍历的结果一一对比,对应相等即为回文结构。
但链表无法逆序遍历,因此数组可以任意访问,那么将链表元素取出来放入数组中,再去判断该数组是否为回文结构即可。
step1:遍历链表,将链表元素放入辅助数组中;
step2:准备另一个辅助数组,将第一个数组的全部元素存入再将其反转;
step3:依次遍历原数组和反转后的数组,若元素都是相等的则为回文结构,若存在任何一个不相等的元素则不为回文结构。
代码:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param head ListNode类 the head
# @return bool布尔型
#
class Solution:
def isPail(self , head: ListNode) -> bool:
# write code here
nums=[]
while head:
nums.append(head.val)
head=head.next
left=0
right=len(nums)-1
while left<=right:
if nums[left]!=nums[right]:
return False
left+=1
right-=1
return True
12.链表的奇偶重排
题目描述:
给定一个单链表,将奇数节点和偶数节点分别放在一起,重排后输出。注意是节点的编号而非节点的数值。数据范围:节点数量满足:0<=n<=100000.节点值满足0<=val<=10000.
方法一: 双指针
思路:第一个节点是奇数位,第二个节点为偶数位,第三个节点又为奇数位。那么是否可以断掉节点一和二之间的链接,节点一直接连接节点三,依次类推。第三个节点后为第四个节点为偶数位,那么断开节点二和三之间的链接,节点二和节点四直接连接。
本段描述的代码:
//奇数位连接偶数位后一位
odd.next=even.next;
//odd进入后一个奇数位
odd=odd.next;
//偶数位连接奇数位后一位
even.next=odd.next;
//even进入后一个偶数位
even=even.next;
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode oddEvenList (ListNode head) {
// write code here
if(head==null) return head;
ListNode even=head.next;
ListNode odd=head;
ListNode evenHead=even;
while(even!=null&&even.next!=null){
odd.next=even.next;
odd=odd.next;
even.next=odd.next;
even=even.next;
}
odd.next=evenHead;
return head;
}
}
Python代码:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param head ListNode类
# @return ListNode类
#
class Solution:
def oddEvenList(self , head: ListNode) -> ListNode:
if head==None:
return head
even=head.next
odd=head
evenHead=even
while even and even.next:
odd.next=even.next
odd=odd.next
even.next=odd.next
even=even.next
odd.next=evenHead
return head
# write code here
13.删除有序链表中的重复元素(一)
题目描述:给定一个有序链表,删除链表中值重复的节点,每个值只保留一个节点。
方法一:遍历删除
思路:连续相同的元素,只保留第一个节点。
if(cur.val==cur.next.val)
cur.next=cur.next.next;
跳过值重复的节点。
具体步骤:
step1:判断链表是否为空,若为空直接返回空链表;
step2:使用一个指针遍历当前链表,若指针当前节点值与下一个节点值相等那么跳过下一个节点值,当前节点直接连接下一个节点的后一个节点;
step3:如果当前节点值与后一个节点值不相等,则继续访问下一个节点;
step4:循环过程中每次用到了两个节点值,所以要判断连续两个节点值是否为空。
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates (ListNode head) {
// write code here
if(head==null) return null;
ListNode cur=head;
while(cur!=null&&cur.next!=null){
if(cur.val==cur.next.val){
cur.next=cur.next.next;
}
else
cur=cur.next;
}
return head;
}
}
Python代码:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param head ListNode类
# @return ListNode类
#
class Solution:
def deleteDuplicates(self , head: ListNode) -> ListNode:
# write code here
if head==None:
return head
cur=head
while cur!=None and cur.next!=None:
if cur.val==cur.next.val:
cur.next=cur.next.next
else:
cur=cur.next
return head
14.删除链表重复元素(二)
题目描述:给定一个非降序的单链表,删除链表中的重复元素;重复元素一个不留全部删掉。
方法一:直接遍历,然后比较删除。
非降序链表,重复的值都在一起,然后将所有连续相同的节点都删掉。
if(cur.next.valcur.next.next.val){
int temp=cur.next.val;
while(cur.next!=null&&cur.next.valtemp){
cur.next=cur.next.next;
}
}
step1:给链表加上表头:
ListNode res=new ListNode(0);
res.next=head;
ListNode cur=res;
step2:遍历链表,每次比较两个相邻节点,如果遇到两个相邻的节点值相等,新开内循环将这一段所有相同的都遍历过去;
step3:在step2中这一连串相同的节点前直接连接不同值的节点;
step4:去掉添加的表头后返回链表。
代码:
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* }
*/
public class Solution {
/**
*
* @param head ListNode类
* @return ListNode类
*/
public ListNode deleteDuplicates (ListNode head) {
// write code here
if(head==null) return null;
ListNode res=new ListNode(0);
res.next=head;
ListNode cur=res;
while(cur.next!=null&&cur.next.next!=null){
if(cur.next.val==cur.next.next.val)
{
int temp=cur.next.val;
while(cur.next!=null&&cur.next.val==temp){
cur.next=cur.next.next;
}
}
else
cur=cur.next;
}
return res.next;
}
}
Python代码:
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param head ListNode类
# @return ListNode类
#
class Solution:
def deleteDuplicates(self , head: ListNode) -> ListNode:
# write code here
if head==None:
return head
res=ListNode (0)
res.next=head
cur=res
while cur.next!=None and cur.next.next!=None:
if cur.next.val==cur.next.next.val:
temp=cur.next.val
while cur.next!=None and cur.next.val==temp:
cur.next=cur.next.next
else:
cur=cur.next
return res.next
15.合并k个已排序的链表
题目描述:给定k个已排好序的升序链表,将这k个链表合并形成一个大的升序链表,并返回这个链表得到表头。
方法一:归并的排序思想
知识点1:双指针
双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至多个),两个指针或是同方向访问两个链表、或是同方向访问同一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。
知识点2:分治
分治即“分而治之”,“分”指的是将一个大而复杂的问题划分成多个性质相同但规模更小的子问题,子问题继续这样划分,直到问题可以被轻易解决;“治”指的是将子问题单独进行处理。经过分治后的子问题需要将解合并后才能得到原问题的解,因此整个分治过程经常用递归来实现。
思路:
两个有序链表的合并中,我们可以利用归并排序合并阶段的思想:准备双指针分别放在两个链表头,每次取出较小的一个元素加入新的大链表,将其指针后移,继续比较,这样我们出去的都是最小的元素,自然就完成了排序。
本题我们可以两两比较,遍历链表数组,取出开头的两个链表,按照上述思路合并,然后新链表再与下一个链表合并,如此循环,直到全部合并完成,但这样太浪费时间。
既然已经想到用归并,那么我们可以直接用归并的分治来做,而不是顺序合并链表。
归并排序:简单来说就是将一个数组每次划分成等长的两部分,对两部分进行排序即是子问题,对子问题继续划分,直到子问题只有一个元素。还原的时候,将每个子问题和它相邻的另一个子问题利用上述双指针的方式,一个与一个合并成两个,两个与两个合并成4个,因为这每个单独的子问题合并好的都是有序的,直到合并成原本长度的数组。
对于这k个链表,就相当于上述合并阶段的k个子问题,需要划分为链表数量更小的子问题,直到每一组合并时是两两合并,然后继续往上合并,这个过程基于递归:
1.终止条件:划分的时候直到左右区间相等或左边大于右边。
2.返回值:每级返回已经合并好的子问题链表。
3.本级任务:对半划分,对划分后的子问题合并成新的链表。
具体步骤:
step1:从链表的首和尾开始,每次划分从中间开始划分,划分成两个链表,得到左边n/2个链表和右边n/2个链表;
step2:继续不断递归划分,直到每部分链表数为1。
step3:将划分好的相邻两部分链表,按照两个有序链表合并的方式合并,合并好的两部分继续往上合并,直到最终合并成一个链表。
Java代码:
import java.util.*;
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2){
if(list1==null) return list2;
if(list2==null) return list1;
ListNode head=new ListNode(0);
ListNode cur = head;
while(list1!=null&&list2!=null){
if(list1.val<=list2.val){
cur.next=list1;
list1=list1.next;
}
else{
cur.next=list2;
list2=list2.next;
}
cur=cur.next;
}
if(list1!=null)
cur.next=list1;
else
cur.next=list2;
return head.next;
}
public ListNode divideMerge(ArrayList<ListNode> lists,int left,int right){
if(left>right) return null;
else if(left==right)
return lists.get(left);
int mid=(left+right)/2;
return Merge(divideMerge(lists,left,mid),divideMerge(lists,mid+1,right));
}
public ListNode mergeKLists(ArrayList<ListNode> lists) {
return divideMerge(lists,0,lists.size()-1);
}
}
Python代码:
from pickle import NONE
from heapq import merge
import sys
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
#
# 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
#
#
# @param lists ListNode类一维数组
# @return ListNode类
#
sys.setrecursionlimit(100000)
class Solution:
def Merge(self,list1:ListNode,list2:ListNode)->ListNode:
if list1==None:
return list2
if list2==None:
return list1
head=ListNode(0)
cur=head
while list1 and list2:
if list1.val<=list2.val:
cur.next=list1
list1=list1.next
else:
cur.next=list2
list2=list2.next
cur=cur.next
if list1!=None:
cur.next=list1
else:
cur.next=list2
return head.next
def divideMerge(self,lists:List[ListNode],left:int,right:int) -> ListNode:
head=ListNode(0)
if left > right:
return head.next
else:
if left==right:
return lists[left]
mid=(int)((left+right)/2)
return self.Merge(self.divideMerge(lists,left,mid),self.divideMerge(lists,mid+1,right))
def mergeKLists(self , lists: List[ListNode]) -> ListNode:
# write code here
return self.divideMerge(lists,0,len(lists)-1)
方法二:优先队列
知识点:优先队列