欢迎访问我的博客首页。
高质量的代码
高质量的代码要具有完整性和鲁棒性。类似于数学上的分类讨论,完整性要求代码能完整地考虑到每一类情况。比如分段函数就要对自变量分类讨论,因为不同的自变量可能对应不同的映射关系。鲁棒性主要要求代码能考虑到各种输入,能发现违法的输入并给出恰当处理,而不是直接处理违法输入得到不合逻辑的结果甚至崩溃。
1. 代码的完整性
1.1 数值的整数次方
题目:实现函数 double Power(double base, int exponent),用于求 base 的 exponent 次方。不需要考虑大数问题。
1. 快速求幂
我们先实现主要功能:exponent 大于 0 的情况。
double Power_core_iterate(double base, int exponent) {
double temp = base, res = 1;
while (exponent != 0) {
if ((exponent&1) == 1)
res *= temp;
exponent = exponent >> 1;
temp *= temp;
}
return res;
}
double Power_core_recurse(double base, int exponent) {
if (exponent == 0 )
return 1;
double res = Power_core_recurse(base, exponent >> 1);
if ((exponent&1) == 0)
return res * res;
else
return res * res * base;
}
这两个函数用于快速求幂。其中用到了一些有利于快速计算的方法:
- 用与 1 的与运算代替对 2 取余,如第 4、15 行。只有当被取余的数是 2 的 n 次方时才能用与比它小 1 的数的与运算代替取余,即 x % 2 n = x & ( 2 n − 1 ) x \% 2^n = x \& (2^n - 1) x%2n=x&(2n−1)。因为与运算优先级较低,所以最好用小括号括住。
- 用右移一位代替除以 2,如第 6、14 行。注意右移不会改变操作数。
2. 代码完整性
bool g_invalidInput = false;
double Power(double base, int exponent) {
g_invalidInput = false;
if (abs(base - 0.0) < 1e-7) {
if (exponent == 0)
return 0;
if (exponent < 0) {
g_invalidInput = true;
return 0;
}
}
if (exponent >= 0)
return Power_core_iterate(base, exponent);
else
return 1.0 / Power_core_iterate(base, -1 * exponent);
}
完整性:
- 指数与 0 的大小关系。指数大于等于 0 时由快速求幂的函数处理,如第 13 行。指数小于 0 时对指数取反,调用快速求幂的函数,返回快速求幂函数返回值的倒数,如第 15 行。
- 底数为 0 的情况。如果指数为负,会出现除 0 的情况,这时返回 0 并使用全局变量标记输入违法。因为 Power 函数可能被多次调用,所以每次调用时要先给 g_invalidIput 赋值 false,如第 3 行。如果指数为 0, 0 0 0^0 00 没有意义,我们返回的是 0。
1.2 打印从 1 到最大的 n 位数
1. 模拟数字加法
使用元素为字符 ‘0’ 到 ‘9’ 的字符串模拟加法:
class Solution {
public:
vector<int> printNumbers(int n) {
if (n < 1)
return{};
char* arr = new char[n + 1];
char* end = new char[n + 1];
memset(arr, '0', n);
memset(end, '9', n);
vector<int> res;
while (true) {
add_one(arr, n, res);
if (memcmp(arr, end, n) == 0)
break;
}
delete[] arr;
delete[] end;
return res;
}
private:
void add_one(char* arr, int n, vector<int>& res) {
// 1.不进位。
if (arr[n - 1] < '9')
arr[n - 1] += 1;
// 2.进位。
else {
arr[n - 1] = '0';
for (int i = n - 2; i >= 0; i--) {
if (arr[i] == '9')
arr[i] = '0';
else {
arr[i] += 1;
break;
}
}
}
// 3.转整数。
res.push_back(atoi(arr));
}
};
大数问题。力扣使用 vector<int> 存放结果,也就是说力扣的测试数据 n 很小,最大的 n 位数不会超过 int 的表示范围。而我们的算法使用字符模拟数字加法,稍加修改就可以处理大数。 注意,存放 n 个字符需要申请 n+1 字节的内存,因为需要最后一个字节存储字符串结束标志 ‘\0’。
结束条件。每输出一个数字就使用 memcmp 函数查看是否应该结束。这种方法调用 memcmp 函数太频繁且还需要 end 这样的额外空间。其实可以利用进位来发现什么时候应该结束程序:申请 n+2 个字节的内存,进位使 arr[0]=1 时结束程序。
2. 全排列
全排列可以实现题目的要求,这很容易理解,但是怎么实现呢?下面利用循环实现 n = 3 时的输出。
void print() {
for (int i = 0; i < 10; i++)
for (int j = 0; j < 10; j++)
for (int k = 0; k < 10; k++)
cout << i << j << k << ' ';
cout << endl;
}
使用三层循环可以输出 0 到 999(上面的程序没有去掉数字前面的 0)。当 n 比较大时,使用这样的循环不太现实,但我们可以利用递归算法:
class Solution {
public:
vector<int> printNumbers(int n) {
if (n < 1)
return{};
vector<int> res;
permutation(n, res);
return vector<int>(res.begin() + 1, res.end());
}
private:
void permutation(int n, vector<int>& res, string str = string{}, int index = 0) {
if (index == n) {
res.push_back(atoi(str.c_str()));
return;
}
for (int i = 0; i < 10; i++) {
str.push_back('0' + i);
permutation(n, res, str, index + 1);
str.pop_back();
}
}
};
大数问题。该全排列的算法稍加修改也能处理大数。
默认参数。函数 permutation 的 str 参数使用了默认参数。使用 string str = string{} 和 string str = string() 都可以。但定义成引用类型时,在力扣中提交会报错。
1.3 删除链表的结点
完整性:要考虑到待删除的结点是头结点、中间结点还是尾结点。
1. 快速删除结点
题目:给定单向链表的头指针和一个结点指针 x。设计时间复杂度为 O(1) 的算法删除结点指针 x 指向的结点。
分析:一般情况下,删除节点需要分 2 种情况:删除头结点、删除非头结点。删除头结点的时间复杂度是 O(1),这很容易理解。删除非头结点需要找到待删除结点的前置结点,查找前置结点的时间复杂度是 O(n),所以不能有查找。这时可以考虑删除 x 后面的结点,删除前把它的数据域拷贝给 x。但如果待删除的结点是尾结点,删除尾结点后需要把它前置结点的指针域置空,这就必须通过遍历找到尾结点的前置结点。
综上所述,这种利用结点指针删除结点的算法要考虑 3 种情况:删除头节点、删除非头非尾节点、删除尾结点。删除尾结点的时间是 n,删除剩余 n-1 个节点的总时间是 n-1,所以时间复杂度是
O
(
(
n
−
1
)
+
n
n
)
O(\frac{(n-1) + n}{n})
O(n(n−1)+n),即 O(1)。
void delete_node(node*& list, node* x) {
// 1. 指针为空。
if (list == nullptr || x == nullptr)
return;
// 2.删除表头。
if (x == list) {
list = list->next;
delete x;
return;
}
// 3.删除非头非尾结点。
if (x->next != nullptr) {
x->data = x->next->data;
node* p = x->next;
x->next = p->next;
delete p;
return;
}
// 4.删除尾结点。
if (x->next == nullptr) {
node* p = list;
while (p->next != x && p != nullptr)
p = p->next;
if (p->next == x) { // 这一步为了检查x指向的是不是list的尾结点。
delete x;
p->next = nullptr;
}
}
}
注意每一种情况的代码执行完,要使用 return 结束函数,否则可能会继续执行。
2. 删除链表中重复的结点
删除单向链表中重复的结点,重复的结点一个都不留。如删除 {1,1,1,2,2,3,4,4,5,5,6,7,7} 的重复元素后剩下 {3,6}。
void delete_repeat(node*& list) {
if (list == nullptr || list->next == nullptr)
return;
// 1.找到一个不重复的元素作为头结点。
while (list->data == list->next->data) {
node* toBeDelete = list;
while (list->data == toBeDelete->data) {
list = list->next;
if (list == nullptr)
break;
}
// 从toBeDelete指向的结点到list的前置结点都要删除。
while (toBeDelete != list) {
node* nextToBeDelete = toBeDelete->next;
delete toBeDelete;
toBeDelete = nextToBeDelete;
}
if (list == nullptr)
return;
}
// 2.头结点不会被删除的情况。
node* pre = list;
// 在pre后面找重复的结点,至少有两个结点才可能有重复。
while (pre->next != nullptr && pre->next->next != nullptr) {
if (pre->next->data == pre->next->next->data) {
int repeat = pre->next->data;
node* toBeDelete = pre->next;
while (toBeDelete->data == repeat) {
pre->next = toBeDelete->next;
delete toBeDelete;
toBeDelete = pre->next;
if (toBeDelete == nullptr)
break;
}
}
else
pre = pre->next;
}
}
注意,通过指针使用成员引用变量符时,要确保指针非空。如第 9 行确保第 7 行的 list 非空,第 18 行确保第 5 行的 list 非空,第 32 行确保第 28 行的 toBeDelete 非空。
既然删除头节点需要单独考虑,那我们可以不删除头节点。为了不删除头结点,我们可以给链表加个额外的头结点,然后就不用考虑删除头结点的事了。
void delete_repeat(node*& list) {
// 1.至少有两个结点才可能出现重复。
if (list == nullptr || list->next == nullptr)
return;
// 2.在list前加个头结点。
node* head = new node(0);
head->next = list;
// 3.从头结点后开始查找重复元素。
node* pre = head;
while (pre->next->next != nullptr) {
if (pre->next->data == pre->next->next->data) {
int repeat = pre->next->data;
node* toBeDelete = pre->next;
while (toBeDelete->data == repeat) {
pre->next = toBeDelete->next;
delete toBeDelete;
toBeDelete = pre->next;
if (toBeDelete == nullptr)
break;
}
}
else
pre = pre->next;
if (pre->next == nullptr)
break;
}
list = head->next;
head->next = nullptr;
delete head;
}
第 7 行给链表加个额外的头结点,然后用 pre 指向头结点,接下来只需考虑删除 pre 后面结点的情况。第 27 行把链表的额外头节点去掉。
1.4 正则表达式匹配
题目:请实现一个函数用来匹配包含 ‘.’ 和 ‘*’ 的正则表达式。如字符串 “aaa” 与模式 “a.a” 和 “ab*ac*a” 匹配,但与 “aa.a” 和 “ab*a” 不匹配。需要注意的匹配:
- “ab” 与 “abc*d*” 匹配,与 “abcc*”、“abc*d” 不匹配。
- “.*” 可以匹配 “aa” 和 “ab”,即它能与任意个任意字符匹配。
1. 递归
bool check_pattern_residua(const string& pattern, int index) {
for (int i = pattern.size() - 1; i >= index; i -= 2)
if (pattern[i] != '*')
return false;
return true;
}
bool regex(const string& str, const string& pattern, const int str_i, const int pat_i) {
// 1.str和pattern都为空。
if (str_i == str.size() && pat_i == pattern.size())
return true;
// 2.pattern为空。
if (pat_i == pattern.size())
return false;
// 3.str为空。
if (str_i == str.size())
return check_pattern_residua(pattern, pat_i);
// 4.两者都不为空。
bool current = str[str_i] == pattern[pat_i] || pattern[pat_i] == '.';
// 如果到达pattern末尾:str也要到达末尾且能匹配才返回true,否则返回false。
if (pat_i == pattern.size() - 1)
if (current == true && str_i == str.size() - 1)
return true;
else
return false;
// 如果没有到达pattern末尾:看pattern的下一个字符是不是星号。
if (pattern[pat_i + 1] == '*') {
if (current == false)
return regex(str, pattern, str_i, pat_i + 2);
return regex(str, pattern, str_i, pat_i + 2) || regex(str, pattern, str_i + 1, pat_i + 2) || regex(str, pattern, str_i + 1, pat_i);
}
else {
if (current == false)
return false;
return regex(str, pattern, str_i + 1, pat_i + 1);
}
}
bool isMatch(string s, string p) {
if (s.size() == 0 || p.size() == 0)
return false;
return regex(s, p, 0, 0);
}
主要匹配过程有 regex 函数实现,参数 str_i 和 pat_i 用于指定当前匹配字符串 str 和模式 pattern 的哪个字符。函数的前三部分是递归结束的条件:
- 第一部分:字符串 str 和模式 pattern 完全匹配,返回 true。
- 第二部分:模式已经匹配完,字符串还有剩余,返回 false。
- 第三部分:字符串匹配完,模式还有剩余。因为模式的星号可以代表 0,所以模式的剩余部分必须有偶数个字符且第偶数个字符必须是星号。比如模式的剩余部分是 “a*b*c*” 这样的,则匹配成功。函数 check_pattern_residua 就是用于检测 pattern 是否满足这样的条件。
函数 regex 的第四部分是在 str 和 pattern 的下标都不出界的前提下进行的。
第 19 行。先检测当前字符是否匹配。
第 21 至 25 行。因为从第 27 行开始,考虑 pattern 下一个字符是不是星号需要用到 pat_i + 1,为了确保这时的 pat_i +1 不出界,先在第 21 行确保 pat_i 不是 pattern 的末尾。
第 28 至 30 行。如果下一个字符是星号,如果当前字符不匹配,就去掉模式的前两个字符。比如 “ab” 和 “c*abd*”,因为 ‘a’ 与 ‘c’ 不匹配,所以去掉 ‘c*’,继续比较 “ab” 与 “abd*”。如果当前字符匹配,有要分 3 种情况,以 “aab” 与 “a*b” 为例:
- 星号代表 0 则继续比较 “aab” 与 “b",对应函数 regex(str, pattern, str_i, pat_i + 2)。这和不匹配时的处理一样。
- 星号代表 1 则继续比较 “ab” 和 “b”,对应函数 regex(str, pattern, str_i +1, pat_i + 2)。
- 星号代表 ‘多’ 则继续比较 “ab” 和 “a*b”,对应函数 regex(str, pattern, str_i +1, pat_i)。
第 33 至 35 行。pattern 的下一个字符不是星号的情况。
2. 简洁的递归
bool isMatch(string s, string p) {
if (p.size() <= 0)
return s.size() <= 0;
// p.size() > 0, s.size() >= 0.
bool match = s.size() > 0 && (p[0] == s[0] || p[0] == '.');
if (p.size() > 1 && p[1] == '*')
return isMatch(s, p.substr(2)) || (match && isMatch(s.substr(1), p));
else
return match && isMatch(s.substr(1), p.substr(1));
}
3. 动态规划
bool isMatch(string s, string p) {
vector<vector<bool>> dp(s.size() + 1, vector<bool>(p.size() + 1));
dp[s.size()][p.size()] = true;
for (int i = s.size(); i >= 0; i--)
for (int j = p.size() - 1; j >= 0; j--) {
bool match = i < s.size() && (p[j] == s[i] || p[j] == '.');
if (j + 1 < p.size() && p[j + 1] == '*')
dp[i][j] = dp[i][j + 2] || (match && dp[i + 1][j]);
else
dp[i][j] = match && dp[i + 1][j + 1];
}
return dp[0][0];
}
1.5 表示数值的字符串
字符串 (+/-)(e/E)[+/-]1 不是数字,其中小括号中的字符有且只有 1 个,中括号内的字符最多有 1 个,‘1’ 表示 1 或多个数字。
1. 功能正确但代码量太大的方法
// 从str[start_index]开始检查是否有0或更多个数字。
bool check_digit(string& str, int& start_index, bool flag, string model) {
if (flag == false)
return false;
if (model.compare("zero_or_more") == 0) {
while (start_index < str.size() && str[start_index] >= '0' && str[start_index] <= '9')
start_index += 1;
return true;
}
else if (model.compare("one_or_more") == 0) {
if (start_index >= str.size())
return false;
if (str[start_index] < '0' || str[start_index] > '9')
return false;
while (start_index < str.size() && str[start_index] >= '0' && str[start_index] <= '9')
start_index += 1;
return true;
}
else {
logic_error ex("model can only be 'zero_or_more' or 'one_or_more'!");
throw exception(ex);
}
}
// 检查str[start_index]是不是小数点。
bool check_point(string& str, int& start_index, bool flag, string model = "zero_or_one") {
if (flag == false || start_index >= str.size())
return false;
if (model.compare("one") == 0) {
if (str[start_index] == '.') {
start_index += 1;
return true;
}
else
return false;
}
else if (model.compare("zero_or_one") == 0) {
if (str[start_index] == '.')
start_index += 1;
return true;
}
else {
logic_error ex("model can only be 'zero_or_one' or 'one'!");
throw exception(ex);
}
}
// 从str[start_index]开始检查是否有(e/E)[+/-]。
bool check_e(string& str, int& start_index, bool flag) {
if (flag == false || start_index >= str.size())
return false;
if (str[start_index] == 'e' || str[start_index] == 'E') {
start_index += 1;
if (start_index < str.size() - 1 && (str[start_index] == '+' || str[start_index] == '-'))
start_index += 1;
return true;
}
return false;
}
// 检查str[start_index]是否越界。
bool check_end(string& str, int& start_index, bool flag) {
if (flag == false)
return false;
if (start_index == str.size())
return true;
else
return false;
}
// 去掉字符串前后的空格。
void drop_space(string& str) {
std::string::iterator end_pos = std::remove(str.begin(), str.end(), ' ');
str.erase(end_pos, str.end());
}
上面是工具函数,用于验证字符串的每一段是否满足要求。下面是判断字符串是不是数值的主要函数:
bool isNumeric(string& str) {
if (str.size() == 0)
return false;
drop_space(str);
if (str[0] == '.') {
int ai = 1;
return check_end(str, ai, check_digit(str, ai, true, "one_or_more"));
}
int ai = 0, bi = 0, ci = 0, di = 0;
if (str[0] == '+' || str[0] == '-')
ai = bi = ci = di = 1;
bool a = check_end(str, ai, check_digit(str, ai, check_point(str, ai, check_digit(str, ai, true, "one_or_more"), "one"), "zero_or_more")); // [+/-]1.[1]
bool b = check_end(str, bi, check_digit(str, bi, check_point(str, bi, true, "zero_or_one"), "one_or_more")); // [+/-][.]1
bool c = check_end(str, ci, check_digit(str, ci, check_e(str, ci, check_digit(str, ci, true, "one_or_more")), "one_or_more")); // [+/-]1(E/e)[+/-]1
bool d = check_end(str, di, check_digit(str, di, check_e(str, di, check_digit(str, di, check_point(str, di, check_digit(str, di, true, "one_or_more"), "one"), "zero_or_more")), "one_or_more")); // 1.[1][E/e]1
return a || b || c || d;
}
1.6 调整数组顺序使奇数位于偶数前面
题目:输入一个整数数组,实现算法让奇数在数组的前部分,偶数在数组的后部分。
1. O(n^2)的算法
类似于插入排序,从数组头部开始,每发现一个偶数 x,就把 x 后面的元素向前移动一位,然后把 x 放在末尾。
2. O(n) 的算法
使用首尾指针分别指向数组的首尾元素,首指针向后移动、尾指针向前移动直到它们分别找到双数、单数,交换它们的值。当首位指针指向同一个元素时函数结束。
template<size_t N>
void odd_first(int(&arr)[N]) {
int start = 0, end = N - 1;
while (start < end) {
while (arr[start] % 2 != 0) // 应该检查下标是否越界!
++start;
while (arr[end] % 2 == 0)
--end;
if (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
++start;
--end;
}
}
}
3. 可扩展的 O(n) 算法
上面的代码根据单数 / 双数调整元素的位置。如果要根据正数 / 负数、质数 / 合数等调整元素位置,只需要修改第 5、7 行的判断标准。我们可以利用一个函数实现判断标准,通过参数把这个函数传递给 odd_first 函数:
bool is_odd(int x) {
if (x % 2 == 1)
return true;
return false;
}
bool is_positive(int x) {
if (x > 0)
return true;
return false;
}
template<size_t N>
void odd_first(int(&arr)[N], bool(*f)(int)) {
int start = 0, end = N - 1;
while (start < end) {
while (f(arr[start]) == true)
++start;
while (f(arr[end]) == false)
--end;
if (start < end) {
int temp = arr[start];
arr[start] = arr[end];
arr[end] = temp;
++start;
--end;
}
}
}
函数 odd_first 的第 3 个参数是函数指针,我调用 odd_first 时,只需要给它的第 3 个函数传递判断函数,就可以按照对应的标准重拍元素。
2. 代码的鲁棒性
2.1 链表中倒数第 k 个结点
题目:找出链表中倒数第 k 个结点,其中链表尾部的元素是倒数第 1 个结点。
分析:先遍历链表得到结点个数,再找出第 k 个结点,这种方法需要遍历两次。使用双指针法只需遍历一次。
鲁棒性:链表是否为空、k 是否小于等于 0、k 是否大于链表长度。
node* fac(node* list, const int k) {
if (list == nullptr || k <= 0)
return nullptr;
node *first = list, *second = list;
for (int i = 0; i < k - 1; i++) {
if (first->next != nullptr)
first = first->next;
else
return nullptr;
}
while (first->next != nullptr) {
first = first->next;
second = second->next;
}
return second;
}
第 3 行在链表为空或 k 小于等于 0 时返回空指针,第 9 行在 k 大于链表长度时返回空指针。
1. 相关题目
题目:找出链表的中间结点,如果链表的结点数是偶数,返回两个中间结点的任意一个。
分析:可以使用快慢指针法。两个指针从表头开始同时遍历,按快指针走两步慢指针走一步的速度遍历,直到快指针所指元素的指针域为空。
2.2 链表中环的入口结点
判断单向链表是否有环。如果没有环,返回空指针;如果有环,找出环的入口结点。
ListNode* EntryNodeOfLoop(ListNode* pHead) {
if (pHead == nullptr)
return nullptr;
ListNode *quick = pHead, *slow = pHead;
// 1. 确认是否有环。
while (true) { // 不能使用这个条件:quick->next != nullptr && quick != slow。
if (quick->next == nullptr)
return nullptr;
if (quick->next->next == nullptr)
return nullptr;
quick = quick->next->next;
slow = slow->next;
if (quick == slow)
break;
}
// 2. 获得环的长度。
quick = quick->next;
int len = 1;
while (quick != slow) {
len += 1;
quick = quick->next;
}
// 3. 找入口结点:先走指针。
quick = pHead, slow = pHead;
for (int i = 0; i < len; i++)
quick = quick->next;
while (quick != slow) {
quick = quick->next;
slow = slow->next;
}
return quick;
}
上面的代码分为三步:
- 确认链表是否有环:使用快慢指针法。快指针每次走两步,慢指针每次走一步,如第 11、12 行。因为 quick 每次走两步,所以要判断 quick->next 和 quick->next->next 是否为空,两者有一个为空就没有环。如果快慢指针相同则有环。注意不能在第 6 行设置循环结束的条件,否则循环体内的 return 语句和 break 语句就不会起作用了!
- 获取环的长度,即环上的结点数。如果有环,第一步结束时,快慢指针指向同一个元素。接下来只需让其中一个指针每次走一步,再次相遇时,它走的步数就是环的长度 len。
- 找环的入口结点:先走指针法。指针 p1 先从头节点走 len 步,然后指针 p2 从头节点开始,两个指针同时走,直到相遇。这时 p2 走到环的入口结点,p1 把环走了一遍又回到环的入口结点。
2.3 反转链表
分析:反转前的链表头结点是反转后链表的尾结点,可以用个指针 pHead2tail 一直指向该结点。反转操作与删除操作类似,不同的是反转把 pHead2tail 的下一个结点放在链表头部,而不是删除。这样操作直到 pHead2tail 后面没有结点。
2.4 合并两个排序的链表
题目:输入两个递增排序的链表,合并这两个链表使新链表的元素依然是递增的。
1. 非递归算法
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
// 1.空链表。
if (l1 == nullptr)
return l2;
if (l2 == nullptr)
return l1;
// 2.确定表头,把链表lb合并到la。
ListNode *la = nullptr, *lb = nullptr;
if (l1->val <= l2->val) {
la = l1;
lb = l2;
}
else {
la = l2;
lb = l1;
}
// 3.合并。
ListNode *pa = la, *pb = lb;
while (pa->next != nullptr && pb != nullptr) {
if (pa->next->val > pb->val) {
ListNode* temp = pb;
pb = pb->next;
temp->next = pa->next;
pa->next = temp;
}
pa = pa->next;
}
if (pa->next == nullptr)
pa->next = pb;
return la;
}
2. 递归算法
void merge(ListNode* l1, ListNode* l2, ListNode*& res) {
if (l1 == nullptr && l2 == nullptr)
return;
if (l1 == nullptr) {
res = l2;
merge(l1, l2->next, res->next);
}
else if (l2 == nullptr) {
res = l1;
merge(l1->next, l2, res->next);
}
else {
if (l1->val <= l2->val) {
res = l1;
merge(l1->next, l2, res->next);
}
else {
res = l2;
merge(l1, l2->next, res->next);
}
}
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* res = nullptr;
merge(l1, l2, res);
return res;
}
3. 更简洁的递归算法
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr)
return l2;
if (l2 == nullptr)
return l1;
ListNode* node = nullptr;
if (l1->val <= l2->val) {
node = l1;
node->next = mergeTwoLists(l1->next, l2);
}
else {
node = l2;
node->next = mergeTwoLists(l1, l2->next);
}
return node;
}
2.5 树的子结构
题目:输入两棵二叉树 A 和 B,判断 B 是否是 A 的子结构。
1. 错误的思路
我最开始的想法是,使用前中后序遍历,如果 B 是 A 的子结构则 B 的遍历结果是 A 的字串。这是不对的,比如:
以先序遍历为例,B 的遍历结果是 {2,3},A 的遍历结果是 {1,2,3},但 B 不是 A 的子结构。
2. 递归
bool equal(TreeNode* node_a, TreeNode* node_b) {
if (abs(node_a->val - node_b->val) < 1e-7)
return true;
return false;
}
bool continue_check(TreeNode* whole, TreeNode* sub) {
if (sub == nullptr)
return true;
if (whole == nullptr) // 这个if和上个if顺序不能颠倒。
return false;
if (equal(whole, sub) == false)
return false;
return continue_check(whole->left, sub->left) && continue_check(whole->right, sub->right);
}
bool isSubStructure(TreeNode* A, TreeNode* B) {
if (A == nullptr || B == nullptr)
return false;
bool res = continue_check(A, B);
if (res == false)
res = isSubStructure(A->left, B);
if (res == false)
res = isSubStructure(A->right, B);
return res;
}