前置
定义一个结构体,如下:
1 2 3 4
| typedef struct node { int data; struct node *next; } Node;
|
使用 malloc()
来为结构体申请内存,如下:
1 2 3
| Node *head = (Node*)malloc(sizeof(Node)); head->data = 1; head->next = NULL;
|
我们多次使用 malloc()
函数来申请 Node 结构体大小的内存,并将所有的 Node 节点通过 next 指针连起来。这样就可以得到一个链表,由于所有的节点都指向下一个节点,所以它是单向的,这样链表被称为单向链表。head 保存了第一个节点的内存地址,通过 head 我们就可以遍历所有的 Node 节点。最后一个节点的 next 指针赋值为 NULL 作为链表的结束标志。所有节点的内存地址在内存中不一定就是连续的,它是通过指针赋值地址的方式将所有的节点连在一起的。
删除节点时,要 free(节点内存地址)
释放对应节点的内存地址,释放前要确保节点指针的断开与连接,以免造成节点丢失的问题。
完整单向链表示例:
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
| #include <stdio.h> #include <stdlib.h>
struct Node { int data; struct Node* next; };
struct Node* createNode(int data) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = data; newNode->next = NULL; return newNode; }
void insertAtHead(struct Node** head, int data) {
struct Node* newNode = createNode(data); newNode->next = *head; *head = newNode; }
void insertAtTail(struct Node** head, int data) { struct Node* newNode = createNode(data); if (*head == NULL) { *head = newNode; } else { struct Node* temp = *head; while (temp->next != NULL) { temp = temp->next; } temp->next = newNode; } }
void deleteAtHead(struct Node** head) { if (*head == NULL) { printf("List is empty.\n"); return; } struct Node* temp = *head; *head = (*head)->next; free(temp); }
void deleteNode(struct Node** head, int data) { if (*head == NULL) { printf("List is empty.\n"); return; }
struct Node* temp = *head; if (temp->data == data) { *head = temp->next; free(temp); return; }
while (temp->next != NULL && temp->next->data != data) { temp = temp->next; }
if (temp->next != NULL) { struct Node* toDelete = temp->next; temp->next = temp->next->next; free(toDelete); } else { printf("Node with data %d not found.\n", data); } }
void printList(struct Node* head) { if (head == NULL) { printf("List is empty.\n"); return; }
struct Node* temp = head; while (temp != NULL) { printf("%d -> ", temp->data); temp = temp->next; } printf("NULL\n"); }
void destroyList(struct Node* head) { while (head != NULL) { struct Node* temp = head; head = head->next; free(temp); } }
int main() { struct Node* head = NULL;
insertAtHead(&head, 10); insertAtHead(&head, 20); insertAtTail(&head, 30); insertAtTail(&head, 40);
printf("Linked list: "); printList(head);
deleteNode(&head, 20);
printf("After deleting 20: "); printList(head);
deleteAtHead(&head);
printf("After deleting head: "); printList(head);
destroyList(head); return 0; }
|
反转单向链表
描述
给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。
数据范围:
要求:空间复杂度 O(1) ,时间复杂度 O(n)。
如当输入链表{1,2,3}时,
经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。
以上转换过程如下图所示:

示例1
输入:
返回值:
示例2
输入:
返回值:
说明:
Solution
对于单向链表的反转,关键点在在于正确处理节点的断开与连接。
而节点的断开与连接,关键点在于正确处理节点 next 指针的赋值。
在处理链表反转时,要注意不要丢失节点的指针。
代码很短很简单,但是在逻辑上要格外注意指针的赋值,以免丢失节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
struct ListNode* ReverseList(struct ListNode* head ) { struct ListNode *pre = NULL, *cur = head, *nxt = NULL; while (cur) { nxt = cur->next; cur->next = pre; pre = cur; cur = nxt; } return pre; }
|
时间复杂度 O(n),一次 while 循环整个链表。
空间复杂度 O(1)。
代码解释
步骤 1:初始化
1
| pre = NULL, cur = head (1), nxt = NULL
|
链表结构:
1
| NULL <- 1 -> 2 -> 3 -> 4 -> 5 -> NULL
|
nxt = cur->next
,nxt = 2
。
cur->next = pre
,将 1->next
指向 NULL
。
pre = cur
,pre
移动到 1
。
cur = nxt
,cur
移动到 2
。
步骤 2:处理 2
1
| pre = 1, cur = 2, nxt = NULL
|
链表结构:
1
| NULL <- 1 <- 2 -> 3 -> 4 -> 5 -> NULL
|
nxt = cur->next
,nxt = 3
。
cur->next = pre
,将 2->next
指向 1
。
pre = cur
,pre
移动到 2
。
cur = nxt
,cur
移动到 3
。
步骤 3:处理 3
1
| pre = 2, cur = 3, nxt = NULL
|
链表结构:
1
| NULL <- 1 <- 2 <- 3 -> 4 -> 5 -> NULL
|
nxt = cur->next
,nxt = 4
。
cur->next = pre
,将 3->next
指向 2
。
pre = cur
,pre
移动到 3
。
cur = nxt
,cur
移动到 4
。
步骤 4:处理 4
1
| pre = 3, cur = 4, nxt = NULL
|
链表结构:
1
| NULL <- 1 <- 2 <- 3 <- 4 -> 5 -> NULL
|
nxt = cur->next
,nxt = 5
。
cur->next = pre
,将 4->next
指向 3
。
pre = cur
,pre
移动到 4
。
cur = nxt
,cur
移动到 5
。
步骤 5:处理 5
1
| pre = 4, cur = 5, nxt = NULL
|
链表结构:
1
| NULL <- 1 <- 2 <- 3 <- 4 <- 5 -> NULL
|
nxt = cur->next
,nxt = NULL
(最后一个节点)。
cur->next = pre
,将 5->next
指向 4
。
pre = cur
,pre
移动到 5
。
cur = nxt
,cur
变为 NULL
,退出循环。
图示
步骤 |
pre |
cur |
nxt |
当前链表状态 |
初始 |
NULL |
1 |
NULL |
1 -> 2 -> 3 -> 4 -> 5 -> NULL |
步骤 1 |
1 |
2 |
2 |
NULL <- 1 -> 2 -> 3 -> 4 -> 5 -> NULL |
步骤 2 |
2 |
3 |
3 |
NULL <- 1 <- 2 -> 3 -> 4 -> 5 -> NULL |
步骤 3 |
3 |
4 |
4 |
NULL <- 1 <- 2 <- 3 -> 4 -> 5 -> NULL |
步骤 4 |
4 |
5 |
5 |
NULL <- 1 <- 2 <- 3 <- 4 -> 5 -> NULL |
步骤 5 |
5 |
NULL |
NULL |
5 -> 4 -> 3 -> 2 -> 1 -> NULL |
链表内指定区间反转
描述
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4,
返回 1→4→3→2→5→NULL.
数据范围: 链表长度 0<size≤1000,0<m≤n≤size,链表中每个节点的值满足 ∣val∣≤1000
要求:时间复杂度 O(n)) ,空间复杂度 O(n)
进阶:时间复杂度 O(n),空间复杂度 O(1)
示例1
输入:
返回值:
示例2
输入:
返回值:
Solution
在 head 节点前面加上一个辅助节点,可以方便反转操作。代码有注释:
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
|
struct ListNode* reverseBetween(struct ListNode* head, int m, int n ) { if(!head || m == n) return head; struct ListNode *start = (struct ListNode*)malloc(sizeof(struct ListNode)); start->next = head; struct ListNode *revPre = start; for (int i = 0; i < m - 1; i++) revPre = revPre->next; struct ListNode *rev = revPre->next; struct ListNode *tmp = NULL; for (int i = m; i < n; i++) { tmp = rev->next; rev->next = tmp->next; tmp->next = revPre->next; revPre->next = tmp; } tmp = start->next; free(start); return tmp; }
|
提交代码:过辣!!!
