算法与数据结构3-链表

常见链表处理

  1. 反转链表
    题目描述
    输入一个链表,反转链表后,输出新链表的表头。
    题目分析:这是链表最常用简单的操作之一,使用三个分别指向前中后的链表就可以完成题目要求
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(pHead == nullptr) return nullptr;
        ListNode* cur = pHead;
        ListNode* p = cur->next;
        ListNode* q = nullptr;
        cur->next = q;
        while(p != nullptr){
            q = cur;
            cur = p;
            p = p->next;
            cur->next = q;
        }
        return cur;
    }
};
  1. 链表中的倒数第k个节点
    题目描述
    输入一个链表,输出该链表中倒数第k个结点
    题目分析:这是一个快慢指针问题,快慢指针是链表操作很常见的操作,对于查找链表中某个节点类的题目就是金钥匙。当然这道题需要注意,首先要有对链表是否为空的判断,还要有对链表长度的判断
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
            val(x), next(NULL) {
    }
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
        if(pListHead == nullptr) return nullptr;
        ListNode* p = pListHead;
        ListNode* q = pListHead;
        int tmp = 0;
        while(tmp < k){
            if(p == nullptr) return nullptr;
            p = p->next;
            tmp++;
        }
        while(p != nullptr){
            p = p->next;
            q = q->next;
        }
        return q;
    }
};

  1. 从尾到头打印链表
    题目描述
    输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
    题目分析:这道题实现并不难,给出多种求解方案,便于加深对链表结构的理解
链接:https://www.nowcoder.com/questionTerminal/d0267f7f55b3412ba93bd35cfa8e8035?toCommentId=3620077
来源:牛客网

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
 
/*
 *方法1:迭代方法
 */
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> vec;
        printCore(head, vec);
        return vec;
    }
     
    void printCore(ListNode* head, vector<int> &vec){
        if(head == nullptr) return;
        ListNode* p = head;
        if(p != nullptr){
            if(p->next != nullptr){
                 printCore(p->next, vec);
            }
            vec.push_back(p->val);
        }
    }
};
 
/*
 *方法二:代码简洁,时间效率较低,直接向向量头部插入法
 */
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> vec;
        if(head == nullptr) return vec;    
        ListNode* cur = head;
        while(cur != nullptr){
           vec.insert(vec.begin(),cur->val);
           cur = cur->next;
        }
        return vec;
    }
};
/*
 *方法三:练习链表翻转操作
 */
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) { 
        vector<int> vec;
        if(head == nullptr) return vec;
        ListNode* cur = head;
        ListNode* p = nullptr;
        ListNode* q = head->next;
        cur->next = nullptr;
        while(q!= nullptr){
            p = cur;
            cur = q;
            q = q->next;
            cur->next = p;
        }
        while(cur != nullptr){
            vec.push_back(cur->val);
            cur = cur->next;
        }
        return vec;
    }
};  
 
/*
 *方法4 :利用栈先进后出的方式实现
 */
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> result;
        std::stack<ListNode*> nodes;
        ListNode* pNode = head;
        while(pNode != nullptr)
        {
            nodes.push(pNode);
            pNode = pNode->next;
        }
        while(!nodes.empty())
        {
            pNode = nodes.top();
            result.push_back(pNode->val);
            nodes.pop();
        }
        return result;
    }
};
  1. 复杂链表的复制
    题目描述
    输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
    题目分析:这道题需要对链表有深入了解,要明白链表用的是链接,所以改变一个链表指向的下一个节点,那么原来链表也会改变
/*
struct RandomListNode {
    int label;
    struct RandomListNode *next, *random;
    RandomListNode(int x) :
            label(x), next(NULL), random(NULL) {
    }
};
*/
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(pHead == nullptr) return nullptr;
        RandomListNode* p = pHead;
        RandomListNode* pResult = nullptr;
        //首先创建对next的复制
        while(p != nullptr){
            RandomListNode* pClone = p->next;
            p->next = new RandomListNode(p->label);
            p->next->next = pClone;
            p = pClone;
        }
        
        p = pHead;
        while(p != nullptr){
            if(p->random != nullptr){
                p->next->random = p->random->next;
            }else{
                p->next->random = nullptr;
            }
            p = p->next->next;
        }
        
        p = pHead;
        pResult = p->next;
        for(RandomListNode* tmp = pResult;;){
            p->next = tmp->next;
            p = p->next;
            if(p == nullptr) break;
            tmp->next = p->next;
            tmp = tmp->next;
        }
        return pResult;
    }
};
  1. 链表的入口节点
    题目描述
    给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
    题目分析,又是一道快慢指针问题,多体会
/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead == nullptr || pHead->next == nullptr)
            return nullptr;
        ListNode* p1 = pHead;
        ListNode* p2 = pHead;
        while(p2 != nullptr && p2->next!= nullptr)
        {
            p1 = p1->next;
            p2 = p2->next->next;
            if(p1 == p2){
                p2 = pHead;
                while(p1 != p2)
                {
                    p1 = p1->next;
                    p2 = p2->next;
                }
                return p1;
            }
        }
        return nullptr;
    }
};
  1. 圆圈中最后剩下的数
    题目描述
    每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
    如果没有小朋友,请返回-1
    题目分析:这道题有两种解法,一种比较经典,一种巧妙。首先第一种,使用环形链表模拟圆圈,每当迭代器扫描到链表尾部的时候,把迭代器移到链表头部,这样就相当于在一个圆圈中遍历了,代码如下
class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if(n == 0 || m == 0) return -1;
        list<int> numbers;
        for(int i = 0; i < n; i++){
            numbers.push_back(i);
        }
        list<int>::iterator iter = numbers.begin();
        while(numbers.size() > 1){
            for(int i = 1; i < m; i++){
                ++iter;
                if(iter == numbers.end())
                    iter = numbers.begin();
                 
            }
            list<int>::iterator next = ++iter;
            if(next == numbers.end())
                next = numbers.begin();
            --iter;
            numbers.erase(iter);
            iter = next;
        }
        return *(iter);
    }
     
};

还有一种巧妙的方法是使用数学推导,得出m和n之间的递归关系,求解,可以参考剑指offer面试题62

//非递归解法
int LastRemaining(int n, int m){
	if(n < 1 || m < 1)
		return -1;
	int last = 0;
	for(int i = 2; i <= n; i++){
		last = (last + m) % i;
	return last;
}
//递归方法
int LastRemaining(int n, int m){
	if(n < 1 || m < 1)
		return -1;
	int last = 0;
	if(n == 1) return 0;
	
	return (LastRemaining(n-1, m) + m) % n;
}
  1. 链表排序问题
    问题描述
    在O(nlogn)的时间内使用常数级空间复杂度对链表进行排序(要求空间复杂度为O(1))
    问题分析:如果没有空间复杂度的要求,直接使用递归合并方法即可;但是由于空间复杂度的要求,递归不合适,由于递归是从顶至下的方法,所以可以考虑使用逆递归的bottom-to-up的方法,两种方法代码如下
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
//使用合并排序方法,经典,增加了空间复杂度,不符合要求,主要是理解一下合并方法
class Solution {
public:
    ListNode *sortList(ListNode *head) {
        if(head==NULL||head->next==NULL)
            return head;
        return mergeSort(head);
    }
    ListNode* mergeSort(ListNode *head)
    {
        if(head->next == NULL)
            return head;
        ListNode* pHead, *qHead, *pre;
        pHead = head;
        qHead = head;
        pre = NULL;
        while(qHead != NULL && qHead->next != NULL)
        {
            //使用了快慢指针的方法分开成两个链表
            qHead = qHead->next->next;
            pre = pHead;
            pHead = pHead->next;
        }
        pre->next = NULL;
        ListNode *left, *right;
        left = mergeSort(head);
        right = mergeSort(pHead);
        return merge(left, right);
    }
    ListNode* merge(ListNode *left, ListNode *right)
    {
        ListNode *pRes = new ListNode(0);
        ListNode *temp = pRes;
        while(left != NULL && right != NULL)
        {
            if(left->val <= right->val)
            {
                temp->next = left;
                temp = temp->next;
                left = left->next;
            }
            else{
                temp->next = right;
                temp = temp->next;
                right = right->next;
            }
        }
        if(left != NULL)
            temp->next = left;
        if(right != NULL)
            temp->next = right;
        temp = pRes->next;
        return temp;
    }
};
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */

//反递归的bottom-to-up方法
class Solution {
public:
    ListNode *sortList(ListNode *head) {
        if(!head || !(head->next)) return head;
        //获取链表长度
        ListNode* cur = head;
        int length = 0;
        while(cur){
            length++;
            cur = cur->next;
        }
        
        ListNode p(0);
        p.next = head;
        ListNode* left, *right, *tail;
        for(int step = 1; step < length; step<<=1){
            cur = p.next;
            tail = &p;
            while(cur){
                left = cur;
                right = cut(left, step);
                cur = cut(right, step);
                tail = Merge(left, right, tail);//每次都指向合并后的下一个位置
            }
        }
        return p.next;
    }
    
    ListNode* cut(ListNode* head, int n){
        //cut不断将链表切成n,n/2,n/4,...,1段
        ListNode* p = head;
        while(--n && p){
            p = p->next;
        }
        if(!p) return nullptr;//如果到达了链表末尾,就无需继续切割
        ListNode* next = p->next;
        p->next = nullptr;
        return next;
    }
    
    ListNode* Merge(ListNode* left, ListNode* right, ListNode*head){
        ListNode* cur = head;
        while(left && right){
            if(left->val > right->val){
                cur->next = right;
                cur = right;
                right = right->next;
            }else{
                cur->next = left;
                cur = left;
                left = left->next;
            }
        }
        cur->next = left ? left : right;
        while(cur->next) cur = cur->next;
        return cur;
    }
};
  1. Convert sorted list to binary search tree
    问题描述
    给定一个链表,其中的元素按照升序排序,请将它转化成平衡二叉搜索树(BST)
    题目分析:由于链表已经排序好,所以可以使用查找中间节点,左右递归的方式得到想要的结果
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode *sortedListToBST(ListNode *head) {
        return cutList(head, nullptr);
    }
    
    TreeNode *cutList(ListNode* head, ListNode* tail){
        if(head == tail)
            return nullptr;
        ListNode* slow, *fast;
        slow = head;
        fast = head;
        while(fast != tail && fast->next != tail){
            fast = fast->next->next;
            slow = slow->next;
        }
        TreeNode* root = new TreeNode(slow->val);
        root->left = cutList(head, slow);
        root->right = cutList(slow->next, tail);
        return root;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值