题目分析:简单的来说就是把链表中的节点按照题目中给定话划分值target,给隔开,小于target的节点串到一块,等于target的节点串到一块,大于target的链表节点差混到一块。最后连接小于区,等于区,大于区,所断开的节点
解题思想:
1.遍历链表,计算出链表节点的个数,然后创建一个和链表长度相同节点类型的数组。
2.把链表中的各个节点都挨个放到数组中,然后对其进行荷兰国旗问题的求解。
那么什么是荷兰国旗问题呢?其实他一点都不神秘,也就是根据划分值,划分小于区,等于区,大于区而已。
我们不妨设现在有一个数组,数组中的元素为:4 , 2 ,3 ,5 ,6 ,1 ,3 , 0,给定的划分值为3
1.设置小于区,小于区初始下标为less = -1
, 大于区等初始下标为more = array.length
.
在index遍历数组时,遇到比划分值target大的元素
,就把这个元素和大于区的前一个元素交换
,交换之后原来在大于区前的元素,在index位置,此时index不能向后遍历
,因为交换过来的值还没有和target进行比较,并且大于区向左进一步
,
2.如果遇到了比target小的元素
,就把这个元素和小于区前的第一个元素进行交换,并且小于区向右进一步 less++
;
3.如果在遍历的过程中,遇到了和target相等的元素
,就把index++
,遍历下面的元素。
4.最后等到index == more
的时候,遍历结束。
经过一系列操作后,就把数组成功的分成了3部分。
代码实现:
//交换代码
public void swap(Node []array,int left,int right){
Node temp = array[left];
array[left] = array[right];
array[right] = temp;
}
public void partition(Node []array, int val){
int less = -1;
int more = array.length;
int index = 0;
while(index < more){
//如果划分值和在数组中遍历到的元素相同时index下标向右移动
if(array[index].val == val){
index++;
//在数组中标遍历到的元素比划分值小,index向后移动,遍历到的这个元素和小于区的后一位元素交换
//index向后移动,小于区向后移动一位
}else if(array[index].val < val){
swap(array,index++,++less);
//在数组中遍历到的元素比划值大,和大于区的前一个元素做交换,但是index下标不往后移动,因为交换之后是一个新值
//然后这个新值和划分值进行比较
}else if(array[index].val > val){
swap(array,index,--more);
}
}
}
public Node returnSpaceLinked(int val){
if(head == null){
return null;
}
//建一个 和链表等长的数组
Node []newArr = new Node[getLength(head)];
Node cur = head;
int index = 0;
while(cur != null){
newArr[index] = cur;
cur = cur.next;
index++;
}
partition(newArr,val);
int i = 1;
//连接链表
for(;i<newArr.length;i++){
newArr[i-1].next = newArr[i];
}
newArr[i-1].next = null;
return newArr[0];
}
其实这个思路,就和博主在开篇说的几乎一样,设置三个链表,一个链表中的节点时小于target的节点,把这些节点串起来,等于target的节点串起来,大于target的节点串起来。其实这样的做法还一定长上的减小了空间复杂度,孔家复杂度为O(1),思路一那种算法的空间复杂度为O(n),两种算法的时间复杂度都为O(n).
设置六个变量,SS,SE分别代表小于区链表的头节点和尾节点,MS,ME分别代表等于区的头节点和尾节点,ES,EE分别代表大于区的头节点和尾节点。
public Node returnSpaceLinkedTwo(Node head,int val){
if(head == null){
return null;
}
Node SS = null;
Node SE = null;
Node MS = null;
Node ME = null;
Node ES = null;
Node EE = null;
Node cur = head;
Node curNext = null;
while(cur != null){
curNext = cur.next;
cur.next = null;
if(cur.val < val){
if(SS == null){
SS = cur;
SE = cur;
}else{
//和尾节点连接
SE.next = cur;
//更新尾节点
SE = cur;
}
}else if(cur.val == val){
if(MS == null){
MS = cur;
ME = cur;
}else{
ME.next = cur;
ME = cur;
}
}else if(cur.val > val){
if(ES == null){
ES = cur;
EE = cur;
}else{
EE.next = cur;
EE = cur;
}
}
cur = curNext;
}
//现在小于区,等于区,大于区,已经建好了,但是怎样连接呢
//如果小于区不为空,判断等于区是否为空
if(SE != null){
SE.next = MS;
//如果等于区等于空,就返回小于区的尾
//如果等于区为空,ME = SE
ME = ME == null ? SE : ME;
}
if(ME != null){
ME.next = ES;
}
//如果小于区为空,等于区为空,返回大于区.......
return SS == null ? (MS == null ? ES : MS):(SS);
}
给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。
构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。
例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。
返回复制链表的头节点。
用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:
val:一个表示 Node.val的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。
题解一:其实我们可以利用哈希表,来存起链表中的每一个节点。把原来链表中的节点当成key值,然后所对应的value值时新建的节点但是这个节点所对应的数值域中的值为key值节点中的值。
通过map.get()方法得知value节点下一步串到那个节点。
代码展示:
public class Node {
public int val;
public Node rand;
public Node next;
public Node(int val){
this.val = val;
}
}
public Node choneNode(Node head){
if(head == null){
return null;
}
Node cur = head;
HashMap<Node,Node> map = new HashMap<>();
while(cur != null){
map.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
//遍历链表
while(cur != null){
//得到链表中的value值的节点下一步要指向的节点
map.get(cur).next = map.get(cur.next);
map.get(cur).rand = map.get(cur.rand);
cur = cur.next;
}
return map.get(head);
}
题解二:
其实我们还可以这样做,在原链表的节点之间,增添一个节点,也就是这样的 1-----1!------2-----2!-------3-------3!------null-----null.
然后连接上1!,2 !,3!所对应的随机指针所对应的节点。然后断开1和1!等之间的连接。
代码展示:
public Node choneNodeNorHash(Node hhead){
if(head == null){
return null;
}
Node cur = head;
Node curNext = null;
//1.在原始的链表中添加克隆的节点
//1----2
//1--1`--2
while(cur != null){
//记录以前节点
curNext = cur.next;
cur.next = new Node(cur.val);
cur.next.next = curNext;
cur = curNext;
}
//2.添加rand指针对应的节点
cur = head;
Node cloneNode = null;
while(cur != null){
curNext = cur.next.next;
cloneNode = cur.next;
cloneNode.rand = cur.rand == null ? null : cur.rand.next;
cur = curNext;
}
//3.分离链表
cur = head;
Node ret = cur.next;
while(cur != null){
curNext = cur.next.next;
cloneNode = cur.next;
cur.next = curNext;
cloneNode.next = curNext == null ? null : curNext.next;
cur = curNext;
}
return ret;
}
题目是这样的,两个不知道是有环,还是无环的链表,要求是,如果他们两个相交,就有一个相交节点,返回这个相交的节点。
链表是否有环,并返回入环节点。
判断链表是否有环,这个题目在博主以前的文章中提到。算了这里也请问的说一下吧。
第一使用快慢指针思想,
快慢指针,快指针一次走两步,慢指针一次走一步,如果链表有环,那么快慢指针就会相遇,然后一个节点从新指向原来的链表头节点。
这个节点从头节点开始出发,和快慢指针相遇节点,一步一块走,最终就会相遇。
第二使用hashSet方法。
hashSet方法,把链表中的每个节点都添加到hashSet中,如果出现了两个相同的节点,就可以肯定这个链表有环,这样也可以知道链表的入环节点。
快慢指针方法:
public Node isNodeCycle2(Node head){
if(head == null){
return null;
}
Node cur = head;
Node fast = cur.next.next;
Node slow = cur.next;
while(fast != slow){
//没有环
if(fast.next == null || fast.next.next == null){
return null;
}
fast = fast.next.next;
slow = slow.next;
}
slow = head;
while(slow != fast){
slow = slow.next;
fast = fast.next;
}
return slow;
}
hashSet方法:
public Node isNodeCycle1(Node head){
if(head == null){
return null;
}
HashSet<Node> set = new HashSet<>();
Node cur = head;
while(cur != null){
if(!set.contains(cur)){
set.add(cur);
}else{
return cur;
}
cur = cur.next;
}
return null;
}
如果两个链表没有环,那就成了,两个无环链表找相交节点。
这个问题,博主也在以前的文章中提到,在这里也说一下吧。
要得到两个非环形链表的相交节点,就要让两个链表在一个距离尾节点相同距离的节点开始遍历,不然两个节点始终不会相遇。
那么我们就要知道两个链表的长度差值。长链表先走差值步。
注意在遍历两个链表的时候,遍历到两个链表得到尾节点就可以了,即为cur.next != null,这样我们在遍历完链表之后,cur跟随节点,就走到了两个链表的尾节点,如果两个链表的尾节点不同,那么直接返回null.
然后从长链表的差值步开始,和短链表一块遍历,现在肯定是有相交节点的。
代码展示:
//判断两个非环形链表是否相交
public Node norCycleTogther(Node head1,Node head2){
if(head1 == null ||head2 == null){
return null;
}
Node cur1 = head1;
int n = 0;
while(cur1.next != null){
n++;
cur1 = cur1.next;
}
Node cur2 = head2;
while(cur2.next != null){
n--;
cur2 = cur2.next;
}
//遍历到的两个链表的最后一个节点,如果这两个节点,不相同,直接返回null
if(cur1 != cur2){
return null;
}
//然后得到长链表,短链表
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2 : head1;
Math.abs(n);
while(n != 0){
n--;
cur1 = cur1.next;
}
while(cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}
//如果两个链表都
如果一个链表有环,一个链表无环,这种情况是不会产生相交节点的。
如果两个链表都有环,找到两个链表的相交的节点
这种情况分为两种。
如图所示,第一种情况,两个环形链表的相交节点不在环上。
那么要得到相交节点就和链表中的环没有关系了,现在就等于说不要看环形,两个链表共用一个环,所以所两个链表的入环节点相同,如果两个链表的入环节点不相同,那么直接返回null,否则然后就和得到两个链表的相交节点思想相同。
第二种情况,两个环形链表的相交节点在环上。
从一个环形链表的入环节点的下一个节点开始遍历,如果在遍历环形链表的过程中,找到了另一个环形链表的入环节点,那么返回哪一个入环节点都可以。
//如果两个链表都是环形链表
public Node TwoCycle(Node head1,Node loop1,Node head2,Node loop2){
//入环之前,两个链表相交
int n = 0;
Node cur1 = head1;
Node cur2 = head2;
if(loop1 == loop2){
while(cur1 != loop1){
n++;
cur1 = cur1.next;
}
while(cur2 != loop2){
n--;
cur2 = cur2.next;
}
cur1 = n > 0 ? head1 : head2;
cur2 = cur1 == head1 ? head2: head1;
Math.abs(n);
while(n != 0){
n--;
cur1 = cur1.next;
}
while(cur1 != cur2){
cur1 = cur1.next;
cur2 = cur2.next;
}
return cur1;
}else{
Node cur = loop1.next;
while(cur != loop1){
if(cur == loop2){
return loop2;
}
cur = cur.next;
}
}
return null;
}
代码总和:
public Node getIntersectionNode(Node head1,Node head2){
if(head1 == null || head2 == null){
return null;
}
//如果两个链表是有环节点
Node loop1 = isNodeCycle2(head1);
Node loop2 = isNodeCycle2(head2);
if(loop1 == null && loop2 == null){
return norCycleTogther(loop1,loop2);
}
if(loop1 != null && loop2 != null){
return TwoCycle(head1,loop1,head2,loop2);
}
return null;
}
OJ链接
其实大家都知道反转链表很简单,但是这个题刻不一般哦。
反转的是链表中的部分链表,把子链表进行翻转,只有和原来在翻转链表之前,和之后的节点连接起来。
首先我们要找到翻转子链表的前一个节点fprev和后一个节点fend。在翻转子链表时,让翻转之前的子链表的头节点,指向fend,即 prev.next = fend,等到把子链表翻转之后,prev指向了子链表的最后一个节点。fprev.next = prev,然后让子链表的前一个节点,连到prev,这样就构成了一条链
class Solution {
//反转部分链表
public ListNode reverseBetween(ListNode head, int left, int right) {
int len = 0;
ListNode fprev = null;
ListNode fend = null;
ListNode cur = head;
while(cur != null){
len++;
fprev = len == left - 1 ? cur : fprev; //得到翻转链表的前一个节点
fend = len == right + 1 ? cur : fend; //得到翻转链表的最后一个节点
cur = cur.next;
}
if(left > right || left > len || right > len){
return head;
}
//得到翻转链表的第一个节点
//如果返转链表前的第一个节点为null,那就是从主链表的第一个节点开始翻转
ListNode prev = fprev == null ? head : fprev.next;
cur = prev.next;
prev.next = fend;//连接节点
ListNode curNext = null;//标记节点
while(cur != fend){
curNext = cur.next;
cur.next = prev;
prev = cur;
cur = curNext;
}
if(fprev != null){
fprev.next = prev;
return head;
}
return prev;
}
}