1.链表反转

单链表的反转,是面试中的一个高频题目。
需求:
原链表中数据为:1->2->3>4
反转后链表中数据为:4->3->2->1
反转API:
public void reverse():对整个链表反转
public Node reverse(Node curr):反转链表中的某个结点curr,并把反转后的curr结点返回
使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。

链表反转 java 递归 java反转链表原理思想_链表反转 java 递归


链表反转 java 递归 java反转链表原理思想_链表_02

public class Test2 <T> implements Iterable<T>{
    public static void main(String[] args)throws Exception {
        Test2<String> list = new Test2<>();
        list.insert(0, "张三");
        list.insert(1, "李四");
        list.insert(2, "王五");
        list.insert(3, "赵六");
        for (String s : list) {
            System.out.println(s);
        }
        System.out.println("============");
        list.change();
        for(String s:list){
            System.out.println(s);
        }

    }
    private static Node head;
    private static int N;

    public static void change() {
        if (N==0){//当前是空链表,不需要反转
            return;
        }
        reverse(head.next);
    }
    /**
     *
     * @param curr 当前遍历的结点
     * @return 反转后当前结点上一个结点
     */
    public static Node reverse(Node curr){
        if(curr.next==null){//已经到了最后一个元素
            head.next=curr;//反转后,头结点应该指向原链表中的最后一个元素
            return curr;
        }
        //当前结点的上一个结点
        Node pre = reverse(curr.next);
        pre.next=curr;
        curr.next= null;//当前结点的下一个结点设为null
        return curr;//返回当前结点
    }

    public Test2() {
        this.head = new Node(null,null);//初始化头结点、
        this.N=0;//初始化元素个数
    }

    public void insert(T t){//向链表中添加元素t
        Node n = head;//找到最后一个节点
        while(n.next!=null){
            n=n.next;
        }
        Node newNode = new Node(t,null);
        n.next = newNode;
        N++;//链表长度+1
    }

    public void insert(int i,T t){//向指定位置i处,添加元素t
        //寻找位置i之前的结点
        Node pre = head;
        for(int index=0;index<i-1;index++){
            pre=pre.next;
        }
        //位置i的结点
        Node curr = pre.next;
        //构建新的结点,让新结点指向位置i的结点
        Node newNode= new Node(t,curr);
        pre.next = newNode;
        //长度+1
        N++;
    }



    @Override
    public Iterator<T> iterator() {
        return new LIterator();
    }


    private class LIterator implements Iterator{
        private Node n;
        public LIterator(){
            this.n= head;
        }

        @Override
        public boolean hasNext() {
            return n.next != null;
        }

        @Override
        public Object next() {
            n=n.next;
            return n.item;
        }
    }

    public static class Node<T>{
        public T item;//存储元素
        public Node next;//指向下一个结点
        public Node(T item,Node next){
            this.item = item;
            this.next= next;
        }
    }
}
李四
王五
赵六
张三
============
张三
赵六
王五
李四

2.快慢指针

快慢指针指的是定义两个指针,这两个指针的移动速度一快一慢,以此来制造出自己想要的差值。我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。

1)中间值问题

public class Test2 <T> {
    public static void main(String[] args)throws Exception {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);
        //完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //查找中间值
        String mid = getMid(first);
        System.out.println("中间值为:"+mid);


    }

    /**
     * @param first 链表的首结点
     * @return 链表的中间结点的值
     */
    public static String getMid(Node<String> first) {
        return null;
    }
    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }

需求:
请完善测试类Test中的getMid方法,可以找出链表的中间元素值并返回。
利用快慢指针,我们把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的。
如下图,最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。

链表反转 java 递归 java反转链表原理思想_链表反转 java 递归_03

public class Test2 <T> {
    public static void main(String[] args)throws Exception {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);
        //完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //查找中间值
        String mid = getMid(first);
        System.out.println("中间值为:"+mid);


    }

    /**
     * @param first 链表的首结点
     * @return 链表的中间结点的值
     */
    public static String getMid(Node<String> first) {
        Node<String> slow = first;
        Node<String> fast = first;
        while(fast!=null && fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow.item;
    }
    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}
中间值为:dd

2)单向链表是否有环问题

链表反转 java 递归 java反转链表原理思想_java_04

public class Test2 <T> {
    public static void main(String[] args)throws Exception {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);
        //完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //产生环
        seven.next = third;
        //判断链表是否有环
        boolean circle = isCircle(first);
        System.out.println("first链表中是否有环:"+circle);
    }

    /**
     * 判断链表中是否有环
     * @param first 链表首结点
     * @return ture为有环,false为无环
     */
    public static boolean isCircle(Node<String> first) {
        return false;
    }
    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}
first链表中是否有环:false

需求:
请完善测试类Test中的isCircle方法,返回链表中是否有环
使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇,只要相遇那么就说明有环。

链表反转 java 递归 java反转链表原理思想_链表反转 java 递归_05


链表反转 java 递归 java反转链表原理思想_链表_06

public class Test2 <T> {
    public static void main(String[] args)throws Exception {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);
        //完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //产生环
        seven.next = third;
        //判断链表是否有环
        boolean circle = isCircle(first);
        System.out.println("first链表中是否有环:"+circle);
    }

    /**
     * 判断链表中是否有环
     * @param first 链表首结点
     * @return ture为有环,false为无环
     */
    public static boolean isCircle(Node<String> first) {
        Node<String> slow = first;
        Node<String> fast = first;
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if (fast.equals(slow)){
                return true;
            }
        }
        return false;
    }
    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}

3)有环链表入口问题

需求:

请完善Test类中的getEntrance方法,查找有环链表中环的入口结点

当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识,这里略,只讲实现。

链表反转 java 递归 java反转链表原理思想_结点_07

public class Test2 <T> {
    public static void main(String[] args)throws Exception {
        Node<String> first = new Node<String>("aa", null);
        Node<String> second = new Node<String>("bb", null);
        Node<String> third = new Node<String>("cc", null);
        Node<String> fourth = new Node<String>("dd", null);
        Node<String> fifth = new Node<String>("ee", null);
        Node<String> six = new Node<String>("ff", null);
        Node<String> seven = new Node<String>("gg", null);
        //完成结点之间的指向
        first.next = second;
        second.next = third;
        third.next = fourth;
        fourth.next = fifth;
        fifth.next = six;
        six.next = seven;
        //产生环
        seven.next = third;
        //判断链表是否有环
        boolean circle = isCircle(first);
        System.out.println("first链表中是否有环:"+circle);
        System.out.println("=============");
        //查找环的入口结点
        Node<String> entrance = getEntrance(first);
        System.out.println("first链表中环的入口结点元素为:"+entrance.item);
    }
    /**
     * 查找有环链表中环的入口结点
     * @param first 链表首结点
     * @return 环的入口结点
     */
    public static Node getEntrance(Node<String> first) {
        Node<String> slow = first;
        Node<String> fast = first;
        Node<String> temp = null;
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow=slow.next;
            if (fast.equals(slow)){
                temp = first;
                continue;
            }
            if (temp!=null){
                temp=temp.next;
                if (temp.equals(slow)){
                    return temp;
                }
            }
        }
        return null;
    }

    /**
     * 判断链表中是否有环
     * @param first 链表首结点
     * @return ture为有环,false为无环
     */
    public static boolean isCircle(Node<String> first) {
        Node<String> slow = first;
        Node<String> fast = first;
        while(fast!=null && fast.next!=null){
            fast = fast.next.next;
            slow = slow.next;
            if (fast.equals(slow)){
                return true;
            }
        }
        return false;
    }
    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}
first链表中是否有环:true
=============
first链表中环的入口结点元素为:cc

3.循环链表

循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可。

链表反转 java 递归 java反转链表原理思想_java_08

public class LinkList {
    public static void main(String[] args) {
        Node<Integer> first = new Node<Integer>(1,null);//构建结点
        Node<Integer> second = new Node<Integer>(2,null);
        Node<Integer> third = new Node<Integer>(3,null);
        Node<Integer> fourth = new Node<Integer>(4,null);
        first.next= second;//生成链表
        second.next = third;
        third.next = fourth;
        //构建循环链表,让最后一个结点指向第一个结点
        fourth.next = first;
    }
    public static class Node<T>{
        public T item;//存储元素
        public Node next;//指向下一个结点
        public Node(T item,Node next){
            this.item = item;
            this.next= next;
        }
    }
}

4.约瑟夫问题

问题描述:

传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与

第31个位置,从而逃过了这场死亡游戏 。

问题转换:

41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。

1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;

2.自退出那个人开始的下一个人再次从1开始报数,以此类推;

3.求出最后退出的那个人的编号。

链表反转 java 递归 java反转链表原理思想_数据结构_09


解题思路:

1.构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;

2.使用计数器count,记录当前报数的值;

3.遍历链表,每循环一次,count++;

4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;

public class Test2 <T> {
    public static void main(String[] args)throws Exception {
        //1.构建循环链表
        Node<Integer> first = null;
//记录前一个结点
        Node<Integer> pre = null;
        for (int i = 1; i <= 41; i++) {
//第一个元素
            if (i==1){
                first = new Node(i,null);
                pre = first;
                continue;
            }
            Node<Integer> node = new Node<>(i,null);
            pre.next = node;
            pre = node;
            if (i==41){
//构建循环链表,让最后一个结点指向第一个结点
                pre.next=first;
            }
        }
//2.使用count,记录当前的报数值
        int count=0;
//3.遍历链表,每循环一次,count++
        Node<Integer> n = first;
        Node<Integer> before = null;
        while(n!=n.next){
//4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
            count++;
            if (count==3){
//删除当前结点
                before.next = n.next;
                System.out.print(n.item+",");
                count=0;
                n = n.next;
            }else{
                before=n;
                n = n.next;
            }
        }
        /*打印剩余的最后那个人*/
        System.out.println(n.item);
    
    }

    //结点类
    private static class Node<T> {
        //存储数据
        T item;
        //下一个结点
        Node next;
        public Node(T item, Node next) {
            this.item = item;
            this.next = next;
        }
    }
}
3,6,9,12,15,18,21,24,27,30,33,36,39,1,5,10,14,19,23,28,32,37,41,7,13,20,26,34,40,8,17,29,38,11,25,2,22,4,35,16,31