1.链表反转
单链表的反转,是面试中的一个高频题目。
需求:
原链表中数据为:1->2->3>4
反转后链表中数据为:4->3->2->1
反转API:
public void reverse():对整个链表反转
public Node reverse(Node curr):反转链表中的某个结点curr,并把反转后的curr结点返回使用递归可以完成反转
,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。
public class Test2 <T> implements Iterable<T>{
public static void main(String[] args)throws Exception {
Test2<String> list = new Test2<>();
list.insert(0, "张三");
list.insert(1, "李四");
list.insert(2, "王五");
list.insert(3, "赵六");
for (String s : list) {
System.out.println(s);
}
System.out.println("============");
list.change();
for(String s:list){
System.out.println(s);
}
}
private static Node head;
private static int N;
public static void change() {
if (N==0){//当前是空链表,不需要反转
return;
}
reverse(head.next);
}
/**
*
* @param curr 当前遍历的结点
* @return 反转后当前结点上一个结点
*/
public static Node reverse(Node curr){
if(curr.next==null){//已经到了最后一个元素
head.next=curr;//反转后,头结点应该指向原链表中的最后一个元素
return curr;
}
//当前结点的上一个结点
Node pre = reverse(curr.next);
pre.next=curr;
curr.next= null;//当前结点的下一个结点设为null
return curr;//返回当前结点
}
public Test2() {
this.head = new Node(null,null);//初始化头结点、
this.N=0;//初始化元素个数
}
public void insert(T t){//向链表中添加元素t
Node n = head;//找到最后一个节点
while(n.next!=null){
n=n.next;
}
Node newNode = new Node(t,null);
n.next = newNode;
N++;//链表长度+1
}
public void insert(int i,T t){//向指定位置i处,添加元素t
//寻找位置i之前的结点
Node pre = head;
for(int index=0;index<i-1;index++){
pre=pre.next;
}
//位置i的结点
Node curr = pre.next;
//构建新的结点,让新结点指向位置i的结点
Node newNode= new Node(t,curr);
pre.next = newNode;
//长度+1
N++;
}
@Override
public Iterator<T> iterator() {
return new LIterator();
}
private class LIterator implements Iterator{
private Node n;
public LIterator(){
this.n= head;
}
@Override
public boolean hasNext() {
return n.next != null;
}
@Override
public Object next() {
n=n.next;
return n.item;
}
}
public static class Node<T>{
public T item;//存储元素
public Node next;//指向下一个结点
public Node(T item,Node next){
this.item = item;
this.next= next;
}
}
}
李四
王五
赵六
张三
============
张三
赵六
王五
李四
2.快慢指针
快慢指针指的是定义两个指针,这两个指针的移动速度一快一慢,以此来制造出自己想要的差值。我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍。
1)中间值问题
public class Test2 <T> {
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) {
return null;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
需求:
请完善测试类Test中的getMid方法,可以找出链表的中间元素值并返回。
利用快慢指针,我们把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的。
如下图,最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。
public class Test2 <T> {
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> slow = first;
Node<String> fast = first;
while(fast!=null && fast.next!=null){
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;
}
}
}
中间值为:dd
2)单向链表是否有环问题
public class Test2 <T> {
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;
//产生环
seven.next = third;
//判断链表是否有环
boolean circle = isCircle(first);
System.out.println("first链表中是否有环:"+circle);
}
/**
* 判断链表中是否有环
* @param first 链表首结点
* @return ture为有环,false为无环
*/
public static boolean isCircle(Node<String> first) {
return false;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
first链表中是否有环:false
需求:
请完善测试类Test中的isCircle方法,返回链表中是否有环。
使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇,只要相遇那么就说明有环。
public class Test2 <T> {
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;
//产生环
seven.next = third;
//判断链表是否有环
boolean circle = isCircle(first);
System.out.println("first链表中是否有环:"+circle);
}
/**
* 判断链表中是否有环
* @param first 链表首结点
* @return ture为有环,false为无环
*/
public static boolean isCircle(Node<String> first) {
Node<String> slow = first;
Node<String> fast = first;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
3)有环链表入口问题
需求:
请完善Test类中的getEntrance方法,查找有环链表中环的入口结点。
当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识,这里略,只讲实现。
public class Test2 <T> {
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;
//产生环
seven.next = third;
//判断链表是否有环
boolean circle = isCircle(first);
System.out.println("first链表中是否有环:"+circle);
System.out.println("=============");
//查找环的入口结点
Node<String> entrance = getEntrance(first);
System.out.println("first链表中环的入口结点元素为:"+entrance.item);
}
/**
* 查找有环链表中环的入口结点
* @param first 链表首结点
* @return 环的入口结点
*/
public static Node getEntrance(Node<String> first) {
Node<String> slow = first;
Node<String> fast = first;
Node<String> temp = null;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow=slow.next;
if (fast.equals(slow)){
temp = first;
continue;
}
if (temp!=null){
temp=temp.next;
if (temp.equals(slow)){
return temp;
}
}
}
return null;
}
/**
* 判断链表中是否有环
* @param first 链表首结点
* @return ture为有环,false为无环
*/
public static boolean isCircle(Node<String> first) {
Node<String> slow = first;
Node<String> fast = first;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
//结点类
private static class Node<T> {
//存储数据
T item;
//下一个结点
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
first链表中是否有环:true
=============
first链表中环的入口结点元素为:cc
3.循环链表
循环链表
,顾名思义,链表整体要形成一个圆环状
。在单向链表
中,最后一个节点的指针为null
,不指向任何结点,因为没有下一个元素了。要实现循环链表
,我们只需要让单向链表的最后一个节点的指针指向头结点即可。
public class LinkList {
public static void main(String[] args) {
Node<Integer> first = new Node<Integer>(1,null);//构建结点
Node<Integer> second = new Node<Integer>(2,null);
Node<Integer> third = new Node<Integer>(3,null);
Node<Integer> fourth = new Node<Integer>(4,null);
first.next= second;//生成链表
second.next = third;
third.next = fourth;
//构建循环链表,让最后一个结点指向第一个结点
fourth.next = first;
}
public static class Node<T>{
public T item;//存储元素
public Node next;//指向下一个结点
public Node(T item,Node next){
this.item = item;
this.next= next;
}
}
}
4.约瑟夫问题
问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,39 个犹太人与约瑟夫及他的朋友躲到一个洞中,39个犹太人决定宁愿死也不要被敌人抓到,于是决定了一个自杀方式,41个人排成一个圆圈,第一个人从1开始报数,依次往后,如果有人报数到3,那么这个人就必须自杀,然后再由他的下一个人重新从1开始报数,直到所有人都自杀身亡为止。然而约瑟夫和他的朋友并不想遵从。于是,约瑟夫要他的朋友先假装遵从,他将朋友与自己安排在第16个与
第31个位置,从而逃过了这场死亡游戏 。
问题转换:
41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
1.编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
2.自退出那个人开始的下一个人再次从1开始报数,以此类推;
3.求出最后退出的那个人的编号。
解题思路:
1.构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;
2.使用计数器count,记录当前报数的值;
3.遍历链表,每循环一次,count++;
4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
public class Test2 <T> {
public static void main(String[] args)throws Exception {
//1.构建循环链表
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> node = new Node<>(i,null);
pre.next = node;
pre = node;
if (i==41){
//构建循环链表,让最后一个结点指向第一个结点
pre.next=first;
}
}
//2.使用count,记录当前的报数值
int count=0;
//3.遍历链表,每循环一次,count++
Node<Integer> n = first;
Node<Integer> before = null;
while(n!=n.next){
//4.判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
count++;
if (count==3){
//删除当前结点
before.next = n.next;
System.out.print(n.item+",");
count=0;
n = n.next;
}else{
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;
}
}
}
3,6,9,12,15,18,21,24,27,30,33,36,39,1,5,10,14,19,23,28,32,37,41,7,13,20,26,34,40,8,17,29,38,11,25,2,22,4,35,16,31