【数据结构与算法】《剑指offer》学习笔记----第三章 高质量的代码(含16-26题)

第3章 高质量的代码

从3个方面保证代码的完整性:

测试内容 要求
功能测试 完成常规的功能要求
边界测试 考虑各种边界值,如循环终止条件,递归终止条件
负面测试 考虑各种可能的错误输入,不合法输入的处理

有3中错误处理方法:

方法 优点 缺点
返回值 和系统API一致 不能方便地使用计算结果
全局变量 能方便地使用计算结果 用户可能会忘记检查全局变量
异常 可以为不同错误原因定义不同的异常类型,逻辑清晰 有些语言不支持异常,抛出异常时对性能有负面影响

面试题16. 数值的整数次方

实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。

示例 1:
输入: 2.00000, 10
输出: 1024.00000


示例 2:
输入: 2.10000, 3
输出: 9.26100


示例 3:
输入: 2.00000, -2
输出: 0.25000
解释: 2-2 = 1/22 = 1/4 = 0.25
 

说明:
-100.0 < x < 100.0
n 是 32 位有符号整数,其数值范围是 [231, 2311]

这道题是让实现指定的库函数pow。

class Solution {
public:
    double myPow(double x, int n) {
        if(x == 1 || n == 0) return 1;//1的任意次方,和任何数的0次方,都让返回1
        double res = 1;
        long num = n;
        if(n < 0){//指数为负值时,将指数变为正值,且底数变为底数的倒数
            num = -num;
            x = 1/x;
        }
        while(num){//如果指数没有消耗完,两次方两次方的降
            if(num & 1) res *= x;//指数的低位存在,res = res * 底数
            x *= x;//x = x*x,x扩大为它的平方,因为二进制每位的差距是平方关系
            num >>= 1;//指数除以2
        }
        return res;
    }
};

面试题17. 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

示例 1:
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]
 
说明:
用返回一个整数列表来代替打印
n 为正整数

递归解法:

class Solution
{
public:
	vector<int> res;
	vector<int> printNumbers(int n) {
		if (n <= 0) return res;
		string number(n, '0');
        //从高位到低位进行全排列
		for (int i = 0; i <= 9; ++i)
		{
			number[0] = i + '0';//首字符赋初值
			permutationNumbers(number, n, 1);//设置下一位
		}
		return res;
	}
	//对数字全排列
	void permutationNumbers(string& number, int length, int index) {
		if (index == length) {//递归边界
			saveNumber(number);//存储结果
			return;
		}
		else
		{
			for (int i = 0; i <= 9; i++)
			{
				number[index] = '0' + i;//设置第index位的字符
				permutationNumbers(number, length, index + 1);
			}
		}
	}
	//存储结果
	//只能存储前导非0的排列
	void saveNumber(string number) {
		bool isBegin0 = true;
		string tempStr = "";
		string::iterator it = number.begin();
		while (it != number.end())//遍历字符串
		{
			if (isBegin0 && *it != '0') isBegin0 = false;//如果标志位为true,且当前确实不是'0',将标志位更新为false
			if (!isBegin0) {
				tempStr += *it;
			}
			it++;
		}
		//从高位到低位全排列,要注意首字符为0时,tempStr为空,不能执行stoi
		if (tempStr != "") {
			int tempNum = stoi(tempStr);
			res.push_back(tempNum);
		}
	}
};

大数string表示:

class Solution {
public:
	vector<int> res;
	vector<int> printNumbers(int n) {
		if (n <= 0) return res;
		//创建一个能容纳最大值的字符数组
		string number(n, '0');
		//初始全部设置为0
		while (!Increment(number))
		{
			saveNumber(number);
		}
		return res;
	}
	bool Increment(string& number) {
		//注意要使用引用传递,否则无法修改number
		bool isOverflow = false;//检测是否越界
		int nTakeOver = 0;//存储进位
		int nLength = number.size();
		for (int i = nLength - 1; i >= 0; i--)
		{
			int nSum = number[i] - '0' + nTakeOver;
			if (i == nLength - 1)
				//如果是第一位,进位
			{
				nSum++;
			}
			if (nSum >= 10)//有进位
			{
				if (i == 0)
					//如果是最高位有进位,说明超过了给定得到最大值,越界
				{
					isOverflow = true;
				}
				else
				{
					nTakeOver = 1;
					number[i] = nSum - 10 + '0';//对第i位进行设置
				}
			}
			else//没有进位
				//设置第i位数字
				//并直接跳出循环
			{
				number[i] = nSum + '0';
				break;
			}
		}
		return isOverflow;
	}
	void saveNumber(string number)
		//由于此处输出,不需要修改number,因此不需要加引用
	{
		string s = "";
		bool isBegin0 = true;
		string::iterator it = number.begin();
		while (it != number.end())
		{
			if (isBegin0 && *it != '0') isBegin0 = false;
			if (!isBegin0)
			{
				s += *it;
			}
			it++;
		}
		int num = stoi(s);
		res.push_back(num);
	}
};

面试题18. 删除链表的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

注意:此题对比原题有改动

示例 1:
输入: head = [4,5,1,9], val = 5
输出: [4,1,9]
解释: 给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.


示例 2:
输入: head = [4,5,1,9], val = 1
输出: [4,5,9]
解释: 给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.
 

说明:
题目保证链表中节点的值互不相同
若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

中规中矩:

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head==NULL)     return NULL;
        if(head->val==val) return head->next;

        ListNode* pNode = head;
        while(pNode->next){
            if(pNode->next->val==val){
                pNode->next = pNode->next->next;
                break;
            }
            pNode=pNode->next;
        }
        return head;
    }
};

【原题】
而原题比这道题更有味道,至少让我学会了还能直接根据给定的节点指针,无需遍历,以O(1)的时间复杂度删除这个节点,具体说来就是:
当我们想删除一个节点时,并不一定要删除这个节点本身。可以先把下一个节点的内容复制出来覆盖被删除节点的内容,然后把下一个节点删除。

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

void DeletedNode(ListNode** pListHead, ListNode* pToBeDeleted) {
	if (!pListHead || !pToBeDeleted) {
		return;
	}

	//要删除的节点不是尾结点
	if (pToBeDeleted->next != NULL) {
		//绝妙
		//找到待删节点,与其下一节点进行数据交换,删除下一节点
		ListNode* pNext = pToBeDeleted->next;
		pToBeDeleted->val = pNext->val;
		pToBeDeleted->next = pNext->next;
		//释放内存
		delete pNext;
		pNext = NULL;
	}
	//要删除的节点是尾结点,但是链表只有一个节点(此时,要删除的节点也是头节点)
	else if (*pListHead == pToBeDeleted) {
		delete pToBeDeleted;
		pToBeDeleted = NULL;
		*pListHead = NULL;
	}
	//要删除的节点是尾结点,且链表中有多个节点(此时,要删除的节点不是头节点)
	else {
		ListNode* pNode = *pListHead;
		while (pNode->next != pToBeDeleted) {
			pNode = pNode->next;
		}
		pNode->next = NULL;
		delete pToBeDeleted;
		pToBeDeleted = NULL;
	}
}

【原题-引申的题目二】
删除链表中的重复节点们,遇到重复节点,一个不留,斩草除根

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


void DeleteDuplication(ListNode** pHead) {//这里不可写成ListNode* pHead?
	//如果指向头节点的指针为空
	if (pHead == NULL || *pHead == NULL) {
		return;
	}
	ListNode* pPreNode = NULL;
	ListNode* pNode = *pHead;
	while (pNode != NULL) {
		ListNode* pNext = pNode->next;
		bool needDelete = false;
		if (pNext != NULL && pNext->val == pNode->val) {
			needDelete = true;
		}
		if (!needDelete) {//如果当前pNode节点不需要被删除,那么继续往下个节点遍历
			pPreNode = pNode;
			pNode = pNode->next;
		}
		else {//如果当前pNode节点需要被删除
			int value = pNode->val;
			ListNode* pToBeDel = pNode;
			while (pToBeDel != NULL && pToBeDel->val == value) {//一直删除,直到链表为空,或下个节点的值不等于这个数字
				pNext = pToBeDel->next;
				delete pToBeDel;
				pToBeDel = pNext;
			}

			if (pPreNode == NULL) {//如果下个节点为空,说明删到链表尾了
				*pHead = pNext;
			}
			else {//如果下个节点并不为空,却跳出了循环,说明下个节点值不等于value
				pPreNode->next = pNext;
			}
			pNode = pNext;//从这里开始继续寻找重复的节点
		}
	}
}

面试题19. 正则表达式匹配(难掉牙了)

请实现一个函数用来匹配包含'. '和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a""ab*ac*a"匹配,但与"aa.a""ab*a"均不匹配。

示例 1:
输入:
s = "aa"
p = "a"
输出: false
解释: "a" 无法匹配 "aa" 整个字符串。


示例 2:
输入:
s = "aa"
p = "a*"
输出: true
解释: 因为 '*' 代表可以匹配零个或多个前面的那一个元素, 在这里前面的元素就是 'a'。因此,字符串 "aa" 可被视为 'a' 重复了一次。


示例 3:
输入:
s = "ab"
p = ".*"
输出: true
解释: ".*" 表示可匹配零个或多个('*')任意字符('.')。


示例 4:
输入:
s = "aab"
p = "c*a*b"
输出: true
解释: 因为 '*' 表示零个或多个,这里 'c'0, 'a' 被重复一次。因此可以匹配字符串 "aab"。


示例 5:
输入:
s = "mississippi"
p = "mis*is*p*."
输出: false
s 可能为空,且只包含从 a-z 的小写字母。
p 可能为空,且只包含从 a-z 的小写字母以及字符 .*,无连续的 '*'

法一:

class Solution {
public:
    bool isMatch(string s, string p) {
        if(p.empty()) return s.empty();
        if(p[1] == '*'){
            return isMatch(s, p.substr(2)) || //目标字符串全都能匹配上模板从位置2往后的内容
            (!s.empty() && (s[0] == p[0] || p[0] == '.')) && //目标字符串不能为空 && (两串的首字母相同,或者模板为'.')
            isMatch(s.substr(1), p);//从目标字符串的第2个位置开始的子串,要能和整个模板匹配上
        }
        else{
            return !s.empty() && //目标字符串不能为空,不然不管模板是啥都没办法匹配
            (s[0] == p[0] || p[0] == '.') && //如果第二个字符不为*,那么第一个字符务必相同,或者模板为'.'
            (isMatch(s.substr(1), p.substr(1)));//且,从位置1开始的字符串(包括位置1字符)比如能满足匹配要求
        }
    }
};

法二:

class Solution {
public:
    bool isMatch(string s, string p) {
        s=" "+s;//防止该案例:""
"c*"
        p=" "+p;
        int m=s.size(),n=p.size();
        bool dp[m+1][n+1];
        memset(dp,false,(m+1)*(n+1));
        dp[0][0]=true;
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(s[i-1]==p[j-1] || p[j-1]=='.'){
                    dp[i][j]=dp[i-1][j-1];
                }
                else if(p[j-1]=='*'){
                    if(s[i-1]!=p[j-2] && p[j-2]!='.')
                        dp[i][j]=dp[i][j-2];
                    else{
                        dp[i][j]=dp[i][j-1] || dp[i][j-2] || dp[i-1][j];

                    }
                }
            }
        }
        return dp[m][n];
    }
};

面试题20. 表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100"、“5e2”、"-123"、“3.1416”、“0123"都表示数值,但"12e”、“1a3.14”、“1.2.3”、“±5”、"-1E-16"及"12e+5.4"都不是。

class Solution {
public:
    bool isNumber(string s) {
        s = trim(s);
        const char* str = s.c_str();//将字符串string转变为字符数组
        if(str==NULL){
            return false;
        }
        //遍历正负号和纯数字,停留在纯数字的尾后位置
        bool numeric = scanInteger(&str);
        // 如果当前str是'.',则接下来应该是数字的小数部分
        if(*str=='.'){
            ++str;
            numeric = scanUnsignedInteger(&str) || numeric;//如果有数字,遍历,并移动到纯数字的尾后位置
        }
        //看是否遇到'e'或'E'
        if(*str=='e' || *str=='E'){
            ++str;
            numeric = numeric && scanInteger(&str);
        }
        return numeric && *str=='';
    }
    bool scanUnsignedInteger(const char ** str){
        const char* before = *str;
        while(**str!='' && **str>='0' && **str<='9'){
            ++(*str);
        }
        return *str>before;//指针移动了位置,说明存在纯数字
    }
    bool scanInteger(const char** str){
        if(**str=='+' || **str=='-'){
            ++(*str);
        }
        return scanUnsignedInteger(str);
    }
    //去除字符串中的首尾空格
    std::string& trim(std::string &s) 
    {
        if (!s.empty()) 
        {
            s.erase(0,s.find_first_not_of(" "));
            s.erase(s.find_last_not_of(" ") + 1);
        }
        return s;
    }

};

面试题21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。

示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4] 
注:[3,1,2,4] 也是正确的答案之一。
 

提示:
1 <= nums.length <= 50000
1 <= nums[i] <= 10000
class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int n = nums.size();
        if(n<=0) return nums;
        int left = 0,right = nums.size()-1;
        while(left<right){//很关键,必须有,否则,只处理一对奇数偶数交换
            while(left<right && (nums[left]&0x1)!=0){//左边的指针遇到的是奇数就继续往后找
                ++left;
            }
            while(left<right && (nums[right]&0x1)==0){//右边的指针遇到的是偶数就继续往前找
                --right;
            }
            if(left<right){
                std::swap(nums[left],nums[right]);
            }
        }
        return nums;
    }
};

解耦思想:利用解耦思想,拆分函数功能,将判断一个数字该在前面还是后面的函数,与拆分数组的函数解耦,提高代码复用性。涉及函数指针的使用。

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        return ReOrder(nums,iseven);
    }
    vector<int> ReOrder(vector<int>& nums,bool (*func)(int)) {
        int n = nums.size();
        if(n<=0) return nums;
        int left = 0,right = nums.size()-1;
        while(left<right){//很关键,必须有,否则,只处理一对奇数偶数交换
            while(left<right && !func(nums[left])){//左边的指针遇到的是奇数就继续往后找
                ++left;
            }
            while(left<right && func(nums[right])){//右边的指针遇到的是偶数就继续往前找
                --right;
            }
            if(left<right){
                std::swap(nums[left],nums[right]);
            }
        }
        return nums;
    }
    static bool iseven(int n){
        return (n&0x1)==0;
    }
};

防御性编程习惯

编写鲁棒性强的代码,在函数开始的时候验证用户输入内容是否符合要求,对于不符合要求的输入也能合理处理,使各种情况都在程序员掌控之中。
多问问:如果不…那么…,此类的问题。

面试题22. 链表中倒数第k个节点

输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,它们的值依次是1、2、3、4、5、6。这个链表的倒数第3个节点是值为4的节点。

示例:
给定一个链表: 1->2->3->4->5, 和 k = 2.
返回链表 4->5.
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        if(head==NULL || k==0){//代码鲁棒性
            return NULL;
        }
        ListNode * pNode1 = head;
        //指针1先走k-1步
        for(int i=0;i<k-1;++i){
            if(pNode1->next != NULL){//代码鲁棒性
                pNode1=pNode1->next;
            }else{
                return NULL;
            }
        }
        //指针2初始化后开始和指针1一起走,直到指针1走到头
        ListNode * pNode2 = head;
        while(pNode1->next != NULL){
            pNode1 = pNode1->next;
            pNode2 = pNode2->next;
        }
        return pNode2;
    }
};

快慢指针拓展

快慢指针的思想:当我们用一个指针遍历链表不能解决问题的时候,可以尝试用两个指针遍历链表。可以让一个指针遍历速度快一些(比如一次在链表中走两步),或者让它先在链表上走上若干步。

利用快慢指针的思想,求链表的中间节点也就不是难事:定义两个指针,同时从表头出发,一个指针一次走一步,另一个指针一次走两步。当走的快的指针走到链表尾后时,走得慢的指针恰好在链表中间。

141. 环形链表

给定一个链表,判断链表中是否有环。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。


示例 2:

输入:head = [1,2], pos = 0
输出:true
解释:链表中有一个环,其尾部连接到第一个节点。


示例 3:

输入:head = [1], pos = -1
输出:false
解释:链表中没有环。

思路:
可以用快慢指针解决问题。定义两个指针,一个指针一次走一步,另一个指针一次走两步。如果走的快的指针追上了走得慢的指针,说明链表包含环。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==NULL){//链表为空,不存在环
            return false;
        }

        ListNode* pSlow = head->next;
        if(pSlow==NULL){//链表只有一个节点时,不存在环
            return false;
        }
        ListNode* pFast = pSlow->next;//初始值不能和慢指针一样
        while(pFast!=NULL && pSlow!=NULL){
            if(pFast==pSlow){//判断快慢指针是否相等,如果相等,说明存在环
                return true;
            }

            pSlow = pSlow->next;//快慢指针一起走路
            pFast = pFast->next;//快慢指针一起走路

            if(pFast!=NULL){//快指针每次比慢指针多走一步
                pFast=pFast->next;
            }
        }
        return false;
    }
};

142. 环形链表 II

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。

说明:不允许修改给定的链表。

示例 1:

输入:head = [3,2,0,-4], pos = 1
输出:tail connects to node index 1
解释:链表中有一个环,其尾部连接到第二个节点。


示例 2:

输入:head = [1,2], pos = 0
输出:tail connects to node index 0
解释:链表中有一个环,其尾部连接到第一个节点。


示例 3:

输入:head = [1], pos = -1
输出:no cycle
解释:链表中没有环。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* meetingNode = MeetingNode(head);
        if(meetingNode==NULL){//不存在环
            return NULL;
        }
        //获得环中节点的数目
        int count = 1;
        ListNode* pNode1 = meetingNode;
        while(pNode1->next != meetingNode){
            pNode1 = pNode1->next;
            ++count;
        }
        //先移动指针1,次数为环中节点数目
        pNode1 = head;
        for(int i=0;i<count;++i){
            pNode1=pNode1->next;
        }
        //再移动指针1和指针2
        ListNode* pNode2 = head;
        while(pNode1!=pNode2){
            pNode1=pNode1->next;
            pNode2=pNode2->next;
        }
        return pNode1;
    }
    ListNode* MeetingNode(ListNode* pHead){
        if(pHead==NULL){return NULL;}

        ListNode* pSlow = pHead->next;
        if(pSlow == NULL){
            return NULL;
        }
        ListNode* pFast = pSlow->next;
        while(pFast!=NULL && pSlow!=NULL){
            if(pSlow==pFast){
                return pFast;
            }
            pSlow = pSlow->next;
            pFast = pFast->next;
            if(pFast!=NULL){
                pFast = pFast->next;
            }
        }
        return NULL;
    }
};

面试题24. 反转链表

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

示例:

输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
 

限制:

0 <= 节点个数 <= 5000

处理这个问题的时候,要注意我们需要知道3个关键:
(1)当前节点本身
(2)当前节点的上一个节点(要指向的目标)
(3)当前节点的下一个节点(防止链表断裂)
完成3项检查:
(1)输入链表头指针为空,或整个链表只有一个节点时,程序的鲁棒性
(2)反转后不要出现断裂
(3)返回的反转后的头节点应该是原始链表的尾节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* pReversedHead = NULL;//定义反转后的头节点
        ListNode* pNode=head,*pPrev=NULL;//定义当前节点指针(并初始化为头指针),和前一个节点的指针
        while(pNode!=NULL){//当前节点不为空,才进行下面的步骤,直到它为空
            ListNode* pNext = pNode->next;//记录下一个节点指针
            if(pNext==NULL){//到了尾节点了
                pReversedHead = pNode;
            }
            pNode->next = pPrev;//反转的关键一步
            pPrev = pNode;//往后走一步
            pNode = pNext;//往后走一步
        }
        return pReversedHead;
    }
};

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

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。

示例1:

输入:1->2->4, 1->3->4
输出:1->1->2->3->4->4
限制:

0 <= 链表长度 <= 1000
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //解决空表的情况,一个表为空,不需要合并,直接返回另一个表即可
        if(l1==NULL){
            return l2;
        }else if(l2==NULL){
            return l1;
        }
        ListNode* pMergeHead=NULL;
        if(l1->val < l2->val){
            pMergeHead = l1;
            pMergeHead->next = mergeTwoLists(l1->next,l2);
        }else{
            pMergeHead = l2;
            pMergeHead->next = mergeTwoLists(l1,l2->next);
        }
        return pMergeHead;
    }
};

面试题26. 树的子结构

输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)

B是A的子结构, 即 A中有出现和B相同的结构和节点值。

例如:
给定的树 A:

     3
    / 
   4   5
  / 
 1   2
给定的树 B:

   4 
  /
 1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。

示例 1:

输入:A = [1,2,3], B = [3,1]
输出:false
示例 2:

输入:A = [3,4,5,1,2], B = [4,1]
输出:true
限制:

0 <= 节点个数 <= 10000
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        bool result = false;
        if(A!=NULL && B!=NULL){
            if(A->val==B->val){
                result = DoesTree1HaveTree2(A,B);
            }
            if(!result){
                result = isSubStructure(A->left,B);
            }
            if(!result){
                result = isSubStructure(A->right,B);
            }
        }
        return result;
    }

    bool DoesTree1HaveTree2(TreeNode* A,TreeNode* B){
        if(B==NULL){
            return true;
        }
        if(A==NULL){
            return false;
        }
        if(A->val != B->val){
            return false;
        }
        return DoesTree1HaveTree2(A->left,B->left) && DoesTree1HaveTree2(A->right,B->right);
    }
};

总结

编程时,注意代码规范性、完整性、鲁棒性:

(1)确保规范性:书写清晰,布局清晰,命名合理

(2)确保完整性:编程前,全面考虑所有可能的输入,确保完成基本功能,考虑边界条件,做好错误处理

(3)增强鲁棒性:采取防御性编程,处理无效输入

原文地址:https://www.cnblogs.com/dindin1995/p/13059073.html