day_03 - 链表部分
传送门
1. 链表基础知识
C语言的链表定义
struct ListNode {
int val;
struct ListNode *next;
};
java的链表定义
// 没放在同一个包里面,所以用了 public
public class ListNode {
public int val;
public ListNode next;
ListNode() {}
ListNode(int val) { this.val = val; }
ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}
2. 题解部分
203 - 移除链表元素
- 思路及代码
- 添加虚拟头,让处理的时候排除头节点的特判情况
- 遍历整个链表,在这里选择了一前一后指针判断,分情况进行处理
struct ListNode* removeElements(struct ListNode* head, int val){
typedef struct ListNode ListNode; // 里面也可以给结构体改名,作用域为当前文件
ListNode* add_head = malloc(sizeof(ListNode));
add_head -> next = head; // 新添加的头节点指向原来的链表
// 定义 p1, p2 指针一前一后对链表进行处理
ListNode* p1 = add_head; // p1 在前
ListNode* p2 = head; // p2 在后
ListNode* p_free = NULL;
while(p2 != NULL){ // p2 遍历链表
if(p2->val == val) { // 遇到要删除的
p_free = p2;
p2 = p2 -> next;
// free(tmp);
p1 -> next = p2;
}
else{
p1 -> next = p2;
p2 = p2 -> next;
p1 = p1 -> next;
}
}
return add_head -> next;
}
- 优化,遍历处理的时候,我们需要知晓相邻的两个节点,链表天生就有这样的优势
- 当前的节点可以很轻松的获取到 next 节点
- 上面的双指针移动变为单指针移动!可以减少一半的指针移动操作!
// 直接抄袭了卡哥文章里的代码
struct ListNode* removeElements(struct ListNode* head, int val){
typedef struct ListNode ListNode;
ListNode *shead;
shead = (ListNode *)malloc(sizeof(ListNode));
shead->next = head;
ListNode *cur = shead;
while(cur->next != NULL){
if (cur->next->val == val){
ListNode *tmp = cur->next;
cur->next = cur->next->next;
free(tmp);
}
else{
cur = cur->next;
}
}
head = shead->next;
free(shead);
return head;
}
- 之前写过的java题解,现在看来,太烂了,自己看着都费劲
public class RemoveLinkedListElement {
public ListNode removeElements(ListNode head, int val) {
while (head!=null && head.val == val) // 处理原先的表头,直至表头不为 val
head = head.next;
ListNode helper = head; // 用于遍历的指针
while (helper != null && helper.next != null){ // 经过上面的处理后,链表的可能情况: [] ,[ e1 ], [e1,e2,e3...]
if (helper.next.val == val && helper.next != null) // [e1,e2,e3...] 情况
helper.next = helper.next.next;
else if(helper.next.val == val && helper.next == null) // [ e1 ] 情况
helper.next = null;
else
helper = helper.next;
}
return head;
}
}
707 - 设计链表
有一种梦回数据结构的感觉,呜呜呜
这个实现暂时先放一边,后续过来补上
206 - 反转链表
-
思路一 : 新建链表,单指针操作
- 构建一个虚拟头节点
- 单指针遍历原来的链表,对每个节点使用头插法插入虚拟头后面
- 结束后返回虚拟头的next
-
代码如下 (思路一)
- 循环式
// java 实现 class Solution { public ListNode reverseList(ListNode head) { if(head == null) return null; ListNode newHead = new ListNode(-999); while (head != null){ ListNode add = new ListNode(head.val); add.next = newHead.next; newHead.next = add; head = head.next; } return newHead.next; } }
- 递归式
// Java实现 class Solution { public ListNode reverseList(ListNode head) { if(head == null) return null; ListNode newHead = new ListNode(-999); add(head,newHead); return newHead.next; } // 头插法处理 public void add(ListNode add,ListNode head){ if (add == null) return; ListNode temp = new ListNode(add.val); temp.next = head.next; head.next = temp; add(add.next,head); } }
休息休息
-
思路二 : 修改原表,双指针操作
- 新建一个 null 的头节点
- 新建两个指针,p1在前,指向新的头节点,p2在后,指向原来的头节点
- p2向前遍历链表,每次遍历的同时修改, 令p2.next = p1,p2向后移动(需要一个额外指针帮助正确移动)
-
代码如下 (思路二)
-
循环代码
- C语言
struct ListNode* reverseList(struct ListNode* head){ typedef struct ListNode ListNode; ListNode* pre = NULL; // 就是 p1 ListNode* cur = head; // 就是 p2 ListNode* ptr = head; // 帮助找到下一节点的指针 while(cur != NULL){ ptr = ptr->next; cur -> next = pre; pre = cur; cur = ptr; } return pre; }
- java (直接抄了卡哥的)
class Solution { public ListNode reverseList(ListNode head) { ListNode prev = null; ListNode cur = head; ListNode temp = null; while (cur != null) { temp = cur.next;// 保存下一个节点 cur.next = prev; prev = cur; cur = temp; } return prev; } }
-
递归代码
- C语言
// 递归时,每次只处理相邻两个节点的关系 // 所以将待处理的两个节点传入递归函数 // 退出递归的条件是: 传入的两个一前一后节点, 后面的节点为 NULL // 递归退出时,返回一个目标值 typedef struct ListNode ListNode; ListNode* reverse(ListNode* pre, ListNode* cur){ if(cur != NULL) { ListNode* nextCur = cur -> next; // 下一个要传入的cur cur -> next = pre; return reverse(cur, nextCur); } return pre; } struct ListNode* reverseList(struct ListNode* head){ return reverse(NULL, head); }
- java (抄了卡哥的)
class Solution { public ListNode reverseList(ListNode head) { return reverse(null, head); } private ListNode reverse(ListNode prev, ListNode cur) { if (cur == null) { return prev; } ListNode temp = null; temp = cur.next;// 先保存下一个节点 cur.next = prev;// 反转 // 更新prev、cur位置 // prev = cur; // cur = temp; return reverse(cur, temp); } }
-
3. 使用C语言定义链表时的一些疑问
-
定义时,指针的内存大小
-
C语言语句的编译顺序,以及 * 如何被解释的
-
语法上如何判断指针修饰的是什么
-
如何正确的声明一个链表的结构体
-
malloc申请内存 + 赋值过程
-
如何释放内存 - free
-
指针释放细节
-
感谢爪哇!不用与直面指针了
-
C很需要再补一补基础