刷了幾天leetcode ,發現以前的很多解法都很冗餘,代碼不夠精簡,重新整理了一波,準備做個集合。
每一題解法爭取用最少的行數解決問題
以下是鏈表相關問題,持續更新中…
反轉鏈表
- 反轉過程中需要保存當前節點 curr 的下一個節點 next,當 next 空時,反轉結束,返回 curr
- 全程只需要關注 curr 是否爲空即可。
- 每一輪反轉結束,需要更新 prev,curr 的值,而 next 是在最開始就更新的。
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode* prev = NULL;
struct ListNode* curr = head;
struct ListNode* next = NULL;
while(curr)
{
next = curr->next; //save next
curr->next = prev; //reverse
if (next==NULL){ //last node of list
break;
}
prev = curr; //move forword
curr = next; //move forword
}
return curr;
}
k個一組反轉鏈表
- 這題用遞歸解法會比較簡單
- 找到翻轉點很重要,代碼裏 kTail 是當前k個一組節點的尾節點,爲了找到 kTail ,需要從頭節點開始走 k-1 步。
- 通過 kTail 是否空來判斷是否需要翻轉,如果 kTail 爲空,說明剩下的節點數小於 K個了。
- 同時需要保存下一組 k 個節點的頭節點,也就是 nextHead
- 只關注當前一組節點的翻轉,翻轉代碼與上述翻轉單鏈表一樣,唯一不同的是,break 的地方變成了 next==nextHead
- 當前組的K個節點翻轉完畢之後,需要將尾節點(oldHead)的next 指向下一組節點的頭節點。
struct ListNode* reverseKGroup(struct ListNode* head, int k) {
struct ListNode* kTail = head; //k個節點的尾節點
struct ListNode* nextHead = NULL;//下一組k個節點的頭節點
int n = k-1;
while(n-- && kTail){
kTail = kTail->next;
}
if (kTail){
nextHead = kTail->next;
}
if (kTail){ //滿足k個節點即可反轉
struct ListNode* oldHead = head; //保存下舊的頭部
struct ListNode* prev = NULL;
struct ListNode* curr = head;
struct ListNode* next = NULL;
while(curr){
next = curr->next;
curr->next = prev;
if(next == nextHead){ //翻轉到下一組的頭節點就截止
break;
}
prev = curr;
curr = next;
}
oldHead->next = reverseKGroup(next, k); //舊的頭節點(即翻轉後的尾節點)指向下一組的頭節點
return curr; //返回翻轉後新的head
}
return head; //不滿足k個節點則直接返回 head
}
合併有序鏈表
- 合併之前需要判兩個鏈表是否有空鏈表
- 合併時需要一個頭指針和一個尾指針,尾指針不斷更新,最後返回的是頭指針
- 合併完之後需要再次判斷兩個鏈表剩餘的部分
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
if (l1 == NULL || l2 == NULL)
return l1 != NULL? l1 : l2;
struct ListNode head = {0, NULL}; //virtual head node
struct ListNode *tail = &head;
while(l1 && l2){
if (l1->val < l2->val){
tail->next = l1;
l1=l1->next;
}else{
tail->next = l2;
l2=l2->next;
}
tail=tail->next;
}
tail->next = l1==NULL ? l2:l1;
return head.next;
}
合併k個有序鏈表
//找到K個鏈表中最小的頭節點
struct ListNode* findMinNode(struct ListNode** lists, int listsSize, int*index)
{
int init=0;
struct ListNode* min = NULL;
for (int i=0;i<listsSize;i++){
if (lists[i]==NULL)
continue;
if (init==0){ //初始化第一個最小節點
min=lists[i];
*index = i;
init=1;
} else {
if (lists[i]->val < min->val){
*index = i;
min=lists[i];
}
}
}
return min;
}
struct ListNode* mergeKLists(struct ListNode** lists, int listsSize) {
struct ListNode head = {0, NULL};
struct ListNode* tail = &head;
struct ListNode* minNode = NULL;
int i = 0;
while(minNode = findMinNode(lists, listsSize, &i)){
tail->next = minNode;
tail = tail->next;
lists[i] = lists[i]->next;
}
return head.next;
}
環形鏈表
- 快慢指針找到相遇點
bool hasCycle(struct ListNode *head) {
struct ListNode *slow = head;
struct ListNode *fast = head;
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if (fast == slow)
return true;
}
return false;
}
環形鏈表入口
- 先快慢指針找到相遇點
- 再2個指針分別從起點和相遇點開始,直到再次相遇則爲入口
struct ListNode *detectCycle(struct ListNode *head) {
struct ListNode *slow = head;
struct ListNode *fast = head;
struct ListNode *meet = NULL;
//find meet node
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if (slow==fast){
meet = slow;
break;
}
}
slow = head;
while(slow && meet){
if (meet==slow){ //find entry point
return meet;
}
slow = slow->next;
meet = meet->next;
}
return NULL;
}
刪除倒數第N個節點
- 快指針先向前走n步,注意是n步,不是n-1步
- 快慢指針同時走,直到 fast 或 fast->next 爲空
- 如果 fast 爲空,表示要刪除的是第一個節點,此時 slow 指向的是 head
- 如果 fast !=NULL && fast->next==NULL,表示要刪除的是head節點之後的節點,此時slow 指向的是被刪節點的前置節點。
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode* slow = head;
struct ListNode* fast = head;
while(fast && n--){
fast=fast->next;
}
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next;
}
if (fast==NULL){ //刪除的是第一個節點
head = head->next;
}else if (slow && slow->next){ //刪除的不是第一個節點
slow->next = slow->next->next;
}
return head;
}
相交鏈表的交點
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
if (headA==NULL || headB==NULL){
return NULL;
}
//先找到其中一個鏈表的尾節點
struct ListNode *tailA = headA;
while(tailA->next){
tailA = tailA->next;
}
//將尾節點和頭結點連起來形成環
tailA->next = headA;
//問題轉化爲求環的入口
struct ListNode *slow = headB;
struct ListNode *fast = headB;
struct ListNode *meet = NULL;
while(slow && fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if (slow==fast){
meet = slow;
break;
}
}
slow = headB;
while(slow && meet){
if (slow==meet){
break;
}
slow = slow->next;
meet = meet->next;
}
////將尾節點和頭結點解開
tailA->next = NULL;
return meet;
}
移除鏈表指定元素
struct ListNode* removeElements(struct ListNode* head, int val) {
//移除最開始相等的節點,直到第一個不相等的
while(head && head->val == val){
head = head->next;
}
//head 一定是不相等的節點,此時如果下一個節點爲空可以直接返回了
if (head==NULL || head->next==NULL)
return head;
//2個指針一前一後
struct ListNode* slow = head;
struct ListNode* fast = head->next;
while(slow && fast){
if (fast->val==val){ //快指針相等的話,慢指針直接刪除該節點
slow->next = fast->next;
}else{
slow = slow->next;
}
fast = fast->next;
}
return head;
}
迴文鏈表
以下面2個鏈表爲例
list1: 1->2->3->2->1
list2: 1->2->3->2
- 首先需要處理特殊情況,即鏈表長度爲0,1,2 的時候。
- 接下來用快慢指針,直到 fast 或 fast->next 爲空
- 如果fast 爲空則表示鏈表長度爲偶數,此時 slow 指向鏈表第 n/2 +1 個節點,也就是list1的第3 個節點
- 如果fast 不爲空則 fast->next 爲空,鏈表長度爲奇數,此時 slow 指向鏈表中間節點,也就是 list2 的第3個節點
- 針對偶數鏈表做翻轉,需要反轉 1->2 的部分,其下一個節點正是 slow
- 針對奇數鏈表做反轉,也只需要反轉 1->2 的部分,其下一個節點正是 slow
- 所以當 next==slow 的時候反轉停止,返回反轉後的頭節點,也就是 slow 的上一個節點
- 接下來同時反向遍歷比較,這裏針對奇偶有區別,奇數鏈表的右半部分從 slow->next 開始,偶數鏈表右半部分從 slow 開始。
struct ListNode* reverse(struct ListNode* head, struct ListNode* givenNode){
struct ListNode* prev = NULL;
struct ListNode* curr = head;
struct ListNode* next = NULL;
while(curr){
next = curr->next;
curr->next = prev;
if (next == givenNode){
break;
}
prev = curr;
curr = next;
}
return curr;
}
bool isPalindrome(struct ListNode* head) {
//1個節點
if (head && head->next==NULL){
return true;
}
//2個節點
if (head && head->next && head->next->next==NULL){
if (head->val == head->next->val){
return true;
}else{
return false;
}
}
struct ListNode* slow = head;
struct ListNode* fast = head;
while (slow!=NULL && fast!=NULL && fast->next!=NULL) {
slow = slow->next;
fast = fast->next->next;
}
struct ListNode* left = reverse(head, slow);
struct ListNode* right = NULL;
if (fast){ //奇數鏈表
right = slow->next;
}else{ //偶數鏈表,此時slow指向第len/2+1個節點
right = slow;
}
while(left && right){
if (left->val != right->val){
return false;
}else{
left = left->next;
right = right->next;
}
}
return true;
}
奇偶鏈表
- 用4個指針分別指向奇數鏈表的首尾節點和偶數鏈表的首尾節點
- 最後再把偶數鏈表和奇數鏈表連起來
struct ListNode* oddEvenList(struct ListNode* head) {
if (head==NULL || head->next==NULL)
return head;
struct ListNode* oddHead = head; //奇數節點的頭
struct ListNode* oddTail = oddHead; //奇數節點的尾
struct ListNode* eventHead = head->next; //偶數節點頭
struct ListNode* eventTail = eventHead; //偶數節點尾
bool isOdd = true;
head = head->next->next; //從第3個節點開始
while(head){
printf("%d\n", head->val);
if (isOdd){
oddTail->next = head;
oddTail = oddTail->next;
isOdd = false;
}else{
eventTail->next = head;
eventTail = eventTail->next;
isOdd = true;
}
head = head->next;
}
if (!isOdd) //一定要將偶數節點的最後一個節點置爲空
eventTail->next = NULL;
oddTail->next = eventHead;
return oddHead;
}
兩數相加
給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,並且它們的每個節點只能存儲 一位 數字。
輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807
- 和合併有序鏈表比較相似,只是多了一個進位的操作。同時要考慮其中一個鏈表更長的情況。
struct ListNode*newNode(int val)
{
struct ListNode* node = malloc(sizeof(struct ListNode));
node->val = val;
node->next = NULL;
return node;
}
struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {
struct ListNode head = {0,NULL};
struct ListNode* tail = &head;
struct ListNode* p1 = l1;
struct ListNode* p2 = l2;
int carry = 0; //進位
while(p1 && p2){
int sum = p1->val + p2->val + carry;
carry = sum/10;
tail->next = newNode(sum%10);
tail = tail->next;
p1 = p1->next;
p2 = p2->next;
}
if (carry != 0){
while(p1){
int sum = p1->val + carry;
carry = sum/10;
tail->next = newNode(sum%10);
tail = tail->next;
p1 = p1->next;
}
while(p2){
int sum = p2->val + carry;
carry = sum/10;
tail->next = newNode(sum%10);
tail = tail->next;
p2 = p2->next;
}
if (carry!=0){
tail->next = newNode(carry);
tail = tail->next;
}
}else{
tail->next = p1==NULL?p2:p1;
}
return head.next;
}
旋轉鏈表
- 2個指針分別找到鏈表解除鏈接的點,要注意是否是原地旋轉
struct ListNode* rotateRight(struct ListNode* head, int k) {
if (head==NULL || k<=0)
return head;
struct ListNode* slow = head;
struct ListNode* fast = head;
while(k--){
fast = fast->next;
if(fast == NULL){ //k>len 的時候從頭開始
fast = head;
}
}
if (slow==fast){ // 相等表示 k%len==0
return head;
}
while(slow && fast && fast->next){
fast = fast->next;
slow = slow->next;
}
struct ListNode* newHead = slow->next;
slow->next = NULL;
fast->next = head;
return newHead;
}