复习算法 和 数据结构
链表任务列表
- 1. 删除链表的第N个节点
- 2. 删除链表重复项
- 3. 翻转链表
- 4. 翻转链表Ⅱ
- 5. 旋转链表
- 6. 重排链表
- 7. 合并两个有序链表
- 8. 相交链表
- 9. 环形链表
- 10. 对链表进行插入排序
- 11. 复制带随机指针的链表
- 12. 环形链表 II
- 13. 重排链表
- 14.LRU 缓存
1 删除链表的第N个节点
- 思路 :
- 解法一(朴素) : 第一次遍历得链表长度,枚举到倒数第N个节点并删除
- 解法二(快慢指针) : 快指针先走N个,然后一起走,快指针永远快于慢指针N个节点。快指针走到链表尾部 慢指针得下一个节点即为要删除的节点。
- 注意只有一个节点的删除问题。
ac_code 区域
解法一:
小边界问题: p != null 包含尾部 p.next != null 不包含尾部
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode p = head;
int length = 0;
// p != null 包含尾部
// p.next != null 不包含尾部
// 这点要注意
while (p != null) {
p = p.next;
length ++;
}
// 检查参数不合法的情况
if ( n > length) return head;
if (n == length) return head.next;
int idx = 0;
for (p = head ; p.next != null ; p = p.next) {
idx ++;
// 如果找到这个点
if (idx == length - n) {
p.next = p.next.next;
break;
}
}
System.out.println(length);
return head;
}
}
解法二:快慢指针法
快指针细节依然是 边界问题
if (f == null) return head.next; f == null 极端情况 0 的时候走到了 null 即为删除 第一个节点
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode f = head;
ListNode s = head;
// 先让 f 走 n 步
while (n -- > 0) {
f = f.next;
// 合法性的判断
if (f == null) return head.next;
}
for (; f.next != null; f = f.next,s = s.next) ;
s.next = s.next.next;
return head;
}
}
总结 : 两者 都要求边界处理的能力 , 以及链表头指针的删除。
2 删除链表重复项
- 思路 :
- 解法一 : 哈希表存值,和出现频率 ,双重遍历 暴力解决。
- 解法二 : 快慢指针法 快指针前遍历 不同值 连接即可解决
code
解法二 code:
class Solution {
public ListNode deleteDuplicates(ListNode head) {
if (head == null) return null;
ListNode s = head;
ListNode f = head;
for ( ; f != null ; f = f.next){
// 不同值 逻辑 处理 慢指针 依次连接快指针。
if ( s.val != f.val) {
s.next = f;
s = s.next;
}
}
// 这里必须要断开 ,这里不断的 话 , 后面全是重复元素时 s的next 指针永远指的是重复的值。
s.next = null;
return head;
}
}
3 翻转链表
- 思路 :
- 解法1 :迭代 想清楚每步的 链表对应关系即可解答
- 解法2 :递归
code
解法 1
class Solution {
public ListNode reverseList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode pre = null;
ListNode next = null;
while (head != null) {
next = head.next;
head.next = pre;
pre = head;
head = next;
}
return pre;
}
}
解法 2
class Solution {
public ListNode reverseList(ListNode head) {
return dfs (head);
}
private ListNode dfs (ListNode head) {
// 递归 三要素 : 递归出口 递归到底的条件 head 为新头
if (head == null || head.next == null) return head;
// 递归 到底 终止条件 为 新头
ListNode newHead = dfs(head.next);
// 递归最后一层结束 此时的head 为 尾部前一个节点 如此出栈
// 重复执行相同的操作 这是计算机最擅长的
head.next.next = head;
// 这里 想象一下 如果不断开节点 由 1 2 3 4 5 来说
// 5 为 newHead 4 为head 4 和 5 之间就是双向箭头 闭环链表 为图
// 所以此处应该断开 指向新头的节点 保留 逆向的指针
head.next = null;
return newHead;
}
}
4 翻转链表Ⅱ
- 思路 :
- 解法一 : 迭代 同上一题相似 , 只不过多了 区间限制 双指针闭区间 限制区域即可。
class Solution {
public ListNode reverseBetween(ListNode head, int m, int n) {
int k = n - m;
ListNode dummy = new ListNode(-1);
dummy.next = head;
ListNode pre = dummy;
while (m-- > 1){
pre = pre.next;
}
ListNode cur = pre.next;
while (k-- > 0){
ListNode next = cur.next;
cur.next = next.next;
next.next = pre.next;
pre.next = next;
}
return dummy.next;
}
}
晚安