《剑指offer》——代码的鲁棒性

本文深入解析链表的倒数第K个节点、环的入口、反转链表、合并排序链表及二叉树子结构判断等经典算法问题,提供详细思路与实现代码。

面试题22:链表的倒数第K个节点

题目:输入一个链表,输出该链表的倒数第K个节点(计数从1开始)。

思路:由于是找出倒数第K个节点,且计数从1开始,所以设置两个快慢指针,快指针先走K-1步,再让两个指针一起走。当快指针指向最后一个节点时,慢指针指向倒数第K个节点。

边界考虑:1、输入的链表头指针为空;

                  2、输入的k值为0;

                  3、输入的以pHead为头节点的链表的节点总数少于K;

链表节点定义:

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

解题代码:

ListNode* FindKthToTail(ListNode* pListHead, unsigned int k)
    {
        if(pListHead == NULL || k == 0)   //考虑头节点为空或k值为0的情况
            return NULL;

        ListNode *p = pListHead;
        ListNode *q = pListHead;
        for(int i = 1;i < k;i++)
        {
            if(p->next != NULL)
            {
                p = p->next;
            }
            else   //若在循环中p->next为空,说明链表节点数<k,该链表没有倒数第k个节点
                return NULL;
        }
        while(p->next != NULL)
        {
            p = p->next;
            q = q->next;
        }
        return q;
    }

面试题23:链表中环的入口

题目:如果一个链表中包含环,如何找出环的入口节点

思路:首先是判断链表是否有环:设置两个快慢指针,快指针一次走两步,慢指针一次走一步,若快指针和慢指针指向了同一块区域,那么链表中包含了环;若快指针走到了链表的末尾(next域指向空),慢指针还没有追上快指针,那么链表中没有环。

其次是如何找到环的入口。有两个方法:方法一:假设链表环中有n个节点,设置快慢指针,快指针先走n步,然后两个指针以相同的速度向前移动,当慢指针走到入口节点处,快指针已经绕着环走了一圈,回到入口节点处(快指针==慢指针)。方法二:利用数学计算,假设链表到环入口的距离为x,环入口到相遇点的距离为a(已经走过的距离),环的长度为c,当快慢指针相遇时,快指针路程:fast = x + a + c * m(假设快指针已经在圈里转了m圈),慢指针路程:slow = x + a + c * n(假设慢指针已经在圈里转了n圈),fast = 2 * slow,可以推导出x = (n - 2 * m - 1) * c + c - a,即x = c - a,所以设置两个指针,一个从链表头节点开始走,一个从相遇点向后走,两指针速度相同,当两指针相遇时,相遇的节点就是入口节点。

链表节点定义:

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

判断链表中是否有环:

ListNode* HasCycle(ListNode* pHead)  //若有环,返回相遇点的节点指针;若没有,返回空
    {
        if(pHead == NULL || pHead->next == NULL)
            return NULL;
        ListNode *fast = pHead;
        ListNode *slow = pHead;
        while(fast->next != NULL && fast != NULL)
        {
            if(fast == slow)
                return fast;
            fast = fast->next->next;
            slow = slow->next;
        }
        return NULL;
    }

寻找环入口:

方法一:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
    ListNode* meetingNode = HasCycle(pHead);
    if(meetingNode == null)
        return null;
    
    int nodesInLoop = 1;     //得到环中的节点个数
    ListNode* p1 = meetingNode;
    while(p1->next != meetingNode)
    {
        p1 = p1->next;
        ++nodesInLoop;
    }
    
    p1 = pHead;   //移动p1
    for(int i = 0;i < nodesInLoop;i++)
    {
        p1=p1->next;
    }
    
    ListNode p2 = pHead;    //移动p1,p2
    while(p1!=p2)
    {
        p1=p1.next;
        p2=p2.next;
    }
    return p1;
}

方法二://将判断是否有环和寻找入口节点结合在一起
ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead == NULL || pHead->next == NULL)
            return NULL;
        ListNode *fast = pHead;
        ListNode *slow = pHead;
        while(fast->next != NULL && fast != NULL)
        {
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow)
            {
                slow = pHead;
                while(slow != fast)
                {
                    slow = slow->next;
                    fast = fast->next;
                }
                return fast;
            }
        }
        return NULL;
    }

面试题24:反转链表

题目:定义·一个函数,输入一个链表的的头节点,反转该链表并输出反转后链表的头节点

思路:创建一个新的头节点,顺序遍历链表,再将链表的每一节点以头插的方式插入新的头节点上。这样,新的头节点后面的链表是原来链表的逆序,返回新的头节点的下一个节点(newHead->next)。

边界考虑:1、传入的头节点为空或这个链表只有一个节点

                  2、返回的节点不是原链表的尾结点

                  3、注意反转后的链表会不会出现断裂

链表节点定义

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

反转链表:

ListNode* ReverseList(ListNode* pHead)
 {
        if(pHead == NULL)   //考虑输入的链表为空情况
            return NULL;
        
        ListNode *newhead = new ListNode(-1);//创建一个新的头节点
        newhead->next = NULL;
        
        while(pHead)//将原有链表的节点以头插的方式插入新的头节点后
        {
            ListNode *tmp = pHead;
            pHead = pHead->next;
            tmp->next = newhead->next;
            newhead->next = tmp;
        }
        return newhead->next;

面试题25:合并两个排序的链表

题目:输入两个递增排序的链表,合并这两个链表并使新链表中的节点也是递增排序的。

思路:先比较两个链表的头节点值,假设链表1头节点值 < 链表2头节点值,链表1的头节点将是合并后的链表头节点。再来比较两个链表剩余的节点,步骤如上,将两链表的节点一一比较,值较小的节点插入到合并后的链表中,并且指针往后走,当一个链表全部插入后,将另一个链表的剩余节点接到合并后链表的尾端。

边界考虑:考虑两个链表是否为空

节点定义:

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

合并链表:

非递归

//创建一个新的头节点,然后将两个链表进行比较,小的连接到新的头结点上
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if(pHead1 == NULL && pHead2 == NULL)
            return NULL;
        
        ListNode *head = new ListNode(-1) ;
        head->next = NULL;
        ListNode *phead = head;
        
        ListNode *p1 = pHead1;
        ListNode *p2 = pHead2;
        
        while(p1 && p2)
        {
            if(p1->val > p2->val)
            {
                phead->next = p2;
                phead = p2;
                p2 = p2->next;
            }
            else
            {
                phead->next = p1;
                phead = p1;
                p1 = p1->next;
            }
        }
        if(p1 != NULL)
        {
            phead->next = p1;
        }
        if(p2 != NULL)
        {
            phead->next = p2;
        }
        return head->next;
     }

//比较两个链表的头节点,将较小的节点当做合并后链表的头节点
ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
	if(NULL == pHead1 && NULL == pHead2)
		return NULL;

	if(NULL == pHead1)
		return pHead2;

	if(NULL == pHead2)
		return pHead1;

       	ListNode *pNode1 = pHead1;
	ListNode *pNode2 = pHead2;
	ListNode *newListHead = (pNode1->Value < pNode2->Value) ? pNode1 : pNode2;	ListNode *newListNode = newListHead;
	if(newListHead == pNode1)
		pNode1 = pNode1->Next;
	else
		pNode2 = pNode2->Next;

       	while(pNode1 && pNode2)
	{
		if(pNode1->Value < pNode2->Value)
		{
			newListNode->Next = pNode1;
			pNode1 = pNode1->Next;
		}
		else
		{
			newListNode->Next = pNode2;
			pNode2 = pNode2->Next;
		}
		newListNode = newListNode->Next;
	} 
       	if(pNode1)
	{
		newListNode->Next = pNode1;
	} 
       	if(pNode2)
	{
		newListNode->Next = pNode2;
	}
       	return newListHead;
    }

递归:

ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
{
	if(NULL == pHead1)
		return pHead2;

	if(NULL == pHead2)
		return pHead1;

    	ListNode *newListHead = NULL;
 	if(pHead1->Value < pHead2->Value)	
        {	
	        newListHead = pHead1;
		newListHead->Next = Merge(pHead1->Next,pHead2);
	}
	else
	{
		newListHead = pHead2;
		newListHead->Next = Merge(pHead1,pHead2->Next);
	}
       	return newListHead;
}

面试题26:树的子结构

题目:输入两颗二叉树A和B,判断B是不是A的子结构。

思路:第一步,在树A中找到和树B的根节点的值一样的节点R;第二步,判断树A中以R为根节点的子树是否包含和树B一样的结构。

边界考虑:1、树A和树B的头节点有一个或两个都是空指针;

                  2、在树A和树B中所有节点都只有左子节点或右子节点;

                  3、树A和树B的节点中含有分叉;

二叉树节点定义:

struct TreeNode
 {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
}

解题过程:首先注意pRoot1和pRoot2是否为空的判断顺序,先判断pRoot2的,再来判断pRoot1的,判断顺序不能改变。

bool DoesT1hasT2(TreeNode*pRoot1,TreeNode* pRoot2)
    {
        //如果Tree2已经遍历完了都能对应的上,返回true
        if(pRoot2 == NULL)
            return true;
        //如果Tree2还没有遍历完,Tree1却遍历完了。返回false
        if(pRoot1 == NULL && pRoot2 != NULL)
            return false;
        //如果其中有一个点没有对应上,返回false
        if(pRoot1->val != pRoot2->val)
            return false;
        //如果根节点对应的上,那么就分别去子节点里面匹配
        return DoesT1hasT2(pRoot1->left,pRoot2->left) && DoesT1hasT2(pRoot1->right,pRoot2->right);
    }

bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
        bool result = false;
        
        //当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
        if(pRoot1 != NULL && pRoot2 != NULL)
        {
            //如果找到了对应Tree2的根节点的点
            if(pRoot1->val == pRoot2->val)
            {
                //以这个根节点为为起点判断是否包含Tree2
                result = DoesT1hasT2(pRoot1,pRoot2);
            }
            //如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
            if(!result)
                result = HasSubtree(pRoot1->left,pRoot2);
            //如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
            if(!result)
                result = HasSubtree(pRoot1->right,pRoot2);
        }
        return result;
    }

二叉树节点中保存的是int整型,可以直接比较两个节点的值是否相等。若保存的值类型为double或float,就不能直接比较,因为计算机内表示小数时有误差,若判断两个数是否相等,只能判断他们之间的差值的绝对值是否在一个很小的范围内。

bool Equal(double num1,double num2)
{
    if((num1 - num2 > -0.0000001) && (num1 - num2) < 0.0000001)
        return true;
    else
        return false;
}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值