1 链表反转
/**
迭代解法
* 1 先记录要反转的next的值
* 2 将当前指针所指向的反转
* 3 将pre指向curr
* 4 将curr 指向next
* @param node
* @return
*/
public static Node<Integer> reverseNode(Node<Integer> node){
//需要3个指针
Node<Integer> preNode = null;
Node<Integer> currNode = node;
Node<Integer> nextNode = node;
while(currNode != null) {
nextNode = currNode.next;//1 先记录要反转的next的值
currNode.next = preNode;//2 将当前指针所指向的反转
preNode = currNode;//3 将pre指向curr
currNode = nextNode;//4 将curr 指向next
}
return preNode;
}
递归解法
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode curr =reverseList(head.next);
head.next.next = head;//反转指针
head.next = null;
return curr;
}
2 求链表的中间节点
/**
* 求链表的中间节点
* 1 用两个快慢指针(快指针是慢指针速度的2倍。所以当快指针到达节点时,慢指针指向中点)
* @param node
* @return
*/
public static Node<Integer> findMiddleNode(Node<Integer> node){
/**
* 注意点1:
* 当链表有偶数个节点时,中间值会有两个。
* 最终是要返回前一个还是后一个取决与刚开始的slow和fast指针的初始化
* 如果是返回后一个,则初始化slow = fast = node;
* 如果是返回前一个,则初始化slow = fast = head; head.next= node;
*
*/
//Node<Integer> head = new Node<Integer>(0);
//head.next = node;
Node<Integer> slow = node;
Node<Integer> fast = node;
/**
* 注意点2:
* 循环终止条件两个条件满足其一即可
* (一个用于偶数个节点链表的判断,一个用于奇数个节点链表的判断)
*/
while(fast !=null&& fast.next != null) {
slow = slow.next;
fast=fast.next.next;
}
return slow;
}
3.合并两个有序链表(迭代解法)
图示:
代码如下
/**
* 两个有序链表的合并(迭代解法)
* @param node1
* @param node2
*/
public static Node<Integer> combineTwoNodeList(Node<Integer> node1,Node<Integer> node2) {
//先初始化一个合并链表
Node<Integer> prehead =new Node<>(-1);
Node<Integer> combineres = prehead;
//迭代比较两个链表元素的大小
while(node1 != null && node2 != null) {
if(node1.data > node2.data) {
prehead.next = node2;
node2 = node2.next;
}else {
prehead.next = node1;
node1 = node1.next;
}
prehead = prehead.next;
}
//处理还未被合并的链表
if(node1 != null) {
prehead.next = node1;
}
if(node2 != null) {
prehead.next = node2;
}
return combineres.next;
}
4.判断是否环形链表
/**
* 链表中环的检测
* 思路:
* 一个慢指针,一个快指针。
* 慢指针走一步,快指针走二步。
* 如果最后快指针能够追上慢指针,则说明有环
* @param node
*/
public static boolean findIsLoop(Node<Integer> node) {
Node<Integer> slow =node;
Node<Integer> fast =node;
while(fast!=null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if(fast == slow) {
return true;
}
}
return false;
}
5.求出环形链表的环的长度
/**
* 求出环形链表的长度
* 思路:利用快慢指针先求出第一次相遇的节点,
* 然后快指针不动,慢指针继续走。
* 当慢指正与快指针再次相等时,
* 慢指针走的路程正好是一圈
* @param node
* @return
*/
public static int findLoopLength(Node<Integer> node) {
Node<Integer> slow = node;
Node<Integer> fast = node;
if(node == null) {
return 0;
}
slow = slow.next;
fast = fast.next.next;
//先求出第一次遇见的节点
while(slow != fast) {
slow = slow.next;
fast = fast.next.next;
}
int length =1;
//再求出第二次相遇所在的节点。fast点不动,slow继续走
//当slow == fast的时候,正好slow指针走过了一圈
slow = slow.next;
while(slow != fast) {
slow = slow.next;
length++;
}
return length;
}
6.求出环形链表的入环节点
解题思路:
1、一个快指针,一个慢指针。快指针走2步,慢指针走1步
2、假设快指针与慢指针第一次相遇在a点。
从头结点到入环点的距离为D,第一次相遇的点a到入环点o的距离为S
3 、第一次相遇时,慢指针走过 D+S,快指针走过D+S+rn
(r为圈数,n为一圈的距离)。
4、 因为快指针的速度是慢指针的2倍。所以快指针走过的距离也是慢指针距离的两倍。因为可得 2*(D+S)=D+S+rn
5、 简化上述公式 2*(D+S)=D+S+rn ==> D=rn-S
6 、在相遇之后让其中一个指针从头开始走D个距离。另一个指针从第一次相遇点开始走rn-S个距离。两个指针每次都只走一步。
7、联合第5步的公式和第6步可得,当一个指针走过D个距离时(即走到了入环点)。
另一个指针走个rn-S个距离。由图可得。rn-S个距离时,该指针所在位置刚也好为入环点位置。所以此时,两个指针相遇。由此可得入环点。
代码如下:
/**
* 求出环形链表的入环点
* @param node
* @return
*/
public static Node<Integer> findTheRingPoint(Node<Integer> node){
Node<Integer> slow =node;
Node<Integer> fast =node;
//先找到第一个相遇点
while(fast!=null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if(fast == slow) {
break;
}
}
if(fast==null || fast.next == null) return null;
//让其中一个指针从头开始走,找到再次相遇的点
fast = node;
while(fast != slow) {
slow = slow.next;
fast = fast.next;//注意:这里两个指针都只走一步
}
return slow;
}
7 模拟LRU缓存
- LRU缓存:即最近最少使用原则。
- 首先要接收一个 capacity 参数作为缓存的最大容量,然后实现两个 API,一个是 put(key, val) 方法存入键值对,如果缓存满了,则淘汰最之前使用的。另一个是 get(key) 方法获取 key 对应的 val,如果 key 不存在则返回 -1,如果key存在将该key移动到最前面。
- get()和put()的时间复杂度都需要为o(1)
- 数据结构:哈希+链表
- 代码如下
//LRU缓存算法
//1 先构建node
class Node1{
public int key, value;
public Node1 pre, next;
public Node1(int key,int value) {
this.key =key;
this.value=value;
}
}
//2 然后构建双向链表
class DoubleList{
public Node1 head,tail;
public int capacity;
public DoubleList() {
head = new Node1(0, 0);
tail = new Node1(0, 0);
head.next = tail;
tail.pre = head;
capacity = 0;
}
//将node1 移动到头部
public void moveToHead(Node1 x) {
x.next = head.next;
x.pre = head;
head.next.pre = x;
head.next = x;
capacity++;
}
//删除链表中的node1(假设一定存在)
public void removeNode(Node1 x) {
x.pre.next = x.next;
x.next.pre = x.pre;
capacity--;
}
//删除尾节点
public Node1 deleteTail() {
if(tail.pre ==head) {
System.out.println("no");
return null;
}
Node1 node = tail.pre;
removeNode(node);
return node;
}
}
//3 结合哈希和双向链表
public class LRUcache {
private HashMap<Integer, Node1> map;
private DoubleList doubleList;
private int capacity;
public LRUcache(int capacity) {
this.map = new HashMap<>();
this.doubleList = new DoubleList();
this.capacity = capacity;
}
public void put(int key, int val) {
//先构建一个node
Node1 node =new Node1(key,val);
//如果已经存在这个节点,先删除老节点的,然后加到头部
if(map.containsKey(key)) {
doubleList.removeNode(map.get(key));
doubleList.moveToHead(node);
map.put(key, node);
}else {
//如果缓存已满,先删除尾部节点,然后加到头部
if(this.capacity == doubleList.capacity) {
Node1 last =doubleList.deleteTail();
map.remove(last.key);
}
doubleList.moveToHead(node);
//如果缓存未满,直接加到头部
map.put(key, node);
}
}
public int getKey(int key) {
//如果没有key,直接返回-1
if(!map.containsKey(key)) {
return -1;
}
//如果有,调用put方法
Node1 node = map.get(key);
put(key,node.value);
return node.value;
}
}