目录
8.剑指 Offer 52. 两个链表的第一个公共节点(这个很经典,双指针)
前言
剑指offer中的所有链表题都做一遍。
还有快慢指针的题!
0812完成!
明天要完成所有的tree
1.剑指 Offer 35. 复杂链表的复制
使用hashmap解题即可,第一遍不存储关系,只按照顺序存一下所有node,并new出来。
第二遍根据cur.next 和cur.rand 来获取对应的node的指针~
![](https://img-blog.csdnimg.cn/20210813012635917.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
2.二叉搜索树和双向链表
![](https://img-blog.csdnimg.cn/20210813012635863.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
M难度
二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。
要求不能创建任何新的节点,只能调整树中节点指针的指向。
什么叫做二叉搜索树:
![](https://img-blog.csdnimg.cn/20210813012635788.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
![](https://img-blog.csdnimg.cn/20210813012635852.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
文件系统和数据库系统都有用到。
如果某个孩子结点或父结点不存在,则相应属性的值为空(NIL)。根结点是树中唯一父指针为NIL的结点,而叶子结点的孩子结点指针也为NIL。
结构
二叉搜索树是能够高效地进行如下操作的数据结构。
1.插入一个数值
2.查询是否包含某个数值
3.删除某个数值
// 打印中序遍历
void dfs(Node root) {
if(root == null) return;
dfs(root.left); // 左
System.out.println(root.val); // 根
dfs(root.right); // 右
}
算法流程:
dfs(cur): 递归法中序遍历;
终止条件: 当节点 cur 为空,代表越过叶节点,直接返回;
递归左子树,即 dfs(cur.left) ;
构建链表:
当 pre 为空时: 代表正在访问链表头节点,记为 head ;
当 pre 不为空时: 修改双向节点引用,即 pre.right = cur , cur.left = pre ;
保存 cur : 更新 pre = cur ,即节点 cur 是后继节点的 pre ;
递归右子树,即 dfs(cur.right) ;
treeToDoublyList(root):
特例处理: 若节点 root 为空,则直接返回;
初始化: 空节点 pre ;
转化为双向链表: 调用 dfs(root) ;
构建循环链表: 中序遍历完成后,head 指向头节点, pre 指向尾节点,因此修改 head 和 pre 的双向节点引用即可;
返回值: 返回链表的头节点 head 即可;
class Solution {
// 标准的树型的结构,需要转换成排序的 且双向链表
// 而且修安排就地完成转换操作。 left为前驱节点, right 为后置的节点
// 返回第一个节点
// 解法:中序遍历:
// 设前驱节点 pre 和当前节点 cur ,不仅应构建 pre.right = cur ,也应构建 cur.left = pre 。
// 因为,pre.right 前置的右 指向当前的节点, 当前节点的左,指向前置
// 标准的双向链表
// 循环链表最后在考虑
// 循环链表: 设链表头节点 head 和尾节点 tail ,则应构建 head.left = tail 和 tail.right = head 。
Node pre ,head = null;
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
// 头的左指针指向他的尾部
head.left = pre;
// 尾结点的右指针指向头
pre.right = head;
return head;
}
// 这个头和尾怎么看呢,因为是dfs, 所以最后会回到head 也就是头结点啊, 也就是head,但是head的pre 是什么呢
// 因为如果为空 那么head = cur,但是显然不是,所以 这个pre 就是执行到了末尾节点的,然后一层一层的都return了
void dfs(Node cur) {
if(cur == null) return;
dfs(cur.left);
if(pre != null) {
pre.right = cur;
}
else
{
head = cur;
}
cur.left = pre;
pre = cur;
dfs(cur.right);
}
}
3.剑指 Offer 06. 从尾到头打印链表
![](https://img-blog.csdnimg.cn/20210813012635870.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
4.剑指 Offer 18. 删除链表的节点(双指针)
![](https://img-blog.csdnimg.cn/20210813012635867.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
这个用双指针也是好办法: 跟我写得解法差不多
// 删除节点的话,
public ListNode deleteNode(ListNode head, int val) {
// 处理头结点
if(head.val == val) return head.next;
// pre 和cur指针
ListNode pre = head, cur = head.next;
while(cur != null && cur.val != val) {
pre = cur;
cur = cur.next;
}
if(cur != null) pre.next = cur.next;
return head;
}
5链表中倒数第k个节点(快慢指针)
最笨的方法,自己写的。
作为一个java程序员 干啥都想用map或者list
stack倒是想的很少~
好方法应该用快慢指针
![](https://img-blog.csdnimg.cn/20210813012635853.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
Krahets大佬的话,让我想起了链表经常用的快慢指针。
快指针先走k步,然后慢指针跟上,然后就是快指针到结束就完成了。
![](https://img-blog.csdnimg.cn/20210813012635917.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode former = head, latter = head;
for(int i = 0; i < k; i++)
former = former.next;
while(former != null) {
former = former.next;
latter = latter.next;
}
return latter;
}
}
只能说,精彩!
6.反转链表(双指针)
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
注意head==null的处理,第一遍写得时候没有在意这个东西。
![](https://img-blog.csdnimg.cn/20210813012635861.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
别人的解法:
递归我可能想不太好。但是双指针可以
依旧是Krahets 大佬的解法;
![](https://img-blog.csdnimg.cn/20210813012635806.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
就是一个翻转~
![](https://img-blog.csdnimg.cn/20210813012635807.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
这个图讲的特别好,所以先有个pre ,把next 转向pre就好了
pre最早是null 就可以~ 就有了上面的代码
递归
考虑使用递归法遍历链表,当越过尾节点后终止递归,在回溯时修改各节点的 next 引用指向。
![](https://img-blog.csdnimg.cn/20210813012635866.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
// 这个其实就是每次都递归cur 把cur 指向pre就好了
7.合并两个排序的链表
//2021.08新写的:
/**
* Definition for singly-linked list. public class ListNode { int val; ListNode next;
* ListNode(int x) { val = x; } }
*/
class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
// 主要是排序的链表
// 合并的话,找个head存一下头,然后谁大往后面挂一下呗
// 有一个为空,那么就结束了
ListNode head = new ListNode(0);
ListNode cur = head;
if (l1 == null) {
return l2;
} else if (l2 == null) {
return l1;
}
// 进行条件
while (l1 != null && l2 != null) {
if (l1.val > l2.val) {
cur.next = l2;
l2 = l2.next;
} else {
cur.next = l1;
l1 = l1.next;
}
cur = cur.next;
}
// 处理后续,需要需要把剩下的跑完
if (l1 == null) {
while (l2 != null) {
cur.next = l2;
l2 = l2.next;
cur = cur.next;
}
} else {
while (l1 != null) {
cur.next = l1;
l1 = l1.next;
cur = cur.next;
}
}
return head.next;
}
}
官方题解:
public ListNode gf_mergeTwoLists(ListNode l1, ListNode l2) {
ListNode dum = new ListNode(0), cur = dum;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
cur.next = l1;
l1 = l1.next;
} else {
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dum.next;
}
这种做法,主要是需要关心的电视,双指针!!
![](https://img-blog.csdnimg.cn/20210813012635771.png)
![](https://img-blog.csdnimg.cn/20210813012635827.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
我整体思路跟官方差不多,但是就是写的很丑陋呢???
![](https://img-blog.csdnimg.cn/20210813012635782.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
官方的精髓在这句话:
cur.next = l1 != null ? l1 : l2;
我没必要后续的在合并一遍
8.剑指 Offer 52. 两个链表的第一个公共节点(这个很经典,双指针)
让人想到了树的公共节点;
![](https://img-blog.csdnimg.cn/20210813012635835.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
对啊我写写着,hashset 他不香么???
![](https://img-blog.csdnimg.cn/20210813012635855.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
但是还是用刚想到的思路完成了:
官方题解:双指针法
// 202108在刷:
public ListNode getIntersectionNode_08(ListNode headA, ListNode headB) {
// 两个链表的第一个公共节点
// 如果不相交,那么返回NUll
// 可以两个都遍历一遍,拿到长度之后长的多走两步这种解法
// 循环一遍放到hashSET也行啊
ListNode tempA = headA;
ListNode tempB = headB;
int sizeA = 0;
int sizeB = 0;
while(tempA != null){
tempA = tempA.next;
sizeA++;
}
while(tempB != null){
tempB = tempB.next;
sizeB++;
}
// 绝对值
int distance = sizeA - sizeB > 0 ? sizeA - sizeB : sizeB - sizeA;
//让长的多走两步
tempA = headA;
tempB = headB;
if(sizeA - sizeB > 0){
//A 长
for(int i =0; i< distance;i++){
tempA=tempA.next;
}
} else{
for(int i =0; i< distance;i++){
tempB=tempB.next;
}
}
// 同时循环
while(tempA !=null && tempB !=null){
if(tempA == tempB){
return tempA;
}
tempA = tempA.next;
tempB = tempB.next;
}
return null;
}
官方题解:双指针法
![](https://img-blog.csdnimg.cn/20210813012635824.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3BtZHJlYW0=,size_16,color_FFFFFF,t_70)
这个太牛逼了!!看一遍觉得牛逼一遍