1、链表
1.1链表反转
反转一个单链表。
输入: 1->2->3->4->5
输出: 5->4->3->2->1
解法1:迭代,重复某一过程,每一次处理结果作为下一次处理的初始值,这些初始值类似于状态、每
次处理都会改变状态、直至到达最终状态
从前往后遍历链表,将当前节点的next指向上一个节点,因此需要一个变量存储上一个节点prev,当前
节点处理完需要寻找下一个节点,因此需要一个变量保存当前节点curr,处理完后要将当前节点赋值给
prev,并将next指针赋值给curr,因此需要一个变量提前保存下一个节点的指针next
解法2:递归:以相似的方法重复,类似于树结构,先从根节点找到叶子节点,从叶子节点开始遍历
大的问题(整个链表反转)拆成性质相同的小问题(两个元素反转)curr.next.next = curr
将所有的小问题解决,大问题即解决
public class ReverseLinkList {
static class ListNode {
int val;
ListNode next;
public ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
}
public static void main(String[] args) {
ListNode node5 = new ListNode(5, null);
ListNode node4 = new ListNode(4, node5);
ListNode node3 = new ListNode(3, node4);
ListNode node2 = new ListNode(2, node3);
ListNode node1 = new ListNode(1, node2);
ListNode recursion = recursion(node1);
System.out.println(recursion);
}
// 迭代
public static ListNode iterate(ListNode head) {
ListNode prev = null, curr, next;
curr = head;
while (curr != null) {
next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
}
// 递归
public static ListNode recursion(ListNode head) {
// 递归出口
if (head == null || head.next == null) {
return head;
}
ListNode newHead = recursion(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
1.2快慢指针之中间值问题
快慢指针指的是定义两个指针,这两个指针的移动速度一快一慢,以此来制造出自己想要的差值,这个差值可以让我们找到链表上相应的结点。一般情况下,快指针的移动步长为慢指针的两倍
public class FastSlowLinkList {
public static void main(String[] args) {
Node<String> first = new Node<>("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);
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
String mid = getMid(first);
System.out.println("中间值是:" + mid);
}
private 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;
}
}
}
1.3快慢指针之单向链表是否有环
使用快慢指针的思想,还是把链表比作一条跑道,链表中有环,那么这条跑道就是一条圆环跑道,在一条圆环跑道中,两个人有速度差,那么迟早两个人会相遇,只要相遇那么就说明有环。
public class Test {
public static void main(String[] args) {
Node<String> first = new Node<>("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>("ff", 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("单向链表是否有环:" + circle);
}
private 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;
}
}
}
1.4快慢指针之查找有环链表入口
当快慢指针相遇时,我们可以判断到链表中有环,这时重新设定一个新指针指向链表的起点,且步长与慢指针一样为1,则慢指针与“新”指针相遇的地方就是环的入口。
public class Test {
public static void main(String[] args) {
Node<String> first = new Node<>("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>("ff", null);
//完成结点之间的指向
first.next = second;
second.next = third;
third.next = fourth;
fourth.next = fifth;
fifth.next = six;
six.next = seven;
//产生环
seven.next = third;
Node entrance = getEntrance(first);
System.out.println("环形链表入口:" + entrance.item);
}
private static Node getEntrance(Node<String> first) {
Node slow = first;
Node fast = first;
Node 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;
}
private static class Node<T> {
T item;
Node next;
public Node(T item, Node next) {
this.item = item;
this.next = next;
}
}
}
1.5约瑟夫问题
问题描述:
传说有这样一个故事,在罗马人占领乔塔帕特后,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 Test {
public static void main(String[] args) {
// 构建循环链表
Node<Integer> first = null;
// 记录前一个节点
Node<Integer> prev = null;
for (int i = 1; i <= 41; i++) {
// 第一个元素
if (i == 1) {
first = new Node(i, null);
prev = first;
continue;
}
Node<Integer> node = new Node<>(i, null);
prev.next = node;
prev = node;
if (i == 41) {
// 构建循环链表
prev.next = first;
}
}
// 使用count,记录当前的报数值
int count = 0;
// 遍历链表,每循环一次,count++
Node<Integer> n = first;
Node<Integer> before = null;
if (n != n.next) {
// 判断count的值,如果是3,则从链表删除这个节点并打印节点的值,把count置为0
count++;
if (count == 3) {
// 删除节点
before.next = n.next;
System.out.println(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;
}
}
}