链表算法
1.创建单链表,添加节点,遍历链表
import org.junit.Test;
/********************************************
* 创建链表,添加节点,遍历链表 *
* ******************************************/
public class LinkList{
ListNode head;//头结点
ListNode current;//当前节点
class ListNode{
int val;//存储节点数据
ListNode next;//节点指针
public ListNode(int val){
this.val = val;
}
}
//添加节点
public void add(int val){
if(head == null){//若未创建链表
head =new ListNode(val);//创建链表
current = head;
}else{
current.next = new ListNode(val);//更改当前指针
current = current.next;
}
}
//遍历链表
public static void print(ListNode node){
while(node!=null){
System.out.println(node.val);
node = node.next;
}
}
@Test//测试函数
public void Test1(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
print(list.head);
}
}
2.获取链表长度
对链表进行遍历即可
/********************************************
* 获取链表的长度
********************************************/
public int getLength(ListNode node){
int count =0;
while(node!=null){//遍历链表
node = node.next;
count++;
}
return count;
}
@Test//测试函数
public void Test2(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(14);
System.out.println(list.getLength(list.head));
}
3.查找链表中倒数第K个节点
思路一:对链表进行遍历获取总长度,再查找倒数第K个
/********************************************
* 查找链表中倒数第K个节点
********************************************/
//遍历求总长度,再找倒数k个
public ListNode findLastKth(int k){
ListNode cur = head;
int count = 0;
//遍历链表
while(cur!=null){
cur = cur.next;
count++;
}
//判断链表是否存在目标节点
if(count<k){
return null;
}
cur = head;
//找到目标节点
for(int i = 0;i<count-k;i++){
cur = cur.next;
}
return cur;
}
思路二:如果不允许对数组进行遍历,可以设置双指针,第一个指针指向链表头部,第二个指针往后走k-1步,然后同时移动两个指针,当第二个指针到达链表尾部,则第一个指针刚好指向倒数第K个节点
//采用双指针方式找倒数k节点
public ListNode findLastKthTwo(int k){
ListNode first = head,second = head;//设置双指针
for(int i =0;i<k-1;i++){//移动第二个指针
second = second.next;
if(second==null){//判断是否存在目标节点
return null;
}
}
while(second.next!=null){//同时移动两个指针
first = first.next;
second = second.next;
}
return first;
}
@Test//测试函数
public void Test3(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(14);
list.add(16);
System.out.println(list.findLastKthTwo(2).val);
}
4.查找链表的中间节点
思路一:对链表进行遍历,求出链表的总长度,然后计算出中间节点的位置,再从头结点遍历,返回中间节点
思路二:设置双指针,第一个指针移动一步,第二个指针移动两步。当第二个指针移动到链表尾部,则第一个指针刚好指向链表中间节点。
/********************************************
* 查找链表的中间节点
********************************************/
public ListNode findMiddle(ListNode node){
ListNode first =node,second = node;//设置双指针
if(node==null){//判断链表是否为空
return null;
}
while(second!=null && second.next!=null){//移动两个指针
first = first.next;
second = second.next.next;
}
return first;
}
@Test//测试函数
public void Test4(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(14);
list.add(16);
System.out.println(list.findMiddle(list.head).val);
}
5.合并两个链表,使之依然有序
思路一:与合并有序数组类似,采用归并排序的方法来解决
/********************************************
* 合并两个有序链表,使之依然有序
********************************************/
public static ListNode mergeList(ListNode node1,ListNode node2){
//链表1为空
if(node1 == null){
return node2;
}
//链表2为空
if(node2 == null){
return node1;
}
//遍历两个链表的长度
ListNode node3 = null;//存储新链表的头结点
ListNode cur = null;//新链表当前节点
//确定新链表头结点
if(node1.val<=node2.val){
node3 = node1;
cur = node3;
node1 = node1.next;
}else if(node1.val>node2.val){
node3=cur=node2;
node2 = node2.next;
}
//合并链表
while(node1!=null && node2 != null){
if(node1.val<node2.val){
cur.next = node1;
cur = cur.next;
node1 = node1.next;
}else {
cur.next = node2;
cur = cur.next;
node2 = node2.next;
}
}
if(node1 == null){
cur.next = node2;
}else if(node2 == null){
cur.next = node1;
}
return node3;
}
@Test//测试函数
public void Test5(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(14);
list.add(16);
LinkList list2 = new LinkList();
list2.add(14);
list2.add(15);
list2.add(19);
list2.add(20);
print(mergeList(list.head,list2.head));//输出合并后的新链表
}
6.单链表反转
思路一:对于该问题如果不使用递归的思想,可以对链表进行遍历,对每一个节点的指针进行反转,将指向后面的节点的指针变为指向前部节点,再更新头结点。
/********************************************
* 单链表反转(递归非递归)
********************************************/
//非递归
public static ListNode reverseList(ListNode node){
ListNode newNode = node;//存储新链表的头节点
ListNode temp;
ListNode cur = node.next;//
while(cur!=null){//节点不为空
temp = cur.next;//暂存该节点的下一节点
cur.next = newNode;//将该节点的指针指向前一节点
newNode = cur;//更新反转链表的头结点
cur = temp;
}
node.next = null;//更新反转后的链表尾部
return newNode;
}
思路二:采用递归的思想,从头节点开始递归到尾部,然后不断反转指针
//递归
public static ListNode reverseListTwo(ListNode node){
if(node == null || node.next == null){//链表为空或只有一个节点
return node;
}
//递归到链表末尾然后反转next值
ListNode temp = reverseList(node.next);
node.next.next = node;//将该节点的下一节点的指针指向该节点
node.next = null;//该节点作为链表尾部
return temp;
}
@Test//测试函数
public void Test6(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(14);
list.add(16);
list.add(18);
print(reverseList(list.head));
}
7. 删除链表中重复元素
思路一:遍历链表,将重复的元素删除即可
例如:
链表为
1→2→1→4→1→5→2
,删除与第一个节点重复的元素后链表更新为
1→2→4→5→2
之后开始删除与第二个节点相同的元素,不断重复上述操作
/********************************************
* 删除链表中重复元素
********************************************/
public void deleteDups(){
ListNode first = head;//从头结点开始遍历
while(first!=null){
ListNode second = first;
//删除重复元素的节点
while(second.next!=null){
if(first.val == second.next.val){
ListNode temp = second.next.next;
second.next = temp;
}
second = second.next;
}
first = first.next;
}
}
思路二:使用hashtable来解决该问题,遍历链表,将链表节点放入集合,如果无法放入集合中,则表示出现重复元素,则将指针指向下一个节点,删除该节点
import java.util.Hashtable;
//使用HashtTable
public void deleteDupsTwo(){
ListNode first = head;
if(first == null)return;
Hashtable<Integer,Boolean> table = new Hashtable<>();//创建集合
table.put(first.val,true);//放入头结点
while(first.next!=null){
if(table.containsKey(first.next.val)){//判断该节点是否重复
//删除节点
ListNode temp = first.next.next;
first.next = temp;
continue;
}else{
first = first.next;
table.put(first.val,true);
}
}
}
@Test//测试函数
public void Test7(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(13);
list.add(14);
list.add(16);
list.add(16);
list.add(18);
list.deleteDupsTwo();
print(list.head);
}
8.判断链表是否有环
思路一:判断链表是否有环,可以设置双指针,两个指针的移动步数不同,如果有环路则两个指针一定会相遇。
/********************************************
* 判断链表中是否有环
********************************************/
public boolean isLoop(){
ListNode first = head;//指针
ListNode second = head;//指针
while(second!=null && second.next!=null){//移动指针,判断是否相遇
first = first.next;
second = second.next.next;
if(second == first){
return true;
}
}
return false;
}
@Test//测试函数
public void Test8(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(13);
list.add(14);
list.add(16);
list.add(16);
list.add(18);
list.current.next = list.head.next.next;
System.out.println(list.isLoop());
}
9.计算链表环的长度
思路一:计算环的长度可以通过设置一快一慢fast和slow双指针来解决。slow指针移动一步,fast指针移动两步,则当两个指针相遇时,fast指针停止移动,当slow指针走一个环路后两个指针再次相遇,则计算出环的长度。
/********************************************
* 计算链表环的长度
********************************************/
public int getLoopLen(){
if(head == null)return 0;
ListNode first = head;
ListNode second = head;
//计算指针相遇节点
while(second!=null && second.next!=null){
first = first.next;
second = second.next.next;
if(first == second){
break;
}
}
int count=0;//存数环路长度
//计算环路长度
while(second!=null){
second = second.next;
count++;
if(first == second){
return count;
}
}
return 0;//无环路
}
@Test//测试函数
public void Test9(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(13);
list.add(14);
list.add(16);
list.add(16);
list.add(18);
list.current.next = list.head.next.next;
System.out.println(list.getLoopLen());
}
10.如果链表有环,计算环的入口
思路一:如果链表有环,计算环路的入口,这里要了解一个思想:
(1)当fast指针与slow指针相遇时,slow肯定没有走完链表,而fast已经在环路中走了n(n>= 1)圈。假设slow走了s步,那么fast走了2s步。fast的步数等于s走的加上环里转的n圈,环路长度为r,所以有:2s = s + nr。因此,s = nr。
(2)设整个链表长为L,入口距离相遇点X,起点到入口的距离为a。因为slow指针并没有走完一圈,所以:a + x = s,带入第一步的结果,有:a + x = nr = (n-1)r + r = (n-1)r + L - a;即:a = (n-1)r + L -a -x;
这说明:从头结点到入口的距离,等于转了(n-1)圈以后,相遇点到入口的距离。
因此,我们可以在链表头、相遇点各设一个指针,每次各走一步,两个指针必定相遇,且相遇第一点为环入口点
/********************************************
* 计算链表环的入口
********************************************/
public ListNode getLoopNode(){
ListNode first = head;
ListNode second = head;
//计算指针相遇点
while(second != null && second.next!=null){
first = first.next;
second = second.next.next;
if(second == first){
break;
}
}
first = head;
//计算环路入口
while(first!=null && second!=null){
first = first.next;
second = second.next;
if(first == second){
return first;
}
}
return null;//没有环路
}
@Test//测试函数
public void Test10(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(13);
list.add(14);
list.add(16);
list.add(16);
list.add(18);
list.current.next = list.head.next.next.next;
System.out.println(list.getLoopNode().val);
}
11.从尾到头打印链表
思路一:从尾到头打印链表这性质与栈先进后出有些类似,可以通过栈来解决该问题。遍历链表,将各节点的值入栈操作,遍历完链表进行出栈操作即可。
import java.util.Stack;
/********************************************
* 从尾到头打印链表
********************************************/
public void reversePrint(){
ListNode cur = head;
Stack<Integer> stack = new Stack<>();//创建栈
while(cur!=null){
stack.push(cur.val);//入栈操作
cur = cur.next;
}
while(!stack.empty()){//出栈操作
System.out.println(stack.pop());
}
}
@Test//测试函数
public void Test11(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(13);
list.add(14);
list.add(16);
list.add(16);
list.add(18);
list.reversePrint();
}
12.判断两个链表是否相交?若相交,输出交点
思路一:判断两个链表是否相交首先要判断两个链表是否有环路,这里可以细分为三种:都没有环路;一个链表有环路,一个链表没有环路;两个链表都没有环路。之后判断两个链表是否相交。如果没有环路的两个链表相交则链表尾节点一定相同;如果一个链表有环路,一个没有则肯定不相交;如果两个链表都有环路各自遍历链表,若相遇则相交。两个有环路的链表相交交点有可能在环路上,也可能在环路外,也要细分。
/********************************************
* 两个链表是否相交?若相交,输出交点
********************************************/
//不是道链表是否相交,且不知道是否有环
public ListNode getIntersecNode(LinkList list2){
//判断链表是否有环
if(head == null || list2.head ==null)return null;
boolean b1 = this.isLoop();//标记链表1是否有环
boolean b2 = list2.isLoop();//标记链表2是否有环
if(b1 != b2){//不相交
return null;
}
if(!b1 && !b2){//二者都没有环路
return this.getNoLoopIntersecNode(list2);
}else{//二者都有环
return this.getLoopIntersecNode(list2);
}
}
//二者都没有环路
public ListNode getNoLoopIntersecNode(LinkList list2){
if(head == null || list2.head ==null)return null;//链表为空
ListNode first = head;//存储链表1头结点
ListNode second = list2.head;//存储链表2头结点
int count1 = 1;//存储链表1长度
int count2 = 1;//存储链表2长度
//判断是否相交
while(first.next!=null){
first = first.next;
count1++;
}
while(second.next!=null){
second = second.next;
count2++;
}
if(first != second){//不相交
return null;
}else{//链表相交
first = head;
second = list2.head;
if(count1>count2){
int diff = count1-count2;//两个链表的长度差
while(diff--!=0){
first = first.next;
}
}else{
int diff = count2-count1;
while(diff--!=0){
second = second.next;
}
}
//计算交点
while(first!=null){
if(first == second){
return first;
}else{
first = first.next;
second = second.next;
}
}
return null;
}
}
//两个链表都有环路时
public ListNode getLoopIntersecNode(LinkList list2){
if(head == null || list2.head ==null)return null;
//判断是否相交
ListNode first = this.getLoopNode();//存储链表1环的入口
ListNode second = list2.getLoopNode();//存储链表2环的入口
if(first == second){//有交点,交点在环外
//遍历求环外长度
first = head;
int length = 0;//存储链表1的环外长度
while(first!=second){
first = first.next;
length++;
}
second = list2.head;
while(second!=first){
second = second.next;
length--;
}
//判断哪个链表长
if(length==0){
return first;//两个链表相同
}else if(length>0){//链表1长
first = head;
second = list2.head;
}else{
first = list2.head;//链表2长
second = head;
}
length = Math.abs(length);//两个链表的长度差
while(length--!=0){
first = first.next;
}
//计算链表交点
while(first != second){
first = first.next;
second = second.next;
}
return first;
}else{//交点不外环外
//交点是否在环内
ListNode temp = first;
while(first!=second){
first= first.next;
if(first==temp){//没有交点
return null;
}
}
return temp;//输出交点
}
}
@Test//测试函数
public void Test12(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(13);
list.add(14);
list.add(15);
list.add(16);
list.add(16);
list.add(18);
LinkList list2 = new LinkList();
list2.add(25);
list2.add(25);
list2.add(25);
list2.add(25);
list2.current.next = list.head.next.next.next.next.next;
list.current.next = list.head.next.next.next.next;
System.out.println(list.getIntersecNode(list2).val);
}
13.在指定位置添加节点
思路一:
/********************************************
* 指点位置k处添加节点
********************************************/
public boolean addIndex(int k,ListNode node){
ListNode first = this.head;//存储头结点
int length = this.getLength(head);//获取链表长度
if(k>length){
return false;
}
if(k == 0){//添加在链表头部
ListNode temp = this.head;
this.head = node;
this.head.next = temp;
}else if(k == length){//添加在链表尾部
this.current.next = node;
return true;
}else{
int count = 0;
while(first!=null){
count++;
if(k==count){
ListNode temp = first.next;
first.next = node;
node.next = temp;
return true;
}
first = first.next;
}
}
return false;
}
@Test//测试函数
public void Test13(){
LinkList list = new LinkList();
list.add(12);
list.add(13);
list.add(13);
list.add(14);
list.add(16);
list.add(16);
list.add(18);
list.addIndex(8,new ListNode(55));
print(list.head);
}