链表反转
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题
-
直接将链表看成两部分,一部分是已经反转的,一部分是待反转的。
可以如下:
分割开,每次需要使用三个指针,一个是已经反转的部分,一个是待反转部分,一个是待反转部分的下一个。
代码如下:
public ListNode reverseList(ListNode head) { ListNode pre = null, cur = head; while (cur != null) { ListNode tmp = cur.next; cur.next = pre; pre = cur; cur = tmp; } return pre; }
-
递归解法
遍历到链表尾部,再将尾结点的下一个指向前一个结点即可。
这个不好画图,简单说明如下:
先找到链表尾。
由于一般会在head.next为空结束,说以当前节点会是head,那么我们有了当前节点,以及当前节点后的结点,只需要设置next结点即可。
public ListNode reverseList2(ListNode head) { if (head == null || head.next == null) { return head; } ListNode resHead = reverseList2(head.next); head.next.next = head; head.next = null; return resHead; }
合并有序链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法
用伪头结点,将小元素链接到后面,最后返回伪头结点的next即可。
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode resHead = new ListNode();
ListNode res = resHead;
ListNode p1 = l1, p2 = l2;
while (p1 != null &&
p2 != null) {
if (p1.val <= p2.val) {
res.next = p1;
p1 = p1.next;
} else {
res.next = p2;
p2 = p2.next;
}
res = res.next;
}
res.next = p1 != null?
p1:
p2;
return resHead.next;
}
链表的中间结点
给定一个带有头结点 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
示例 1:
输入:[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5])
示例 2:
输入:[1,2,3,4,5,6]
输出:此列表中的结点 4 (序列化形式:[4,5,6])
由于该列表有两个中间结点,值分别为 3 和 4,我们返回第二个结点。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/middle-of-the-linked-list
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法
-
快慢指针,快指针到达尾部,返回慢指针对应的值即可。
T:n。S:1;
public ListNode middleNode(ListNode head) { if (head == null || head.next == null) { return head; } ListNode slow = head, fast = head.next; while (fast != null) { slow = slow.next; fast = fast.next == null? null: fast.next.next; } return slow; }
-
用数组存储,直接用数据的中点索引即可。
T:n;S:n
if (head == null || head.next == null) { return head; } ListNode[] listNodes = new ListNode[100]; int i = 0; ListNode p = head; while (p != null) { listNodes[i] = p; p = p.next; i++; } return (i & 1) == 1 ? listNodes[i / 2] : listNodes[i / 2 - 1];
-
单指针。扫描一遍得到链表长,再扫描一遍取中点。
T:n。S:1
public ListNode middleNode3(ListNode head) { int len = 0; ListNode p1 = head, p2 = head; while (p1 != null) { p1 = p1.next; len ++; } int i = len / 2; while (i-- >= 0) { p2 = p2.next; } return p2; }
LRU
设计和构建一个“最近最少使用”缓存,该缓存会删除最近最少使用的项目。缓存应该从键映射到值(允许你插入和检索特定键对应的值),并在初始化时指定最大容量。当缓存被填满时,它应该删除最近最少使用的项目。
它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) - 如果密钥 (key) 存在于缓存中,则获取密钥的值(总是正数),否则返回 -1。
写入数据 put(key, value) - 如果密钥不存在,则写入其数据值。当缓存容量达到上限时,它应该在写入新数据之前删除最近最少使用的数据值,从而为新的数据值留出空间。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/lru-cache-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法
使用hashmap + 双向链表结点实现,再使用两个哨兵头尾结点简化编程。
public class LruHashMap {
private Map<Integer, BANode> map;
private int capacity;
private int size;
private BANode head;
private BANode tail;
public LruHashMap(int capacity) {
this.capacity = capacity;
}
public void put(int key, int value) {
if (isEmpty()) {
init();
}
BANode n = map.get(key);
// 有值则修改,放到头部
if (n != null) {
n.value = value;
afterAccess(n);
} else {
// 无值则新建,放到头部
n = new BANode(key, value);
map.put(key, n);
moveToHead(n);
size++;
// 判断是否超出容量
afterInsertion();
}
}
private void afterInsertion() {
if (size > capacity) {
BANode pre = tail.before;
BANode realPre = tail.before.before;
map.remove(tail.before.key);
tail.before = realPre;
realPre.after = tail;
pre.after = null;
pre.before = null;
size--;
}
}
public int get(int key) {
if (isEmpty()) {
return -1;
}
BANode n = map.get(key);
if (n == null) {
return -1;
}
afterAccess(n);
return n.value;
}
/**
* 被访问之后,将其前后结点连接起来,将被访问结点移至头部
*/
private void afterAccess(BANode n) {
BANode af = n.after;
BANode be = n.before;
af.before = be;
be.after = af;
n.before = null;
n.after = null;
moveToHead(n);
}
private void moveToHead(BANode n) {
head.after.before = n;
n.after = head.after;
head.after = n;
n.before = head;
}
private boolean isEmpty() {
return size == 0;
}
private void init() {
map = new HashMap<>();
head = new BANode();
tail = new BANode();
head.after = tail;
tail.before = head;
}
}
链表环判断
给定一个链表,判断链表中是否有环。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。
示例 1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/linked-list-cycle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解法
快慢指针,当快指针等于慢指针,表明有环
public boolean hasCycle(ListNode head) {
if (head == null || head.next == null) {
return false;
}
ListNode slow = head, fast = head.next;
while (slow != fast) {
if (fast == null || fast.next== null) {
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
链表环起始点
找到环的起点
思路:快慢指针。
如图,假设从 开始到环起点距离为a,环起点 -> 相遇点距离为b,相遇点 -> 环起点距离为c。
快慢指针碰面的情况,
- 慢指针走的路程为 a + b
- 快指针走的路程为 a + (b + c)k + b【k指代走圈的次数】
因为 2V慢 = V快,所以 2S慢 = S快 【V指速度,S指路程】
所以
2(a + b) = a + (b + c)k + b
a = bk + ck - b
a = (b + c)(k - 1) + c
也就是 a = k圈环长 + c
所以当快慢指针相遇后,慢指针从起点开始,快指针从相遇点开始,都以相同速度开始前进,其碰面的地方就是环的起点。
https://leetcode.cn/problems/c32eOV/
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public ListNode detectCycle(ListNode head) {
// 1 => null
// 1, 2, 1 => 1
// 1, 2, 3, 2 => 2
// 1, 2, 3 => null
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {
slow = head;
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
return slow;
}
}
return null;
}
}
链表环长度
这个没找到题目,我自己验证了几个示例,如果有问题还请指出。
也是快慢指针的思路,记录第一次相遇时的计数器,再记录第二次的,相减就是环长。(因为可能会很凑巧直接碰到,所以没有办法通过一次的碰面确定环长。)
public static int getCircleLen(Node head) {
if (head == null || head.next == null) {
return 0;
}
Node slow = head, fast = head.next;
int firstMeet = -1;
int count = 0;
while (firstMeet == -1 || slow != fast) {
if (fast == null || fast.next == null) {
return 0;
}
if (slow == fast) {
firstMeet = count;
}
count++;
slow = slow.next;
fast = fast.next.next;
}
return count - firstMeet;
}