高质量的代码

欢迎访问我的博客首页


  高质量的代码要具有完整性和鲁棒性。类似于数学上的分类讨论,完整性要求代码能完整地考虑到每一类情况。比如分段函数就要对自变量分类讨论,因为不同的自变量可能对应不同的映射关系。鲁棒性主要要求代码能考虑到各种输入,能发现违法的输入并给出恰当处理,而不是直接处理违法输入得到不合逻辑的结果甚至崩溃。

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. 用与 1 的与运算代替对 2 取余,如第 4、15 行。只有当被取余的数是 2 的 n 次方时才能用与比它小 1 的数的与运算代替取余,即 x % 2 n = x & ( 2 n − 1 ) x \% 2^n = x \& (2^n - 1) x%2n=x&(2n1)。因为与运算优先级较低,所以最好用小括号括住。
  2. 用右移一位代替除以 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);
}

  完整性:

  1. 指数与 0 的大小关系。指数大于等于 0 时由快速求幂的函数处理,如第 13 行。指数小于 0 时对指数取反,调用快速求幂的函数,返回快速求幂函数返回值的倒数,如第 15 行。
  2. 底数为 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(n1)+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” 不匹配。需要注意的匹配:

  1. “ab” 与 “abc*d*” 匹配,与 “abcc*”、“abc*d” 不匹配。
  2. “.*” 可以匹配 “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 的哪个字符。函数的前三部分是递归结束的条件:

  1. 第一部分:字符串 str 和模式 pattern 完全匹配,返回 true。
  2. 第二部分:模式已经匹配完,字符串还有剩余,返回 false。
  3. 第三部分:字符串匹配完,模式还有剩余。因为模式的星号可以代表 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” 为例:

  1. 星号代表 0 则继续比较 “aab” 与 “b",对应函数 regex(str, pattern, str_i, pat_i + 2)。这和不匹配时的处理一样。
  2. 星号代表 1 则继续比较 “ab” 和 “b”,对应函数 regex(str, pattern, str_i +1, pat_i + 2)。
  3. 星号代表 ‘多’ 则继续比较 “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;
}

  上面的代码分为三步:

  1. 确认链表是否有环:使用快慢指针法。快指针每次走两步,慢指针每次走一步,如第 11、12 行。因为 quick 每次走两步,所以要判断 quick->next 和 quick->next->next 是否为空,两者有一个为空就没有环。如果快慢指针相同则有环。注意不能在第 6 行设置循环结束的条件,否则循环体内的 return 语句和 break 语句就不会起作用了!
  2. 获取环的长度,即环上的结点数。如果有环,第一步结束时,快慢指针指向同一个元素。接下来只需让其中一个指针每次走一步,再次相遇时,它走的步数就是环的长度 len。
  3. 找环的入口结点:先走指针法。指针 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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值