😲创建结点示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| struct ListNode { int val; ListNode *next; ListNode(int x) : val(x), next(NULL) {} }; int main(){ ListNode* head = new ListNode(5); ListNode* head = new ListNode(); head->val = 5;
return 0; }
|
🤔一些疑问解答
链表在插入删除之前必须找到需要插入删除的位置, 那么为什么时间复杂度不是O(n)而是O(1)
这个时间复杂度说的是找到之后的删除和插入操作, 而不包含找到这个位置所消耗的时间
线性表删除需要讲后面的数据往前移动一格, 插入需要后面的数据往后移动一格, 所以是O(n)
链表只需要将指针存储的内存位置信息改动一下即可, 没有移动操作所以是O(1)
C++如何手动释放内存
Java、Python,就有自己的内存回收机制, C++最好自己手动释放内存
delete
释放单个指针, delete[]
释放指针数组
1 2 3
| int* ptr = new int;
delete ptr;
|
如果使用new[]
操作符动态分配了一个数组的内存,那么应该使用delete[]
操作符来释放该内存。例如:
1 2 3
| int* arr = new int[5];
delete[] arr;
|
💪力扣刷题
203. 移除链表元素
力扣传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
|
class Solution { public: ListNode* removeElements(ListNode* head, int val) { ListNode* pre = new ListNode(0); pre->next = head; ListNode* temp = pre; while(temp->next!=nullptr){ if(temp->next->val==val){ ListNode* d = temp->next; temp->next = temp->next->next; delete d; } else{ temp = temp->next; } } return pre->next; } };
|
206. 反转链表
力扣传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
class Solution { public: ListNode* reverseList(ListNode* head) { ListNode* pre = nullptr; ListNode* tmp = head; while(tmp!=nullptr){ ListNode* post = tmp->next; tmp->next = pre; pre = tmp; tmp = post; } return pre; } };
|
24. 两两交换链表中的节点
力扣传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
class Solution { public: ListNode* swapPairs(ListNode* head) { ListNode* dummyhead = new ListNode(0,head); ListNode* tmp = dummyhead; ListNode* tmp2; ListNode* tmp3; while(tmp!=nullptr && tmp->next!=nullptr && tmp->next->next!=nullptr){ tmp2 = tmp->next; tmp3 = tmp2->next->next; tmp->next = tmp->next->next; tmp->next->next = tmp2; tmp2->next = tmp3; tmp = tmp2; } return dummyhead->next; } };
|
指针变量本身所占的内存空间会在其作用域结束时自动释放,无需手动删除, 如tmp
,tmp2
,tmp3
实际上tmp不可能是空指针, 它只在后两个明确不是空指针的时候才进入循环, 每一轮循环结束的时候定位在后面第二个, 所以其实tmp不可能是空指针
tmp->next==nullptr
和tmp->next->next==nullptr
的情况不用判断, 都只需要直接返回即可, 因为tmp后面只剩一个或者一个不剩都不需要操作, 直接返回即可
19. 删除链表的倒数第 N 个结点
力扣传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
|
class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { ListNode* dummyhead = new ListNode(0,head); ListNode* tmp1 = dummyhead; ListNode* tmp2 = tmp1; while(n-->0){ tmp2 = tmp2->next; } while(tmp2->next!=nullptr){ tmp1 = tmp1->next; tmp2 = tmp2->next; } ListNode* d = tmp1->next; tmp1->next = tmp1->next->next; delete d; return dummyhead->next; } };
|
面试题 02.07. 链表相交
力扣传送门
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
|
class Solution { public: ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { ListNode* longOne = headA; ListNode* shortOne = headB; int longLen=0, shortLen=0; ListNode* tmp = headA; while(tmp!=NULL){ longLen++; tmp = tmp->next; } tmp = headB; while(tmp!=NULL){ shortLen++; tmp = tmp->next; } if(longLen<shortLen){ swap(longLen,shortLen); swap(longOne,shortOne); } int move = longLen-shortLen; while(move-->0) longOne = longOne->next; ListNode* rs = NULL; while(longOne!=NULL){ if(longOne==shortOne){ rs = longOne; break; } longOne = longOne->next; shortOne = shortOne->next; } return rs; } };
|
142. 环形链表 II
力扣传送门
统计链表大小肯定是不行的, 永远走不到头
如果想存下来所有节点的地址, 然后看看什么时候存到一个重复的地址, 这种方法固然逻辑上讲的通, 但是实际业务中的节点非常多, 那么这种方法必然十分占用空间, 也不行
可以用快慢指针, 快指针一次走两格, 慢指针一次走一格, 这样的话, 只要有圈, 那么快指针一定能从后面再绕一圈追上慢指针
假设从头结点到环形入口节点 的节点数为x, 环形入口节点到 fast
指针与slow
指针相遇节点 节点数为y, 从相遇节点 再到环形入口节点节点数为 z
那么相遇时: slow
指针走过的节点数为: x + y
, fast
指针走过的节点数:x + y + n (y + z)
,n为fast
指针在环内走了n圈才遇到slow
指针, (y+z)
为 一圈内节点的个数A。
因为fast
指针是一步走两个节点,slow
指针一步走一个节点, 所以 fast指针走过的节点数 = slow指针走过的节点数 * 2
:
(x + y) * 2 = x + y + n (y + z)
两边消掉一个(x+y): x + y = n (y + z)
因为要找环形的入口,那么要求的是x,因为x表示 头结点到 环形入口节点的的距离。
所以要求x ,将x单独放在左面:x = n (y + z) - y
,
再从n(y+z)
中提出一个 (y+z)
来,整理公式之后为如下公式:x = (n - 1) (y + z) + z
注意这里n一定是大于等于1的,因为 fast
指针至少要多走一圈才能相遇slow
指针
这个公式说明什么呢?
先拿n为1的情况来举例,意味着fast
指针在环形里转了一圈之后,就遇到了 slow
指针了
当 n为1的时候,公式就化解为 x = z
,
这就意味着,从头结点出发一个指针,从相遇节点也出发一个指针,这两个指针每次只走一个节点, 那么当这两个指针相遇的时候就是 环形入口的节点
如果n不等于1, 情况也一样, 仍然会相遇, 只不过从相遇节点出发的指针需要走几圈才能碰上另一个指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
class Solution { public: ListNode *detectCycle(ListNode *head) { ListNode* fast = head; ListNode* slow = head; while(fast!=NULL && fast->next!=NULL){ slow = slow->next; fast = fast->next->next; if(fast==slow) break; } if(fast==NULL || fast->next==NULL) return NULL; ListNode* find = head; while(fast!=find){ fast = fast->next; find = find->next; } return find; } };
|