赞
踩
每每刷完一道题后,其思想和精妙之处没有地方记录,本篇博客用以记录刷题过程中的遇到的算法和技巧
法一:需要单独处理头结点和非头结点
struct ListNode *deleteNode(struct ListNode *head, int val) { struct ListNode *tmp = head; struct ListNode *pre = NULL; struct ListNode *cur = NULL; int index = 0; while (tmp->next != NULL) { if (tmp->val == val) { break; } tmp = tmp->next; index++; } cur = tmp; // 1. 删除的是 head 节点. if (index == 0) { struct ListNode *node = tmp->next; tmp->next = NULL; return node; } // 2. 否则,非 head 节点,那就先找到 pre 节点. tmp = head; while (index > 1) { tmp = tmp->next; index--; } pre = tmp; pre->next = cur->next; cur->next = NULL; cur->val = 0; return head; }
法二:创建虚拟头结点,来处理
struct ListNode *removeElements(struct ListNode *head, int val)
{
struct ListNode *dummyHead = malloc(sizeof(struct ListNode));
dummyHead->next = head;
struct ListNode *temp = dummyHead;
while (temp->next != NULL) {
if (temp->next->val == val) {
temp->next = temp->next->next;
} else {
temp = temp->next;
}
}
return dummyHead->next;
}
typedef struct ListNode_t { int val; struct ListNode_t *next; } ListNode; // 1.创建链表 ListNode *ListCreate() { // 在链表的第一个结点之前会额外增设一个结点,头结点 ListNode *head = (ListNode *)malloc(sizeof(ListNode)); if (head == NULL) { return NULL; } head->next = NULL; head->val = 0; // 头结点的val用来指示链表长度 return head; } // 2.获取链表中第index个节点的索引值,如果索引无效,则返回-1 int ListIndexGet(ListNode *obj, int index) { int cnt = 0; ListNode *tmp = obj->next; // 手法: 因为我们是以首元结点开始index为0,所以要obj->next以首元开始 // 而不是 ListNode *tmp = obj; while (tmp) { if (index == cnt) { return tmp->val; } cnt++; tmp = tmp->next; } return -1; } // 首元结点:链表中第一个元素所在的结点,它是头结点后边的第一个结点 /* 在链表的第一个元素之前添加值为val的节点。插入后,新节点将成为链表的第一个节点 */ /* obj -> node1 -> node2 */ /* obj -> newNode -> node1 -> node2 */ void ListAddAtHead(ListNode *obj, int val) { ListNode *newNode = (ListNode *)malloc(sizeof(ListNode)); if (newNode == NULL) { return; } obj->val++; // 记录链表长度 newNode->val = val; newNode->next = obj->next; obj->next = newNode; } /* 将值为val的节点附加到链表的最后一个元素 */ /* head -> node1 -> node2 */ /* head -> node1 -> node2 -> tail */ void ListAddAtTail(ListNode *obj, int val) { ListNode *tail = (ListNode *)malloc(sizeof(ListNode)); ListNode *tmp = obj; if (tail == NULL) { return; } obj->val++; // 记录链表长度 tail->val = val; tail->next = NULL; while (tmp->next) { tmp = tmp->next; } tmp->next = tail; } /* 在链表的索引节点之前添加一个值为val的节点。如果索引等于链表的长度, 则节点将附加到链表的末尾。如果索引大于长度,则不会插入节点 */ /* obj -> node1 -> node2 */ /* obj -> node1 -> newNode -> node2 */ void ListAddAtIndex(ListNode *obj, int index, int val) { int cnt = 0; if (index == obj->val) { ListAddAtTail(obj, val); return; } else if (index > obj->val) { return; } ListNode *tmp = obj; ListNode *newNode = (ListNode *)malloc(sizeof(ListNode)); newNode->val = val; while (tmp) { if (cnt == index) { break; } tmp = tmp->next; cnt++; } obj->val++; // 记录链表长度 newNode->next = tmp->next; tmp->next = newNode; return; } /* 如果索引有效,请删除链表中的第index个节点 */ /* head -> node1 -> node2 -> node3 */ /* head -> node1 -> node3 */ void ListDeleteAtIndex(ListNode *obj, int index) { ListNode *node = NULL; ListNode *tmp = obj; int cnt = 0; while (tmp) { if (cnt == index) { break; } cnt++; tmp = tmp->next; } if (tmp->next == NULL) { // 删除的是最后的NULL,直接返回,手法 return; } obj->val--; node = tmp->next; tmp->next = node->next; free(node); } /* head -> node1 -> node2 -> node3 */ void ListFree(ListNode *obj) { ListNode *tmp = obj; ListNode *freeNode; while (tmp) { // 释放头结点->首元结点内的 freeNode = tmp; tmp = tmp->next; freeNode->next = NULL; freeNode->val = 0; free(freeNode); } }
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
进阶:
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?
下图一到图二进行了四个步骤的变化:
法一:双指针法
struct ListNode *reverseList(struct ListNode *head) {
ListNode *pre = NULL; // 需要指向前一个元素的指针,因为要转向,所以先来一个pre
ListNode *cur = head;
while (cur != NULL) // cur移动到NULL,此时pre就是新链表的第一个节点
{
// 1. 转向
ListNode *next = cur->next; // 保存 cur 的 next 节点
cur->next = pre; // cur新的next指向pre,也就是节点反向,同时,此时cur之前的next就断了,也是为什么要先保留cur->next 的原因
// 2. 平移 继续之前的操作,先保留next,然后反向cur->next,然后平移
pre = cur; // 平移pre节点
cur = next; // 平移cur节点
}
return pre; // 新链表的头结点,pre
}
法二:数组记录法
因为题目其实只看数值是否反转了,所以也可以使用将链表存放到数组里,再反转的思路解题:
struct ListNode *reverseList(struct ListNode *head) { int capacity; struct ListNode *tmp = head; int index = 0; // 1.计算capacity while (tmp != NULL) { index++; tmp = tmp->next; } capacity = index; int *res = (int *)malloc(sizeof(int)*capacity); index = 0; tmp = head; // 2.将链表的值转移到数组里 while (tmp != NULL) { res[index++] = tmp->val; tmp = tmp->next; } tmp = head; index = index - 1; // 3.反转赋值回链表里 while (tmp != NULL) { tmp->val = res[index--]; tmp = tmp->next; } free(res); return head; }
思路:
这题常规解决要处理的思路是不好删除首节点,比如:链表 [1,2] 想删除倒数第2个节点 1.
在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的 next 指针指向链表的头节点。这样一来,想操作头节点一样的处理手法即可,不需要额外处理。
dummy->next = head; 和 203. 删除链表节点 一个手法~
// 特殊处理如果删除的首节点的情况,比如 [1,2] 删除倒数第2个节点. int getLength(struct ListNode *head) { int length = 0; while (head) { ++length; head = head->next; } return length; } // 如果要删除头结点,那么会找到dummy节点,通过将 cur->next = cur->next->next 来删除头节点 struct ListNode *removeNthFromEnd(struct ListNode *head, int n) { struct ListNode *dummy = malloc(sizeof(struct ListNode)); dummy->val = 0, dummy->next = head; int length = getLength(head); struct ListNode *cur = dummy; // 倒数第N个,即正数第 len-n 个, 遍历到第 len−n+1 个节点进行处理 for (int i = 1; i < length - n + 1; ++i) { cur = cur->next; } cur->next = cur->next->next; // 删除原来的cur->next 即 len-n 个节点 struct ListNode *ans = dummy->next; free(dummy); return ans; }
将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
struct ListNode *mergeTwoLists(struct ListNode *l1, struct ListNode *l2) { int length_l1 = 0; int length_l2 = 0; struct ListNode *head = NULL; struct ListNode *res = NULL; struct ListNode *l1_t = l1; struct ListNode *l2_t = l2; if (l1 == NULL && l2 == NULL) { return NULL; } // 1.计算 l1 l2 的length while(l1_t != NULL) { l1_t = l1_t->next; length_l1++; } while(l2_t != NULL) { l2_t = l2_t->next; length_l2++; } // 新建一个新的链表 head = (struct ListNode *)malloc((length_l1 + length_l2)*sizeof(struct ListNode )); res = head; while (l1 != NULL || l2 != NULL) { // 特殊情况处理 if (l1 == NULL) { head->next = l2; l2 = l2->next; head = head->next; } else if (l2 == NULL) { head->next = l1; l1 = l1->next; head = head->next; } // 2.哪个小赋值到新的链表里去 else if (l1 != NULL && l2 != NULL) { if (l1->val <= l2->val) { head->next = l1; head = head->next; l1 = l1->next; } else { head->next = l2; head = head->next; l2 = l2->next; } } } return res->next; }
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
// 注意是 malloc(sizeof(struct ListNode));
// 不是 malloc(sizeof(struct ListNode *));
// 区别是 malloc 一个指针指向 大小为 ListNode
struct ListNode *head = NULL;
head = (struct ListNode *)malloc(sizeof(struct ListNode));
struct ListNode *deleteDuplicates(struct ListNode *head) { struct ListNode *cur = head; int value = 0; int value_next = 0; while (cur != NULL && cur->next != NULL) { value = cur->val; value_next = cur->next->val; if (value == value_next) { cur->next = cur->next->next; // 注意不是:cur = cur->next->next; } else { cur = cur->next; } } return head; }
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) { struct ListNode *p = headA; struct ListNode *q = headB; while(p!=q) { if(p) p = p->next; else p = headB; if(q)## 标题 q = q->next; else q = headA; } return q; }
–相交链表:“只要我们走一走彼此走过的路,就一定会相遇”
况一:两个链表相交
给你一个链表和一个特定值 x ,请你对链表进行分隔,使得所有小于 x 的节点都出现在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
struct ListNode { int val; struct ListNode *next; }; struct ListNode *partition(struct ListNode *head, int x) { struct ListNode *p1 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *p2 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *head_tmp = head; struct ListNode *p1_tmp = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *p2_tmp = (struct ListNode *)(malloc(sizeof(struct ListNode))); p1->next = NULL; p2->next = NULL; p1_tmp = p1; p2_tmp = p2; while (head_tmp != NULL) { if (head_tmp->val < x) { p1_tmp->next = head_tmp; p1_tmp = head_tmp; } else { p2_tmp->next = head_tmp; p2_tmp = head_tmp; } head_tmp = head_tmp->next; } // contact p2_tmp->next = NULL; p1_tmp->next = p2->next; return p1->next; } int main(void) { struct ListNode *value0 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *value1 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *value2 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *value3 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *value4 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *value5 = (struct ListNode *)(malloc(sizeof(struct ListNode))); struct ListNode *res = (struct ListNode *)(malloc(sizeof(struct ListNode))); value0->val = 1; value1->val = 4; value2->val = 3; value3->val = 2; value4->val = 5; value5->val = 2; value0->next = value1; value1->next = value2; value2->next = value3; value3->next = value4; value4->next = value5; value5->next = NULL; res = partition(value0, 3); }
int *reversePrint(struct ListNode *head, int *returnSize) { int len = 0; struct ListNode *tmp = head; int i = 0; while (tmp != NULL) { len++; tmp = tmp->next; } *returnSize = len; int *resTmp = (int *)malloc(sizeof(int)*len); int *res = (int *)malloc(sizeof(int)*len); tmp = head; while (tmp != NULL) { resTmp[i++] = tmp->val; tmp = tmp->next; } i = i - 1; for (int j = 0; j < len; j++) { res[j] = resTmp[i--]; } free(resTmp); return res; }
回文链表和回文数一样,是指完全可以中心对称的链表。
思路一:
一共为两个步骤:
复制链表值到数组列表中。
使用双指针法判断是否为回文。
思路二:
我们可以将链表的后半部分反转(修改链表结构),然后将前半部分和后半部分进行比较。比较完成后我们应该将链表恢复原样。
从实现上,第一种思路易于实现
bool isPalindrome(struct ListNode *head) { struct ListNode *tmp = head; int index = 0; int capacity = 0; int j; // 1. 计算链表容量 capacity. while (tmp != NULL) { tmp = tmp->next; index++; } capacity = index + 1; index = 0; tmp = head; // 2. 将链表转移至数组. int *res = (int *)malloc(sizeof(int)*capacity); while (tmp != NULL) { res[index++] = tmp->val; tmp = tmp->next; } // 3.判断数组的值是否回文. j = index - 1; for (int i = 0; i <= j; ++i) { if (res[i] != res[j--]) { return false; } } free(res); return true; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。