链表–常见应用场景
链表反转
单向表的反转,是面试中的一个高频题目。
需求:
单向链表
- 原链表中数据为:1->2->3>4
- 反转后链表中数据为:4->3->2->1
反转API:
原来解析:
- 使用递归可以完成反转,递归反转其实就是从原链表的第一个存数据的结点开始,依次递归调用反转每一个结点,直到把最后一个结点反转完毕,整个链表就反转完毕。
代码实现:
public void reverse(){
if (isEmpty()){
return;
}
reverse(head.next);
}
public Node reverse(Node curr){
if (curr.next==null){
head.next=curr;
return curr;
}
Node pre = reverse(curr.next);
pre.next=curr;
curr.next=null;
return curr;
}
测试:
@Test
public void test01(){
LinkList<Integer> list = new LinkList<>();
list.insert(1);
list.insert(2);
list.insert(3);
list.insert(4);
for (Integer i : list) {
System.out.print(i+" ");
}
System.out.println();
System.out.println("--------------------");
list.reverse();
for (Integer i : list) {
System.out.print(i+" ");
}
}
快慢指针
- 快慢指针指的是定义两个指针,这两个指针的移动速度一块一慢,以此来制造出自己想要的差值,这个差值可以然我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍
1. 中间值问题
需求;
- 找到一串节点当中的中间值元素
原理分析:
- 利用快慢指针,我们把一个链表看成一个跑道,假设a的速度是b的两倍,那么当a跑完全程后,b刚好跑一半,以此来达到找到中间节点的目的。
图解:
- 如下图,最开始,slow与fast指针都指向链表第一个节点,然后slow每次移动一个指针,fast每次移动两个指针。
代码:
public static String getMid(Node<String> first) {
Node<String> fast = first;
Node<String> slow = first;
while(fast!=null &&fast.next!=null){
fast = fast.next.next;
slow=slow.next;
}
return slow.item;
}
测试:
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);
}
public static String getMid(Node<String> first) {
Node<String> fast = first;
Node<String> slow = 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;
}
}
}
2. 单向链表是否有环问题
原理解析:
- 使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇,只要相遇那么就说明有环。
代码
public static boolean isCircle(Node<String> first) {
Node<String> fast = first;
Node<String> slow = first;
while(fast!=null && fast.next!=null){
fast = fast.next.next;
slow = slow.next;
if (fast.equals(slow)){
return true;
}
}
return false;
}
测试
public class CircleListCheckTest {
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);
}
public static boolean isCircle(Node<String> first) {
Node<String> fast = first;
Node<String> slow = 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. 有环链表入口问题:
原理解析:
- 当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。证明这一结论牵涉到数论的知识,这里略,只讲实现。
代码:
public static Node getEntrance(Node<String> first) {
Node<String> fast = first;
Node<String> slow = 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)){
break;
}
}
}
return temp;
测试:
package main.java.Algorithms.linear;
public class CircleListInTest {
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;
Node<String> entrance = getEntrance(first);
System.out.println("first链表中环的入口结点元素为:"+entrance.item);
}
public static Node getEntrance(Node<String> first) {
Node<String> fast = first;
Node<String> slow = 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)){
break;
}
}
}
return temp;
}
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
约瑟夫问题
问题描述:
问题转换:
首先41个人坐一圈,第一个人编号为1,第二个人编号为2,第n个人编号为n。
- 编号为1的人开始从1报数,依次向后,报数为3的那个人退出圈;
- 自退出那个人开始的下一个人再次从1开始报数,以此类推;
- 求出最后退出的那个人的编号。
图示:
解题思路:
- 构建含有41个结点的单向循环链表,分别存储1~41的值,分别代表这41个人;
- 使用计数器count,记录当前报数的值;
- 遍历链表,每循环一次,count++;
- 判断count的值,如果是3,则从链表中删除这个结点并打印结点的值,把count重置为0;
代码:
善于创建临时变量指针来记录关键元素
package main.java.Algorithms.linear;
import org.jetbrains.annotations.NotNull;
public class JosephTest01 {
public static void main(String[] args) {
Node<Integer> first = getNodeList();
run(first);
}
private static Node<Integer> getNodeList() {
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;
if (i == 41) {
pre.next = first;
}
}
return first;
}
private static void run(Node<Integer> first) {
int count = 0;
Node<Integer> n = first;
Node<Integer> before = null;
while (n != n.next) {
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;
}
}
}
|