单链表的面试题——Java数据结构

单链表的面试题——Java数据结构

说明

本篇博客介绍的是Java数据结构中的单链表,以及一点栈的知识,主要进行代码演示面试中对单链表这一部分的工作要求。
本文介绍顺序如下:
(1)什么是单链表,以及单链表的特点
(2)单链表的增删改查
(3)如何求单链表中的有效节点个数
(4)如何查找单链表的倒数第k个节点
(5)单链表的反转
(6)单链表的逆序打印(前提:不改变单链表的的结构,通过栈来实现)
(7)合并二个有序单链表,使之合并之后形成的单链表仍然有序。

单链表,以及单链表的特点

单链表的示意图如下:
在这里插入图片描述
单链表的特点:
在这里插入图片描述
接下来我们通过代码来详细的展示如下任务:
(2)单链表的增删改查
(3)如何求单链表中的有效节点个数
(4)如何查找单链表的倒数第k个节点
(5)单链表的反转
(6)单链表的逆序打印(前提:不改变单链表的的结构,通过栈来实现)
(7)合并二个单链表,使其合并之仍然有序

代码实现:

代码实现分二个部分,第一部分代码为如下需求的实现:
(2)单链表的增删改查
(3)如何求单链表中的有效节点个数
(4)如何查找单链表的倒数第k个节点
(5)单链表的反转
(6)单链表的逆序打印(前提:不改变单链表的的结构,通过栈来实现)

第二部分我们来实现
(7)合并二个单链表,使其合并之仍然有序,用二种方法来实现。

具体思路以及注解,解释请看源码注释

代码实现第一部分
import java.util.Stack;

public class SingleLinkedListDemo {

	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, "林冲", "豹子头");
		
		//创建要给链表
		SingleLinkedList singleLinkedList = new SingleLinkedList();
		
		//加入
		singleLinkedList.add(hero1);
		singleLinkedList.add(hero4);
		singleLinkedList.add(hero2);
		singleLinkedList.add(hero3);

		// 测试一下单链表的反转功能
		System.out.println("原来链表的情况~~");
		singleLinkedList.list();
		
		System.out.println("反转单链表~~");
		reversetList(singleLinkedList.getHead());
		singleLinkedList.list();
		
		//System.out.println("测试逆序打印单链表, 没有改变链表的结构~~");
		//reversePrint(singleLinkedList.getHead());
		
/*		
		//加入按照编号的顺序
		singleLinkedList.addByOrder(hero1);
		singleLinkedList.addByOrder(hero4);
		singleLinkedList.addByOrder(hero2);
		singleLinkedList.addByOrder(hero3);
		
		//显示一把
		singleLinkedList.list();
		
		//测试修改节点的代码
		HeroNode newHeroNode = new HeroNode(2, "小卢", "玉麒麟~~");
		singleLinkedList.update(newHeroNode);
		
		System.out.println("修改后的链表情况~~");
		singleLinkedList.list();
		
		//删除一个节点
		singleLinkedList.del(1);
		singleLinkedList.del(4);
		System.out.println("删除后的链表情况~~");
		singleLinkedList.list();
		
		//测试一下 求单链表中有效节点的个数
		System.out.println("有效的节点个数=" + getLength(singleLinkedList.getHead()));//2
		
		//测试一下看看是否得到了倒数第K个节点
		HeroNode res = findLastIndexNode(singleLinkedList.getHead(), 3);
		System.out.println("res=" + res);
*/		
		
	}
	
	//方法
	//可以利用栈这个数据结构,将各个节点压入到栈中,然后利用栈的先进后出的特点,就实现了逆序打印的效果
	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;// 指向当前节点[cur]的下一个节点
		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. 如果找到了,则返回该节点,否则返回nulll
	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; //3 // 3 - 1 = 2
		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;
	}

}


//定义SingleLinkedList 管理我们的英雄
class SingleLinkedList {
	//先初始化一个头节点, 头节点不要动, 不存放具体的数据
	private HeroNode head = new HeroNode(0, "", "");
	
	
	//返回头节点
	public HeroNode getHead() {
		return head;
	}

	//添加节点到单向链表
	//思路,当不考虑编号顺序时
	//1. 找到当前链表的最后节点
	//2. 将最后这个节点的next 指向 新的节点
	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.printf("准备插入的英雄的编号 %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.printf("没有找到 编号 %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.printf("要删除的 %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 name, String nickname) {
		this.no = no;
		this.name = name;
		this.nickname = nickname;
	}
	//为了显示方法,我们重新toString
	@Override
	public String toString() {
		return "HeroNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";
	}
	
}
代码实现第二部分

合并二个有序的单链表,使其合并之后仍然有序的代码实现,该需求我们用二种方法来是实现:


//这一部分需要借鉴代码实现第一部分的一些方法,类。总之,将这二个放在同一个包下,便能
//成功运行
public class twoLianBiaoPaiXU {

	public static void main(String[] args) {
		
		//先创建单链表1的节点
		HeroNode hero1 = new HeroNode(1, "宋江", "及时雨");
		HeroNode hero2 = new HeroNode(7, "秦明", "霹雳火");
		HeroNode hero3 = new HeroNode(3, "吴用", "智多星");
		HeroNode hero4 = new HeroNode(14, "武松", "行者");
		
		//创建单链表1
		SingleLinkedList singleLinkedList1 = new SingleLinkedList();
		
		//将单链表1的节点按照顺序加入到单链表1
		singleLinkedList1.addByOrder(hero1);
		singleLinkedList1.addByOrder(hero4);
		singleLinkedList1.addByOrder(hero2);
		singleLinkedList1.addByOrder(hero3);
		
		
		
		//先创建单链表2的节点
		HeroNode hero5 = new HeroNode(9, "花荣", "小李广");
		HeroNode hero6 = new HeroNode(2, "卢俊义", "玉麒麟");
		HeroNode hero7 = new HeroNode(22, "李逵", "黑旋风");
		HeroNode hero8 = new HeroNode(4, "林冲", "豹子头");
		
		//创建单链表2
		SingleLinkedList singleLinkedList2 = new SingleLinkedList();
		
		//将单链表2的节点按照顺序加入到单链表2
		singleLinkedList2.addByOrder(hero5);
		singleLinkedList2.addByOrder(hero6);
		singleLinkedList2.addByOrder(hero7);
		singleLinkedList2.addByOrder(hero8);
		
		System.out.println("链表1");
		singleLinkedList1.list();
		System.out.println("链表2");
		singleLinkedList2.list();
//		System.out.println("用方法1合并链表");
//		
//		heBingLianBiao(singleLinkedList1,singleLinkedList2).list();
		
		System.out.println("用方法2合并链表");
		heBingLianBiao2(singleLinkedList1,singleLinkedList2).list();
	}
	
	
//	合并二个有序单链表,(如果二个单链表无序则该方法不行)使其合并之后的单链表仍然有序的方法1。
//创建一个新的链表,来实现,具体思路如下
/*
	 方法一思路:
    1.创建一个新的头结点
    2.遍历两个链表,p代表head1的头结点,q代表head2的头结点
    3.如果p的下一个节点的值<q的下一个节点的值,就在head1链表中删除p节点,并将
      该节点插入到新链表中,新链表的指针后移(该程序中为h),否则的话就q节点执行上
      述操作.
    4. 任何一个链表遍历结束,循环停止,检查两个链表中是否还有剩下的节点,如果有
      的话,肯定是比新链表中的所有值都大的,直接插入到新链表的末尾.
	 */
public static SingleLinkedList heBingLianBiao(SingleLinkedList singleLinkedList1,SingleLinkedList singleLinkedList2){
//	获取二个链表的头节点
	HeroNode p =singleLinkedList1.getHead();
	HeroNode q =singleLinkedList2.getHead();
//	创建一个新的链表,并获取其头节点
	SingleLinkedList singleLinkedList = new SingleLinkedList();
	HeroNode h =singleLinkedList.getHead();
	
//	遍历二个链表总是选择小的插入到新链表中,当跳出while循环的时候必然有一个链表遍历完。
	while(p.next!=null && q.next!=null){
		if(p.next.no<q.next.no){
			h.next=p.next;
			p.next=p.next.next;
		}else{
			h.next=q.next;
			q.next=q.next.next;
		}
		h=h.next;
	}
//检查未遍历完的链表,有剩下直接除头节点,其余部分总体插入到新链表的后边
	if(p.next!=null)
	{
		h.next=p.next;
//		这样的话就可以直接把链表整体插入到新链表尾部
		p.next=null;
	}else{
		h.next=q.next;
		q.next=null;
	}
	return singleLinkedList;
}






//合并二个有序单链表的方法二,不创建新的单链表,就在原有的单链表上面操作,前提也是
//二个单链表必须有序
/*
 方法二思路:
 1.遍历两个链表,p代表head1的头结点,q代表head2的头结点
 2.如果p的下一个节点的值<q的下一个节点的值,就在h1链表中删除p节点,并将该节点插入
 到被插入链表中,被插入链表的两个指针后移(当前节点和当前节点的下一个节点).
 3. 如果大于被插入链表的当前节点,原链表的指针一直后移,直到遇到比自己大的节点,或
 者链表结束了,才停止遍历.
 4. 如果原链表中有剩余节点,插入到被插入链表中
  */

//   方法二:在原链表上操作
public static SingleLinkedList heBingLianBiao2(SingleLinkedList singleLinkedList1,SingleLinkedList singleLinkedList2) {
// 获取两个链表的头结点
    HeroNode p = singleLinkedList1.getHead();
    HeroNode q = singleLinkedList2.getHead();
 // 该指针用来记录被插入的链表的当前节点的后一个节点.
    HeroNode l = q.next;
   while(p.next!=null && q.next!=null){
   // 小于被插入链表的当前节点时,该节点在原链表中删除,插入的被插入链表中,同时,被插入链表的指针后移.
       if(p.next.no < q.next.no){
           q.next = p.next;
           p.next = p.next.next;
           q.next.next = l;
       }
       // 如果大于被插入链表的当前节点,原链表的指针一直后移,直到遇到比自己大的节点,或者链表结束了,才停止遍历.
           q = q.next;
           l = q.next;
       }

   if(p.next != null){
      q.next = p.next;
      singleLinkedList1.getHead().next = null;
   }
    return singleLinkedList2;
}


}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
1.算法是程序的灵魂,优秀的程序在对海量数据处理时,依然保持高速计算,就需要高效的数据结构和算法支撑。2.网上数据结构和算法的课程不少,但存在两个问题:1)授课方式单一,大多是照着代码念一遍,数据结构和算法本身就比较难理解,对基础好的学员来说,还好一点,对基础不好的学生来说,基本上就是听天书了2)说是讲数据结构和算法,但大多是挂羊头卖狗肉,算法讲的很少。 本课程针对上述问题,有针对性的进行了升级 3)授课方式采用图解+算法游戏的方式,让课程生动有趣好理解 4)系统全面的讲解了数据结构和算法, 除常用数据结构和算法外,还包括程序员常用10大算法:二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法、马踏棋盘算法。可以解决面试遇到的最短路径、最小生成树、最小连通图、动态规划等问题及衍生出的面试题,让你秒杀其他面试小伙伴3.如果你不想永远都是代码工人,就需要花时间来研究下数据结构和算法。教程内容:本教程是使用Java来讲解数据结构和算法,考虑到数据结构和算法较难,授课采用图解加算法游戏的方式。内容包括: 稀疏数组、单向队列、环形队列、单向链表、双向链表、环形链表、约瑟夫问题、栈、前缀、中缀、后缀表达式、中缀表达式转换为后缀表达式、递归与回溯、迷宫问题、八皇后问题、算法的时间复杂度、冒泡排序、选择排序、插入排序、快速排序、归并排序、希尔排序、基数排序(桶排序)、堆排序、排序速度分析、二分查找、插值查找、斐波那契查找、散列、哈希表、二叉树、二叉树与数组转换、二叉排序树(BST)、AVL树、线索二叉树、赫夫曼树、赫夫曼编码、多路查找树(B树B+树和B*树)、图、图的DFS算法和BFS、程序员常用10大算法、二分查找算法(非递归)、分治算法、动态规划算法、KMP算法、贪心算法、普里姆算法、克鲁斯卡尔算法、迪杰斯特拉算法、弗洛伊德算法马踏棋盘算法。学习目标:通过学习,学员能掌握主流数据结构和算法的实现机制,开阔编程思路,提高优化程序的能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值