链表的相关面试题
定义一个节点类,为后面的操作做准备
package com.datestructures.linkedlist;
public class HeroNode {
//定义节点
//这里以水浒传为例
public int no;//排名
public String name;
public String nickname;
public HeroNode next;
public HeroNode(int no, String name, String nickname) {
this.no=no;
this.name=name;
this.nickname=nickname;
}
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname + '\'' +
'}';
}
}
1.求单链表中有效节点的个数
思路
这是一道比较简单的面试题,当我们拿到这道题的时候,先知道什么是有效节点,通过观察定义的节点,我们知道当一个节点不等于null就是有效节点,我们定义一个length,当我们遇到有效节点就length++;
完整代码如下
/**
* 用来统计链表中有效节点的个数,不包括头结点
*
* @param heroNode 链表的头结点
* @return 返回的就是有效头结点的个数
*/
public int getLength(HeroNode heroNode) {
//如果头结点的next的为null,说明这个链表是空链表
if (heroNode.next == null) {
return 0;
}
HeroNode temp = heroNode.next;
int length = 0;
while (temp != null) {
length++;
temp = temp.next;
}
return length;
}
2.查找单链表中的倒数第k个结点
思路
1.编写一个方法,接收一个整数n
2.n表示是倒数第几个节点
3.先把节点从头遍历一遍,得到链表的有效元素的个数
4.得到size()后,我们从链表的第一个开始遍历,遍历(length-n)个,就可以得到想要的节点
5.如果找到了就返回该节点,没有找到就返回null
完整代码如下
/**
* 先统计链表中的有效个数,然后再次遍历到length-index的位置
*
* @param n 查找倒数的第index个节点
* @return 返回找到的节点或null
*/
public HeroNode getHeroNode(int n) {
//如果为空,返回null
if (headNode.next == null) {
return null;
}
//第一次遍历得到链表中的有效个数
int sum = getLength(headNode);
if (sum >= n || 0 <= n) {
HeroNode temp = headNode.next;
for (int i = 0; i < sum - n; i++) {
temp = temp.next;
}
return temp;
}
return null;
}
3.单链表的反转
思路
1.先创建一个reversalHeadNode
2.将原链表的第一个元素摘下来
3.将摘下来的元素放到reversalHeadNode
4.重复第二步操作
5.将摘下来的元素再次插入到reversalHeadNode后边
6.以此反复
7.最后将headNode指向反转链表
完整代码如下
public void reversalList(HeroNode headNode) {
//如果当前链表为空或者只有一个元素,直接返回
if (headNode.next == null || headNode.next.next == null) {
return;
}
//定义一个辅助指针 帮助我们遍历原来的链表
HeroNode cur = headNode.next;
//指向当前一个节点的下一个节点
HeroNode next = null;
//定义一个新的链表头节点
HeroNode reversalHead = new HeroNode(0, "", "");
//遍历原始链表 每遍历一个节点,就将其取出,并放在新的链表的最前端
while (cur != null) {
next = cur.next;//先暂时保存当前节点的下一个节点,因为后面要使用
cur.next = reversalHead.next;//将cur的下一个节点指向新的链表的最前端
reversalHead.next = cur;//将cur连接到新的链表上
cur = next;//让cur的后移
}
headNode.next = reversalHead.next;
}
4.从尾到头打印单链表
思路
1.第一种方式: 将原始链表反转后遍历,这种方式会破坏原始链表的结构,不建议
2.第二种方式: 用栈的特性来辅助完成操作 栈是一种先进后出的数据结构
public void reversalPrint(HeroNode headNode) {
//如果链表为空或者链表只有一个元素则直接返回
if (headNode.next == null | headNode.next.next == null) {
return;
}
//创建一个栈
Stack<HeroNode> stack = new Stack<>();
//定义一个辅助指针
HeroNode temp = headNode.next;
//将链表中的元素压入栈中
while (temp != null) {
stack.add(temp);
temp = temp.next;
}
//出栈
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
5.合并两个有序的单链表,合并之后的链表依然有序
思路
1.创建一个新的链表用于存放合并的两边元素
2.用链表中的addByOrder()方法分别遍历两个链表
3.因为这是一个有序插入方法,所以直接调用后就可以得出新的有序链表
完整代码如下
public SingleLinkedList mergeList(HeroNode headNode0, HeroNode headNode1) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
if (headNode0.next == null & headNode1.next == null) {
return null;
}
HeroNode temp = headNode0;
HeroNode next = null;
while (temp!=null) {
next = temp.next;
singleLinkedList.addByOrder(temp);
temp = next;
}
temp = headNode1;
while (temp!=null) {
next = temp.next;
singleLinkedList.addByOrder(temp);
temp = next;
}
return singleLinkedList;
}
//添加方式,为顺序添加,根据hero.no的排名添加
//如果链表中有这个节点,那么添加失败
public void addByOrder(HeroNode heroNode) {
//头节点不能动 定义一个辅助指针
HeroNode temp = headNode;
//定义一个flag 用来判断链表中是否含有这个值
boolean flag = false;
while (true) {
//当temp==null时,就遍历到链表的最后位置还没有找到加入的位置,就加入到最后
if (temp.next == null) {
break;
}
//当temp.next.no>heroNode.no时,就找到位置,在temp后边
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {//当temp.next.no==heroNode.no时,说明链表中有该值,则flag=true;
flag = true;
}
temp = temp.next;
}
if (flag) {
System.out.println(heroNode.no + "该值存在,不能再次加入!");
return;
}
heroNode.next = temp.next;
temp.next = heroNode;
}
完整代码
SingleLinkedList类
package com.datestructures.linkedlist;
import java.util.Stack;
//单向链表
public class SingleLinkedList {
//初始化链表
//创建一个头结点
private HeroNode headNode = new HeroNode(0, "", "");
//返回头结点
public HeroNode getHeadNode() {
return headNode;
}
//添加元素到链表,头结点不能动
//第一种添加英雄的方式 不按顺序,往链表尾部添加
public void add(HeroNode heroNode) {
//头结点不能动 所以定义一个辅助节点用来遍历链表
HeroNode temp = headNode;
//先找到链表尾,从尾部加入新的节点,所以先遍历
while (true) {
//当temp==null的时候,就找到最后节点的位置
if (temp.next == null) {
break;
}
temp = temp.next;
}
//当跳出循环后,就说明找到了链表尾部
temp.next = heroNode;
}
//第二种添加方式,为顺序添加,根据hero.no的排名添加
//如果链表中有这个节点,那么添加失败
public void addByOrder(HeroNode heroNode) {
//头节点不能动 定义一个辅助指针
HeroNode temp = headNode;
//定义一个flag 用来判断链表中是否含有这个值
boolean flag = false;
while (true) {
//当temp==null时,就遍历到链表的最后位置还没有找到加入的位置,就加入到最后
if (temp.next == null) {
break;
}
//当temp.next.no>heroNode.no时,就找到位置,在temp后边
if (temp.next.no > heroNode.no) {
break;
} else if (temp.next.no == heroNode.no) {//当temp.next.no==heroNode.no时,说明链表中有该值,则flag=true;
flag = true;
}
temp = temp.next;
}
if (flag) {
System.out.println(heroNode.no + "该值存在,不能再次加入!");
return;
}
heroNode.next = temp.next;
temp.next = heroNode;
}
//修改节点信息
//以no为查询条件,进行修改
public void update(HeroNode newHeroNode) {
//先判断是否为空
if (headNode.next == null) {
System.out.println("链表为空,不能修改!");
return;
}
//头结点不能动,定义一个辅助指针
HeroNode temp = headNode.next;
//定义一个flag,用来判断是否找到该节点
boolean flag = false;
while (true) {
//如果temp==null 说明找到链表尾部还没有找到
if (temp == null) {
System.out.println("该链表没有该元素!");
break;
} else if (temp.no == newHeroNode.no) {
//temp.no==newHeroNode.no 说明找到了
flag = true;
break;
}
temp = temp.next;//后移操作
}
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
}
}
//删除指定no的节点
//思路: 先找到要删除节点的前一个元素
//再将 temp.next=temp.next.next temp.next就没有引用指向它,那么他就会被jvm回收
public void delete(int no) {
//先判断是否为空
if (headNode.next == null) {
return;
}
//头结点不能动,定义一个辅助指针
HeroNode temp = headNode;
//定义一个flag 用来判断链表中是否含有这个值
boolean flag = false;
while (true) {
//如果temp==null 说明找到链表尾部还没有找到
if (temp.next == null) {
break;
} else if (temp.next.no == no) {
flag = true;
break;
}
temp = temp.next;//后移
}
if (flag) {
temp.next = temp.next.next;
}
}
/**
* 用来统计链表中有效节点的个数,不包括头结点
*
* @param heroNode 链表的头结点
* @return 返回的就是有效头结点的个数
*/
public int getLength(HeroNode heroNode) {
//如果头结点的next的为null,说明这个链表是空链表
if (heroNode.next == null) {
return 0;
}
HeroNode temp = heroNode.next;
int length = 0;
while (temp != null) {
length++;
temp = temp.next;
}
return length;
}
//查找单链表中的倒数第k个结点 【新浪面试题】
/**
* 先统计链表中的有效个数,然后再次遍历到length-index的位置
*
* @param n 查找倒数的第index个节点
* @return 返回找到的节点或null
*/
public HeroNode getHeroNode(int n) {
//如果为空,返回null
if (headNode.next == null) {
return null;
}
//第一次遍历得到链表中的有效个数
int sum = getLength(headNode);
if (sum >= n || 0 <= n) {
HeroNode temp = headNode.next;
for (int i = 0; i < sum - n; i++) {
temp = temp.next;
}
return temp;
}
return null;
}
//遍历链表
public void list() {
//先判断链表是否为空
if (headNode.next == null) {
System.out.println("链表为空,不能遍历!");
return;
}
//不为空,那么向后遍历,头结点不能动
HeroNode temp = headNode.next;
while (true) {
if (temp == null) {
break;
}
System.out.println(temp);
temp = temp.next;
}
}
//链表的反转
/**
* 1.先创建一个reversalHeadNode
* 2.将原链表的第一个元素摘下来
* 3.将摘下来的元素放到reversalHeadNode后边
* 4.重复第二步操作
* 5.将摘下来的元素再次插入到reversalHeadNode后边
* 6.以此反复
* 7.最后将headNode指向反转链表,让reversalHeadNode指向空
*/
public void reversalList(HeroNode headNode) {
//如果当前链表为空或者只有一个元素,直接返回
if (headNode.next == null || headNode.next.next == null) {
return;
}
//定义一个辅助指针 帮助我们遍历原来的链表
HeroNode cur = headNode.next;
//指向当前一个节点的下一个节点
HeroNode next = null;
//定义一个新的链表头节点
HeroNode reversalHead = new HeroNode(0, "", "");
//遍历原始链表 每遍历一个节点,就将其取出,并放在新的链表的最前端
while (cur != null) {
next = cur.next;//先暂时保存当前节点的下一个节点,因为后面要使用
cur.next = reversalHead.next;//将cur的下一个节点指向新的链表的最前端
reversalHead.next = cur;//将cur连接到新的链表上
cur = next;//让cur的后移
}
headNode.next = reversalHead.next;
}
/**
* 从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
* 第一种方式: 会破坏原始链表的结构 不可取
* 第二种方式: 用栈的特性来辅助完成操作 栈是一种先进后出的数据结构
*
* @param headNode
*/
public void reversalPrint(HeroNode headNode) {
//如果链表为空或者链表只有一个元素则直接返回
if (headNode.next == null | headNode.next.next == null) {
return;
}
//创建一个栈
Stack<HeroNode> stack = new Stack<>();
//定义一个辅助指针
HeroNode temp = headNode.next;
//将链表中的元素压入栈中
while (temp != null) {
stack.add(temp);
temp = temp.next;
}
//出栈
while (stack.size() > 0) {
System.out.println(stack.pop());
}
}
/**
* 合并两个有序的单链表,合并之后的链表依然有序
* 思路
* 1.创建一个新的链表用于存放合并的两边元素
* 2.每次从两个链表中取出两个元素,比较大小,然后小的加到新的链表
* 注意 这里是以水浒传的编号进行比较的
*
* @param headNode0
* @param headNode1
*/
public SingleLinkedList mergeList(HeroNode headNode0, HeroNode headNode1) {
SingleLinkedList singleLinkedList = new SingleLinkedList();
if (headNode0.next == null & headNode1.next == null) {
return null;
}
HeroNode temp = headNode0;
HeroNode next = null;
while (temp!=null) {
next = temp.next;
singleLinkedList.addByOrder(temp);
temp = next;
}
temp = headNode1;
while (temp!=null) {
next = temp.next;
singleLinkedList.addByOrder(temp);
temp = next;
}
return singleLinkedList;
}
}
SingleLinkedListDemo类
package com.datestructures.linkedlist;
public class SingleLinkedListDemo {
public static void main(String[] args) {
HeroNode heroNode1=new HeroNode(1, "宋江", "及时雨");
HeroNode heroNode2=new HeroNode(2, "卢俊义", "玉麒麟");
HeroNode heroNode3=new HeroNode(3, "吴用", "智多星");
HeroNode heroNode4=new HeroNode(4, "林冲", "豹子头");
SingleLinkedList singleLinkedList = new SingleLinkedList();
/*
//顺序加入
singleLinkedList.add(heroNode1);
singleLinkedList.add(heroNode2);
singleLinkedList.add(heroNode3);
singleLinkedList.add(heroNode4);*/
//自动排序加入
singleLinkedList.addByOrder(heroNode1);
singleLinkedList.addByOrder(heroNode4);
singleLinkedList.addByOrder(heroNode3);
singleLinkedList.addByOrder(heroNode2);
//重复加入
singleLinkedList.addByOrder(heroNode2);
//先显示一次
singleLinkedList.list();
//new 一个需要修改的对象
HeroNode newHeroNode=new HeroNode(2, "小卢", "玉麒麟~~");
singleLinkedList.update(newHeroNode);
System.out.println("修改后的链表为:");
//面试题 求单链表中有效节点的个数
System.out.println("有效节点的个数为:"+singleLinkedList.getLength(singleLinkedList.getHeadNode()));
singleLinkedList.list();
//从尾到头打印单链表 【百度,要求方式1:反向遍历 。 方式2:Stack栈】
System.out.println("从尾到头打印单链表为");
singleLinkedList.reversalPrint(singleLinkedList.getHeadNode());
System.out.println("原始链表为");
singleLinkedList.list();
singleLinkedList.reversalList(singleLinkedList.getHeadNode());
//单链表的反转【腾讯面试题,有点难度】
System.out.println("反转后的链表为");
singleLinkedList.list();
//查找单链表中的倒数第k个结点 【新浪面试题】 测试
System.out.println("查找单链表中的倒数第k个结点为"+singleLinkedList.getHeroNode(0));
//删除操作
singleLinkedList.delete(1);
singleLinkedList.delete(4);
singleLinkedList.delete(2);
singleLinkedList.delete(3);
System.out.println("修改后的链表为:");
singleLinkedList.list();
System.out.println("+++++++++++++++");
HeroNode heroNode11=new HeroNode(1, "宋江", "及时雨");
HeroNode heroNode22=new HeroNode(4, "卢俊义", "玉麒麟");
HeroNode heroNode33=new HeroNode(5, "吴用", "智多星");
HeroNode heroNode44=new HeroNode(7, "林冲", "豹子头");
HeroNode heroNode5=new HeroNode(2, "宋江", "及时雨");
HeroNode heroNode6=new HeroNode(6, "卢俊义", "玉麒麟");
HeroNode heroNode7=new HeroNode(7, "吴用", "智多星");
HeroNode heroNode8=new HeroNode(8, "林冲", "豹子头");
SingleLinkedList singleLinkedList1 = new SingleLinkedList();
singleLinkedList.addByOrder(heroNode11);
singleLinkedList.addByOrder(heroNode44);
singleLinkedList.addByOrder(heroNode33);
singleLinkedList.addByOrder(heroNode22);
singleLinkedList1.addByOrder(heroNode5);
singleLinkedList1.addByOrder(heroNode6);
singleLinkedList1.addByOrder(heroNode7);
singleLinkedList1.addByOrder(heroNode8);
System.out.println("合并前的链表分别为");
System.out.println("链表一");
singleLinkedList.list();
System.out.println("链表二");
singleLinkedList1.list();
SingleLinkedList singleLinkedList2 = new SingleLinkedList().mergeList(heroNode11,heroNode5);
System.out.println("合并后的链表为~~");
singleLinkedList2.list();
}
}