链表类编程题

1.从尾到头打印链表

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

思路:(1)利用栈先入后出的特性完成; (2)第二就是存下来然后进行数组翻转。
(3)利用递归。

代码

//利用c++中自带的翻转函数reverse实现 
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> value;
        ListNode *p = head;
        while(p != nullptr)
        {
            value.push_back(p->val);//vector是一个能够存放任意类型的动态数组,能够增加和压缩数据。push_back将p->val加入到value这个vector中
            p = p->next;
        }
        reverse(value.begin(),value.end());//c++中自带的逆置函数
        return value;
    }
};

//利用栈先入后出的特性
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
                vector<int> v;
                stack<int> s;
                int p;
                while(head != nullptr)
                {
                    s.push(head->val);
                    head = head->next;
                }
                while( !s.empty())
                {
                    p = s.top();//取出栈顶元素给p
                    s.pop();    //出栈
                    v.push_back(p);
                }
                return v;
    }
};

// 递归思路
class Solution {
public:
    vector<int> value;
    vector<int> printListFromTailToHead(ListNode* head) {
        ListNode *p = nullptr;
        p = head;
        if(p != nullptr){
            if(p->next != nullptr){
                printListFromTailToHead(p->next);
            }
            value.push_back(p->val);
        }
        return value;
    }
};

2. 链表中倒数第k个结点

输入一个链表,输出该链表中倒数第k个结点。

思路
方法1,遍历两次,第一次计算总长度,第二次找length-k+1个即可。(略)
方法2,遍历一次,用两个指针,第一个指针指向第i个,第二个指针指向i+k-1个即可,当第二个指针指向最后一个时,第一个指针指向的是倒数第k个。注意考虑空指针,k为0的情况(k为无符号,无符号-1很大的)

代码

/*
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 || k == 0) return nullptr;
        ListNode *p1 = pListHead, *p2 = pListHead;
        for(int i = 0; i < k-1; i++)
            if(p1->next != nullptr)
                p1 = p1->next;
           //考虑列表的长度小于k值
            else
                return nullptr;
        while(p1->next != nullptr)
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p2;
    }
};

3. 反转链表

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

思路:遍历时,新链表newhead指向当前遍历结点,newhead->next指向上一个遍历的结点。为了防止断链,需要加临时变量存储当前的next。

代码

/*
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 *p = nullptr, *pNode = pHead, *r = nullptr;
        while(pNode != nullptr)
        {
            ListNode *s = pNode->next;
            p = pNode;
            p->next = r;
            r = pNode;
            pNode = s;
            // pNode = pNode->next;  //不能省去第一个s赋值直接将pNode->next赋值给pNode。
        }
        return p;
    }
};

4. 合并两个排序链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

代码1(递归)

/*
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;
        ListNode *p = nullptr;
        if(pHead1->val <= pHead2->val)
        {
            p = pHead1;
            p->next = Merge(pHead1->next, pHead2);
        }
        else
        {
            p = pHead2;
            p->next = Merge(pHead1, pHead2->next);
        }
        return p;
    }
};

代码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;
        ListNode *p = nullptr, *r = nullptr;
        while(pHead1 != nullptr && pHead2 != nullptr)
        {
            if(pHead1->val <= pHead2->val)
            {
                if(p == nullptr)  p = r = pHead1;
                else{
                    r->next = pHead1;//有疑问???
                    r = r->next;
                }
                pHead1 = pHead1->next;
            }
            else
            {
                if(p == nullptr)  p = r = pHead2;
                else{
                    r->next = pHead2;
                    r = r->next;
                }
                pHead2 = pHead2->next;
            }
         }
        if(pHead1 != nullptr) r->next = pHead1;
        if(pHead2 != nullptr) r->next = pHead2;
        return p;
    }
};

5. 复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)

思路
way1:复制原始链表上的每一个结点,并通过pNext连接起来;然后再设置每个结点的pSibling指针。
假设原始链表中某个结点N的pSibling指针指向结点S,那么就需要从头到尾遍历查找结点S,如果从原始链表的头指针开始,经过m步之后达到结点S,那么在复制链表中的结点N’的pSibling指针指向的结点也是距离复制链表s步的结点。通过这种办法就可以为复制链表上的每个结点设置pSibling指针。
时间复杂度:O(N^2)

way2:方法1是通过链表查找来得到pSibling指针所指向的结点,实际上我们可以通过空间换取时间,将原始链表和复制链表的结点通过哈希表对应起来,这样查找的时间就从O(N)变为O(1)。具体如下:
复制原始链表上的每个结点N创建N’,然后把这些创建出来的结点用pNext连接起来。同时把<N,N’>的配对信息方法一个哈希表中;然后设置复制链表中的每个结点的pSibling指针,如果原始链表中结点N的pSibling指向结点S,那么在复制链表中,对应的N’应该指向S’。
时间复杂度:O(N)
代码

/*
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* newHead = new RandomListNode(pHead->label);//建立复制链表
        RandomListNode *p = pHead, *np = newHead;// 流动链表节点
        map<RandomListNode*, RandomListNode*> M;  //map功能:自动建立Key - value的对应。key 和 value可以是任意你需要的类型。
        M[pHead] = newHead;
        //第一步:复制主干
        while(p->next !=nullptr)
        {
            np->next = new RandomListNode(p->next->label);
            np = np->next;
            p = p->next;
            M[p] = np;
        }
        p = pHead, np = newHead;
        //第二步:复制随意节点
        while(p != nullptr)
        {
            //RandomListNode* r = p->random; 采用该种方式进行判断p->random是否为空,反而会降低时间效率
            if(p->random != nullptr) np->random = M[p->random];
            np = np->next;
            p = p->next;
        }
        return newHead;
    }
};

方法3:在不使用辅助空间的情况下实现O(N)的时间效率。
第一步:根据原始链表的每个结点N创建对应的N’,然后将N‘通过pNext接到N的后面;

第二步:设置复制出来的结点的pSibling。假设原始链表上的N的pSibling指向结点S,那么其对应复制出来的N’是N->pNext指向的结点,同样S’也是结点S->pNext指向的结点。

第三步:把长链表拆分成两个链表,把奇数位置的结点用pNext连接起来的就是原始链表,把偶数位置的结点通过pNext连接起来的就是复制链表。

代码:

/*
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, *newHead, *t;
        //第一步:在每个节点后面复制相同节点并连接成一个链表
        while(p != nullptr)
        {
            t = new RandomListNode(p->label);
            t->next = p->next;
            p->next = t;
            p = t->next;
        }
        //第二步:复制随意节点
        p = pHead;
        while(p != nullptr)
        {
            t = p->random;
            if(t != nullptr) p->next->random = t->next;
            p = p->next->next;
        }
        //第三步:分离
        p = pHead, newHead = pHead->next;
        t = newHead;
        while(t->next != nullptr)
        {
            p->next = p->next->next;
            t->next = t->next->next;
            p = p->next;
            t = t->next;
        }
        p->next = nullptr; //去除这一步测试错误???
        return newHead;
    }
};

6. 二叉搜索树与双向链表

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

思路:原先指向左子结点的指针调整为链表中指向前一个结点的指针,原先指向右子结点的指针调整为链表中指向下一个结点的指针。由于是排序双链表所以采用中序遍历。详见代码

代码

/*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)
    {
        TreeNode *pLastNode = nullptr;
        ConvertNode(pRootOfTree, &pLastNode);//传递的是pLastNode的地址
        //pLastNode 指向尾结点,此时一直往左走找到头结点返回
        while(pLastNode != nullptr && pLastNode->left != nullptr)
            pLastNode = pLastNode->left;
        return pLastNode;
    }
    void ConvertNode(TreeNode* pRoot, TreeNode **pLast)//注意是二维指针
    {
        if(pRoot == nullptr)
            return;
        //遍历左子树
        if(pRoot->left != nullptr)
            ConvertNode(pRoot->left, pLast);
        //根结点:左子树的最后一个结点为根结点的左结点,根结点是左子树最后一个结点的右节点
        pRoot->left = *pLast;
        if(*pLast != nullptr)
           (*pLast)->right = pRoot;
        //遍历右子树
        *pLast = pRoot;
        if(pRoot->right != nullptr)
            ConvertNode(pRoot->right, pLast);
    }
};

7. 两个链表的第一个公共点

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

思路:两个有公共节点而部分重合的链表,其拓扑形状看起来像一个Y,如图所示:
在这里插入图片描述
way1:蛮力法解决:在第一个链表上顺序遍历每一个节点,每遍历到一个节点,就在第二个链表上顺序遍历每个节点。如果在第二个链表上有一个节点与第一个链表上的节点一样,则说明两个链表在这个节点上重合,于是就找到了他们的公共节点。如果第一个链表的长度为m,第二个链表的长度为n,那么,显然该方法的时间复杂度是O(mn)。

way2:如果我们从两个链表的尾部开始往前比较,那么最后一个相同的结点就是我们要找的结点。可问题是,在单向链表中,我们只能从头结点开始按顺序遍历,最后才能到达尾节点。最后到达的尾节点却要最先被比较,这听起来是不是像“后进先出”?于是我们就能想到用栈的特点来解决这个问题:分别把两个链表的结点放入两个栈里,这样两个链表的尾节点就位于两个栈的栈顶,接下来比较两个栈顶的结点是否相同。如果相同,则把栈顶弹出接着比较下一个栈顶,直到找到最后一个相同的节点。
在上述思路中,我们需要用两个辅助栈。如果链表的长度分别为m和n,那么空间复杂度是O(m+n)。这种思路的时间复杂度也是O(m+n)。和最开始的蛮力法相比,时间效率得到了提高,相当于是用空间消耗换取了时间效率。

way3:首先遍历两个链表得到他们的长度,就能知道哪个链表比较长,以及长的链表比短的链表多几个结点。在第二次遍历的时候,在较长的链表上先走若干步,接着同时在两个链表上遍历,找到的第一个相同的结点就是他们的第一个公共结点。way3和way2相比时间复杂度为O(m+n),但我们不在需要辅助栈,因此提高了空间效率。

代码:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        int lenA = 0, lenB = 0;
        ListNode *a = pHead1;
        while(a != nullptr)
        {
            lenA ++;
            a = a->next;
        }
        a = pHead2;
        while(a != nullptr)
        {
            lenB ++;
            a = a->next;
        }
        if(lenA < lenB)
        {
            int t;
            t = lenA; lenB = lenA; lenB = t;
            a = pHead1; pHead1 = pHead2; pHead2 = a;
        }
        int d = lenA - lenB;
        while(d --) pHead1 = pHead1->next;
        while(pHead1 != nullptr)
        {
            if(pHead1 == pHead2) return pHead1;
            pHead1 = pHead1->next;
            pHead2 = pHead2->next;
        }
        return nullptr;
    }
};

8. 链表中环的入口结点

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

思路
第一步,找环中相汇点。分别用p1,p2指向链表头部,p1每次走一步,p2每次走二步,直到p1==p2找到在环中的相汇点。

第二步,找环的长度。从环中的相汇点开始, p2不动, p1前移, 当再次相遇时,p1刚好绕环一周, 其移动即为环的长度K,使用一快一慢两个指针(比如慢指针一次走一步,慢指针一次走两步),如果走的过程中发现快指针追上了慢指针, 说明遇见了环,而且相遇的位置一定在环内, 考虑一下环内, 从任何一个节点出现再回到这个节点的距离就是环的长度, 于是我们可以进一步移动慢指针,快指针原地不动, 当慢指针再次回到相遇位置时, 正好在环内走了一圈, 从而我们通过计数就可以获取到环的长度

第三步, 求换的起点, 转换为求环的倒数第N-K个节点,则两指针p1和p2均指向起始, p2先走K步, 然后两个指针开始同步移动, 当两个指针再次相遇时, p2刚好绕环一周回到起点, p1则刚好走到了起点位置

代码

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
       if(pHead == NULL || pHead->next == NULL || pHead->next->next ==NULL)
            return NULL;
        //第一步:求是否存在环
        ListNode* p1 = pHead->next->next;
        ListNode* p2 = pHead->next;
        while (p1 != p2)
        {
           if (p1->next != NULL && p1->next->next != NULL)
           {
               p1 = p1->next->next;
               p2 = p2->next;
           }
           else
                return NULL;
        }
        //第二步:寻找环的长度
        int count = 1;
        while(p2->next != p1)
        {
            p2 = p2->next;
            count++;
        }
        //第三步:寻找环的入口结点
        p1 = pHead;
        p2 = pHead;
        for (int i=0; i<count; i++)
            p2 = p2->next;
        while(p1 != p2)
        {
            p1 = p1->next;
            p2 = p2->next;
        }
        return p2;
    }
};

9. 删除链表中重复的结点

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

思路:首先,检查边界条件(链表有0个节点或链表有1个节点)时,返回头结点;其次,避免由于第一个节点是重复节点而被删除,新建一个指向头结点的节点;再次,建立三个指针pre/p/next,分别指向当前节点的前序节点、当前节点、当前节点的后续节点;最后循环遍历整个链表,如果节点p的值和节点next的值相同,则删除节点p和节点next,pre和下一个没有重复的节点连接。如果节点p的值和节点next的值不同,则三个指针向后移动一个指针。

代码

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
        // 链表有0个/1个节点,返回第一个节点
        if(pHead == nullptr || pHead->next == nullptr)
            return pHead;
        // 新建一个头节点,防止第一个结点被删除
        ListNode* newpHead = new ListNode(-1);
        newpHead->next = pHead;
         // 建立索引指针
        ListNode* p = pHead;  // 当前节点
        ListNode* pre = newpHead;// 当前节点的前序节点
        ListNode* next= p->next;// 当前节点的后序节点
        while(p != nullptr && p->next != nullptr)
        {
            if(p->val == next->val)
            {
                 // 循环查找,找到与当前节点不同的节点
                while(next != nullptr && next->val == p->val)
                {
                    ListNode* tmp = next;
                    next = next->next;
                    // 删除内存中的重复节点
                    delete tmp;
                    tmp = nullptr;
                }
                pre->next = next;
                p = next;
            }
            //如果当前节点和下一个节点值不等,则向后移动一位
            else
            {
                pre = p;
                p = p->next;
            }
            next = p->next;
        }
        //返回头结点的下一个节点
        return newpHead->next;
    }
};

附加题
删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
例如
给出的链表为1->2->3->3->4->4->5 处理后为 1->2->3->4->5

代码

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @return ListNode类
     */
    ListNode* deleteDuplicates(ListNode* head) {
        // write code here
        if(head == nullptr || head->next == nullptr)
            return head;
        ListNode *p = head;
        while(p)
        {
            ListNode *next =p->next;
            while(p->val == next->val && next != nullptr)
            {
                ListNode *tmp = next;
                next = next->next;
                free(tmp);
            }
            p = p->next = next;
        }
        return head;
    }
};

10. 判断一个链表是否为回文结构

给定一个链表,请判断该链表是否为回文结构。
示例1
输入

[1,2,2,1]

输出

true

思路: 回文结构即判断该链表是否是对称的,或者说其倒序和正序的序列应该是一样,所以可以采用栈来判断其是否为回文结构。
代码

class Solution {
public:
    /**
     * 
     * @param head ListNode类 the head
     * @return bool布尔型
     */
    bool isPail(ListNode* head) {
        // write code here
        if(head == nullptr)
            return false;
        stack<int> stk;
        ListNode *p = head;
        while(p)
        {
            stk.push(p->val);
            p = p->next;
        }
        p = head;
        while(p)
        {
            if(!stk.empty() && stk.top() == p->val)
            {
                stk.pop();
                p = p->next;
            }
            else
                return false;
        }
        return true; 
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值