【数据结构与算法(十五)】

题目

链表中环的入口节点

如果一个链表中包含环,如何找出环的入口节点?例如,在下图中,环的入口节点是节点3
这里写图片描述

思路:

1、第一步是如何确定一个链表中包含环。还是用两个指针来—定义两个指针,同时从链表的头节点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表中就包含环;如果走得快的指针走到了链表的末尾(末尾就是当前指针的下一个指针式nullptr)都没有追上第一个指针,那么链表就不包含环。
2、第二步式如何找到环的入口。还是使用两个指针来解决问题。先定义两个指针p1和p2指向链表的头节点。如果链表中的环有n个节点,则指针p1 先在链表上向前移动n步,然后两个指针以相同的速度向前移动。当第二个指针指向环的入口节点时,第一个指针已经围绕着环走了一圈,又回到了入口节点。
3、所以为了完成第二步,我们就需要知道环中节点的数目n。 使用一快一慢两个指针,找到一个让两个指针相遇的任意节点。之后从这个节点出发,再次移动其中一个指针,当再次回到这个节点时,就可以得到环中的节点数了。

struct ListNode {
    int value;
    ListNode* nextNode;
};

//找出环中任意一个令两个指针相遇的节点
ListNode* MeetingNode(ListNode* pHead)
{
    if (pHead == nullptr)
        return nullptr;
    ListNode* pSlow = pHead->nextNode;
    if (pSlow == nullptr)//只有一个指针的链表
        return nullptr;

    ListNode* pFast = pSlow->nextNode;
    while (pFast != nullptr&&pSlow != nullptr) {
        if (pFast == pSlow)
            return pSlow;

        pSlow = pSlow->nextNode;
        pFast = pFast->nextNode;
        //每次循环如果不到末尾节点,快的指针就要比慢的指针多走一步
        if (pFast != nullptr)
            pFast = pFast->nextNode;
    }
    return nullptr;
}
//在上面找到能让两个指针相遇的节点之后,即找到了环中的任意一个节点
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
    ListNode* meetingNode = MeetingNode(pHead);
    if (meetingNode == nullptr)
        return nullptr;
    //计算出环中节点的数目
    int nodesInLoop = 1;
    ListNode* pNode1 = meetingNode;//标记的节点
    while (pNode1->nextNode != meetingNode) {
        pNode1 = pNode1->nextNode;
        ++nodesInLoop;//走过一个节点就加一个
    }
    //先移动pNode1,次数为上面数的环中的节点数
    pNode1 = pHead;
    for (int i = 0; i < nodesInLoop; i++)
        pNode1 = pNode1->nextNode;
    //开始同时移动两个节点,此时pNode1在pNode2前面了
    ListNode* pNode2 = pHead;
    while (pNode1 != pNode2) {
        pNode1 = pNode1->nextNode;
        pNode2 = pNode2->nextNode;
    }
    return pNode1;
}

考虑可能的输入和输出:
1、包含环的链表、不包含环的链表(走到一个末尾,下一个节点时nullptr),链表中有多个或者只有一个节点
2、特殊输入:链表的头节点为nullptr,也就是链表根本不存在

二叉树的镜像

输入一个二叉树,完成一个函数,输出它的镜像

struct BinaryTreeNode
{
    int value;
    BinaryTreeNode* leftNode;
    BinaryTreeNode* rightNode;
};

思路

1、举一个例子说树的镜像这概念
这里写图片描述
2、首先可以看到两棵树的根节点相同,但它们的左、右两个子节点交换了位置,所以第一步先直接交换第一棵树的左、右两个子节点(当然左右节点对应的子树也会跟着它们的父节点移动的)
3、接着对于左、右两个子节点对应的左右子节点也使用相同的做法,交换各自的左右子节点的位置,这样下去就形成递归了
4、总的来说,就是要先前序遍历这棵树的每个节点,如果遍历到的节点有子节点,就交换它的两个子节点。当交换完所有的非叶节点的左右子节点(反正就是把所有的左右子节点都对应交换)之后,就得到了树的镜像
反正碰到树,又是一个遍历和递归的问题,对于树,还是得画图分析

struct BinaryTreeNode
{
    int value;
    BinaryTreeNode* leftNode;
    BinaryTreeNode* rightNode;
};
void MirrorRecusively(BinaryTreeNode* pNode)
{
    if (pNode == nullptr)
        return;
    if (pNode->leftNode == nullptr&&pNode->rightNode == nullptr)//只有一个节点的树或者说是树的叶节点
        return;
    BinaryTreeNode* pTemp = pNode->leftNode;
    pNode->leftNode = pNode->rightNode;
    pNode->rightNode = pTemp;
    if (pNode->leftNode != nullptr)
        MirrorRecusively(pNode->leftNode);
    if (pNode->rightNode != nullptr)
        MirrorRecusively(pNode->rightNode);
}

对称的二叉树

请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树以及它的镜像树是一样的,那么它是对称的。下面举个例子
这里写图片描述

思路

1、有三种二叉树的遍历方法,即前序遍历、中序遍历和后序遍历,在三种变脸算法中,左子节点总是比右子节点要先遍历。
2、这里就尝试定义一种遍历算法,先遍历右子节点再遍历左子节点,然后使用这种新定义的算法遍历输入的二叉树,之后再用原先的对应的遍历算法(如果新的遍历算法是针对前序遍历写的,那这个原先算法就是前序遍历序列)遍历输入的二叉树,如果两个序列是一样的,那么这个二叉树就是对称二叉树。
3、但是又有一种特殊情况,就是二叉树中每个节点都是一样的,但它又不是一个真的对称二叉树,比如下图
这里写图片描述
对于这种情况,可以把nullptr节点也读到序列中

bool isSymmertrical(BinaryTreeNode* pRoot)
{
    return isSymmertrical(pRoot, pRoot);
}
//pRoot1是前序遍历这一次遍历的节点,pRoot2是新的前序遍历这一次遍历的节点
bool isSymmertrical(BinaryTreeNode* pRoot1, BinaryTreeNode* pRoot2)
{
    if (pRoot1 == nullptr&&pRoot2 == nullptr)
        return true;
    if (pRoot1 == nullptr || pRoot2 == nullptr)//其中一个已经遍历结束了,肯定不对称
        return false;
    if (pRoot1->value != pRoot2->value)//对称的遍历的值不等
        return false;
    return isSymmertrical(pRoot1->leftNode, pRoot2->rightNode) && 
        isSymmertrical(pRoot1->rightNode, pRoot2->leftNode);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值