面试中的Java链表
链表作为常考的面试题,并且本身比较灵活,对指针的应用较多。本文对常见的链表面试题Java实现做了整理。
链表节点定义如下:
static class Node {
int num;
Node next;
}
1. 求单链表中结点的个数
依次遍历链表
public static int size(Node head) {
int size = 0;
while (head != null) {
size++;
head = head.next;
}
return size;
}
2. 将单链表反转
构建一个新的链表,依次将本链表的节点插入到新链表的最前端,即可完成链表的反转。
public static Node reverse(Node head) {
Node p1 = head, p2;
head = null;
while (p1 != null) {
p2 = p1;
p1 = p1.next;
//头插法
p2.next = head;
head = p2;
}
return head;
}
3. 查找单链表中的倒数第K个结点(k > 0)
第一种解法是得到顺数的第 size+k-1 个节点,即为倒数的第K歌节点
第二种解法是快慢指针,主要思路就是使用两个指针,先让前面的指针走到正向第k个结点,后面的指针才走,这样前后两个指针的距离差是k-1,之后前后两个指针一起向前走,前面的指针走到最后一个结点时,后面指针所指结点就是倒数第k个结点,下面采用这种解法。
public static Node getKNode(Node head, int k) {
if (k < 0 || head == null) {
return null;
}
Node p2 = head, p1 = head;
while (k-- > 1 && p1 != null) {
p1 = p1.next;
}
// 说明k>size,因此返回null
if (k > 1 || p1 == null) {
return null;
}
while (p1.next != null) {
p1 = p1.next;
p2 = p2.next;
}
return p2;
}
4. 查找单链表的中间结点
采用快慢指针,p1每次走两步,p2每次走一步,奇数返回size/2+1,偶数返回size/2,
注意链表为空,链表结点个数为1和2的情况。
public static Node getMidNode(Node head) {
if (head == null) {
return null;
}
Node p1 = head, p2 = head;
while (p1.next != null) {
if (p1 == null) {
break;
}
p1 = p1.next.next;
p2 = p2.next;
}
return p2;
}
5. 从尾到头打印单链表
用栈
public static void reversePrint(Node node) {
Stack<Node> stack = new Stack<>();
while (node != null) {
stack.push(node);
node = node.next;
}
while (!stack.isEmpty()) {
System.out.print(stack.pop().num + " ");
}
}
递归
public static void reversePrint2(Node node) {
if (node != null) {
reversePrint2(node.next);
System.out.print(node.num + " ");
}
}
6. 已知两个单链表pHead1和pHead2各自有序,把它们合并成一个链表依然有序
类似于归并排序
public static Node merge(Node head1, Node head2) {
Node p1 = head1, p2 = head2, head;
if (head1.num < head2.num) {
head = head1;
p1 = p1.next;
} else {
head = head2;
p2 = p2.next;
}
Node p = head;
while (p1 != null && p2 != null) {
if (p1.num <= p2.num) {
p.next = p1;
p1 = p1.next;
p = p.next;
} else {
p.next = p2;
p2 = p2.next;
p = p.next;
}
}
if (p1 != null) {
p.next = p1;
}
if (p2 != null) {
p.next = p2;
}
return head;
}
7. 判断一个单链表中是否有环
这里也是用到两个指针。如果一个链表中有环,也就是说用一个指针去遍历,是永远走不到头的。因此,我们可以用两个指针去遍历,一个指针一次走两步,一个指针一次走一步,如果有环,两个指针肯定会在环中相遇。时间复杂度为O(n)。
public static boolean hasRing(Node head) {
Node p1 = head, p2 = head;
while (p1 != null && p1.next != null) {
p1 = p1.next.next;
p2 = p2.next;
if (p1 == p2) {
return true;
}
}
return false;
}
8. 已知一个单链表中存在环,求进入环中的第一个节点
解题思路:
由上题可知,按照 p1 每次两步,p2 每次一步的方式走,发现 p2 和 p1 重合,确定了单向链表有环路了。接下来,让 p1 回到链表的头部,重新走,每次步长不是走2了,而是走1,那么当 p1 和 p2 再次相遇的时候,就是环路的入口了。
为什么?
假定起点到环入口点的距离为 a,p1 和 p2 的相交点M与环入口点的距离为b,环路的周长为L,当 p1 和 p2 第一次相遇的时候,假定 p2 走了 n 步。那么有:
p2走的路径: a+b = n;
p1走的路径: a+b+kL = 2n; p1 比 p2 多走了k圈环路,总路程是p2的2倍
根据上述公式可以得到 k*L=a+b=n ,显然,如果从相遇点M开始,p2 再走 n 步的话,还可以再回到相遇点,同时p2从头开始走的话,经过n步,也会达到相遇点M。
显然在这个步骤当中 p1 和 p2 只有前 a 步走的路径不同,所以当 p1 和 p2 再次重合的时候,必然是在链表的环路入口点上。
public static Node getFirstRingNode(Node head) {
Node p1 = head, p2 = head;
while (p1 != null && p1.next != null) {
p1 = p1.next.next;
p2 = p2.next;
if (p1 == p2) {
p1 = head;
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
break;
}
}
return p1;
}
9. 判断两个单链表是否相交
如果两个链表相交,那么相交之后的节点应该相同,那么最后那个节点应该也相同
public static boolean isIntersect(Node head1, Node head2) {
Node p1 = head1, p2 = head2;
while (p1.next != null) {
p1 = p1.next;
}
while (p2.next != null) {
p2 = p2.next;
}
return p1 == p2;
}
10. 求两个单链表相交的第一个节点
采用对齐的思想。计算两个链表的长度 L1 , L2,分别用两个指针 p1 , p2 指向两个链表的头,
然后将较长链表的 p1(假设为 p1)向后移动L2 - L1个节点,然后再同时向后移动p1 , p2,
直到 p1 = p2。相遇的点就是相交的第一个节点。
public static Node firstIntersectNode(Node head1, Node head2) {
int len1 = size(head1);
int len2 = size(head2);
Node p1 = head1, p2 = head2;
if (len1 > len2) {
for (int i = 1; i < len1 - len2; i++) {
p1 = p1.next;
}
} else {
for (int i = 1; i < len2 - len1; i++) {
p2 = p2.next;
}
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
11. 给出一单链表头指针 head 和一节点指针 deletedNode,O(1)时间复杂度删除节点deletedNode
将deletedNode下一个节点的值复制给deletedNode节点,然后删除deletedNode节点,但是对于要删除的节点是最后一个节点的时候要做处理。
public static Node firstIntersectNode(Node head1, Node head2) {
int len1 = size(head1);
int len2 = size(head2);
Node p1 = head1, p2 = head2;
if (len1 > len2) {
for (int i = 1; i < len1 - len2; i++) {
p1 = p1.next;
}
} else {
for (int i = 1; i < len2 - len1; i++) {
p2 = p2.next;
}
}
while (p1 != p2) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
12. 链表的冒泡排序
对于数组的冒泡排序是上层for循环控制次数,下次for循环控制距离,对于链表的冒泡排序而言,首先让tail指针为null,一次循环比较完之后,在等于最后一个节点,倒数第二个节点。。。就是通过tail指针控制循环比较的次数和距离。
public static void bubbleSort(Node head) {
Node tail = null;
Node p1;
while (head != tail) {
for (p1 = head; p1.next != tail; p1 = p1.next) {
if (p1.num > p1.next.num) {
int temp = p1.num;
p1.num = p1.next.num;
p1.next.num = temp;
}
}
tail = p1;
}
show(head);
}
13. 单链表的双冒泡排序
public static void doubleBubblesort(Node start, Node end) {
if (start != end) {
Node p1 = start;
Node p2 = p1.next;
while (p2 != end) {
if (p2.num < start.num) {
p1 = p1.next;
int temp = p1.num;
p1.num = p2.num;
p2.num = temp;
}
p2 = p2.next;
}
int temp = p1.num;
p1.num = start.num;
start.num = temp;
doubleBubblesort(start, p1);
doubleBubblesort(p1.next, null);
}
}
全部代码放在 https://github.com/morethink/algorithm/blob/master/src/main/java/algorithm/list/LinkedList.java
参考文档
面试中的Java链表的更多相关文章
- 面试中关于Java你所需知道的的一切
本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺. 1. Java中的原始数据类型都有哪些, ...
- 面试中关于Java中涉及到知识点(转)
本篇文章会对面试中常遇到的Java技术点进行全面深入的总结,帮助我们在面试中更加得心应手,不参加面试的同学也能够借此机会梳理一下自己的知识体系,进行查漏补缺. 1. Java中的原始数据类型都有哪些, ...
- 聊聊面试中的 Java 线程池
背景 关于 Java 的线程池我想大家肯定不会陌生,在工作中或者自己平时的学习中多多少少都会用到,那你真的有了解过底层的实现原理吗?还是说只停留在用的阶段呢?而且关于 Java 线程池也是在面试中的 ...
- 面试中关于Java虚拟机(jvm)的问题看这篇就够了
最近看书的过程中整理了一些面试题,面试题以及答案都在我的文章中有所提到,希望你能在以问题为导向的过程中掌握虚拟机的核心知识.面试毕竟是面试,核心知识我们还是要掌握的,加油~~~ 下面是按jvm虚拟机知 ...
- (转)面试大总结之一:Java搞定面试中的链表题目
面试大总结之一:Java搞定面试中的链表题目 分类: Algorithm Interview2013-11-16 05:53 11628人阅读 评论(40) 收藏 举报 链表是面试中常出现的一类题目, ...
- 面试大总结:Java搞定面试中的链表题目总结
package LinkedListSummary; import java.util.HashMap; import java.util.Stack; /** * http://blog.csdn. ...
- 面试大总结之二:Java搞定面试中的二叉树题目
package BinaryTreeSummary; import java.util.ArrayList; import java.util.Iterator; import java.util.L ...
- 面试:用 Java 逆序打印链表
昨天的 Java 实现单例模式 中,我们的双重检验锁机制因为指令重排序问题而引入了 volatile 关键字,不少朋友问我,到底为啥要加 volatile 这个关键字呀,而它,到底又有什么神奇的作用呢 ...
- JAVA面试中需要准备的点
零基础入门学习Java,如何准备Java初级和高级的技术面试 本人最近几年一直在做java后端方面的技术面试官,而在最近两周,又密集了面试了一些java初级和高级开发的候选人,在面试过程中,我自认 ...
随机推荐
- Java集合系列[3]----HashMap源码分析
前面我们已经分析了ArrayList和LinkedList这两个集合,我们知道ArrayList是基于数组实现的,LinkedList是基于链表实现的.它们各自有自己的优劣势,例如ArrayList在 ...
- go golang 判断base64数据 获取随机字符串 截取字符串
go golang 判断base64数据 获取随机字符串 截取字符串 先少写点,占个坑,以后接着加. 1,获取指定长度随机字符串 func RandomDigits(length int) strin ...
- Free Pascal初次体验(有亮点哦)
感觉上Pascal语言写的非常有条理,和英语很像,应该是比较容易学,但是写起来真的是麻烦的要死,平时一行代码用C/C++可能就是几秒钟,用Pascal就要几分钟,Free Pascal感觉也不是很好用 ...
- 使用gitbook 发布一个教程文档网站
gitbook是一个好用的发布电子书的项目:使用gitbook 可以在本地写好文档再远程推送到库:也可以在gitbook提供的在线平台上制作电子书:要想在自己的服务器上使用gitbook 发布一个网站 ...
- 久未更 ~ 三之 —— CardView简单记录
> > > > > 久未更 系列一:CardView 点击涟漪效果实现 //在 cardview 中 实现点击涟漪效果 android:clickable="t ...
- Content Provider Test过程中遇到的坑
Content Provider(内容提供器) 一.什么是Content Provider? 直接贴官方文档简介图,笔者太懒了,而且 坑 不在这
- UIScrollerview的contentsize设置
最近被同行的一个朋友问到一个问题"UIScrollerview上添加子控件,给子控件约束好布局之后,还需要给scrollerview重新设置contentsize吗?"于是想到了我 ...
- EhCache 在集群环境中使用缓存系统
EhCache 分布式缓存/缓存集群 EhCache提供了很多种解决方案 这里只介绍一种最常用而且简单的RMI方式分布式缓存决绝方案 Automatic Peer Discovery 自动成员发现方 ...
- Spark性能调优之JVM调优
Spark性能调优之JVM调优 通过一张图让你明白以下四个问题 1.JVM GC机制,堆内存的组成 2.Spark的调优为什么会和JVM的调 ...
- Bundle使用&NSBundle
之 前在初始化一个类的时候:TestViewController *viewcontroller=[[TestViewController alloc]initWithNibName:@"T ...