前言
为了面试,时隔一年不得不重新复习数据结构与算法,这次重新复习很有收获,以前在上课总有不清晰的知识,通过这次手写一遍和敲一遍代码,以前不清晰的知识都变得清晰了,果然学编程还是得多敲代码,多敲代码,多敲代码,也要多总结,多总结,多总结。
学习资源
视频 黑马程序员 数据结构与算法
4.反转链表
单链表的反转,是面试中的一个高频题目。
需求:
原链表中数据为:1->2->3>4
反转后链表中数据为:4->3->2->1
使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。
//反转整个链表
public void reverse(){
//判断当前链表是否为空链表,如果是空链表,则结束运行,如果不是,则调用重载的reverse方法完成反转
if (isEmpty()){
return;
}
reverse(head.next);
}
// (递归) 反转指定的结点curr,并把反转后的结点返回
public Node reverse(Node curr){
if (curr.next==null){
head.next=curr;
return curr;
}
//递归的反转当前结点curr的下一个结点;返回值就是链表反转后,当前结点的上一个结点
Node pre = reverse(curr.next);
//让返回的结点的下一个结点变为当前结点curr;
pre.next=curr;
//把当前结点的下一个结点变为null
curr.next=null;
return curr;
}
// (迭代)
public void reverse(){
Node curr = head.next; //头结点不存元素
Node pre =null;
while(curr!=null){
Node nextNode = curr.next;
curr.next = pre;
pre = curr ;
curr = nextNode;
}
}
5.快慢指针
快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,
以此来制造出自己想要的差值,这个差值可以然我们找到链表上相应的结点。
一般情况下,快指针的移动步长为慢指针的两倍
public class FastSlowTest {
public static void main(String[] args) throws Exception {
//创建结点
Node<String> first = new Node<String>("aa", null);
Node<String> second = new Node<String>("bb", null);
Node<String> third = new Node<String>("cc", null);
Node<String> fourth = new Node<String>("dd", null);
Node<String> fifth = new Node<String>("ee", null);
Node<String> six = new Node<String>("ff", null);
Node<String> seven = new Node<String>("gg", null);
//完成结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//查找中间值
String mid = getMid(first);
System.out.println("中间值为:"+mid);
}
/**
* @param first 链表的首结点
* @return 链表的中间结点的值
*/
public static String getMid(Node<String> first) {
//定义两个指针
Node<String> fast = first;
Node<String> slow = first;
//使用两个指针遍历链表,当快指针指向的结点没有下一个结点了,就可以结束了,结束之后,慢指针指向的结点就是中间值
while(fast!=null &&fast.next!=null){
//变化fast的值和slow的值
fast = fast.next.next;
slow=slow.next;
}
return slow.item;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
5.1 中间值
//快指针比慢指针多走一倍 ,快指针走完,慢指针到中间 中间值
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow =slow.next;
}
return slow.item; //中间值
5.2 链表是否有环
//快指针比慢指针多走一倍 如果有环 就跳不出循环,两指针一定会相遇(环形跑道)
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow =slow.next;
if(fast.equals.(slow)){ //两指针相遇
return true;
}
}
return false; //无环
5.2 有环链表的入口问题
Node fast = head;
Node slow = head;
Node tmp = null; //临时指针
//快指针比慢指针多走一倍 如果有环 就跳不出循环,两指针一定会相遇(环形跑道)
while(fast!=null&&fast.next!=null){
fast=fast.next.next;
slow =slow.next;
if(fast.equals.(slow)){ //两指针相遇
tmp=head; //临时指针从头开始跑
}
if(tmp!=null){
tmp=tmp.next;
if(tmp.equals.(slow)){ //两指针相遇
return tmp; //环入口
}
}
}
return null; //无环
6.循环链表
循环链表,顾名思义,链表整体要形成一个圆环状。在单向链表中,最后一个节点的指针为null,不指向任何结点,因为没有下一个元素了。要实现循环链表,我们只需要让单向链表的最后一个节点的指针指向头结点即可
7.约瑟夫问题
问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往
后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与第31个位置,从而逃过了这场死亡游戏 。问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
求出最后退出的那个人的编号。
解题思路:
1.构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;
2.使用计数器count,记录当前报数的值;
3.遍历链表,每循环一次,count++;
4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
//解决约瑟夫问题
public class JosephTest {
public static void main(String[] args) {
//1.构建循环链表,包含41个结点,分别存储1~41之间的值
//用来就首结点
Node<Integer> first = null;
//用来记录前一个结点
Node<Integer> pre = null;
for(int i = 1;i<=41;i++){
//如果是第一个结点
if (i==1){
first = new Node<>(i,null);
pre = first;
continue;
}
//如果不是第一个结点
Node<Integer> newNode = new Node<>(i, null);
pre.next=newNode;
pre=newNode;
//如果是最后一个结点,那么需要让最后一个结点的下一个结点变为first,变为循环链表了
if (i==41){
pre.next=first;
}
}
//2.需要count计数器,模拟报数
int count=0;
//3.遍历循环链表
//记录每次遍历拿到的结点,默认从首结点开始
Node<Integer> n = first;
//记录当前结点的上一个结点
Node<Integer> before = null;
while(n!=n.next){
//模拟报数
count++;
//判断当前报数是不是为3
if (count==3){
//如果是3,则把当前结点删除调用,打印当前结点,重置count=0,让当前结点n后移
before.next=n.next;
System.out.print(n.item+",");
count=0;
n=n.next;
}else{
//如果不是3,让before变为当前结点,让当前结点后移;
before=n;
n=n.next;
}
}
//打印最后一个元素
System.out.println(n.item);
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}