您的位置:首页 > 理论基础 > 数据结构算法

算法与数据结构-链表 讲解与java代码实现

2018-01-31 19:23 1086 查看

1.链表基础









从左到右,使用两个指针进行性翻转



空间复杂度最优解可以做到O(1)



2.例题

1. 有序环形链表插入节点



import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
//构造有序环形链表插入节点
public class InsertValue {
public ListNode insert(int[] A, int[] nxt, int val) {
//如果原链表为空,直接插入
if(A.length==0){
ListNode head=new ListNode(val);
return head;
}

//如果链表不为空
ListNode head,pre;
head=new ListNode(A[0]);
/*
//处理链表长度为1的情况
if(A.length==1){
if(A[0]<=val){
head.next=new ListNode(val);
}else{
head=new ListNode(val);
head.next=new ListNode(A[0]);
}
return head;
}
*/

pre=head;
boolean flag=false;
//构建单链表同时插入节点,flag记录是否已经插入了节点,避免重复插入
for(int index=1;index<A.length;index++){

if(flag==false&&pre.val<=val&&val<=A[index]){
ListNode now=new ListNode(val);
now.next=new ListNode(A[index]);
pre.next=now;
pre=now.next;

flag=true;
continue;
}
ListNode now=new ListNode(A[index]);
pre.next=now;
pre=now;
}
//val在链表头或者尾
if(flag==false){
ListNode now=new ListNode(val);
pre.next=now;
//bug 要实现为单链表
//now.next=head;
if(pre.val<=val){
return head;
}else{
return now;
}
}
//bug 要实现为单链表
//pre.next=head;
return head;

}
}


2.删除单链表节点

实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点。

给定带删除的头节点和要删除的数字,请执行删除操作,返回删除后的头结点。链表中没有重复数字

import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Remove {
public ListNode removeNode(ListNode pNode, int delVal) {
//如果删除头结点情况
if(delVal==pNode.val){
pNode=pNode.next;
return pNode;
}
ListNode pre=pNode,cur=pNode.next;

while(cur!=null){
if(cur.val==delVal){
pre.next=cur.next;
break;
}

pre=cur;
cur=cur.next;
}
return pNode;
}
}


3.链表分化

对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。

给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。

测试样例:

{1,4,2,5},3

{1,2,4,5}

方法一:



方法二:





方法一实现:

import java.util.*;
//链表分化
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Divide {
public ListNode listDivide(ListNode head, int val) {
//将链表值放入数组
ArrayList<Integer> list=new ArrayList<Integer>();
ListNode pos=head;
int valIndex=0;
for(int i=0;pos!=null;i++){
list.add(pos.val);
if(pos.val==val){
valIndex=i;
}
pos=pos.next;

}
//对数组进行一次快排,保证结点内部位置关系不变
int archor=-1;
int lastIndex=list.size()-1;
int size=list.size();
swap(list,valIndex,lastIndex);
for(int i=0;i<size-1;i++){
if(list.get(i)<list.get(lastIndex)){
swap(list,i,++archor);
}
}
insert(list,++archor);

//将一次快排后的数组转为单链表
ListNode headnew=new ListNode(list.get(0));
ListNode pre=headnew;

for(int i=1;i<size;i++){
ListNode temp=new ListNode(list.get(i));
pre.next=temp;
pre=temp;
}

return headnew;
}

public void swap(ArrayList<Integer> A,int index1,int index2){
int temp=A.get(index1);
A.set(index1,A.get(index2));
A.set(index2,temp);
}

public void insert(ArrayList<Integer> A,int index){
int temp=A.get(A.size()-1);
for(int i=A.size()-1;i>index;i--){
A.set(i,A.get(i-1));
}
A.set(index,temp);
}
}


注释:

测试用例:{1620,1134,861,563,1},1134

输出:{1,861,563,1134,1620}

这里和快排的一个区别是结点的相对位置要保持不变

快排是不稳定的,这里要求是稳定的,那么区别在于最后的元素重新插入到相应位置时不能直接和插入位置上的元素进行交换,而是需要其插入位置及其后面的元素依次往后移动,就可以保证相对顺序不变

这道题的测试程序有问题,它的标准输出不满足题目要求

说:对应输出应该为:

{1134,861,563,1,1620}

方法二实现:

第二种分成三个链表的方法,注意连接三个链表时要判断小于节点的链表是否为空

import java.util.*;
//链表分化
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Divide {
public ListNode listDivide(ListNode head, int val) {
//遍历链表分成三个链表,分别是小于,等于,大于val的链表
ListNode head1=null,tail1=null,head2=null,tail2=null,head3=null;
ListNode temp1=null,temp2=null,temp3=null;
ListNode pos=head;
while(pos!=null){
if(pos.val<val){
if(head1==null){
head1=new ListNode(pos.val);
temp1=head1;
}else{
temp1.next=new ListNode(pos.val);
temp1=temp1.next;
}

tail1=temp1;
}else if(pos.val==val){
if(head2==null){
head2=new ListNode(pos.val);
temp2=head2;
}else{
temp2.next=new ListNode(pos.val);
temp2=temp2.next;
}

tail2=temp2;
}else{
if(head3==null){
head3=new ListNode(pos.val);
temp3=head3;
}else{
temp3.next=new ListNode(pos.val);
temp3=temp3.next;
}

}
pos=pos.next;
}

//把三个分化的链表连接起来,注意等于的链表是一定有的,但是小于的链表不一定有,此时要判断返回不为空的链表头,否则会出现空指针错误
tail2.next=head3;
if(tail1!=null){
tail1.next=head2;
}

return head1!=null?head1:head2;
}
}


4.两个有序链表寻找公共值

现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。

给定两个链表的头指针headA和headB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值

测试样例:

{1,2,3,4,5,6,7},{2,4,6,8,10}

返回:[2.4.6]

import java.util.*;
//寻找两个链表的公共值
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Common {
public int[] findCommonParts(ListNode headA, ListNode headB) {
//分别遍历两个链表,如果任一链表的值小于另一个,则指针后移,如果两者相等,则保存并且两个指针均后移
//结束条件是任一指针为空,则结束
ArrayList<Integer> list=new ArrayList<Integer>();

if(headA==null||headB==null){
return null;
}
ListNode p1=headA,p2=headB;
while(p1!=null&&p2!=null){
if(p1.val==p2.val){
list.add(p1.val);
p1=p1.next;
p2=p2.next;
}else if(p1.val<p2.val){
p1=p1.next;
}else{
p2=p2.next;
}
}

int size=list.size();
int[] result=new int[size];
int index=0;
for(int i=0;i<size;i++){
result[index++]=list.get(i);
}
return result;
}
}


5.链表逆序调整

有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。

给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。

每隔K个元素对链表结点进行逆序,最后如果不满K个,则按原来的顺序输出,不做逆序操作。

import java.util.*;
//单链表每隔K个节点进行逆序,最后不满足K则不逆序
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class KInverse {
public ListNode inverse(ListNode head, int k) {
//如果head为空或者长度为1或者k小于2,则不调整
//处理头结点的逆序,保存新的头结点和尾节点
//每隔K进行逆序,使用栈进行逆序操作,注意如果压入栈的元素小于k则不调整
if(head==null||head.next==null||k<2){
return head;
}
Stack<Integer> stack=new Stack<Integer>();
ListNode newHead=null,cur=null,tail=null,pos=head;
while(pos!=null){
stack.push(pos.val);
if(stack.size()==k){
cur=new ListNode(stack.pop());
if(newHead==null){
newHead=cur;
}else{
tail.next=cur;
}
tail=cur;
while(stack.size()>0){
tail.next=new ListNode(stack.pop());
tail=tail.next;
}
}
pos=pos.next;
}
//处理未满K个的尾节点,输出为正常次序
if(stack.size()>0){
ArrayList<Integer> temp=new ArrayList<Integer>();

while(stack.size()>0){
temp.add(stack.pop());
}
for(int i=temp.size()-1;i>=0;i--){
tail.next=new ListNode(temp.get(i));
tail=tail.next;
}

}

return newHead;
}
}


6.删除单链表中的指定值

现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。

给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。

测试样例:

{1,2,3,4,3,2,1},2

{1,3,4,3,1}

import java.util.*;
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class ClearValue {
public ListNode clear(ListNode head, int val) {
//处理两种特殊情况,如果头结点等于val,那么要建立新的头结点
ListNode pre=head,cur=head;
while(cur!=null){
if(cur.val==val){
if(cur==head){
head=cur.next;
cur=cur.next;
pre=cur;
}else{
pre.next=cur.next;
}
}else{
pre=cur;
}

cur=cur.next;
}

return head;
}
}


7.链表元素是否为回文结构的判断









方法三:利用链表反向,实现空间复杂度为O(1)的方法

import java.util.*;
//链表回文结构判断
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Palindrome {
public boolean isPalindrome(ListNode pHead) {
//如果只有一个数,则是回文
if(pHead==null||pHead.next==null){
return true;
}

//使用快指针即每次移动两个单位和一个慢指针遍历链表以求得中间指针
ListNode last=pHead,mid=pHead;
while(last.next!=null&&last.next.next!=null){
mid=mid.next;
last=last.next.next;
}
//把从快指针到慢指针下一个节点之间的链表方向转向,以便从右边向中间进行遍历
ListNode right=mid.next;//right指向右边部分第一个节点
mid.next=null;
ListNode pre=null;//pre指向right的右边节点
ListNode post=mid;//指向right的左边节点
while(right!=null){
pre=right.next;
right.next=post;
post=right;
right=pre;
}
//右边从快指针往回遍历到慢指针的下一个指针,左边从头指针遍历到慢指针,判断两边是否相等
ListNode left=pHead;
right=post;//最右边的节点
boolean res=true;

while(right!=null&&left!=null){
if(left.val!=right.val){
res=false;
}
left=left.next;
right=right.next;
}

//把从快指针到中间的链表方向复原
right=last;
pre=last.next;
last.next=null;
while(pre!=null){
ListNode temp=pre.next;
pre.next=right;
right=pre;
pre=temp;
}
return res;
}
}


8.杂链表的复制练习



/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
//复杂链表的复制
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
//首先遍历整个链表,复制整个链表,即每个节点的复制节点放到它的后面
if(pHead==null){
return null;
}
RandomListNode pos=pHead,copyNode;
while(pos!=null){
copyNode=new RandomListNode(pos.label);
copyNode.next=pos.next;
pos.next=copyNode;

pos=pos.next.next;
}

//重新遍历整个长度翻倍的链表,复制每个原始结点的random指针到复制节点上
pos=pHead;
while(pos!=null){
pos.next.random=pos.random!=null?pos.random.next:null;
pos=pos.next.next;
}
//从整个链表中分离出复制后的链表
pos=pHead;
RandomListNode newHead=pHead.next;//指向新链表的头结点
RandomListNode cur=null;
while(pos!=null){
cur=pos.next;
pos.next=cur.next;
pos=pos.next;
cur.next=pos!=null?pos.next:null;
cur=cur.next;

}

return newHead;
}
}


9.链表判环

链表判环:使用快指针和慢指针,慢指针初始位置为head.next,快指针初始位置为head.next.next,构成环的条件是长度至少为2,也就是如果head.next==null则肯定无环













import java.util.*;
//以额外空间复杂度O(1)的方法判断一个链表是否有环
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class ChkLoop {
public int chkLoop(ListNode head, int adjust) {
if(head==null||head.next==null){
return -1;
}
//使用两个指针,一个快指针,每次走2步,一个慢指针,每次走一步
ListNode fast=head.next.next,slow=head.next;
//如果快指针到达null,则无环
//如果快指针和慢指针相等,则说明有环,
//此时快指针从头开始以每步1步的方式遍历,慢指针从相遇的地方继续遍历,两者再次相遇的节点即为环入口点
boolean flag=false;
while(fast!=null){
if(fast==slow){
flag=true;
break;
}
fast=fast.next!=null?fast.next.next:null;
slow=slow.next;
}

if(flag==true){
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return fast.val;
}
return -1;
}
}


10.判断两个无环单链表是否相交

现在有两个无环单链表,若两个链表的长度分别为m和n,请设计一个时间复杂度为O(n + m),额外空间复杂度为O(1)的算法,判断这两个链表是否相交。

给定两个链表的头结点headA和headB,请返回一个bool值,代表这两个链表是否相交。保证两个链表长度小于等于500。



import java.util.*;
//设计额外空间复杂度为O(1),时间复杂度为O(m+n)的方法判断两个无环单链表的相交节点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class CheckIntersect {
public boolean chkIntersect(ListNode headA, ListNode headB) {
//首先遍历两个链表,得到两个链表的长度M和N
ListNode posA=headA,posB=headB;
if(posA==null||posB==null){
return false;
}

int lengthA=0,lengthB=0;
while(posA!=null||posB!=null){
if(posA!=null){
posA=posA.next;
lengthA++;
}
if(posB!=null){
posB=posB.next;
lengthB++;
}
}
//假设M>N,让长度长的链表遍历到第N号位置,然后两个链表一起遍历,判断是否有值相等的节点
//不用考虑长度相等,因为长度相等意味着这两个链表就是同一个链表,也就是headA==headB,对于这道题没有意义
posA=headA;
posB=headB;
int count=0;
if(lengthA>lengthB){
while(count<(lengthA-lengthB)){
posA=posA.next;
count++;
}

}
else{
while(count<(lengthB-lengthA)){
posB=posB.next;
count++;
}
}

while(posA!=null){
if(posA==posB){
return true;
}
posA=posA.next;
posB=posB.next;
}

return false;
}
}


11.两个有环单链表判断相交节点



分成两种情况:一个是两个入环节点是同一个节点的情况,也就是要找入环节点前相交的节点,第二种是两个入环节点不是同一个节点





从node1开始遍历,如果node1能直接回到node1,没有遇到node2,则说明不想交,则返回空;

如果node1遍历过程中遇到了node2,则说明相交,则返回node1或者node2都是正确的



import java.util.*;
//判断两个有环单链表是否相交,相交则返回相交的节点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class ChkIntersection {
public boolean chkInter(ListNode head1, ListNode head2, int adjust0, int adjust1) {
//首先找到两个有环单链表各自的入环节点
ListNode node1=findNode(head1),node2=findNode(head2);

//判断两个入环节点是否相等,如果相等,那么寻找入环节点前相交的点
if(node1==node2){
int length1=getLength(head1,node1);
int length2=getLength(head2,node2);
//判断两个无环单链表在入环之前是否就相交
int count=0;
ListNode pos1=head1,pos2=head2;
if(length1>length2){
count=0;
while(count<(length1-length2)){
count++;
pos1=pos1.next;
}
while(pos1!=node1){
if(pos1==pos2){
return true;
}
pos1=pos1.next;
pos2=pos2.next;
}
}else{
while(count<(length2-length1)){
count++;
pos2=pos2.next;
}
while(pos2!=node2){
if(pos1==pos2){
return true;
}
pos1=pos1.next;
pos2=pos2.next;
}
}
return true;
}else{
//如果入环节点不相等,那么从第一个链表的入环节点开始遍历直到回到原点,遍历过程如果遇到等于链表2的入环节点处,则说明相交
ListNode cur=node1.next;
while(cur!=node1){
if(cur==node2){
return true;
}
cur=cur.next;
}
return false;
}

}
//找到入环节点
public ListNode findNode(ListNode head){
if(head==null||head.next==null){
return null;
}
ListNode fast=head.next.next,slow=head.next;
while(fast!=null){
if(fast==slow){
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return fast;
}
fast=fast.next.next;
slow=slow.next;
}

return null;
}

public int getLength(ListNode head,ListNode end){
int length=0;
ListNode pos=head;
while(pos!=end){
length++;
pos=pos.next;
}
return length;
}

}


11.判断两个单链表是否相交,返回第一个相交节点



import java.util.*;
//判断两个但聊表是否相交,返回相交节点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class ChkIntersection {
public boolean chkInter(ListNode head1, ListNode head2, int adjust0, int adjust1) {
//寻找两个单链表各自的入环节点
ListNode node1=findNode(head1),node2=findNode(head2);
int length1=getLength(head1,node1);
int length2=getLength(head2,node2);
int count=0;
ListNode pos1=head1,pos2=head2;
//如果两个链表都没有环,那么就是两个无环单链表相交判断
if(node1==null&&node2==null){
if(length1>length2){
while(count<(length1-length2)){
pos1=pos1.next;
count++;
}

}else{
while(count<(length2-length1)){
pos2=pos2.next;
count++;
}
}
while(pos1!=null&&pos2!=null){
if(pos1==pos2){
return true;
}
pos1=pos1.next;
pos2=pos2.next;
}
return false;
}else if(node1!=null&&node2!=null){
//如果两个链表都有环,就是两个有环单链表相交判断
if(node1==node2){
//判断两个无环单链表在入环之前是否就相交
if(length1>length2){
count=0;
while(count<(length1-length2)){
count++;
pos1=pos1.next;
}
while(pos1!=node1){
if(pos1==pos2){
return true;
}
pos1=pos1.next;
pos2=pos2.next;
}
}else{
while(count<(length2-length1)){
count++;
pos2=pos2.next;
}
while(pos2!=node2){
if(pos1==pos2){
return true;
}
pos1=pos1.next;
pos2=pos2.next;
}
}
return true;
}else{
//如果入环节点不相等,那么从第一个链表的入环节点开始遍历直到回到原点,遍历过程如果遇到等于链表2的入环节点处,则说明相交
ListNode cur=node1.next;
while(cur!=node1){
if(cur==node2){
return true;
}
cur=cur.next;
}
return false;
}
}
else{
//如果一个链表有环,一个链表无环,则说明不相交
return false;
}
}

//找到入环节点
public ListNode findNode(ListNode head){
if(head==null||head.next==null){
return null;
}
ListNode fast=head.next.next,slow=head.next;
while(fast!=null){
if(fast==slow){
fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
return fast;
}
fast=fast.next.next;
slow=slow.next;
}
return null;
}
public int getLength(ListNode head,ListNode end){
int length=0;
ListNode pos=head;
while(pos!=end){
length++;
pos=pos.next;
}
return length;
}
}


3.JAVA实现tips

JAVA:

* arraylist set(index,value)替换index上的元素为value值
* Math.random() 返回一个double数值,在[0,1)之间,包括0但是不包括1https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html Returns a double value with a positive sign, greater than or equal to 0.0 and less than 1.0.
* 如果一个节点的next为空,要显式地设置它为null,否则测试可能通不过,出现空指针错误
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: