链表的一些典型题目

没啥说的,列出来下面要写啥:

  1. 链表倒数第K个结点
  2. 合并两个有序链表
  3. 逆序打印链表
  4. 判断两个链表是否相交
  5. 判断链表是否有环

1、找出单链表倒数第K个结点

如果是一个顺序表,这道题的处理可以是逆序遍历到第K个就可以了。

很可惜,这道题要是双向链表,借助pre,也可以这么做;可惜换不得。

单链表只有next域,是单向的,所以我们只能顺序遍历。

思路: 这道题最基础的方法,是我们得到链表的长度 len,那么倒数第K个不就是 len - k个。
图解:
在这里插入图片描述
(这块应该说明一下,没有第0个)

Java中的链表LinkedList(双向的哦),是维护了一个size()方法的;即使没有,在给定头节点的情况下,我们也可以遍历链表、得到它的长度。

方法一:
代码:

public Node<T> findVal(Node<T> node, int k) {
    if (k <= 0 || node == null) return null;
    //倒数第 k 个不就是 list.getLength() - k
    int len = getLen(node);
    int index = len - k;
    Node<T> temp = theFirst;
    //从头到尾遍历
    while (temp != null && index > 0) {
        temp = temp.next;
        index--;
    }
    if (index <= 0) {
        return temp;
    }
    return null;
}
private int getLen(Node<T> node) {
    int len = 0;
    while (node != null) {
        node = node.next;
        len  = len + 1;
    }
    return len;
}

其实在遍历的时候,我相信有的人就会有疑问,该是node != null还是node.next != null,其实都可以,我是在不确定的情况下就数一数:
node != null
在这里插入图片描述
node.next != null
这个也是一样的:
在这里插入图片描述
这个虽然整体的时间复杂读是O(N),但它需要遍历两次链表
方法二:
只需要遍历一次链表,牵扯一点点的数学知识

思路: 就是设置两个东西p1、p2,就叫它们指针(Java无此概念,好用尔)吧。

p1先走K - 1步,再让两个指针一起走,直至遍历到链表尾,这时p2恰好会走到倒数第K个!
图解:
在这里插入图片描述

public Node<T> findVal(Node<T> node, int k) {
    if (k < 1 || node == null) return null;
    Node<T> p1 = node;
    Node<T> p2 = node;
    //让快指针(快引用)成功的先走 k - 1步
    for (int i = 0; i < k - 1; i++) {
        if (p1.next != null) {
            p1 = p1.next;
        } else {
            return null;
        }
    }
    //再让快慢指针一起走,当快指针走到链表尾,慢指针就走到了倒数第k个
    while (p1.next != null) {
        p1 = p1.next;
        p2 = p2.next;
    }
    return p2;
}

至于这块的 p != null还是p.next != null,我就不说了,上面已经说了个方法,留给读者自己去揣摩。

2、合并两个有序链表
看到这个题啊,又想起刚开始学数据结构的时候,老师讲这个东西,就像是一场梦,醒来还是不敢动~

那时候链表都搞不明白,不会合并,还TM有序合并。。。

现在不一样了,现在会百度啦!!!

思路:
new新的链表头,然后我们可以比较两个链表的头,哪个小,哪个就是新链表的头;

然后同时遍历两个链表,比较,将小的结点的值链到新链表的next域,直到有一个链表遍历完

这个时候会剩下一个链表,那么这个链表的值肯定都大于新的链表的值,你品品?

图解:
在这里插入图片描述
代码:

public Node<T> mergeLink(Node<T> node1, Node<T> node2) {
    if (node1 == null) return node2;
    if (node2 == null) return node1;
    Node<T> newHead = null;
    //找出两个链表中最小的
    //比较两个链表的第一个,哪个小,就是新链表的头
    if (node1.data <= node2.data) {
        newHead = node1;
        node1 = node1.next;
    } else {
        newHead = node2;
        node2 = node2.next;
    }
    //然后while循环比较剩下的元素
    //最后返回 newNode,所以让tmp代替它跑
    Node<T> tmp = newHead;
    while (node1 != null && node2 != null) {
        if (node1.data <= node2.data) {
            tmp.next = node1;
            node1 = node1.next;
            tmp = tmp.next;
        } else {
            tmp.next = node2;
            node2 = node2.next;
            tmp = tmp.next;
        }
    }
    //比较完还有两种可能,就像归并排序那里
    //有可能链表1没比较完,有可能链表2没比较完
    if (node1 == null) {
        //如果node1跑完了,
        tmp.next = node2;
    } else {
        //node2跑完了
        tmp.next = node1;
    }
    return newHead;
}

3、逆序打印链表
思路:
方法1,可以借助栈。栈,先进后出、后进先出,等到链表所有元素入栈,再依次出栈;

方法2,递归,在输出这个结点之前,我们可以先去打印这个结点的下一个

方法1:

public void reversePrint(Node<T> node) {
    Stack<Node<T>> stack = new Stack<>();
    Node<T> tmp = node;
    while (tmp != null) {
        stack.push(tmp);
        tmp = tmp.next;
    }
    while (!stack.isEmpty()) {
        System.out.print(stack.pop().data + " ");
    }
    System.out.println();
}

方法2:

public void reversePrint(Node<T> node) {
    if (node!= null) {
        reversePrint(node.next);
        System.out.print(node.data + " ");
    }
    System.out.println();
}

递归调用过程:

在这里插入图片描述
画的有点乱,我不会搞动图。
红色的线代表递归调用步骤,蓝色的线代表退出递归。

4、两个链表是否相交

思路: 可以用暴力搜索,去判断每个结点是否相等。这个就不写了

另一种思路就是我从书上看到的:如果两个链表相交,那么它们就有着相同的尾结点。

一开始我也不太理解,难道不相交,恰好相等了,怎么办?

其实就是书上说的,相交尾结点就是相等的。

我们知道,链表的结点类型在Java里面也就是一种类 类型,对于每一个结点都是new出来的。会在堆上开辟出来一块空间,供这个结点使用。

那么不同结点的堆信息肯定不同。

相等,就说明地址相同,它们是同一个结点。

则遍历两个链表,比较尾结点就OK

public boolean isIntersect(Node<T> node1, Node<T> node2) {
    if (node1 == null || node2 == null) return false;
    Node<T> tmp1 = node1;
    Node<T> tmp2 = node2;
    while (tmp1.next != null) tmp1 = tmp1.next;
    while (tmp2.next != null) tmp2 = tmp2.next;
    return tmp1 == tmp2;
}

两个链表相交后,相交的第一个结点?

思路: 如果有两个货相交,总存在node1 == node2
在这里插入图片描述
那我们要去找到这两个相等的结点,观察上面辣个图,会发现这相等的结点是在node1的第四个,node2的第三个

也就是,node1走一步,这个相等结点就是node1跟node2一起再走三步

也就是,让长的链表先走,先走多少 -> 与短链表的差

然后两个链表一起走,相等就找到了
代码:

//两个链表相交,找到它们相交的第一个结点
public Node<T> getFirstMeetNode(Node<T> node1, Node<T> node2) {
    //如果两个链表不相交
    if (!isIntersect(node1, node2) || node1 == null || node2 == null) return null;
    int len1 = 1;//咳咳
    int len2 = 1;
    Node<T> tmp1 = node1;
    Node<T> tmp2 = node2;
    //遍历出两个链表的长度
    while (tmp1.next != null) {
        len1++;
        tmp1 = tmp1.next;
    }
    while (tmp2.next != null) {
        len2++;
        tmp2 = tmp2.next;
    }
    //求出这个长度
    //不知道 len1 大还是 len2 大
    //求出绝对值
    int dif = Math.abs(len1 - len2);
    //如果 len1 大,链表1先走
    if (len1 > len2) {
        while (dif > 0) {
            node1 = node1.next;
            dif--;
        }
    } else {
        //len2 大,链表2先走
        while (dif > 0) {
            node2 = node2.next;
            dif--;
        }
    }
    //让两个链表一起走,相等就是第一个相交的结点
    while (node1 != node2) {
        node1 = node1.next;
        node2 = node2.next;
    }
    return node1;//return node2
}

5、链表是否有环

思路: 链表有环是怎样遍历的呢?
在这里插入图片描述
它的node.next != null是一直成立的,会在圈里打转。

所以这个题目,设置一个快指针,设置一个慢指针。

快指针、慢指针一起走,快指针一次走两步,慢指针一次走一步。

不出意外的话,快指针总能追得上慢指针、fast == slow,说明有环。

如果没环的话,那么慢指针是追不上快指针的、更别指望快指针追上慢指针。

代码:

//链表是否有环
public boolean isLoop(Node<T> node) {
    if (node == null) return false;
    Node<T> fast = node;
    Node<T> slow = node;
    //快指针一次走两步,
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        if (fast == slow) return true;
    }
    return false;
}

若链表有环,环的入口节点?
思路: 这个其实是比较涉及到我的知识盲区的
在这里插入图片描述
书上的这段话,让我看出来
在这里插入图片描述
如果fast一次两步, slow一次一步,最终相遇后

从链表头到环入口的距离L2 == 环入口到相遇点的距离L1

也就是,让 fast 继续在相遇点往下遍历,让 slow 从头遍历,当它们相等时,就是环的入口节点。 (这时其实没有slow、fast之分,它们每次走一步)

代码:
在上面代码的基础上

public Node<T> findLoopPort(Node<T> node) {
    if (node == null) return null;
    Node<T> fast = node;
    Node<T> slow = node;
    while (fast != null && fast.next != null) {
        fast = fast.next.next;
        slow = slow.next;
        //相等就跳出
        if (fast == slow) break;
    }
    if (fast == null || fast.next == null) return null;
    //让一个指针去头遍历
    slow = node;
    while (slow != fast) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值