回文链表
- 创建一个栈,将链表中所有数据存入栈中,再将栈中数据与链表中的所有数据进行比较。
- 利用单步指针和双步指针,当双步指针最后到链表尾的时候,将后半部分链表指向取反,再从链表头和链表尾同时开始对比数据大小,实现回文链表的判断。空间复杂度为O(1)。
public static class Node{
public int value ;
public Node next;
public Node(int data){
this.value = data;
}
}
//需要n个额外空间
public static boolean isPalinddrome1(Node head){
Stack<Node> stack = new Stack<Node>();//建立一个node类型的栈
Node cur = head;
while(cur!=null){
stack.push(cur);
cur = cur.next;
}
while(head!= null){
if(head.value != stack.pop().value){
return false
}
head = head.next;
}
return true;
}
//需要n/2个额外空间
public static boolean isPalindrome2(Node node){
//在只包含0个或1个数的时候,必定满足回文结构
if(head == null ||head.next == null){
return true;
}
//设置right为单步遍历,cur为双步遍历,因此需要提前将只包含0,1个数据的链表剔除,不然无法进行判断。
Node right = head.next;
Node cur = head;
while(cur.next!=null &&cur.next.next !=null){
right = right.next;
cur = cur.next.next;
}
Stack<Node> stack = new Stack<Node>();
while(right != null){
stack.push(right);
right = right.next;
}
while(!stack.isEmpty()){
if(head.value != stack.pop().value){
return false;
}
head = head.next;
}
return ture;
}
//需要O(1)额外空间
public static boolean isPalindrome3(Node head){
if(head == null|| head.next == null){
return ture;
}
Node n1 = head;
Node n2 = head;
while(n2.next !=null&&n2.next.next != null){
n1 = n1.next;
n2 = n2.next.next;
}
//n2变成了右半部分第一个点,将左右两边分开
n2 = n1.next;
n1.next = null;
Node n3 = null;
//通过while循环将右半部分的链表指向取反
while(n2! = null){
n3 = n2.next;
n2.next = n1;
n1 = n2;
n2 = n3;
}
n3 = n1;//n3表示的是最后的一个节点
n2 = head;//n2为左边第一个节点
boolean res = true;
while(n1 != null && n2 != null){
if(n1.value != n2.value){
return false;
}
n1 = n1.next;
n2 = n2.next;
}
//最后再将整个链表复原
n1 = n3.next;
n3.next = null;
while(n1 != null){
n2 = n1.next;
n1.next = n3;
n3 = n1;
n1 = n2;
}
return res;
}
单向链表按照某值划分成左边小,中间相等,右边大的形式
- 荷兰国旗问题,额外准备一个数组。荷兰国旗问题不具有稳定性。
- 设置三个节点,分别为小于mem,等于mem,大于mem。每个节点设置两个变量,分别为head和end,每次增加数值则将end++,数据存放入该链表之中。
public static class Node{
public int value;
public Node next;
public Node(int data){
this.value = data;
}
}
public static Node listPartition1(Node head,int pivot){
if(head == null){
return head;
}
Node cur = head;
int i = 0;
//负责将整个链表长度计算出来,从而可以知道新建多长的数组
while(cur !=null){
i++;
cur = cur.next;
}
//新建数组
Node[] nodeArr = new Node[i];
i = 0;
cur = head;
//将链表中的数据放入数组之中
for(i = 0;i!= nodeArr.length;i++){
nodeArr[i] = cur;
cur = cur.next;
}
//利用荷兰国旗问题进行排序,荷兰国旗在之前讲解快速排序的时候讲到了,不了解可以前翻。
arrPartition(nodeArr,pivot);
//将数组改成链表的形式返回
for(i = 1;i != nodeArr.length;i++){
nodeArr[i-1].next =nodeArr[i];
}
nodeArr[i-1].next = null;
return nodeArr[0];
}
//实现了将整个Node数组中的数据进行了交换
public static void arrpartition(Node[] nodeArr,int pivot){
int small = -1;
int big = nodeArr.length;
int index = 0;
while(index != big){
if(nodeArr[index].value<pivot){
swap(nodeArr,++small,index++);
}else if(nodeArr[index].value>pivot){
swap(nodeArr,--big,index);
}else{
index++;
}
}
}
public static void swap(Node[] nodeArr,int a,int b){
Node tmp = nodeArr[a];
nodeArr[a] = nodeArr[b];
nodeArr[b] = tmp;
}
public static Node listPartition2(Node head ,int pivot){
Node sH = null;//H代表head,T代表Tail,s表示small,e代表equal,b代表big。
Node sT = null;
Node eH = null;
Node eT = null;
Node bH = null;
Node bT = null;
Node next = null;//下一个节点
//每一个节点都需要进行判断,然后分别挂到三个链表上。
while(head !=null ){
//下面两行是将head单独拿出进行比较,将head后面部分跟head分开,从而实现每一步单独比较
next = head.next;
head.next = null;
if(head.value<pivot){
if(sH == null){
sH = head;
sT = head;
}else{
sT.next = head;
sT = head;
}
}else if(head.value == pivot){
if(eH == null){
eH = head;
eT = head;
}else{
eT.next = head;
eT = head;
}
}else{
if(bH == null){
bH = head;
bT = head;
}else{
bT.next = head;
bT = head;
}
}
head = next;
}
//将小于和等于部分相连接,之所以若eT为空则让eT = sT,是为后面等于和大于部分相连接做准备。
if(sT!= null) {
sT.next = eH;
eT = eT == null?sT:eT;
}
//全部连接
if(eT != null){
eT.next = bH;
}
//最后返回的内容中还需要进行三段中前两段是否为空的情况。
return sH != null ? sH :eH != null ? eH :bH ;
}
复制含有随机指针节点的链表
- 使用hashmap
- 首先进行节点的遍历。1->1’->2->2’->3->3’->null,使得节点的下个节点都是本身的拷贝节点,可以通过random节点使其链接,同时找到对应的下一个节点,使得1’可以找到3‘,最后将最后串好的新老链表分开,就完成了整个链表的拷贝。
//第一种方法就是利用hashmap进行整个链表的复制
public static Node copyListWithRand1(Node head){
HashMap<Node,Node> map = new HashMap<Node,Node>();
Node cur = head;
while(cur != null){
map.put(cur,new Node(cur.value));
cur = cur.next;
}
cur = head;
//复制的主体部分,map中获取cur的值对应的key的下一个值等于cur的下一个值对应的key,利用键值对将原始的链表和要复制的链表联系起来,从而实现深度的拷贝。
while(cur != null){
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
public static Node copyListWithRand2(Node head){
if(head == null){
return null;
}
Node cur =head;
Node next = null;
//复制所有的节点和链接
while(cur != null){
next = cur.next;
cur.next = new Node(cur.value);
cur.next.next = next;
cur = next;
}
//下面的两个while循环一个是将rand连接进行复制,另外一个是将正常的连接进行复制
cur = head;
Node curCopy = null ;
while(cur != null){
next = cur.next.next;
curCopy = cur.next;
curCopy.rand = cur.rand != null ? cur.rand.next:null;
cur = next;
}
Node res = head.next;
cur = head;
//split
while(cur != null){
next = cur.next.next;
curCopy = cur.next;
cur.next = next;
curCopy.next = next != null ? next.next :null;
cur = next;
}
return res;
}
两个单链表相交的一系列问题
判断是否有环:
- 利用hashset对两个单链表进行遍历,判断链表中的数值再hashset中是否存在,存在则有环,不存在则无环。
- 不用哈希表的话需要准备两个指针,快指针和慢指针,快指针一次走两步,慢指针一次走一步,如果有环,则快指针和慢指针一定会在环上相遇。快指针和慢指针相遇之后,将快指针调整到链表开始,此时快指针和慢指针均按一次一步,则会再第一个入环节点相遇。
判断是否相交: - 如果loop1为空,loop2为空,则将链表1所有节点放入map中,遍历链表2,查找map,是否在map中存在。
- 不使用map则先遍历map1,得到链表1的长度及链表1的最后一个节点。再遍历链表2的长度,及链表2的最后一个节点。如果end1跟end2相等,则比较链表长度,多的部分让多的链表先走多的部分,之后两个链表同时走,最终会走到首次相交的地方。
单链表结构一个有环一个无环不可能相交。 - 有环的拓扑结构只可能有三种情况:如图所示。
- loop1,loop2,head1,head2就足够区分开三种情况.
1)loop1等于loop2,则为情况2.砍掉环的部分,则又转化为之前的无环链表结构。
2)loop1不等于loop2,则为结构1或结构3,loop1接着往下走,如果loop1往下走没遇见loop2,则不相交,如果loop1在遍历过程中遇到了loop2,则相交,返回loop1或loop2.
此时只使用了有限的几个变量空间,没有使用hashmap。
public static Node getIntersectNode(Node head1,Node head2){
if(head1 == null || head2 = null){
return null;
}
Node loop1 = getLoopNode(head1);
Node loop2 = getLoopNode(head2);
if(loop1 == null && loop2 == null){
return noLoop(head1,head2);
}
if(loop1 != null && loop2 !=null){
return bothLoop(head1,loop1,head2,loop2);
}
return null;
}
//根据结论获得的环的起始位置
public static Node getLoopNode(Node head){
if(head == null || head.next == null || head.next.next){
return null;
}
Node n1 = head.next;
Node n2 = head.next.next;
while(n1 != n2){
if(n2.next == null || n2.next.next == null){
return null;
}
n2 = n2.next.next;
n1 = n1.next;
}
n2 = head;//n2从头重新开始走
while(n1 != n2){
n1 = n1.next;
n2 = n2.next;
}
return n1;
}
public static Node noLoop (Node head1,Node head2){
if(head1== null || head2 == null){
return null;
}
Node cur1 = head1;
Node cur2 = head2;
int n = 0;
while(cur1.next != null){
n++;
cur1 = cur1.next;
}
while(cur2.next != null){
n--;
cur2 = cur2.next;
}
//两条链都走到最后,如果最后的数值不相同,则说明中间没有交点
if(cur1 != cur2){
return null;
}
//这个判断非常巧妙,判断出来哪条长之后直接对cur进行赋值
cur1 = n >0 ?head1:head2;
cur2 = cur1 == head1? head2:head1;
//求一下绝对值
n = Math.abs(n);
//将多出来的长度先走完
while(n!= 0 ){
n--;
cur1 = cur1.next;
}
while(cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2){
Node cur1 = null ;
Node cur2 = null;
//有共同的环之后再将重新化作无环的情况去分析,从而寻找到交点起始位置。
if( loop1 == loop2){
cur1 = head1;
cur2 = head2;
int n = 0;
while(cur1 != loop1){
n++;
cur1 = cur1.next;
}
while(cur2 != loop2){
n--;
cur2 = cur2.next;
}
cur1 = n>0? head1: head2;
cur2 = cur1 == head1 ? head2:head1;
n = Math.abs(n);
while(n!= 0){
n--;
cur1 = cur1.next;
}
while(cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else{
cur1 = loop1.next;
//判断该环上loop1和loop2是不同的位置,loop1旋转一周必定会经过loop2,此时可以返回loop1
while(cur1 != loop1){
if(cur1 == loop2){
return loop1;
}
cur1 = cur1.next;
}
return null;
}
}