算法中的数据结构之链表

定义一个结点结构

public class Node {
    //使用内部类定义链表的每个节点
    public int value;//表示此节点上的数
    public Node next;//此节点的下一个节点
    public Node pro;//此节点的上一个节点
    //构造函数,给此节点赋值
    public Node(int data){
        this.value = data;
    }
    public Node(){

    }
}

题目一

反转单向链表和双向链表

分别实现反转单向链表和反转双向链表的函数

要求:如果链表长度为N,时间复杂度要求为O(N),额外空间复杂度要求为O(1)

单向链表的反转

public class ReOneLink {

    //进行反转,借用数组反转
    public static Node reOneLink(Node node){
        List<Node> nodes = new ArrayList<Node>();
        while(node != null){
            nodes.add(node);
            node = node.next;
        }
        Node head = new Node();
        Node n = new Node();
        for(int i=nodes.size()-1;i>=0;i--){
            n = nodes.get(i);
            n = n.next;
        }
        head = nodes.get(nodes.size()-1);
        return head;
    }
}

双向链表的反转
结点结构的定义

    public static class Node{
        public int value;//表示此节点上的数
        public Node next;//此节点的下一个节点
        public Node pro;//此节点的上一个节点
        //构造函数,给此节点赋值
        public Node(int data){
            this.value = data;
        }
    }
    public class ReTowLink {
    //对于双向链表的特点,头节点有下一个next没有上一个,尾节点有上一个没有上一个
    public static Node reTowLink(Node node){
        Node[] nodes = null;
        int i = 0;
        while(node.next != null){
            nodes[i++] = node;
            node = node.next;
        }
        Node n = null;
        int length = nodes.length-1;
        for(i=length;i>=0;i--){
            node = nodes[length];
            n = node;
            node.next = node.pro;
            node.pro = n.next;
        }
        nodes[0].next = null;//变成尾结点
        nodes[nodes.length-1].pro = null;//变成头结点
        return nodes[nodes.length-1];
    }

}

判断一个链表是否为回文结构

//题目:给定一个单链表的头节点head,请判断该链表是否为回文结构
//例子:1->2->3,返回true;1->2->3,返回false
//例子:如果链表长度为N,时间复杂度达到O(N),额外空间复杂度达到O(1)

方法1:使用栈(笔试的时候用就行)


    public static boolean isPalindrome1(Node head){
        //使用java中定义的栈结构
        Stack<Node> stack = new Stack<>();
        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;
    }

方法2:使用n/2的方法,找快慢指针

    //方法2:使用n/2的方法,找快慢指针
    /*
    有两个指针,一个一次跳一个,一个一次跳两个,第二个跳到尾的时候,第一个调到一半位置
    实际就是想找中点位置,因为链表不能直接找到,所以靠这种方法找到
     */

    public static boolean isPalindrome2(Node head){
        //判断链表是否为空,或者链表的下一个节点是否为空
        if(head == null || head.next == null){
            return false;
        }
        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<>();//创建一个栈结构
        while(right != null){
            stack.push(right);//此时的处于一半的位置,入栈
            right = right.next;
        }
        while (!stack.isEmpty()){//栈不为空就循环
            if(head.value != stack.pop().value){//进行比较,回文判断,有一个不一样的就返回false
                return false;
            }
            head = head.next;//当前节点指向下一个
        }
        return true;
    }

方法3:十分重要(面试谈资)

//方法3:就是降低了空间复杂度,降低了一半,不使用栈结构,反转链表
    public static boolean isPalindrome3(Node head){
        if (head == null || head.next == null){
            return true;
        }
        Node n1 = head;
        Node n2 = head;
        while(n2.next != null && n2.next.next != null){
            n1 = n1.next;
            n2 = n2.next.next;
        }
        n2 = n1.next;
        n1.next = null;
        Node n3 = null;
        //后半部分的链表反转代码,可以画图进行演示
        while(n2 != null){
            n3 = n2.next;
            n2.next = n1;
            n1 = n2;
            n2 = n3;
        }
        n3 = n1;//让n3存储最后一个结点,用于最后的反转,因为n1要移动
        n2 = head;//n2指向头结点,开始比较
        boolean res = true;
        while(n1 != null && n2 != null){
            if(n1.value != n2.value){
                res = false;
                break;
            }
            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;
    }

题目二

将单向链表按某值划分成左边小、中间相等、右边大的形式
给定一个单链表的头节点head,节点的值类型是整型,再给定一个整数pivot。实现一个调整链表的函数,将链表调整为左部分都是值小于pivot的节点,中间部分都是值等于pivot的结点,右部分都是值大于pivot的节点

进阶:在实现原问题功能的基础上增加如下的要求
要求:调整后所有小于pivot的节点之间的相对顺序和调整前一样
要求:调整后所有等于pivot的节点之间的相对顺序和调整前一样
要求:调整后所有大于pivot的节点之间的相对顺序和调整前一样
要求:时间复杂度请达到O(N),额外空间复杂度请达到O(1)

    public static Node listPartition2(Node head,int pivot){
        //以下定义了三个区域,之后遍历完之后,会将这三个区域链接起来
        Node sH = null;
        Node sT = null;
        Node eH = null;
        Node eT = null;
        Node mH = null;
        Node mT = null;
        Node next = null;//定义一个空节点
        while(head != null){
            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(mH == null){
                    mH = head;
                    mT = head;
                }else{
                    mT.next = head;
                    mT = head;
                }
            }
            head = next;//将head节点下移一位
        }
        //进行连接每个区域,难点在于不知道哪个区域有值,所有要进行判断
        if(sH != null){
            sT.next = eH;//连接等于的区域,不管等于的区域有没有值
            eT = eT == null ? sT : eT;//下一步,假如等于区域有值,则eT为等于区域的尾,如果等于区域没值,则eT为小于区域的sT
        }

        //上面的if,不管跑了没有,et
        if(eT != null){//表示上面两个区域至少有一个
            eT.next = mH;
        }
        return sH != null ? sH : (eH != null ? eH : mH);
    }

题目3:一种特殊的单链表节点类描述如下

class Node{
  int value;
  Node next;
  Node rand;
  Node(int val){
    value = val;
  }
}

rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。给定一个由Node节点类型组成的无环单链表的头节点head,请实现一个函数完成这个链表的复制,并返回复制之后新链表的头节点

要求:时间复杂度O(N),额外空间复杂度O(1)

方法1:使用hash表的方式

//rand指针的
    public static Node copyListWithRand1(Node head){
        HashMap<Node, Node> map = new HashMap<>();
        Node cur = head;
        while(cur != null){
            //将旧数据的值赋值给新数据
            map.put(cur,new Node(cur.value));
            cur = cur.next;
        }
        cur = head;//数据的指针重新指向头节点
        while(cur != null){
            //cur 老
            //map.get(cur)新
            map.get(cur).next = map.get(cur.next);//将旧数据的next指针赋值给新数据
            map.get(cur).rand = map.get(cur.rand);//将旧数据的rand指针赋值给新数据
            cur = cur.next;//数据下移
        }
        return map.get(head);
    }

方法2:不使用hash函数,将数据存在一个数组

//方法2
    public static Node copyListWithRand2(Node head){
        //链表不为null
        if(head == null){
            return null;
        }
        //定义一个节点,用于遍历的时候
        Node cur = head;
        Node next = null;//定义一个空节点,用于存储当前cur的下一个节点,一会断开
        while(cur != null){
            next = cur.next;//方便后面直接找到
            cur.next = new Node(cur.value);//将cur的下一个变成和它一样的节点,重新给cur一个指向,此时断开了与上面的联系
            cur.next.next = next;//原本是cur的下一个(也就是第二个节点),现在是下一个的下一个,也就是第三个节点
            cur = next;//cur向下移动两个,也就是cur = cur.next.next
        }
        cur = head;//让当前的节点重新回到头结点
        Node curCopy = null;//定义一个空节点,用于复制的时候使用
        while(cur != null){
            next = cur.next.next;//存储第三个节点,如果不事先存储,一会断开会找不到
            curCopy = cur.next;//第二个节点,也就是和第一个一样的结点,需要被赋予和第一个一样的rand指针一样的东西
            curCopy = cur.rand != null ? cur.rand.next : null;//赋予rand指针
            cur = next;//指针移到第三个,接着循环
        }
        Node res = head.next;//将第二个节点变成结果的头
        while(cur != null){
            next = cur.next.next;//空节点next 表示此时的第三个节点
            curCopy = cur.next;//空节点curCopy 表示此时的第二个节点
            cur.next = next;//此时改变第一个节点指向为第三个节点,也就是变回原来的单链表
            curCopy.next = next != null ? next.next : null;//赋予第二个节点指向,第四个节点或者为空
            cur = next;//将指针移到第三个节点,接着循环
        }
        return res;//最后返回复制后的头结点
    }

两个单链表相交的一系列问题

给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null

要求:如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度请达到O(1).

对于找入环的第一个节点

方法1:使用HashSet方法

①把链表添加进hashset

②添加进去之前,进行判断hashset里面是否有此节点

③有的话,不仅有环,而且此节点就是第一个节点

④否则,无环

代码省略

方法2:快慢指针法

①定义两个指针,都指向头

②快指针一次走两个节点,慢指针一次走一个节点

③如果有环,这俩指针一定会在环中相遇

④相遇的时候,慢指针不动,快指针指向头结点

⑤以后每次走一个节点,再次相遇的时候,一定是环的第一个节点

找到链表第一个入环节点,如果无环,返回null

//找到链表第一个入环节点,如果无环,返回null
    public static Node getLoopNode(Node head){//传一个链表的头节点
        //头节点不为空,头节点的下一个节点不为空,头节点下一个节点的下一个节点不为空
        if(head == null || head.next == null || head.next.next == null){
            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;//快指针跑到头节点
        while(n1 != n2){
            n1 = n1.next;//慢指针一次走一个节点
            n2 = n2.next;//快指针一次也走一个节点
        }
        return n1;
    }

对于找相交的节点

如果两个链表都无环,返回第一个相交的节点,如果不相交,返回null

①两个链表找相交的节点的时候,会将指针指向同等地位

②举个例子,假如说一个链表长度10,另一个长度5

③相交的节点,一定是长链表5以后的节点

④让长链表先走到五位置,同时走,比较两个结点是否相同

⑤相同的时候,一定是相交,而且是第一个相交的节点

    //如果两个链表都无环,返回第一个相交节点,如果不相交,返回null
    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--;
            cur1 = cur1.next;
        }
        if(cur1 != cur2){
            return null;
        }
        cur1 = n > 0 ? head1 : head2;//谁长,谁的头变成cur1
        cur2 = cur1 == head1 ? head2 : head1;//谁短,谁的头变成cur2
        n = Math.abs(n);
        while(n != 0){
            n--;
            cur1 = cur1.next;
        }
        while(cur1 != cur2){
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }

题目的最终解法

1、可能的结构

2、解法

    //有两个有环链表,返回第一个相交节点,如果不相交返回null
    public static Node bothLoop(Node head1,Node loop1,Node head2,Node loop2){
        Node cur1 = null;
        Node cur2 = null;
        if(loop1 == loop2){
            //if里面的代码是进行相交节点的查找
            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;
            //此时进行第三种结构的判断
            while(cur1 != loop1){
                if(cur1 == loop2){
                    return loop1;
                }
                cur1 = cur1.next;
            }
            //第三种情况走不通就是第一种情况
            return null;
        }
    }
posted @ 2022-01-22 19:29  刘小呆  阅读(46)  评论(0编辑  收藏  举报