链表 面试题(Java)

链表的逆置

给定一个单链表,实现链表的逆置功能

思路
      在前面的单链表练习中,讲到了头插头插的链表和正常的序列是刚好相反的,利用这点来进行链表的逆置

代码讲解
      使用cur指针指向第一个数据域,把head单独拿出来,重新对head头插一次,在头插中,cur的next域要先把head的next域接过来,然后把head的next置为cur,这样做的话会丢失cur后面的所有未插入节点,就要使用一个指针next来进行记录,然后cur从next中拿值,不断的更新next。

    private void reverse() {
        Entry cur = head.next;
        Entry next = null;
        //开始插入第一个,先把head的next置为空
        head.next = null;
        while (cur != null) {
        	//更新next,next用来保存cur后面的未插入的指针
            next = cur.next;
            //头插
            cur.next = head.next;
            head.next = cur;
            //更新cur
            cur = next;
        }
    }
合并两个有序的链表

      给定两个有序的链表,写一个函数实现合并两个链表的功能,而且合并后的链表是有序的。

思路
      使用归并的思想,两个指针分别指向两个链表,哪个指针所指的节点数据域小,那么就把该节点连接到合并后的序列上

代码讲解
      首先明确该方法是链表类的一个方法,所以第一条链表就是本身的head,第二条链表是传进来的list2,分别用两个指针指向单链表,last指向head意思是把list2链表合并到list1上面,所以合并后的序列是last,然后在p和q都没有到最后的情况下,哪个小就接入到last后面,跳出循环后,哪个链表有剩余直接插入到last后面。

在这里插入图片描述
这里的两个链表都有4,但是我比较的条件是
在这里插入图片描述
所以会更改路径,先连接q的4,这块可以写成<=

代码实现

    public void merge(MiNiLinkedListTest list2) {
        //用p,q分别引用两个单链表
        Entry p = head.next;
        Entry q = list2.getHead().next;
        //使用last指针来进行归并这两个指针
        Entry last = head;
        last.next = null;

        //两个链表都有节点
        while(p!=null && q!=null) {
            //谁小接到last的后面
            if(p.data > q.data){
                last.next = q;
                q = q.next;
                last = last.next;
            } else {
                last.next = p;
                p = p.next;
                last = last.next;
            }
        }

        //list1还有剩余节点
        if(p != null) {
            last.next = p;
        }
        //list2还有剩余节点
        if(q != null) {
            last.next = q;
        }

        //已经把list2全部加到了list1上面,list2直接把head的next置为空
        list2.getHead().next = null;
    }
判断链表是否有环

给定一个链表,判断链表是否有环

思路
      采用快慢指针进行判断,道理很简单,圆形操场跑步,跑的快的人总会追上跑得慢的人,也就是套圈。

给一个有环的例子:
在这里插入图片描述
      这里从67到78就构成了一个环,来构造一下这个链表,正常的插入肯定是不会出现这种情况的,所以链表的插入就需要我们自己来构造,要自己来设定下一个节点就需要打开private,可以在外部修改链表

这里直接给出构造的代码,方便读者进行测试

        MiNiLinkedListTest list1 = new MiNiLinkedListTest();
        Entry head = list1.getHead();
        Entry node1 = new Entry(34,null);
        head.next = node1;
        Entry node2 = new Entry(21,null);
        node1.next = node2;
        Entry node3 = new Entry(67,null);
        node2.next = node3;
        Entry node4 = new Entry(42,null);
        node3.next = node4;
        Entry node5 = new Entry(12,null);
        node4.next = node5;
        Entry node6 = new Entry(78,null);
        node5.next = node6;
        node6.next = node3;

代码讲解
      代码的实现是比较简单的,定义快指针和慢指针同时指向第一个数据节点,然后在不为空的情况下,两个指针开始走,一个走两步,一个走一步,如果再次相遇,则证明链表是有环的,如果有其中一个为空,那么就说明链表是可以走完的,那么就没有环。

    private boolean isLoop() {
        //定义快慢指针
        Entry fast = head.next;
        Entry slow = head.next;
        
        while(fast != null && slow != null && fast.next != null) {
            //快指针每次走两步,慢指针每次走一步
            fast = fast.next.next;
            slow = slow.next;
            
            if(fast == slow) {
                //如果再次相遇,则说明有环,返回true
                return true;
            }
        }
        return false;
    }
求链表环的入口节点

是上一个问题的延伸,当链表有环时返回链表的入口节点

还是用上一个题的例子来说,我们首先来做一个分析,搞清楚如何去求链表的入口节点。
在这里插入图片描述
如图所示,为这几段距离定义了一写变量

x:第一个数据节点到环入口的距离
m:环入口到相遇之间的距离
n:相遇节点到环末尾之间的距离
规律:由于fast每次走2步,slow每次走1步,所以fast的路程是slow路程的2倍

通过上面一起来进行一系列的推导:

	fast走过的路程是:x+m+n+m
	slow走过的路程是:x+m
	故可得:x+m+n+m = 2 * (x+m)
			x+n+2m = 2x+2m
				 x = n

所以最终得出来的结论是x=n

      所以我们只需要定义一个指针从第一个数据开始,一个指针从相遇的节点开始,两个节点同时开始,每次同时走一步,相遇的时候就是环的入口节点。

代码实现

    private Integer getLoopHead() {
        //定义快慢指针
        Entry fast = head.next;
        Entry slow = head.next;

        while(fast != null && slow != null && fast.next != null) {
            //快指针每次走两步,慢指针每次走一步
            fast = fast.next.next;
            slow = slow.next;

            if(fast == slow) {
                //如果再次相遇,则说明有环
                //定义一个指针指向第一个数据节点
                Entry cur = head.next;

                //cur和slow(fast)同时走,相遇时就是入口节点
                while(true) {
                    cur = cur.next;
                    slow = slow.next;
                    if(cur == slow) {
                        return cur.data;
                    }
                }
            }
        }
        return null;
    }
判断两条链表是否相交

给定两条链表,判断两条链表是否相交

先来通过一张图来直观的看一下链表的相交

在这里插入图片描述
      可以看到 list1 有5个数据节点, list2 有4个数据节点,如何找到相交的那个节点或者说如何判断是否相交呢?

思路
      首先先分别统计两个链表的长度,求出两个链表的差值,然后再重新开始遍历,让长的链表的指针先走两个链表的长度差值,然后两个指针开始同时走,每走一步判断一次是否是同一个节点
具体到这个图中,定义两个指针,分别指向 list1 和 list2,让 list1 先走1步,然后 list1 和 list2 同时开始走,每走一步判断一次,走两步后到6这两个指针就会相交。

为了大家的测试方便,这里先给出构造相交链表的代码:

        MiNiLinkedListTest list1 = new MiNiLinkedListTest();
        MiNiLinkedListTest list2 = new MiNiLinkedListTest();
        Entry node1 = new Entry(1, null);
        Entry node2 = new Entry(2, null);
        Entry node3 = new Entry(3, null);
        Entry node4 = new Entry(4, null);
        Entry node5 = new Entry(5, null);
        Entry node6 = new Entry(6, null);
        Entry node7 = new Entry(7, null);
        list1.head = node1;
        node1.next = node2;
        node2.next = node3;
        node3.next = node6;
        node6.next = node7;

        list2.head = node4;
        node4.next = node5;
        node5.next = node6;

代码实现

    public boolean isCross(MiNiLinkedListTest list2) {
        //统计两个链表的长度
        int size1 = 0;
        int size2 = 0;
        //统计list1
        Entry cur = head;
        while(cur != null) {
            cur = cur.next;
            size1++;
        }

        //统计list2
        cur = list2.head;
        while(cur != null) {
            cur = cur.next;
            size2++;
        }

        //为两条链表定义指针
        Entry p = head.next;
        Entry q = list2.head.next;

        //判断哪条链表长
        if(size1 > size2) {
            int cnt = size1 - size2;
            while (cnt-- > 0) {
                p = p.next;
            }
        } else if(size1 < size2) {
            int cnt = size2 - size1;
            while (cnt-- > 0) {
                q = q.next;
            }
        }

        //开始同时遍历
        while(p != null && q != null) {
            if(p == q) {
                return true;
            }
            p = p.next;
            q = q.next;
        }
        return false;
    }
找链表的倒数第k个节点

在这里插入图片描述
思路
      如果只定义一个指针也是可以的,但是很明显需要遍历两遍,第一遍找到链表的总长度第二遍从头开始走总长度-k个位置即可

      如果只遍历一遍,那么可以定义两个指针,如图,开始时,先让lastK指针指向第一个节点last指针走正数k个位置,然后两个指针同时开始走,当last走到末尾的时候,lastK就是倒数第k个节点。

代码实现

    public int lastOrder(int k) {
        //定义两个指针
        Entry lastK = head.next;
        Entry last = head;

        //last先走k个位置
        while(k-- > 0) {
            last = last.next;
            if(last == null) {
                throw new IllegalArgumentException("超过范围的 k :"+ k);
            }
        }
        //两个指针同时走
        while(last.next != null) {
            last = last.next;
            lastK = lastK.next;
        }
        return lastK.data;
    }
链表实现大数的加法

这种算是链表的一种应用,其实实现起来比较的简单,只需要把每一位数字用链表存储起来,然后加起来,注意进位即可。

需要注意的有两点:

  1. 使用头插,这样可以把数字逆过来,这样个位在前面,比较好处理
  2. 注意进位的问题, 这里使用了一个标志位来进行记录是否需要进位,每次相加都不断的进行更新

代码实现

    public static void main(String[] args) {
        String num1 = "36578768657546342546560870899434";
        String num2 = "254354657658768768543532424";

        //先用链表把这两个大数存起来
        LinkedList<Integer> list1 = new LinkedList<>();
        LinkedList<Integer> list2 = new LinkedList<>();
        for (int i = 0; i < num1.length(); i++) {
            list1.addFirst(num1.charAt(i)-'0');
        }
        for (int i = 0; i < num2.length(); i++) {
            list2.addFirst(num2.charAt(i)-'0');
        }

        //定义一个是否需要进位的标志
        boolean bool = false;

        //定义结果集合
        LinkedList<Integer> result = new LinkedList<>();

        Iterator<Integer> iterator1 = list1.iterator();
        Iterator<Integer> iterator2 = list2.iterator();
        while(iterator1.hasNext() && iterator2.hasNext()) {
            Integer p = iterator1.next();
            Integer q = iterator2.next();
            int temp = p + q;
            if(bool) {
                temp += 1;
                bool = false;
            }
            if(temp > 9) {
                temp = temp%10;
                bool = true;
            }
            result.addFirst(temp);
        }

        //长的数字还没处理完
        while (iterator1.hasNext()) {
            Integer temp = iterator1.next();
            if(bool) {
                temp += 1;
                bool = false;
            }
            if(temp > 9) {
                temp = temp%10;
                bool = true;
            }
            result.addFirst(temp);
        }
        while (iterator2.hasNext()) {
            Integer temp = iterator2.next();
            if(bool) {
                temp += 1;
                bool = false;
            }
            if(temp > 9) {
                temp = temp%10;
                bool = true;
            }
            result.addFirst(temp);
        }

        System.out.println(result);
    }
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值