面试需要的基础知识

欢迎访问我的博客首页


1. 赋值运算符函数


  题目:下面是 CMyString 的声明,请为之添加赋值运算符函数。

class CMyString {
public:
	CMyString(char* pData = nullptr);
	CMyString(const CMyString& str);
	~CMyString();
private:
	char* m_pData;
};

  赋值运算符函数应该满足以下四点:

  1. 有引用类型的返回值。有返回值才能连续赋值 a = b = c;使用引用类型避免复制构造。
  2. 形参类型使用常量引用。形参不需要改变,理应为常量类型;使用引用类型避免复制构造。
  3. 判断传入的实参是否是当前的实例 *this。如果是,则不需要拷贝。
  4. 拷贝的操作应该是释放原有的堆内存,在新申请的堆内存上拷贝一个副本。

  因为申请内存可能会出现异常,比如内存不足,所以执行上面第 4 点时需要注意。下面就是错误的示范:先释放了内存,再申请内存。如果申请内存失败,不仅不能实现拷贝,还丢失了原本的数据。这不符合 “异常安全”原则。

CMyString& CMyString::operator=(const CMyString& str) {
	if (&str != this) {
		delete[] m_pData;
		m_pData = new char[strlen(str.m_pData) + 1];
		strcpy(m_pData, str.m_pData);
	}
	return *this;
}

  下面两种方法是可行的。第一种方法先申请内存,如果失败直接结束。第二种方法利用拷贝构造函数复制。

CMyString& CMyString::operator=(const CMyString& str) {
	if (&str != this) {
		char* temp = new char[strlen(str.m_pData) + 1];
		strcpy(temp, str.m_pData);
		delete[] m_pData;
		m_pData = temp;
	}
	return *this;
}

CMyString& CMyString::operator=(const CMyString& str) {
	if (&str != this) {
		CMyString temp(str);
		swap(m_pData, temp.m_pData);
	}
	return *this;
}

2. 实现单例模式(未)


3. 数组中重复的数字


3.1 可以修改数组


  题目:在一个长度为 n 的数组中所有的数字都在 0~n-1 的范围内。其中至少有 1 个数字出现两次或两次以上,请找出任意一个这样的重复数字。输入 {2, 3, 1, 0, 2, 5, 3};输出 2,或者输出 3。
  方法一:排序是最容易想到的方法。最快的排序时间复杂度是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),空间复杂度是 O(1)。
  方法二:哈希表查找速度快,很适合判断重复。时间复杂度是 O(n),空间复杂度是 O(n)。
  方法三:本题的数组长度为 n,整型元素大小范围在 [0, n-1]。上面的两种方法都没有用到这个信息,否则复杂度能进一步降低。这个方法就是:遍历数组,假设当前下标是 i。如果 nums[i] = i 则处理下一个元素 nums[i+1]。否则,先比较 nums[i] 与 nums[nums[i]] 是否相等,如果相等就找到重复的元素了;如果不相等就交换 nums[i] 与 nums[nums[i]]。像这样先比较再交换直到 nums[i] = i,然后继续处理 nums[i+1]。

class Solution {
public:
	int findRepeatNumber(vector<int>& nums) {
		if (nums.size() == 0)
			return -1;
		for (int i = 0; i < nums.size(); i++) {
			while(nums[i] != i) {
				if (nums[i] == nums[nums[i]])
					return nums[i];
				swap(nums[i], nums[nums[i]]);
			}
		}
		return -1;
	}
};

  上面的代码中虽然有两层循环,但每次循环都会令 nums[nums[i]] = nums[i],即每次循环都能确定 nums[i] 的位置。所以总共的循环次数是 n。时间复杂度是 O(n),空间复杂度是 O(1)。

3.2 不能修改数组(未)


  题目二:一个长度为 n+1 的数组中的所有数字都在 1~n 的范围内,所以至少有一个数是重复的。请不改变数组找出任意一个重复的数。输入 {2, 3, 5, 4, 3, 2, 6, 7};输出 2,或者输出 3。
  方法一:使用长度为 n+1 的额外数组 extra,全部初始化为 0。然后把原数组 nums 中的元素 nums[i] 放在 extra[nums[i]]。放之前检查 nums[i] 和 extra[nums[i]] 是否相等,如果相等就找到重复的元素了。
  方法二:一个长度为 n+1 的数组中的所有数字都在 1~n 的范围内。上面的方法没有利于这个信息。

template<size_t N>
int count(const int(&arr)[N], const int a, const int b) {
	int res = 0;
	for (auto x : arr)
		if (x >= a && x <= b)
			res++;
	return res;
}

template<size_t N>
int fac(const int(&arr)[N]) {
	if (N == 0) return -1;

	int start = 1, end = N - 1;
	while (start <= end) {
		if (start == end)
			if (count(arr, start, end) > 1)
				return start;
			else
				return -1;

		int middle = (start + end) / 2;
		int lcount = count(arr, start, middle);
		int rcount = count(arr, middle + 1, end);
		if (middle - start + 1 < lcount) {
			end = middle;
			continue;
		}
		if (end - middle < rcount)
			start = middle + 1;
	}
}

  时间复杂度是 O(nlogn),空间复杂度是 O(1)。

3.3 找出所有重复的元素(未)


  题目来自《LeetCode 442.数组中重复的数据》。

4. 二维数组中的查找

  题目:二维数组每一行的元素从左向右递增,每一列的元素从上向下递增。查找是否存在某个数。

1289
24912
471013
681115

  方法一:遍历二维数组。时间复杂度是 O(mn),空间复杂度是 O(1)。其中 m、n 是二维数组的两个纬度大小。
  方法二:利于二维数组的有序性,可以实现时间复杂度是 O(m+n),空间复杂度是 O(1)的算法。关键是要从右上角开始查找。

class Solution {
public:
	bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
		if (matrix.size() == 0)
			return false;
			
		int h = matrix.size(), w = matrix[0].size();
		int x = w - 1, y = 0;
		while (x >= 0 && y < h) {
			if (matrix[y][x] == target)
				return true;
			else if (matrix[y][x] > target)
				x--;
			else if (matrix[y][x] < target)
				y++;
		}
		return false;
	}
};

5. 替换空格


5.1 常量字符串


  常量字符串存放在文字常量区。指向相同常量字符串的指针指向的地址相同;用相同常量字符串初始化的数组存放在不同地址。

char* p1 = "hello world.";
char* p2 = "hello world.";
if (p1 == p2) cout << "yes" << endl; else cout << "no" << endl;
char arr1[] = "hello world.";
char arr2[] = "hello world.";
if (arr1 == arr2) cout << "yes" << endl; else cout << "no" << endl;

  p1 和 p2 指向相同的常量字符串,所以它们指向的地址相同。arr1 和 arr2 用相同的常量字符串初始化,但它们是两个字符串。

5.2 替换空格


  题目:把字符数组中的每个空格替换成三个字符 “%20”。
  方法一:创建一个新数组,用原数组的非空格字符向新数组赋值,遇到空格用 “%20” 赋值。时间复杂度是 O(n),空间复杂度是 O(n)。
  方法二:每遇到一个空格,把后面的字符后移 2 位,然后在空格和空出的两位上赋值 “%20”。第一个空格后面的字符向后移 1 次;第二个空格后的字符还要再移 1 次,共移 2 次;第 3 个空格后的字符共移 3 次,以此类推。时间复杂度是 O(n^2)。
  方法三:类似方法二,但从后向前地把数组中的元素后移。每个字符只移一次:先统计空格的个数。然后第 n 个空格后的字符后移 2n,第 n 个空格到第 n-1 个空格间的字符后移 2(n-1),…,第 2 个空格到第 1 个空格间的字符后移 2。使用两个指针更容易实现这样的过程。时间复杂度O(n)。

class Solution {
public:
	string replaceSpace(string s) {
		if (s.size() == 0)
			return s;
			
		int num_space = 0;
		for (auto x : s)
			if (x == ' ')
				num_space++;
				
		int p1 = s.size() - 1, p2 = s.size() + 2 * num_space - 1;
		// s.reserve(p2 + 1);
		s.resize(p2 + 1);
		while(p1 >= 0) {
			if (s[p1] != ' ') {
				s[p2] = s[p1];
				p2--;
			}
			else {
				s[p2] = '0';
				s[p2 - 1] = '2';
				s[p2 - 2] = '%';
				p2-=3;
			}
			p1--;
		}
		return s;
	}
};

  假如字符串 s= “abcde”。调用 s.reserve(10) 后 s.size() 仍然是 5。这时令 s[5] = ‘f’,因为 s.size() = 5,所以输出仍然是 “abcde”。

5.3 合并两个有序数组


  题目来自《LeetCode 88.合并两个有序数组》。
  相关题目:把两个有序数组合并到第一个数组中,同时保证合并后的数组有序。第一个数组有足够的空间存放合并后的数组。

class Solution {
public:
	void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
		//if (m < 1 || n < 1)
		//	return;

		int p1 = m - 1, p2 = n - 1, p3 = m + n - 1;
		// nums1.resize(p3 + 1);
		while (p1 >= 0 && p2 >= 0) {
			if (nums1[p1] > nums2[p2]) {
				nums1[p3] = nums1[p1];
				p1--;
			}
			else {
				nums1[p3] = nums2[p2];
				p2--;
			}
			p3--;
		}
		while (p2 >= 0)
			nums1[p3--] = nums2[p2--];
	}
};

  nums1 的 size 已经不小于 m+n,不需要 resize。 nums1 和 nums1 是可以为空的,即 m 和 n 可能为 0。

6. 从尾到头打印链表


  下面的递归算法在链表过长时会使函数调用栈溢出,所以使用栈更鲁棒。

class Solution {
public:
	vector<int> reversePrint(ListNode* head) {
		if (head == nullptr)
			return{};

		vector<int> res;
		recurse(head, res);
		return res;
	}
private:
	void recurse(ListNode* head, vector<int>& res) {
		if (head == nullptr)
			return;

		recurse(head->next, res);
		res.push_back(head->val);
	}
};

7. 重建二叉树


  先序遍历序列和中序遍历序列可以确定一颗二叉树,后序遍历序列和中序遍历序列可以确定一颗二叉树,先序遍历序列和后序遍历序列不能确定一颗二叉树。使用遍历的结果序列创建二叉树时,因为只能根据这些序列区分结点,所以这些序列中不能有重复,否则无法这样创建二叉树。

  题目:根据先序序列 {1, 2, 4, 7, 3, 5, 6, 8} 和中序序列 {4, 7, 2, 1, 5, 3, 8, 6},创建这颗二叉树。

遍历

图 1 图 1 1

  图 1 展示了根结点、左子树、右子树在前序、中序、后序遍历序列中的位置。蓝色是根节点,绿色是左子树,红色是右子树。根据这个图,使用递归算法重建二叉树的代码如下:

class Solution {
public:
	TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
		if (preorder.size() == 0 || inorder.size() == 0)
			return nullptr;

		return build(
			preorder, 0, preorder.size() - 1,
			inorder, 0, inorder.size() - 1);
	}
private:
	TreeNode* build(
		vector<int>& preorder, int pre_start, int pre_end,
		vector<int>& inorder, int in_start, int in_end) {
		// 1.创建根结点。
		TreeNode* root = new TreeNode(preorder[pre_start]);
		// 2.在中序遍历序列中找根结点。
		int root_in_inorder;
		for (root_in_inorder = in_start; root_in_inorder <= in_end; root_in_inorder++) {
			if (inorder[root_in_inorder] == preorder[pre_start])
				break;
		}
		if (root_in_inorder > in_end)
			throw new runtime_error("Invalid input!");
		// 3.求左右子树结点个数。
		int left_tree_len = root_in_inorder - in_start;
		int right_tree_len = in_end - root_in_inorder;
		// 4.创建左右子树。
		if (left_tree_len > 0)
			root->left = build(
				preorder, pre_start + 1, pre_start + left_tree_len,
				inorder, in_start, root_in_inorder - 1);
		if (right_tree_len > 0)
			root->right = build(
				preorder, pre_start + left_tree_len + 1, pre_end,
				inorder, root_in_inorder + 1, in_end);
		// 5.返回根结点。
		return root;
	}
};

8. 二叉树的下一个结点


  给定一棵二叉树和其中一个结点 node,请找出中序遍历序列中 node 的下一个结点。这棵树每一个结点都包含指向父结点的指针。

下一个结点
图 2 图 2 2

  既然给出了指向父结点的指针,就是不想让我们使用中序遍历算法,而是直接找下一个结点。中序遍历找 node 的下一个结点有三种情况:

  1. node 有右子树:下一个结点是右子树的最左子结点。如结点 b 的下一个结点是结点 h,结点 a 的下一个结点是结点 f。
  2. node 没有右子树,且 node 是它父结点的左子结点:下一个结点是它的父结点。如结点 d 的下一个结点是结点 b,结点 f 的下一个结点是结点 c。
  3. node 没有右子树,且 node 是它父结点的右子结点:下一个结点是它的第一个身为左子节点的祖宗结点的父结点。即下一个节点是它的祖宗结点 p1 的父结点 p2,这个祖宗节点 p1 是 p2 的左子节点。如结点 i 的下一个结点是结点 a,结点 j 的下一个结点是结点 c。

9. 栈和队列的实现


  相关题目:《剑指 Offer 第二版 9.Ⅰ用两个栈实现队列》、《剑指 Offer 第二版 9.Ⅱ用两个队列实现栈》、《剑指 Offer 第二版 30.包含 min 函数的栈》、《剑指 Offer 第二版 59.Ⅱ队列的最大值》、

9.1 用两个栈实现队列


  st1 的栈顶做队首,st2 的栈顶做队尾。入队列时直接向 st2 栈顶入栈。出队列或获取队首元素时,要确保 st1 不为空,否则把 st2 中的元素全部移到 st1。同样获取队尾元素时要确保 st2 不为空,否则把 st1 中的元素全部移到 st2。

template<typename T>
class queue {
public:
	void push(T elem) {
		st2.push(elem);
	}
	void pop() {
		if (st1.empty() == true)
			a2b(st2, st1);
		st1.pop();
	}
	T front() {
		if (st1.empty() == true)
			a2b(st2, st1);
		return st1.top();
	}
	T back() {
		if (st2.empty() == true)
			a2b(st1, st2);
		return st2.top();
	}
	size_t size() {
		return st1.size() + st2.size();
	}
	bool empty() {
		return st1.empty() && st2.empty();
	}
private:
	void a2b(stack<T>& sta, stack<T>& stb) {
		while(sta.empty() != true) {
			stb.push(sta.top());
			sta.pop();
		}
	}
	stack<T> st1;
	stack<T> st2;
};

9.2 用两个队列实现一个栈


  入栈时进入 qu1 队列。出栈时如果 qu1 中的元素多于 1,就把多余的元素进入 qu2,然后让 qu1 中唯一的元素出队列。最后把 qu2 标记为 qu1 且把 qu1 标记为 qu2 使它们角色互换,如果不角色互换可以让 qu2 中的元素全部移回 qu1。

10. 斐波那契数列


  递归和循环算法。

10.1 递归加法


  使用递归算法计算从 1 累加到 n 的结果。

int add_one2n(int n) {
	if(n <= 0)
		return 0;
	return n + add_one2n(n - 1);
}

10.2 斐波那契数列


// 1. 递归。
int fibonacci_recurse(int n) {
	if (n <= 0)
		return 0;
	if (n == 1)
		return 1;
	return fibonacci_recurse(n - 1) + fibonacci_recurse(n - 2);
}
// 2. 额外空间。
int fibonacci_iterate1(int n) {
	if (n < 0)
		return 0;
	vector<int> vt = { 0,1 };
	for (int i = 2; i <= n; i++)
		vt.push_back(vt[i - 1] + vt[i - 2]);
	return vt[n];
}
// 3. 不使用额外空间。
int fibonacci_iterate2(int n) {
	if (n <= 0)
		return 0;
	if (n == 1)
		return 1;
	int a = 0, b = 1, c;
	for (int i = 2; i <= n; i++) {
		c = a + b;
		a = b;
		b = c;
	}
	return c;
}

10.3 青蛙跳台阶


10.4 矩形覆盖


11. 旋转数组的最小数字


  查找和排序。

11.1 计数排序


  给定若干个人的年龄,年龄范围已知。对年龄排序,其中人数远多于年龄范围。

void countSort(vector<int>& nums, int min = 18, int max = 60) {
	if (nums.size() == 0)
		return;

	int range = max - min + 1;
	int *cache = new int[range];
	memset(cache, 0, sizeof(int) * range);
	// 1.计数。
	for (auto x : nums) {
		if (x < min || x > max) {
			delete[] cache;
			throw new runtime_error("Invalid Input!");
		}
		cache[x - min]++;
	}
	// 2.排序。
	int index_in_nums = 0;
	for (int i = 0; i < range; i++)
		for (int j = 0; j < cache[i]; j++)
			nums[index_in_nums++] = i + min;
	delete[] cache;
}

  这个算法对范围在 18 到 65 的年龄排序,年龄范围 ageRangeLen=48。虽然最后一部分有个两层循环,但循环体执行的次数和人数 N 相等,所以时间复杂度是 O(n)。数组 numOfAge[i] = x 表示年龄为 minAge + i 的人数是 x,数组长度为 ageRangeLen,所以空间复杂度是 O(n)。
  这个算法用 O(n) 的时间复杂度实现排序,关键是创建的 numOfAge 数组。当待排数据的范围较小且数据量较大时适合使用这个算法。如果待排数据的范围较大,数组 numOfAge 的长度就会很长。

11.2 旋转数组的最小数字


  问题:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
  切记旋转数组有以下几种:

  1. 最普通的旋转数组:{4,5,6,1,2,3}。我们称之为 “第一种形式”。
  2. 未旋转的旋转数组:{4,5,6}。这样符合 “把一个数组最开始的 0 个元素搬到数组的末尾”。我们称之为 “第二种形式”。
  3. 首尾相等的旋转数组:{4,5,6,1,2,3,4} 和 {4,4,4,4}。因为原数组单调递增但不是严格单调递增。我们称之为 “第三种形式”。

  思路:因为找最小值,所以我们利用旋转数组的(局部)有序性,使用两个指针 start 和 end 按二分查找的方法逼近最小值。当 start = end 时,它们共同指向的元素就是最小值。
  关键点:numbers[middle] 与 numbers[start] 还是 numbers[end] 相比较。这可以很容易根据三种形式的旋转数组分析一下。
  切记:如果比较时出现相等的情况,很明显丢掉一个不影响最值。如果是 numbers[middle] 与 numbers[start] 比较时相等,start 后移一个位置,如果是 numbers[middle] 与 numbers[end] 比较时相等,end 前移一个位置。

class Solution {
public:
	int minArray(vector<int>& numbers) {
		if (numbers.size() == 0)
			return 0;

		int start = 0, end = numbers.size() - 1;
		while (start < end) {
			int middle = (start + end) / 2;
			// 步骤1。
			if (numbers[middle] > numbers[end])
				start = middle + 1;
			// 步骤2。
			else if (numbers[middle] < numbers[end])
				end = middle;
			// 步骤3。
			else
				end--;
		}
		return numbers[start];
	}
};

11.3 旋转数组的最大数字


  由上一题的分析,我们也可以写出查找旋转数组的最大数字的代码。
  这里需要注意的是,middle = (start + end) / 2 则 middle 可能等于 start 但不可能等于 end;middle = ceil((start + end) / 2.0) 则 middle 可能等于 end 但不可能等于 start。所以为了保证步骤 3 出现相等时不是下标相等造成的,求最小值时应该使用 middle = (start + end) / 2,求最大值时应该使用 middle = ceil((start + end) / 2.0)。

class Solution {
public:
	int maxArray(vector<int>& numbers) {
		if (numbers.size() == 0)
			return 0;

		int start = 0, end = numbers.size() - 1;
		while (start < end) {
			int middle = ceil((start + end) / 2.0);
			// 步骤1。
			if (numbers[middle] > numbers[start])
				start = middle;
			// 步骤2。
			else if (numbers[middle] < numbers[start])
				end = middle - 1;
			// 步骤3。
			else
				start++;
		}
		return numbers[start];
	}
};

11.3 搜索旋转排序数组


  题目来自《LeetCode 33.搜索旋转排序数组》、《LeetCode 81.搜索旋转排序数组Ⅱ》。

class Solution {
public:
	bool search(vector<int>& nums, int target) {
		if (nums.size() == 0)
			return false;
		int start = 0, end = nums.size() - 1;
		while (start < end) {
			int middle = (start + end) / 2;
			if (nums[middle] == target)
				return true;
			// 1.后部分存在且middle在前部分。
			if (nums[middle] > nums[end]) {
				// 1.1 target在middle前。
				if (target < nums[middle] && target > nums[end])
					end = middle - 1;
				// 1.2 target在middle后。
				else
					start = middle + 1;
			}
			// 2.后部分可能存在也可能不存在,但middle和end在同一部分。
			else if (nums[middle] < nums[end]) {
				// 2.1 target在middle后。
				if (target > nums[middle] && target <= nums[end])
					start = middle + 1;
				// 2.2 target在middle前。
				else
					end = middle - 1;
			}
			// 3.既然有两个相等的值,就把边界处的值丢掉,从而边界又可以收缩一步。
			else if (nums[middle] == nums[end])
				end--;
		}
		// 循环结束时start=end。上面的middle永远取不到end,所以这里是必需的。如果把循环的条件改成start<=end,这里就不需要了。
		if (nums[start] == target)
			return true;
		return false;
	}
};

  其中 search 函数是仿造 minArray 函数写的查找指定值的函数。查找指定值时最好使用循环条件 while(start <= end);使用边界收缩法查找符合条件的最值时使用 while(start < end)。原因在 二分查找 有详细分析。

11.4 搜索多次旋转的旋转数组


  题目来自《LeetCode 面试题 10.03 搜索旋转数组》

12. 矩阵中的路径


  回溯法。

12.1 矩阵中的路径


  请设计一个函数用于判断一个矩阵种是否存在一条包含某字符串所有字符的路径。路径可以从矩阵种的任意一格开始,每一步可以在矩阵中向上下左右移动一格,每个格只能走一次。如下面的矩阵包含一条字符串 “bfce” 的路径。

矩阵中的路径

  使用递归实现的回溯法如下:

bool has_path_core(const char* matrix, const int w, const int h, const int x, const int y,
	const string& path, const int pathLength, bool* visited) {
	// 1.判断(x, y)是否有效。
	if (visited[y * w + x] == true || x < 0 || x >= w || y < 0 || y >= h)
		return false;
	// 2.判断(x, y)是否在路径上。
	if (matrix[y * w + x] != path[pathLength])
		return false;
	// 3.递归终止。
	if (pathLength == path.size() - 1)
		return true;
	// 4.下一步。
	visited[y * w + x] = true;
	if (has_path_core(matrix, w, h, x, y - 1, path, pathLength + 1, visited) ||
		has_path_core(matrix, w, h, x, y + 1, path, pathLength + 1, visited) ||
		has_path_core(matrix, w, h, x - 1, y, path, pathLength + 1, visited) ||
		has_path_core(matrix, w, h, x + 1, y, path, pathLength + 1, visited))
		return true;
	else {
		visited[y * w + x] = false;
		return false;
	}
}

bool has_path(const char* matrix, const int w, const int h, const string path) {
	if (matrix == nullptr || w < 1 || h < 1 || path == "")
		return false;
	int pathLength = 0;
	bool* visited = new bool[w * h];
	memset(visited, 0, sizeof(bool) * w * h);
	for (int y = 0; y < h; y++)
		for (int x = 0; x < w; x++) {
			if (has_path_core(matrix, w, h, x, y, path, pathLength, visited)) {
				delete[] visited;
				return true;
			}
		}
	delete[] visited;
	return false;
}

13. 机器人的运动范围


  机器人在 m 行 n 列的格子中移动,每次只能向上下左右移动一格,但不能进入行坐标和列坐标的数位之和大于 k 的格子。比如 k=18 时机器人不能进入格子 (35, 38),因为 3+5+3+8=19>k。机器人从 (0,0) 出发,请问它能到达多少个格子。

bool check_k(int x, int y, const int k) {
	int c = 0;
	while (x != 0 || y != 0) {
		c += x % 10 + y % 10;
		x /= 10;
		y /= 10;
	}
	if (c > k)
		return true;
	return false;
}

int get_range(const int h, const int w, const int y, const int x, const int k, bool* visited) {
	if (x < 0 || x >= w || y < 0 || y >= h || check_k(x, y, k) || visited[y * w + x])
		return 0;
	int count = 1;
	visited[y * w + x] = true;
	count += get_range(h, w, y - 1, x, k, visited);
	count += get_range(h, w, y + 1, x, k, visited);
	count += get_range(h, w, y, x - 1, k, visited);
	count += get_range(h, w, y, x + 1, k, visited);
	return count;
}

int range(const int h, const int w, const int k) {
	if (h <= 0 || w <= 0 || k < 0)
		return 0;
	bool* visited = new bool[h * w];
	memset(visited, 0, sizeof(bool) * h * w);
	int res = get_range(h, w, 0, 0, k, visited);
	delete[] visited;
	return res;
}

  我第一次不敢在第 14 行使用 visited[y * w + x] 这一项,因为我觉得机器人可能要多次经过同一个格子,结果程序递归调用不能结束。我当时的想法是机器人第一次到达 (0, 0) 点,第二次还要从 (0, 0) 点到 (0, 1) 点和 (1, 0) 点,第三次还要从 (0, 0) 点经 (0, 1) 点到 (0, 2) 点,以此类推。但根据第 18 至 21 行的代码,机器人到达 (0, 2) 点不是从 (0, 0) 点出发,而是从 (0, 1) 点出发,每个格子可以只遍历一次。所以可以在第 14 行使用 visited[y * w + x] 这一项。

14.剪绳子


  相同题目《LeetCode 343.整数拆分》。
  把长度为 n 的绳子剪成 m 段,求 m 段绳子长度乘积的最大值。其中 n 和 m 都是大于等于 2 的整数。

class Solution {
public:
	int cuttingRope(int n) {
		// 1.无效的输入。
		if (n < 2)
			return 0;
		// 2.两个最小问题的解比较特殊。
		if (n < 4)
			return n - 1;
		// 3.递推。
		vector<int> dp(n + 1);
		dp[2] = 2, dp[3] = 3;
		for (int i = 4; i <= n; i++) {
			for (int j = 1; j <= i/2; j++) {
				dp[i] = max(dp[i], dp[i - j] * j);
			}
		}
		return dp[n];
	}
};

  手动计算几次就会发现,n 大于等于 4 时乘积最大的剪法是:尽可能多的剪出长度为 3 的段,剩下的为长度为 2 的段。所以该题可以使用时间复杂度和空间复杂度都是 O(1) 的算法解决。

class Solution {
public:
	int cuttingRope(int n) {
		// 1.无效的输入。
		if (n < 2)
			return 0;
		// 2.两个最小问题的解比较特殊。
		if (n < 4)
			return n - 1;
		// 3.尽可能多地划分出3,剩余的划分出 2。
		int num_of_3 = n / 3;
		int residue = n - 3 * num_of_3;
		if (residue == 0)
			return pow(3, num_of_3);
		else if (residue == 1)
			return pow(3, num_of_3 - 1) * 2 * 2;
		else //if (residue == 2)
			return pow(3, num_of_3) * 2;
	}
};	

15. 二进制中 1 的个数


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值