今日题目
题目 | 难度 | 备注 |
---|---|---|
203. 移除链表元素 - 力扣(LeetCode) | 简单 | 没有头结点创造头结点 |
707. 设计链表 - 力扣(LeetCode) | 中等 | 单链表知识查漏补缺处 |
206. 反转链表 - 力扣(LeetCode) | 简单 | |
24. 两两交换链表中的节点 - 力扣(LeetCode) | 中等 | 成就感 + 0.5 (思维欠缺一丢丢) |
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode) | 中等 | 成就感 + 1 |
面试题 02.07. 链表相交 - 力扣(LeetCode) | 简单 | 有更好的方法 |
142. 环形链表 II - 力扣(LeetCode) | 中等 | ♥♥♥ |
链表篇
题目:203. 移除链表元素
一、源代码
ListNode* removeElements(ListNode* head, int val) {
ListNode* pre = new ListNode(0,head);
ListNode* p = pre;
while(p->next != NULL){
if(p->next->val == val){
p->next = p->next->next;
}else{
p = p->next;
}
}
return pre->next;
}
二、代码思路
没有头结点就创造头结点,虚假头结点最好用 dummyHead 来定义,比 pre 有更好的可读性
题目:707. 设计链表
一、源代码
private:
struct ListNode{
int val;
ListNode* next;
ListNode():val(0),next(NULL){}
ListNode(int val):val(val),next(NULL){}
ListNode(int val,ListNode* next):val(val),next(NULL){}
};
int len;
ListNode* dummyhead; //链表虚拟头结点
public:
MyLinkedList() {
len = 0;
dummyhead = new ListNode();
}
int get(int index) {
if(index<0||index>len-1) return -1;
ListNode* p = dummyhead->next;
while(index--){
p = p ->next;
}
return p->val;
}
void addAtHead(int val) {
ListNode* p = new ListNode(val);
p->next = dummyhead->next;
dummyhead->next = p;
len++;
}
void addAtTail(int val) {
ListNode* p = new ListNode(val),*cur=dummyhead;
while(cur->next) cur = cur->next;
cur->next = p;
len++;
}
void addAtIndex(int index, int val) {
if(index<=0) addAtHead(val);
else if(index==len) addAtTail(val);
else{
ListNode* p = new ListNode(val),*pre = dummyhead;
while(index--) pre = pre->next;
p->next = pre->next;
pre->next = p;
len++;
}
}
void deleteAtIndex(int index) {
if(index<0||index>len-1) return;
ListNode* pre = dummyhead,*p = NULL;
while(index--) pre = pre->next;
p = pre->next;
pre->next = p->next;
delete p;
len--;
}
};
二、补充
// 创建单链表
ListNode *head = new ListNode,*p = head;
for(int i=1;i<=n;i++){
scanf("%d",&v);
p->next = new ListNode(v);
p = p->next;
}
//头插法
p = q ->next;
q->next = H->next;
H->next = q;
q = p;
//尾插法
ListNode *rear = H->next;
while(){
q = new ListNode(v);
rear->next = q;
rear = q;
}
rear->next = NULL;
题目:206. 反转链表
ListNode* H = new ListNode(),*cur = head,*temp =NULL;
while(cur!=NULL){
temp = cur->next;
cur->next = H->next;
H->next = cur;
cur = temp;
}
return H->next;
题目:24. 两两交换链表中的节点
一、源代码
ListNode* swapPairs(ListNode* head) {
if(head==NULL ||head->next==NULL) return head;
ListNode *newHead = head->next,*q,*p = head;
while(p->next!=NULL){
q = p->next;
p->next = q->next;
q->next = p;
p = p->next;
}
return newHead;
报错
Line 4: Char 18: runtime error: member access within null pointer of type 'ListNode' (solution.cpp)
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior prog_joined.cpp:25:18
二、错误原因
1、语法错误,类似数组越界
当 p == NULL 的时候,还会去访问 p->next;如当while循环中的最后一句,p = p ->next = NULL时,下一次循环开始会访问 p->next;
2、逻辑错误
只考虑两两交换每队链表,没考虑把链表连起来,这样虽然链表两两交换了,当在逻辑上却被拆成很多段了
解决方法:用pre指针搭桥,连接各端
三、修改
ListNode* swapPairs(ListNode* head) {
if(head==NULL ||head->next==NULL) return head;
ListNode *newHead = head->next,*q,*p = head,*pre = new ListNode(0,head);
while(p->next!=NULL){
q = p->next; //p,q 实现每队两两交换
p->next = q->next;
q->next = p;
pre->next = q; //pre用于链接各队
pre = p;
p = p->next;
if(p==NULL){
break;
}
}
return newHead;
}
题目:19. 删除链表的倒数第 N 个结点
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
一、源代码
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head->next == NULL) //长度为1,直接返回NULL
return NULL;
ListNode *p = head, *q = head->next;
int cnt = 1;
while(q->next!=NULL){ //p一次走一步,q一次走两步,这样q到终点时,只走了一半,原本遍历链表O(n),现在O(n/2)
cnt++;
p = p->next;
q = q->next->next;
if(q==NULL){
break;
}
}
int len = (q==NULL)? cnt*2-1 :cnt*2;
if(n < cnt){ //要删除的结点在p后面
int k = len - cnt - n + 1; //p要移动的次数
ListNode *pre = p;
while(k--){
pre = p;
p = p->next;
}
pre->next = p->next;
delete p;
}else{
int k = len - n;
ListNode *p = head,*pre = p;
while(k--){
pre = p;
p = p->next;
}
if(n==len){ //删除表头
return head->next;
}
pre->next = p->next;
delete p;
}
return head;
}
二、代码思路
1、若使用两趟扫描解决问题,固然简单,一趟得出链表长度,再把倒数第n个换算成正数第k个再扫描一次就行了
2、进阶难度是怎么仅使用一趟实现呢?想了很久,发现一种比两趟好一点的方法:用一趟是为了代码跑得快,那我定义两个指针 *fast 和 *slow,fast一次走两步,slow每次走一步,这样fast到底终点后,slow只走了一半。这样只用花一半的时间就能得到链表的长度len了,此时slow还在链表的中间,若 n < len 的话,slow 继续向前next,找到要删除的结点删除就行,这不就一趟实现了。若 n > len,那就把倒数第n 转换成 正数第k 个查找删除就行,最多也只用扫描半趟。
最后提交发现执行时间还是挺少的,但是代码肯定可以优化,写的太差了
三、代码优化
1、用 *fast 和 *slow 指针快速得到单链表长度,
源代码
ListNode *p = head, *q = head->next;
int cnt = 1;
while(q->next!=NULL){
cnt++;
p = p->next;
q = q->next->next;
if(q==NULL){
break;
}
}
int len = (q==NULL)? cnt*2-1 :cnt*2;
修改代码
ListNode *slow = head, *fast = head->next,; //用fast、slow 更有可读性
int cnt = 1;
while(true){
if(fast==NULL || fast->next==NULL) break; //把循环结束条件写一起好点
cnt++;
fast = fast->next->next;
slow = slow->next;
}
int len = (fast == NULL)? cnt*2-1 : cnt*2;
2、代码能共用则 共用
源代码
if(n < cnt){ //要删除的结点在p后面
int k = len - cnt - n + 1; //p要移动的次数
ListNode *pre = p;
while(k--){
pre = p;
p = p->next;
}
pre->next = p->next;
delete p;
}else{
int k = len - n;
p = head,*pre = p;
while(k--){
pre = p;
p = p->next;
}
pre->next = p->next;
delete p;
}
修改代码
if(n == len){ //删除表头
return head->next;
}
if(n > cnt) p = head;
int k = (n < cnt)? len-cnt-n+1 : len - n;
ListNode *pre = p;
while(k--){
pre = p;
p = p->next;
}
pre->next = p->next;
delete p;
题目:面试题 02.07. 链表相交
面试题 02.07. 链表相交 - 力扣(LeetCode)
一、源代码
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = 0,lenB = 0;
ListNode *pa = headA,*pb = headB;
while(pa!=NULL){ //求长度
lenA++;
pa = pa->next;
}
while(pb!=NULL){
lenB++;
pb = pb->next;
}
pa = headA;
pb = headB;
if(lenA < lenB){ //右对齐
int k = lenB - lenA;
while(k--){
pb = pb ->next;
}
}else{
int k = lenA - lenB;
while(k--){
pa = pa ->next;
}
}
while(pa){ //返回第一个相同结点
if(pa ==pb){
return pa;
}else{
pa = pa->next;
pb = pb->next;
}
}
return NULL;
}
二、优化思路
因为是公共结点,即找到第一个相同结点就行,那就可以用vector这类的容器存储链表A中的结点,再从头遍历链表B,在容器中出现的第一个b结点就是答案。但是vector不能用 find() 方法,可以用无序容器unordered_set 来实现
三、优化代码
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_set<ListNode *> visited;
ListNode *temp = headA;
while (temp != nullptr) {
visited.insert(temp);
temp = temp->next;
}
temp = headB;
while (temp != nullptr) {
if (visited.count(temp)) { // count返回元素在集合中出现的次数
return temp;
}
temp = temp->next;
}
return nullptr;
}
题目:142. 环形链表 II
一、思维缺陷
知道用 *fast 和 *slow 指针来判断是否有环,但是不知道怎么找出环入口结点
其实就是数学问题,找关系
设链表共有a+b个结点,a是链表头部到环入口之间的结点数,b是环中的结点数
fast 走的步数是 slow 步数的 2 倍,即 f=2s ①
fast 比 slow 多走了 n 个环的长度,即 f=s+nb ②
所以 ① - ② 得 s = nb,f = 2nb;
再令fast 重新指向头部结点,此时 f = 0,s = nb
fast 和 slow 每轮各走一步
则 当 fast 走了 a 步时,f = a,s = nb + a,两者相遇,并同时指向链表入口
二、参考答案
ListNode *detectCycle(ListNode *head) {
if(head==NULL || head->next==NULL) return NULL;
int flag = 0;
ListNode *fast = head, *slow = head;
while(true){ //跑圈圈,q每次多走一步,有环必能追上
if(fast==NULL || fast->next==NULL) return NULL;
fast = fast->next->next;
slow = slow->next;
if(fast == slow) break;
}
fast = head;
while(slow !=fast){
slow = slow->next;
fast = fast->next;
}
return fast;
}