【数据结构与算法】《剑指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, 231 − 1] 。
这道题是让实现指定的库函数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;//防止该案例:""\n"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=='\0';
}
bool scanUnsignedInteger(const char ** str){
const char* before = *str;
while(**str!='\0' && **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)增强鲁棒性:采取防御性编程,处理无效输入