1 链表
链表线性存储的一种结构,每一个节点存储了值对象,以及引用对象(前区节点和后节点)
java定义的基本结构
class Node{
int value;
Node next;
public Node() {
}
public Node(int value, Node next) {
this.value = value;
this.next = next;
}
@Override
public String toString() {
return "Node{" +
"value=" + value +
", next=" + next +
'}';
}
}
2 链表经典
2.1 链表反转
已知链表12345,求反转54321
其根本思想是:
遍历旧的链表,将下一个节点指向其上一个节点,中间利用临时变量存储。
public class NodeDemo {
@Test
public void testRevertNode(){
Node node=new Node(1,new Node(2,new Node(3,new Node(4,new Node(5,null)))));
System.out.println(node.toString());
Node revertNode=revertNode(node);
System.out.println(revertNode.toString());
}
public Node revertNode(Node oldNode){
Node newNode=null;//新的头结点
while (oldNode!=null){//循环遍历旧的节点
Node next=oldNode.next;//保存下一个节点到临时变量
oldNode.next=newNode;//将旧的当前节点指向新的节点(可以想象旧的链表遍历第一次的时候,旧的第一个元素,反转为最后一个元素,next肯定为null,第二次的时候,旧的第二个元素,next应该指向旧的第一个元素(也就是已经赋值的newNode))
newNode=oldNode;//新的节点,为当前节点
oldNode=next;//旧的节点,赋值为下个节点
}
return newNode;
}
}
2.2 链表区间反转(m,n)
已知链表12345,求反转0,3
结果:32145
其核心思想是,根据m找到节点的前驱,然后根据n-m+1反转链表,在head指针移动的过程中,保存记录变量,待反转完成之后,将前驱指针下一个节点指向新的链表,并综合考虑临界值的情况
具体的实现代码如下:
@Test
public void testRevertNodeMN(){
Node node=new Node(1,new Node(2,new Node(3,new Node(4,new Node(5,null)))));
System.out.println(node.toString());
Node revertNode=reverNodeMN(node,0,3);
System.out.println(revertNode.toString());
}
public Node reverNodeMN(Node head,int m,int n){
int length=n-m+1;
Node preHead=null;
Node result=head;
while (head!=null&&m>0){
preHead=head;
head=head.next;
m--;
}
Node modifyHead=head;//当前的节点反转之后会变成最后一个,暂时记录一下
Node newHead=null;
while (head!=null&&length>0){
Node next=head.next;
head.next=newHead;
newHead=head;
head=next;
length--;
}
modifyHead.next=head;//head移动到这里之后,把之前的暂时记录的next指向当前的指针
if(preHead!=null){
preHead.next=newHead;//如果前驱不指向新的链表
}else {
result=newHead;//前驱为空直接重新赋值
}
return result;
}
2.3 单链表倒数第k个元素
思路分析:
可以遍历链表,设置两个指针节点,fist和second first先移动k步,second后移动
循环遍历frst到结束,second指针的位置即为第k个节点
具体实现:
@Test
public void testFindNodeK(){
Node node=new Node(1,new Node(2,new Node(3,new Node(4,new Node(5,null)))));
System.out.println(node.toString());
Node kNode=findKInNode(node,1);
System.out.println(kNode.toString());
}
public Node findKInNode(Node head,int k){
Node first=head;
Node second=head;
for(int i=0;i
2.4 两个链表求交点
判断两个链表中是否存在交点,如果存在则返回交点
思路1 空间换时间
利用HashSet,遍历链表A将节点放入集合,然后遍历链表B,判断在集合总是否已存在,若存在则返回节点
@Test
public void testFindJDNode(){
Node k=new Node(6,null);
Node nodeA=new Node(1,new Node(2,new Node(3,new Node(4,new Node(5,k)))));
Node nodeB=new Node(1,new Node(3,k));
Node kNode=findJDNode(nodeA, nodeB);
System.out.println(kNode.toString());
};
public Node findJDNode(Node headA,Node headB){
Set nodes=new HashSet<>();
while (headA.next!=null){
nodes.add(headA);
headA=headA.next;
}
nodes.add(headA);
while (headB!=null){
if(nodes.contains(headB)){
return headB;
}
headB=headB.next;
}
return null;
}
思路二 时间换空间
先判断两个链表的长度 ,然后将链表长的移动到和短的同等位置,然后循环遍历A和B链表,若出现相等即为交点,具体实现如下
@Test
public void testFindJDNode2(){
Node k=new Node(6,null);
Node nodeA=new Node(1,new Node(2,new Node(3,new Node(4,new Node(5,k)))));
Node nodeB=new Node(1,new Node(3,k));
Node kNode=findJDNode2(nodeA, nodeB);
System.out.println(kNode.toString());
}
public Node findJDNode2(Node headA,Node headB){
int a=getNodeLength(headA);
int b=getNodeLength(headB);
int count=0;
if(a>b){//则指针移动b-a
count=a-b;
while (count>0){
headA=headA.next;
count--;
}
}else if(a0){
headB=headB.next;
count--;
}
}
while (headA!=null&&headB!=null){
if(headA==headB){
return headA;
}
headA=headA.next;
headB=headB.next;
}
return null;
}
private int getNodeLength(Node node){
int count=0;
while (node!=null){
count++;
node=node.next;
}
return count;
}
2.5 链表求环
检测链表是否存在环路,如果存在,即返回链表的首次环节点
思路一: 可以参考双向链表的交点的思路,同样利用hashSet集合,一边遍历,一边判断集合中是否存在,若存在则返回该节点,该节点即为环入口节点。
@Test
public void testFindCircle(){
Node k=new Node(6,null);
Node nodeA=new Node(1,new Node(2,new Node(3,new Node(4,new Node(5,k)))));
k.next=nodeA;
Node kNode=findCirCle(nodeA);
System.out.println(kNode.toString());
}
public Node findCirCle(Node node){
Set nodes=new HashSet<>();
while (node!=null){
if(nodes.contains(node)){
return node;
}else {
nodes.add(node);
}
node=node.next;
}
return null;
}
思路二 : 快慢指针赛跑
设置一个快指针和一个慢指针,遍历快指针,当快指针和慢指针相等,则说明存在环,记录该交点,同事遍历头指针和交点相等返回相遇点即为初始的环入点
具体实现如下
@Test
public void testFindCircle2(){
Node node1=new Node(1,null);
Node node2=new Node(2,null);
Node node3=new Node(3,null);
Node node4=new Node(4,null);
Node node5=new Node(5,null);
Node node6=new Node(6,null);
node1.next=node2;
node2.next=node3;
node3.next=node4;
node4.next=node5;
node5.next=node6;
node6.next=node2;
Node kNode=findCirCle2(node1);
System.out.println(kNode.toString());
}
public Node findCirCle2(Node node){
Node fast=node.next.next;
Node slow=node.next;
Node meet=null;
while (fast!=null){
if(fast==slow){
System.out.println(fast);
meet=fast;
break;
}
fast=fast.next.next;
slow=slow.next;
}
while (node!=null&&meet!=null){
if(node==meet){
return node;
}
node=node.next;
meet=meet.next;
}
return null;
}