数据结构2:链表

1.链表

1.1 链表节点定义

链表形式如下,其中int可换成其余数据类型;包含当前节点值和下一节点的指针。

struct ListNode {
	int val;
	ListNode *next;
	ListNode() : val(0), next(nullptr) {}
	ListNode(int x) : val(x), next(nullptr) {}
	ListNode(int x, ListNode *next) : val(x), next(next) {}
};

1.2 思想与技巧总结

核心思想:
递归分治

必会技巧:

  • 快慢指针(可用于寻找中点,判断相交、环形链表)
  • 一指针先走K步(用于解决倒数第k个节点)
  • 链表截断
  • 双指针:同数组中的操作(如:用于解决去重复问题)
  • 局部反转链表(pre,cur,tmp保存next)
  • 递归:寻找当前状态和子问题
  • 分治:归并排序链表

2.基础题目

2.1 二进制链表转整数 easy

int getDecimalValue(ListNode* head) {
    ListNode *cur=head;
    int res = 0;
    while(cur!=nullptr){
        res = 2*res+cur->val;
        cur= cur->next;
    }
    return res;
}

解法2
re表示结果,初始化为0
取出节点数据,与re左移一位的结果按位或(左移后补0,不会出现同为1的情况,所以是可行的)
移动到下一个节点。
重点:位运算,result<<1就相当于result*2,result|=1(result|=0)相当于result++(不变)
得总结一下位运算!!

int getDecimalValue(ListNode* head) {
    ListNode *tmp = head;
    int res = 0;
    while (tmp != nullptr) {
        res = (res << 1) | (tmp->val); 
        // 以上为装13的写法,但位操作速度确实更快...等同于
        // res = 2*res + tmp->val; 
        tmp = tmp->next;
    }
    return res;
}

2.2 合并两个有序链表 easy

递归
比较当前待合并的两个链表l1和l2的头结点的值,使得较小的指向除了该节点外的两个链表合并之后的链表,重复以上过程,直到某个链表走到尽头,此时返回另一链表

    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == NULL) {
            return l2;
        }
        if (l2 == NULL) {
            return l1;
        }
        if (l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        l2->next = mergeTwoLists(l1, l2->next);
        return l2;
    }

2.3 回文链表

在这里插入图片描述

基础技巧2:快慢指针寻找链表的中间节点。
注意:快慢指针初始化时,slow=head并且fast=head->next

class Solution {
public:
    // 常规解法:用栈进行反转
    bool isPalindrome(ListNode* head) {
        // 特判
        if(!head || !head->next) return true;

        // 寻找链表的中间节点
        ListNode *slow = head,*fast = head->next;
        while(fast!=NULL && fast->next!=NULL)
        {
            slow = slow->next;
            fast = fast->next->next;
        }
        // 截断
        ListNode *rhead= slow->next;
        slow->next = nullptr;
        cout<<"rhead: "<<rhead->val<<endl;

        // 后半部分入栈
        stack<int> st;
        while( rhead!=NULL){
            st.push(rhead->val);
            rhead = rhead->next;
        }

        // 判断
        while(head!=nullptr && !st.empty() ){
            if(head->val != st.top() ) return false;
            head = head->next;
            st.pop();
        }
        return true;
    }
    // 进阶解法:反转链表进行比对
    
};

2.4 链表的倒数第K个节点 easy

基础技巧3:让一个指针先走k步

略。

2.5 环形链表 easy

1)哈希集法
简单粗暴,以节点指针为哈希集类型。遍历链表,当前节点未在哈希集中出现时,插入该节点,直到当前节点走到链表尾端,表明无环,否则必然指向哈希集中的节点,表明有环。
2)快慢指针法:采用low和fast双指针,fast步进为2,low步进为1
当链表无环时,fast应该率先到达终点,此时fast指向空指针,表明无环则退出;
链表存在环时:fast比low率先入环,设low入环后两者之间距离为k,则k步后fast必然追上low,此时判定有环
因此采用while循环,当fast==low时退出表明有环,否则在fast指向空指针时返回false表明无环

代码先不给了,当做课后复习作业,有空再自己补上。

// 1-哈希集法

// 2-双指针法

2.6 移除链表元素 easy

实在是对链表的基本操作不太熟悉,记录一下一些基本操作。
这题看似很简答,但确实主要难度在于链表头部存在待删除串时,问题将变得很棘手

官方解答:

  • 初始化哨兵节点作为伪头,即定义一个哨兵节点指向head
  • 双指针:取连续两个节点的指针prev和curr,分别指向哨兵节点和head,
  • 当curr节点不为nullptr时
    • 如果curr节点的值待删除时,将prev指向其下一节点。
  • 删除该删除的节点,返回哨兵节点的下一节点
  ListNode* removeElements(ListNode* head, int val) {
    ListNode* sentinel = new ListNode(0);
    sentinel->next = head;

    ListNode *prev = sentinel, *curr = head, *toDelete = nullptr;
    while (curr != nullptr) {
      if (curr->val == val) {
        prev->next = curr->next;
        toDelete = curr;
      } else prev = curr;

      curr = curr->next;

      if (toDelete != nullptr) {
        delete toDelete;
        toDelete = nullptr;
      }
    }

    ListNode *ret = sentinel->next;
    delete sentinel;
    return ret;
  }

2.7 反转链表 easy

三指针法:
定义两个指针: pre 和 cur ;pre 在前 cur 在后。

  • 每次让 pre 的 next 指向 cur ,实现一次局部反转
  • 局部反转完成之后, pre 和 cur 同时往前移动一个位置
  • 循环上述过程,直至 pre 到达链表尾部
ListNode* reverseList(ListNode* head) {
    ListNode* cur = NULL, *pre = head;
	
	// 实际上当前节点为pre,cur为辅助的'前一节点';初始值为空
    while (pre != NULL) {
        ListNode* t = pre->next;//用于保存前节点的下一节点指针
        pre->next = cur; //pre指向指向cur,第一次为指向空节点
        
        cur = pre; //cur移动到pre
        pre = t; //pre位置移动到上一次pre的下一节点
    }
    return cur;
}

递归:让当前节点的下一个节点指向当前节点;并且当前节点指向空节点;

ListNode* reverseList(ListNode* head) {
    if(head==NULL || head->next==NULL)  return head;
    ListNode* ptr=reverseList(head->next);
    head->next->next=head;
    head->next=NULL;
    
    return ptr;
}

2.8 相交链表 easy

刷过leetcode的人应该都知道两个正确的人走上对方的路终究会长长久久的在一起。(酸了)
但是,如果给的是两条不相交的链表,犹如两条平行铁轨,永远不会相交。此时,仅仅需要在某个指针第2次走到链表端点时进行判断即可。
理由是:如果两条链表相交,那么在指针A第一次走到链表1尽头时,从链表2开头继续走,当走到了链表2的尽头时,此时,另一个指针B应当走到了链表1的尽头。消除了长度差之后,之后会一直相等。

在这里插入图片描述

ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    if(headA==NULL || headB==NULL) return NULL;

    ListNode* pA = headA;
    ListNode* pB = headB;
    int count = 0;
    while(pA!=pB){
        pA = pA->next==NULL?headB:pA->next;
        pB = pB->next==NULL?headA:pB->next;
        count = pA->next==NULL?count+1:count;
        if(count==2) break;
    }

    if(count==2 && pA!=pB){
        return NULL;
    }

    return pA;        
}

2.10 排序链表 middle

在这里插入图片描述
基础技巧:归并排序,递归分治

class Solution {
public:

    ListNode* mergeTwoLists(ListNode* la,ListNode* lb){
        if(la==NULL) return lb;
        if(lb==NULL) return la;
        if(la->val < lb->val){
            la->next = mergeTwoLists(la->next,lb);
            return la;
        }
        lb->next = mergeTwoLists(la,lb->next);
        return lb;
    }

    // 归并排序
    // 寻找链表中间节点,分治分别排序,归并
    ListNode* sortList(ListNode* head) {
        // 特判
        if(!head || !head->next) return head;

        // 快慢指针做分割
        ListNode *slow = head,*fast = head->next;
        while(fast!=NULL && fast->next!=NULL){
            slow = slow->next;
            fast = fast->next->next;
        }
        // 截断
        ListNode *rhead = slow->next;
        cout<<"rhead: "<<rhead->val<<endl;
        slow->next = nullptr;
         
        // 递归分治
        return mergeTwoLists(sortList(head),sortList(rhead) );
    }


};

2.11 对链表进行插入排序

3.痛定思痛0928

写在此处:该帖子最早写于3月份,半年前那时候刚开始看数据结构和算法。
0928惨遭美团后台面试滑铁卢,被一道并不难的链表题整蒙了,痛定思痛,提笔再刷一些链表题。

3.1 合并K个升序链表 hard

原题来自leetcode No.23

思路:参考2.2 合并两个有序链表,采用分治思想合并一组有序链表即可。

代码如下:

//
// Created by wbzhang on 2020/9/29.
// 合并K个有序链表 leetcode No.23

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */

class Solution {
public:
	// 合并两个有序链表
	ListNode* mergeTwolists(ListNode* la,ListNode* lb){
		if(la==nullptr) return lb;
		if(lb==nullptr) return la;

		if(la->val < lb->val){
			la->next = mergeTwolists(la->next,lb);
			return la;
		}
		lb->next = mergeTwolists(la,lb->next);
		return lb;
	}

	// 分治合并(闭区间)
	ListNode* merge(vector<ListNode*>& lists ,int low,int high)
	{
		if(low == high) return lists[low];
		if(low >high) return NULL;

		int mid = low + (high-low)/2;
		ListNode* left =  merge(lists,low,mid); // 分治合并
		ListNode* right =  merge(lists,mid+1,high); // 分治合并

		return mergeTwolists(left,right);
	}

	// 合并K个有序链表
	ListNode* mergeKLists(vector<ListNode*>& lists) {
		return merge(lists,0,lists.size()-1 );
	}

};

3.2 旋转链表 middle

原题:leetcode No.61

直接看我题解:题解No.61
差分成两道简单题:

  1. 递归确定链表长度
  2. 寻找链表的倒数第K个节点

代码如下:

class Solution {
public:

    // 递归:求链表的长度
    int getListLength(ListNode* head)
    {
        if(!head) return 0;
        return getListLength(head->next)+1;
    }

    ListNode* rotateRight(ListNode* head, int k) {
        // 特判,考虑k的范围;k有可能大于链表长度
        if(!head || !head->next) return head;

        int len = getListLength(head);
        k = k%len; // 旋转 k%len步
        if(k==0) return head;

        // 寻找倒数第K个节点
        ListNode *slow = head, *fast = head;
        while(fast!=NULL && k>0){
            fast= fast->next;
            --k;
        }
        while(fast->next!=NULL){
            slow = slow->next;
            fast = fast->next;
        }

        // 在slow出切断
        ListNode* res_head = slow->next;
        fast->next = head;
        slow->next = NULL;
        return res_head;
    }
};

3.3 K个一组反转链表 hard

原题来自leetcode No.25

截断链表,反转链表,递归

//
// Created by wbzhang on 2020/9/29.
// leetcode No.25 K个一组反转链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
	// 获取链表长度
	int getListLength(ListNode* head)
	{
		if(!head) return 0;
		return getListLength(head->next)+1;
	}

	// 打印链表
	void printList(ListNode* head)
	{
		if(head==NULL) return;
		cout<<head->val<<" ";
		printList(head->next);
	}

	// 局部反转该链表
	ListNode* reverseList(ListNode* head)
	{
		ListNode *pre = NULL,*curr = head;
		while(curr!=NULL){
			ListNode* tmp = curr->next;
			curr->next = pre;
			pre = curr;
			curr = tmp;
		}
		return pre;
	}

	ListNode* reverseKGroup(ListNode* head, int k) {
		// 特判
		int len = getListLength(head);
		if(k>len) return head; //k>len则不旋转
		if(k==len) return reverseList(head);

		// 找到第k个节点,并且切断,将后续翻转...(直到个数不足以反转位置)
		ListNode* kthNode = head;
		// k==1,则为本身,k=2,则往后移动一次
		while(k>1){
			kthNode = kthNode->next;
			--k;
		}
		cout<<"kth: "<<kthNode->val<<endl;
		// 切断
		ListNode* kNext = kthNode->next;
		cout<<"kNext: "<<kNext->val<<endl;
		kthNode->next = NULL;

		// 打印前k个节点
		cout<<"print..."<<endl;
		printList(head);
		cout<<endl;
		printList(kNext);

		ListNode* resHead = reverseList(head); // 反转前k个节点
		cout<<"print after..."<<endl;
		printList(resHead);

		cout<<"digui..."<<endl;
		kNext = reverseKGroup(kNext,k); //递归反转后半部分
		printList(kNext);

		head->next = kNext; // 反转前k个之后head为末尾
		return resHead;
	}
};

3.4 反转链表2 middle

原题leetcode No.92
在这里插入图片描述

此题需要用的指针稍微多一些…
注意特判!
思路:切成三段(n不在末尾)l1,l2,l3 ,得到l1尾部l2头部(即第m个)l2尾部(即第n-1个)l3头部(即第n个)
采用双指针pre,cur
先找到第m个节点,然后对m-n之间进行局部反转(同反转链表),然后处理连接问题。


3.5 重排链表 middle

原题leetcode No.143

我的题解
1)快慢指针寻找链表中间节点,从中间节点截断
2)使用栈存储后半段(其实亦可以用反转链表)
3)随后进行拼接…

class Solution {
public:
    void reorderList(ListNode* head) {
        // 特判
        if(!head) return;

        // 寻找链表中点,进行切断
        ListNode *slow = head,*fast = head;
        while(fast!=NULL && fast->next!=NULL){
            slow = slow->next;
            fast = fast->next->next;
        }
        ListNode *rightHead = slow->next;
        slow->next = NULL;

        // 使用栈存储后半部分
        stack<ListNode*> st;
        while(rightHead!=NULL){
            st.push(rightHead);
            rightHead = rightHead->next;
        } 
        cout<<"st_size: "<<st.size()<<endl;

        // 前后合并两部分
        // ListNode* dummyNode = new ListNode(-1);
        // dummyNode->next = head;
        ListNode *cur = head;
        // ListNode *resCur = dummyNode ;

        while(cur!=NULL && !st.empty() ){
            ListNode* tmp = cur->next;
            cur->next = st.top();
            st.pop();
            cur->next->next = tmp;
            cur = tmp;
        }

    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值