单链表面试题(新浪. 百度. 腾讯)
-
求单链表中有效节点的个数
// 测试一下 求单链表中有效节点的个数
System.out.println("有效的节点个数=" + getLength(singleLinkedListD.getHead()));
}
/**
*
* @param head 链表的头节点
* @return 返回的就是有效节点的个数
*/
// 方法: 获取到单链表的节点的个数 (如果是带头节点的链表, 需要不统计头节点)
public static int getLength(HeroNode head) {
if (head.next == null) {
// 空链表
return 0;
}
int length = 0;
//定义一个辅助的变量
HeroNode cur = head.next;
while(cur != null) {
length++;
cur = cur.next; // 遍历
}
return length;
}
}
-
查找单链表中的倒数第k个结点 [新浪面试题]
思路:
1. 编写一个方法, 接收head节点, 同时接收一个index
2. index 表示的是倒数第index个节点
3. 先把链表从头到尾遍历, 得到链表的总长度getLength
4. 得到size后, 我们从链表的第一个开始遍历(size - index) 个,就可以得到
5. 如果找到了, 则返回该节点, 否则返回null
//测试一下, 看看是否得到了倒数第k个节点
HeroNode res = findLastIndexNode(singleLinkedListD.getHead(), 2);
System.out.println("res=" + res);
}
// 查找单链表中的倒数第k个结点[新浪面试题]
public static HeroNode findLastIndexNode(HeroNode head, int index) {
// 判断如果链表为空, 返回null
if (head.next == null) {
return null; // 没有找到
}
// 第一个遍历得到链表的长度 (节点个数)
int size = getLength(head);
// 第二次遍历size - index 位置, 就是我们倒数的第k个节点
// 先做一个index的校验
if (index <= 0 || index > size) {
return null;
}
// 定义当前辅助变量, for循环定位到倒数的index
HeroNode cur = head.next;
for (int i = 0; i < size-index; i++) {
cur = cur.next;
}
return cur;
}
-
单链表的反转 [腾讯面试题,有点难度]
思路:
-
先定义一个节点 reverseHead = new HeroNode();
-
从头到尾遍历原来的链表, 每遍历一个节点, 就将其取出, 并放在新的链表reverseHead 的最前端
-
原来的链表的head.next = reverseHead.next
//测试一下, 单链表的反转功能
System.out.println("原来链表的情况~~");
singleLinkedListD.list();
System.out.println("反转单链表~~");
reversetList(singleLinkedListD.getHead());
singleLinkedListD.list();
}
// 将单链表反转
public static void reversetList(HeroNode head) {
//如果当前链表为空, 或者只有一个节点, 无需反转, 直接返回
if (head.next == null || head.next.next == null) {
return;
}
// 定义一个辅助的指针(变量), 帮助我们遍历原来的链接
HeroNode cur = head.next;
HeroNode next = null; //指向当前下一个节点
HeroNode reverseHead = new HeroNode(0,"","");
//遍历原来的链表,
// 并从头到尾遍历原来的链表, 每遍历一个节点, 就将其取出, 并放在新的链表reverseHead 的最前端
while (cur != null) {
next = cur.next; // 先暂时保存当前的下一个节点,因为后面需要使用
cur.next = reverseHead.next; // 将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur; // 将cur连接到新的链表上
cur = next; //让cur后移
}
// 将head.next 指向reverseHead.next, 实现单链表的反转
head.next = reverseHead.next;
}
-
从尾到头打印单链表[百度: 要求方式 1: 反向遍历,2. Stack栈]
思路:
-
逆序打印单链表
-
方式一: 先将单链表进行反转操作, 然后再遍历即可, 这样做的问题是会破坏原来的单链表的结构, 不建议
-
方式二: 可以利用栈这个数据结构, 将各个节点压入到栈中,利用栈的先进后出的特点, 就实现了逆序打印的效果
演示栈的使用Stack
package DataStructures;
import java.util.Stack;
public class TestStack {
public static void main(String[] args) {
Stack<String> stack = new Stack();
// 入栈
stack.add("Li");
stack.add("wen");
stack.add("hui");
// System.out.println(stack.pop() ); // 取出之后就没有了
//出栈
while (stack.size() > 0) {
System.out.print(stack.pop() + " "); // pop就是将栈顶的数据取出
}
}
}
// 测试逆序打印单链表,没有改变链表的结构
System.out.println("测试逆序打印单链表,没有改变链表的结构");
reversePrint(singleLinkedListD.getHead());
}
// 方式二: 可以利用栈这个数据结构, 将各个节点压入到栈中,利用栈的先进后出的特点, 就实现了逆序打印的效果
public static void reversePrint(HeroNode head) {
if (head.next == null) {
return; // 空链表 无法打印
}
//创建要给一个栈, 将各个节点压入栈
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode cur = head.next;
// 将链表的各个节点压入栈
while (cur != null) {
stack.push(cur);
cur = cur.next; // cur后移, 这样就可以压入下一个节点
}
// 将栈中的节点进行打印, pop出栈
while(stack.size() > 0) {
System.out.println(stack.pop());// stack特点是先进后出
}
}
全部代码
package DataStructures;
import java.util.Stack;
public class SingleLinkedList {
public static void main(String[] args) {
// 进行测试
// 先创建节点
HeroNode hero1 = new HeroNode(1, "宋宋", "江江");
HeroNode hero2 = new HeroNode(2, "文文", "雨雨");
HeroNode hero3 = new HeroNode(3, "哈哈", "嘿嘿");
HeroNode hero4 = new HeroNode(4, "依依", "落落");
// 创建要给链表
SingleLinkedListD singleLinkedListD = new SingleLinkedListD();
// 加入
// singleLinkedListD.add(hero1);
// singleLinkedListD.add(hero2);
// singleLinkedListD.add(hero3);
// singleLinkedListD.add(hero4);
// 加入按照编号的顺序
singleLinkedListD.addByOrder(hero1);
singleLinkedListD.addByOrder(hero2);
singleLinkedListD.addByOrder(hero3);
singleLinkedListD.addByOrder(hero4);
singleLinkedListD.addByOrder(hero3);
//显示
singleLinkedListD.list();
HeroNode newHeroNode = new HeroNode(2, "文", "雨~~");
singleLinkedListD.update(newHeroNode);
System.out.println("修改后的链表情况");
singleLinkedListD.list();
// 删除一个节点
singleLinkedListD.del(1);
System.out.println("删除后的链表情况");
singleLinkedListD.list();
// 测试一下 求单链表中有效节点的个数
System.out.println("有效的节点个数=" + getLength(singleLinkedListD.getHead()));
//测试一下, 看看是否得到了倒数第k个节点
HeroNode res = findLastIndexNode(singleLinkedListD.getHead(), 2);
System.out.println("res=" + res);
//测试一下, 单链表的反转功能
System.out.println("原来链表的情况~~");
singleLinkedListD.list();
System.out.println("反转单链表~~");
reversetList(singleLinkedListD.getHead());
singleLinkedListD.list();
// 测试逆序打印单链表,没有改变链表的结构
System.out.println("测试逆序打印单链表,没有改变链表的结构");
reversePrint(singleLinkedListD.getHead());
}
// 方式二: 可以利用栈这个数据结构, 将各个节点压入到栈中,利用栈的先进后出的特点, 就实现了逆序打印的效果
public static void reversePrint(HeroNode head) {
if (head.next == null) {
return; // 空链表 无法打印
}
//创建要给一个栈, 将各个节点压入栈
Stack<HeroNode> stack = new Stack<HeroNode>();
HeroNode cur = head.next;
// 将链表的各个节点压入栈
while (cur != null) {
stack.push(cur);
cur = cur.next; // cur后移, 这样就可以压入下一个节点
}
// 将栈中的节点进行打印, pop出栈
while(stack.size() > 0) {
System.out.println(stack.pop());// stack特点是先进后出
}
}
// 将单链表反转
public static void reversetList(HeroNode head) {
//如果当前链表为空, 或者只有一个节点, 无需反转, 直接返回
if (head.next == null || head.next.next == null) {
return;
}
// 定义一个辅助的指针(变量), 帮助我们遍历原来的链接
HeroNode cur = head.next;
HeroNode next = null; //指向当前下一个节点
HeroNode reverseHead = new HeroNode(0,"","");
//遍历原来的链表,
// 并从头到尾遍历原来的链表, 每遍历一个节点, 就将其取出, 并放在新的链表reverseHead 的最前端
while (cur != null) {
next = cur.next; // 先暂时保存当前的下一个节点,因为后面需要使用
cur.next = reverseHead.next; // 将cur的下一个节点指向新的链表的最前端
reverseHead.next = cur; // 将cur连接到新的链表上
cur = next; //让cur后移
}
// 将head.next 指向reverseHead.next, 实现单链表的反转
head.next = reverseHead.next;
}
// 查找单链表中的倒数第k个结点[新浪面试题]
//思路:
// 1. 编写一个方法, 接收head节点, 同时接收一个index
// 2. index 表示的是倒数第index个节点
// 3. 先把链表从头到尾遍历, 得到链表的总长度getLength
// 4. 得到size后, 我们从链表的第一个开始遍历(size - index) 个,就可以得到
// 5. 如果找到了, 则返回该节点, 否则返回null
public static HeroNode findLastIndexNode(HeroNode head, int index) {
// 判断如果链表为空, 返回null
if (head.next == null) {
return null; // 没有找到
}
// 第一个遍历得到链表的长度 (节点个数)
int size = getLength(head);
// 第二次遍历size - index 位置, 就是我们倒数的第k个节点
// 先做一个index的校验
if (index <= 0 || index > size) {
return null;
}
// 定义当前辅助变量, for循环定位到倒数的index
HeroNode cur = head.next;
for (int i = 0; i < size-index; i++) {
cur = cur.next;
}
return cur;
}
/**
*
* @param head 链表的头节点
* @return 返回的就是有效节点的个数
*/
// 方法: 获取到单链表的节点的个数 (如果是带头节点的链表, 需要不统计头节点)
public static int getLength(HeroNode head) {
if (head.next == null) {
// 空链表
return 0;
}
int length = 0;
//定义一个辅助的变量
HeroNode cur = head.next;
while(cur != null) {
length++;
cur = cur.next; // 遍历
}
return length;
}
}
// 定义SingleLinked 管理我们的英雄
class SingleLinkedListD {
//先初始化一个头节点, 头节点不要动,
private HeroNode head = new HeroNode(0,"","");
// 返回头节点
public HeroNode getHead() {
return head;
}
public void add(HeroNode heroNode) {
//因为head节点不能动, 因此我们需要一个辅助变量temp
HeroNode temp = head;
// 遍历链表, 找到最后
while(true) {
// 找到链表的最后
if (temp.next == null) {
break;
}
// 如果没有找到最后,就将temp后移
temp = temp.next;
}
// 当退出while循环时, temp就指向了链表的最后
// 将最后这个节点的next 指向 新的节点
temp.next = heroNode;
}
// 第二种方式在添加英雄时,根据排名将英雄插入到指定位置(如果有这个排名,则添加失败,并给出提示)
public void addByOrder (HeroNode heroNode) {
// 因为头节点不能动, 因此我们仍然通过一个辅助指针(变量) 来帮助找到添加的位置
//因为单链表, 因此我们找的temp 是位于添加位置的前一个节点, 否则插入不了
HeroNode temp = head;
boolean flag = false; // flag标志添加的编号是否存在, 默认为false
while(true) {
if (temp.next == null) { // 说明temp已经在链表的最后
break;//
}
if (temp.next.no > heroNode.no) { // 位置找到, 就在temp 的后面插入
break;
} else if (temp.next.no == heroNode.no) {
// 说明希望添加的heroNode的编号已然存在
flag = true; // 说明编号存在
break;
}
temp = temp.next; // 后移, 遍历当前链表
}
// 判断flag 的值
if (flag) { // 不能添加,说明编号存在
System.out.println("准备插入的英雄编号%d已经存在了, 不能加入\n" + heroNode.no);
} else {
// 插入到链表中 temp的后面
heroNode.next = temp.next;
temp.next = heroNode;
}
}
// 修改节点的信息, 根据no的编号来修改, 即no编号不能改
//说明
// 1. 根据newHeroNode 的no来修改即可
public void update(HeroNode newHeroNode) {
// 判断是否空
if (head.next == null) {
System.out.println("链表为空");
return;
}
// 根据需要修改的节点, 根据no编写
// 定义一个辅助变量
HeroNode temp = head.next;
boolean flag = false; // 表示是否找到该节点
while(true) {
if (temp == null) {
break;// 已经遍历完链表
}
if (temp.no == newHeroNode.no) {
//找到
flag = true;
break;
}
temp = temp.next;
}
//根据flag 判断是否找到要修改的节点
if (flag) {
temp.name = newHeroNode.name;
temp.nickname = newHeroNode.nickname;
} else { // 没有找到
System.out.println("没有找到编号为%d 的节点, 不能修改\n" + newHeroNode.no);
}
}
//删除节点
//思路
//1.head不能动 因此我们需要temp辅助节点找到待删除节点的前一个节点
// 2. 说明我们在比较时,时temp.next.no和需要删除的节点的no比较
public void del(int no) {
HeroNode temp = head;
boolean flag = false; // 标志是否找到待删除节点的
while (true) {
if (temp.next == null) { //已经到链表的最后
break;
}
if (temp.next.no == no) {
// 找到了待删除的前一个节点temp
flag = true;
break;
}
temp = temp.next;// temp后移 遍历
}
//判断flag
if (flag) { // 找到
// 可以删除
temp.next = temp.next.next;
}else {
System.out.println("要删除的%d 节点不存在\n" + no);
}
}
//显示链表(遍历)
public void list() {
// 判断链表是否为空
if (head.next == null) {
System.out.println("链表为空");
return;
}
// 因为头节点, 不能动 因此我们需要一个辅助变量来遍历
HeroNode temp = head.next;
while(true) {
// 判断是否到链表最后
if (temp == null) {
break;
}
// 输出节点信息
System.out.println(temp);
// 将temp后移, 一定小心
temp = temp.next;
}
}
}
// 定义HeroNode , 每个HeroNode 对象就是一个节点
class HeroNode {
public int no;
public String name;
public String nickname;
public HeroNode next; // 指向下一个节点
// 构造器
public HeroNode(int no, String hName, String hNickname) {
this.no = no;
this.name = hName;
this.nickname = hNickname;
}
// 为了显示方法, 我们重新toString
@Override
public String toString() {
return "HeroNode{" +
"no=" + no +
", name='" + name + '\'' +
", nickname='" + nickname;
}
}