链表算法——基础部分

链表算法

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. 删除链表中重复元素

思路一:遍历链表,将重复的元素删除即可
例如:
链表为 1214152 ,删除与第一个节点重复的元素后链表更新为 12452
之后开始删除与第二个节点相同的元素,不断重复上述操作

/********************************************
* 删除链表中重复元素
********************************************/
    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);
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值