JZ 链表

JZ-3 从尾到头打印链表

题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

方法1:使用std::reverse()函数

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> ret;
        while(head){
            ret.push_back(head->val);
            head = head->next;
        }
        std::reverse(ret.begin(),ret.end());
        return ret;
    }
};

方法2:递归版本

我们可以回顾一下二叉树的递归遍历方法:

void dfs (TreeNode* root) {
    if (!root) return;
    dfs(root->left);
    dfs(root->right);
    // 处理头结点
}

以此借鉴,得到我们的代码:

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> ret;
        if(!head) return ret;
        ret = printListFromTailToHead(head->next);
        ret.push_back(head->val);
        return ret;
    }
};

方法3:反转链表

/**
*  struct ListNode {
*        int val;
*        struct ListNode *next;
*        ListNode(int x) :
*              val(x), next(NULL) {
*        }
*  };
*/
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        ListNode* pre = nullptr;
        ListNode* cur = head;
        ListNode* temp = cur;
        while(cur){ // 1 翻转链表
            temp = cur->next;//需要保存一下现场
            cur->next = pre;
            pre = cur; //pre和cur分别向右平移
            cur = temp;
        }
        vector<int> ret;
        while(pre){   //2 遍历翻转后的链表,并存储到vec中。
            ret.push_back(pre->val);
            pre = pre->next;
        }
        return ret;
    }
};

JZ-14 链表中倒数第k个结点

严格的O(n)解法,相差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) {
    	// 1 指针不为空 || k <= 0
        if(!pListHead || k <= 0) return nullptr;
        auto slow = pListHead,fast = pListHead;
        
        while(k--){  //2 快指针先前进k个
            if(fast)
                fast = fast->next;
            else return nullptr;
        }
        while(fast){ //3 快慢指针一同前进,直到快指针到达终点
            slow = slow->next;
            fast = fast->next;
        }
        return slow;
    }
};

JZ-15 反转链表

题目描述

输入一个链表,反转链表后,输出新链表的表头。

方法1:标准翻转链表方法

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode* nex = nullptr;
        ListNode* pre = nullptr;
        ListNode* cur = pHead;
        while (cur){
            nex = cur->next;
            cur -> next = pre;
            pre = cur;
            cur = nex;
        }
        return pre;
    }
};

方法2 :重新构造链表

class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if (!pHead) return nullptr;
        vector<ListNode*> v;
        while (pHead) {
            v.push_back(pHead);
            pHead = pHead->next;
        }
        reverse(v.begin(), v.end()); // 反转vector,也可以逆向遍历
        ListNode *head = v[0];
        ListNode *cur = head;
        for (int i=1; i<v.size(); ++i) { // 构造链表
            cur->next = v[i]; // 当前节点的下一个指针指向下一个节点
            cur = cur->next; // 当前节点后移
        }
        cur->next = nullptr; // 切记最后一个节点的下一个指针指向nullptr
        return head;
    }
};

JZ-16 合并两个排序的链表

迭代法

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        ListNode *vhead = new ListNode(-1);
        ListNode *cur = vhead;
        while(pHead1 && pHead2){
            if(pHead1->val <= pHead2->val){
                cur->next = pHead1;
                pHead1 = pHead1->next;
            }
            else{
                cur->next = pHead2;
                pHead2 = pHead2->next;
            }
            cur = cur->next;
        }
        cur ->next = pHead1? pHead1:pHead2;
        return vhead->next;
    }
};

方法2:递归法

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == nullptr) return pHead2;
        if(pHead2 == nullptr) return pHead1;
        if(pHead1->val <= pHead2->val){
            pHead1->next = Merge(pHead1->next,pHead2);
            return pHead1;
        }
        else{
            pHead2->next = Merge(pHead1,pHead2->next);
            return pHead2;
        }
    }
};

JZ-25 复杂链表的复制

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

方法1:辅佐退位法(待调试)

在这里插入图片描述

/*
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 pHead;
        RandomListNode* currentNode = pHead;
        //1 复制每一个结点,如复制结点A得到A1,将结点A1插到结点A的后面
        while(currentNode != nullptr){
            RandomListNode* cloneNode = new RandomListNode(currentNode->label);
            cloneNode->label = currentNode->label;
            cloneNode->next = currentNode->next;
            cloneNode->random = currentNode->random;
            RandomListNode* nextNode = currentNode->next;
            currentNode->next = cloneNode;
            cloneNode->next = nextNode;
            currentNode = nextNode;
        }
        currentNode = pHead;
        //2 重新遍历链表,复制老结点的随机指针给新结点,例如 A1.random = A.random.next;
        while(currentNode != nullptr){
            currentNode->next->random = currentNode->random==nullptr? nullptr:currentNode->random->next;
            currentNode = currentNode->next->next;
        }
        //3 拆分链表,将链表分为原链表和复制后的链表
        currentNode = pHead;
        RandomListNode* pCloneHead = pHead->next;
        while(currentNode != nullptr){
            RandomListNode* cloneNode = currentNode->next;
            currentNode->next = cloneNode->next;
            cloneNode->next = cloneNode->next==nullptr?nullptr:cloneNode->next->next;
            currentNode = currentNode->next;
            
        }
        return pCloneHead;

    }
};

方法2:哈希法–>map映射

/*
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 pHead;
        // map<label, 位置>
        map<int, int> imap;
        int count = 0;
        auto pin2 = pHead;  
        // 初始化
        RandomListNode* head = new RandomListNode(pHead->label);
        auto pin1 = head;
        // pair是将2个数据组合成一个数据,当需要这样的需求时就可以使用pair,
        // 如stl中的map就是将key和value放在一起来保存。
        // 另一个应用是,当一个函数需要返回2个数据的时候,可以选择pair。
        // pair的实现是一个结构体,主要的两个成员变量是first second 
        // 因为是使用struct不是class,所以可以直接使用pair的成员变量。
        imap.insert(make_pair(pin2->label, count++));
        while (pin2->next != nullptr){
            // 插入点
            auto temp = new RandomListNode(pin2->next->label);
            pin1->next = temp;
            pin1 = pin1->next;
            pin2 = pin2->next;
            // 记录值
            imap.insert(make_pair(pin2->label, count++));
        }       
        // 此时所有节点新建完成
        pin1 = head;
        pin2 = pHead;
        while(pin2 != nullptr){
            if (pin2->random != nullptr){
                auto pin = head;
                auto target = pin2->random->label;
                auto n = imap.find(target)->second;
                for (int i = n; i != 0; --i)
                    pin = pin->next;
                // pin此时所指是pin2的random节点
                pin1->random = pin;
            }
            pin1 = pin1->next;
            pin2 = pin2->next;
        }
        return head;
    }
};

JZ-26 二叉搜索树与双向链表

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

二叉搜索树介绍:对于树中的每个节点X,它的左子树中所有关键字值小于X的关键字值,而它的右子树中所有关键字值大于X的关键字值。

中序遍历+递归

在这里插入图片描述

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if(!pRootOfTree) return NULL;
        auto pairs = dfs(pRootOfTree);
        return pairs.first;
    }
    //定义一个pair类型的函数,pair里面是两个树的结点
    pair<TreeNode*,TreeNode*> dfs(TreeNode* root){
        if(!root->left && !root->right) return{root,root};
        if(root->left && root->right){
            auto lsides = dfs(root->left),rsides = dfs(root->right);
            //双向链表的修改
            //左边的返回 {1,2}  2->root root->2
            lsides.second->right= root;
            root->left = lsides.second;
            //右边的返回 {1,2}  1->root root->1
            rsides.first -> left = root;
            root -> right = rsides.first;
            return {lsides.first,rsides.second};
        }
        
        if(root->left){
            auto lsides = dfs(root->left);
            //双向链表的修改
            //左边的返回 {1,2}  2->root root->2
            lsides.second->right= root;
            root->left = lsides.second;
            return {lsides.first,root};
        }
        if(root->left && root->right){
            auto rsides = dfs(root->right);
            //双向链表的修改
            //右边的返回 {1,2}  1->root root->1
            rsides.first -> left = root;
            root -> right = rsides.first;
            return {root,rsides.second};
        }
    }
};

中序遍历+非递归

思路:大家普遍的解法是中序遍历+递归,这种方法主要抓住了二叉搜索树的中序遍历结果是有序的特点!
但这里提供一种非递归的方法,这种方法借鉴于平衡二叉树的单旋转方法,目标是把这个二叉搜索树变成一个简易二叉搜索树(这里的简易二叉树是我杜撰的概念,它指根节点的左边没有右子树,根节点的右边没有左子树)。图示说明如下,对于每个二叉搜索树(例如图左),如果通过一定方法将它变为简易二叉搜索树(例如图中),那么自然也就是有序的双向链表(图右)。
在这里插入图片描述
下面说说怎么将普通二叉搜索树变为简易二叉搜索树,这里借鉴了平衡二叉树中的单旋转方法。举个例子,对于根节点10来说,在它左边的数显然应该是8,右边的数是12。那么8和12分别具有什么样的特点呢?观察可以看出,8是根节点10的左子树的最右边的节点,而12是根节点10的右子树的最左边的结点。那么现在如果我令10的左指针指向8,右指针指向12,再让剩余的左子树和右子树的部分续接在8和12两个节点上。就可以实现节点8,10,12的有序性,以此方法往下就可以得到一个简易二叉搜索树。具体的实现方式见代码

class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
        if (pRootOfTree == nullptr) return nullptr;

        TreeNode* pRoot = pRootOfTree;
        // 先处理pRootOfTree的右子树
        while (pRootOfTree->right)
        {
            TreeNode* p_right = pRootOfTree->right;
            if (p_right->left == nullptr) 
            {
                p_right->left = pRootOfTree;
                pRootOfTree = p_right;
            }
            else
            {
                TreeNode* p_3 = p_right;
                TreeNode* p_4 = p_3->left;
                while (p_4->left)
                {
                    p_3 = p_4;
                    p_4 = p_4->left;
                }
                p_3->left = nullptr;
                pRootOfTree->right = p_4;
                p_4->left = pRootOfTree;
                while (p_4->right)
                    p_4 = p_4->right;
                p_4->right = p_right;

                pRootOfTree = pRootOfTree->right;
            }
        }
        // 处理pRootOfTree的左子树
        while (pRoot->left)
        {
            TreeNode* p_left = pRoot->left;
            if (p_left->right == nullptr) 
            {
                p_left->right = pRoot;
                pRoot=p_left;
            }
            else
            {

                TreeNode* p_1 = pRoot->left;
                TreeNode* p_2 = p_1->right;
                while (p_2->right)
                {
                    p_1 = p_2;
                    p_2 = p_2->right;
                }
                p_1->right = NULL;
                pRoot->left = p_2;
                p_2->right = pRoot;
                while (p_2->left)
                    p_2 = p_2->left;
                p_2->left = p_left;

                pRoot = pRoot->left;
            }
        }
        //pRoot此时就是头节点
        return pRoot; 
    }
};

JZ-36 两个链表的第一个公共结点

题目描述

输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

题目抽象

给定两个单链表A,B,假设一定含有公共结点,返回第一个公共结点的指针。

方法:双指针法

假如例子如下:
图片说明
显然第一个公共结点为8,但是链表A头结点到8的长度为2,链表B头结点到8的长度为3,显然不好办?
如果我们能够制造一种理想情况,如下:
图片说明

这里先假设链表A头结点与结点8的长度 与 链表B头结点与结点8的长度相等,那么就可以用双指针。

1 初始化:指针ta指向链表A头结点,指针tb指向链表B头结点
2 如果ta == tb, 说明找到了第一个公共的头结点,直接返回即可。
3 否则,ta != tb,则++ta,++tb 

所以现在的问题就变成,如何让本来长度不相等的变为相等的?
假设链表A长度为a, 链表B的长度为b,此时a != b
但是,a+b == b+a
因此,可以让a+b作为链表A的新长度,b+a作为链表B的新长度。
如图:
图片说明

这样,长度就一致了,可以用上述的双指针解法了。

// 刀刀喜欢用auto

class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        auto p = pHead1, q = pHead2;
        while (p != q) {
            //if(p) p=p->next;
            //else p = pHead2;
            p=p? p->next : pHead2;
            q=q? q->next : pHead1;
        }
        return q;
    }
};
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode *ta = pHead1, *tb = pHead2;
        while (ta != tb) {
            ta = ta ? ta->next : pHead2;
            tb = tb ? tb->next : pHead1;
        }
        return ta;
    }
};

JZ-46 孩子们的游戏(圆圈中最后剩下的数)

题目描述

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0…m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

如果没有小朋友,请返回-1

方法1:模拟

时间复杂度:O(N^2), 每次删除一个节点,需要先找到那个节点,然后再删除,查找的时间复杂度为O(N)
空间复杂度:O(N)

class Solution {
public:
    int LastRemaining_Solution(int n, int m)
    {
        if (n <= 0) return -1; 
        list<int> lt;
        for (int i=0; i<n; ++i) 
            lt.push_back(i);
        int index = 0;
        while (n > 1) {
            index = (index + m - 1) % n;//寻找index所在的位置
            auto it = lt.begin();
            std::advance(it, index); // 让it向后移动index个位置
            lt.erase(it);//删除该元素
            --n;//修改链表长度
        }
        return lt.back();
    }
};

方法2:递归

在这里插入图片描述

class Solution {
public:
    // 
    // f(N,M) = ( f(N−1,M) + M ) % N
    int LastRemaining_Solution(int n, int m)
    {
        if(n <= 0 || m <= 0) return -1;
        if(n == 1) return 0;
        return (LastRemaining_Solution(n-1,m)+m)%n;
    }
};

方法3:for循环

根据方法二可知,
f[1] = 0
f[2] = (f{1] + m) % 2
f[3] = (f[2] + m) % 3

f[n] = (f[n-1] + m) % n

class Solution {
public:
    // f(N,M) = ( f(N−1,M) + M ) % N
    int LastRemaining_Solution(int n, int m)
    {
        if(n <= 0 || m <= 0) return -1;
        int index = 0;
        for(int i = 2;i <= n;++i)
            index = (index + m)%i;
        return index;
    }
};

JZ-55 链表中环的入口结点

题目抽象

给定一个单链表,如果有环,返回环的入口结点,否则,返回nullptr

方法一:哈希法

1 遍历单链表的每个结点
2 如果当前结点地址没有出现在set中,则存入set中
3 否则,出现在set中,则当前结点就是环的入口结点
4 整个单链表遍历完,若没出现在set中,则不存在环

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        unordered_set<ListNode*> st;
        while(pHead){   // 1 遍历单链表的每个结点
            // 2 如果当前结点地址没有出现在set中,则存入set中 
            if(st.find(pHead) == st.end()){
                st.insert(pHead);
                pHead = pHead->next;
            }
            // 3 否则,出现在set中,则当前结点就是环的入口结点 
            else return pHead;
        }
        // 4 整个单链表遍历完,若没出现在set中,则不存在环
        return nullptr;
    }
};

方法2:双指针法

时间复杂度:O(n)
空间复杂度:O(1)

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        //1 初始化:快指针fast指向头结点,慢指针slow指向头结点
        ListNode *fast = pHead;
        ListNode *slow = pHead;
        //2 让fast一次走两步,slow一次走一步,第一次相遇在C处,停止 
        while(fast && fast->next){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow) break;
        }
        //3 然后让fast指向头结点,slow原地不动;
        //  让后fast,slow每次走一步,当再次相遇,就是入口结点。
        if(!fast || !fast->next) return nullptr;
        fast = pHead;
        while(fast != slow){
            fast = fast->next;
            slow = slow->next;
        }
        return fast;
    }
};

JZ-56 删除链表中重复的结点

题目描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

在遍历单链表的时候,检查当前节点与下一点是否为相同值,如果相同,继续查找祥同值的最大长度,然后指针改变指向。

直接删除法

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        ListNode *vhead = new ListNode(-1);
        vhead -> next = pHead;
        ListNode *pre = vhead,*cur = pHead;
        while(cur){
            if(cur -> next && cur->val == cur->next->val){
                cur = cur -> next;
                while(cur -> next && cur -> val == cur->next->val)
                    cur = cur -> next;
                cur = cur->next;
                pre->next = cur;
            }
            else{
                pre = cur;
                cur = cur -> next;
            }
        }
        return vhead -> next;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值