Monthly Archives: March 2020

LeetCode Design a Stack With Increment Operation

1381. Design a Stack With Increment Operation

Design a stack which supports the following operations.

Implement the CustomStack class:

  • CustomStack(int maxSize) Initializes the object with maxSize which is the maximum number of elements in the stack or do nothing if the stack reached the maxSize.
  • void push(int x) Adds x to the top of the stack if the stack hasn’t reached the maxSize.
  • int pop() Pops and returns the top of stack or -1 if the stack is empty.
  • void inc(int k, int val) Increments the bottom k elements of the stack by val. If there are less than k elements in the stack, just increment all the elements in the stack.

Example 1:

Input
["CustomStack","push","push","pop","push","push","push","increment","increment","pop","pop","pop","pop"]
[[3],[1],[2],[],[2],[3],[4],[5,100],[2,100],[],[],[],[]]
Output
[null,null,null,2,null,null,null,null,null,103,202,201,-1]
Explanation
CustomStack customStack = new CustomStack(3); // Stack is Empty []
customStack.push(1);                          // stack becomes [1]
customStack.push(2);                          // stack becomes [1, 2]
customStack.pop();                            // return 2 --> Return top of the stack 2, stack becomes [1]
customStack.push(2);                          // stack becomes [1, 2]
customStack.push(3);                          // stack becomes [1, 2, 3]
customStack.push(4);                          // stack still [1, 2, 3], Don't add another elements as size is 4
customStack.increment(5, 100);                // stack becomes [101, 102, 103]
customStack.increment(2, 100);                // stack becomes [201, 202, 103]
customStack.pop();                            // return 103 --> Return top of the stack 103, stack becomes [201, 202]
customStack.pop();                            // return 202 --> Return top of the stack 102, stack becomes [201]
customStack.pop();                            // return 201 --> Return top of the stack 101, stack becomes []
customStack.pop();                            // return -1 --> Stack is empty return -1.

Constraints:

  • 1 <= maxSize <= 1000
  • 1 <= x <= 1000
  • 1 <= k <= 1000
  • 0 <= val <= 100
  • At most 1000 calls will be made to each method of incrementpush and pop each separately.

设计一个栈Stack的数据结构,简单题,使用vector模拟。完整代码如下:

class CustomStack {
private:
	vector<int> container;
	int msz;
public:
	CustomStack(int maxSize) {
		msz = maxSize;
	}

	void push(int x) {
		if (container.size() < msz) {
			container.push_back(x);
		}
	}

	int pop() {
		if (container.size() > 0) {
			int val = container[container.size() - 1];
			container.pop_back();
			return val;
		}
		else {
			return -1;
		}
	}

	void increment(int k, int val) {
		for (int i = 0; i < k&&i < container.size(); ++i) {
			container[i] += val;
		}
	}
};

本代码提交AC,用时40MS。

LeetCode Lucky Numbers in a Matrix

1380. Lucky Numbers in a Matrix

Given a m * n matrix of distinct numbers, return all lucky numbers in the matrix in any order.

A lucky number is an element of the matrix such that it is the minimum element in its row and maximum in its column.

Example 1:

Input: matrix = [[3,7,8],[9,11,13],[15,16,17]]
Output: [15]
Explanation: 15 is the only lucky number since it is the minimum in its row and the maximum in its column

Example 2:

Input: matrix = [[1,10,4,2],[9,3,8,7],[15,16,17,12]]
Output: [12]
Explanation: 12 is the only lucky number since it is the minimum in its row and the maximum in its column.

Example 3:

Input: matrix = [[7,8],[1,2]]
Output: [7]

Constraints:

  • m == mat.length
  • n == mat[i].length
  • 1 <= n, m <= 50
  • 1 <= matrix[i][j] <= 10^5.
  • All elements in the matrix are distinct.

给定一个没有重复元素的矩阵,要求找出矩阵中的幸运数字,幸运数字的定义是它是所在行的最小值且是所在列的最大值。

有点意思的题目,如果暴力解法,遍历每个元素,同时判断它是否是所在行最小值且所在列最大值的话,时间复杂度高达$O((mn)^2)$,不可取。

更好的方法是,在遍历的过程中,记录当前行的最小值即当前列的最大值,最后判断一下最小值的数组和最大值的数组是否有相等元素,如果有的话,说明这个数是所在行的最小值且是所在列的最大值。可以这样做的原因是矩阵中没有重复元素。

完整代码如下:

class Solution {
public:
	vector<int> luckyNumbers(vector<vector<int>>& matrix) {
		vector<int> ans;
		int m = matrix.size(), n = matrix[0].size();
		vector<int> rowmin(m, INT_MAX), colmax(n, INT_MIN);
		for (int i = 0; i < m; ++i) {
			for (int j = 0; j < n; ++j) {
				rowmin[i] = min(rowmin[i], matrix[i][j]);
				colmax[j] = max(colmax[j], matrix[i][j]);
			}
		}
		for (int i = 0; i < m; ++i) {
			for (int j = 0; j < n; ++j) {
				if (rowmin[i] == colmax[j])ans.push_back(rowmin[i]);
			}
		}
		return ans;
	}
};

本代码提交AC,用时24MS。

LeetCode Frog Position After T Seconds

1377. Frog Position After T Seconds

Given an undirected tree consisting of n vertices numbered from 1 to n. A frog starts jumping from the vertex 1. In one second, the frog jumps from its current vertex to another unvisited vertex if they are directly connected. The frog can not jump back to a visited vertex. In case the frog can jump to several vertices it jumps randomly to one of them with the same probability, otherwise, when the frog can not jump to any unvisited vertex it jumps forever on the same vertex. 

The edges of the undirected tree are given in the array edges, where edges[i] = [fromi, toi] means that exists an edge connecting directly the vertices fromi and toi.

Return the probability that after t seconds the frog is on the vertex target.

Example 1:

Input: n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 2, target = 4
Output: 0.16666666666666666 
Explanation: The figure above shows the given graph. The frog starts at vertex 1, jumping with 1/3 probability to the vertex 2 after second 1 and then jumping with 1/2 probability to vertex 4 after second 2. Thus the probability for the frog is on the vertex 4 after 2 seconds is 1/3 * 1/2 = 1/6 = 0.16666666666666666. 

Example 2:

Input: n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 1, target = 7
Output: 0.3333333333333333
Explanation: The figure above shows the given graph. The frog starts at vertex 1, jumping with 1/3 = 0.3333333333333333 probability to the vertex 7 after second 1. 

Example 3:

Input: n = 7, edges = [[1,2],[1,3],[1,7],[2,4],[2,6],[3,5]], t = 20, target = 6
Output: 0.16666666666666666

Constraints:

  • 1 <= n <= 100
  • edges.length == n-1
  • edges[i].length == 2
  • 1 <= edges[i][0], edges[i][1] <= n
  • 1 <= t <= 50
  • 1 <= target <= n
  • Answers within 10^-5 of the actual value will be accepted as correct.

这题其实不是很hard。

给定一个无向树,节点编号是1~n。有一个青蛙开始在1号节点,每秒钟它会随机跳到与其相邻的未被访问的过的节点中,注意不能走回头路,访问过的节点不能再访问了。如果它周围的节点都被访问了,则它就在原地打转。问经过t秒,它跳到指定节点的概率是多少。

解法。从1号节点开始BFS,每到一个节点时,记录当前走过的时间以及走到当前节点的概率。计算概率也很简单,维护一个visited数组,看看有多少个未被访问的邻居child,则路径概率要乘上(1/child)。

还需要注意的是,如果走到目标节点的时间大于给定时间,则走不到目标节点;如果走的时间小于给定时间,则要看看目标节点还有没有未被访问的节点,如果没有的话,青蛙在目标节点原地打转,否则青蛙就跳走了不会再回来了。

完整代码如下:

struct Point {
	int id, time;
	double prob;
	Point(int i, int t, double p) :id(i), time(t), prob(p) {};
};

class Solution {
public:
	double frogPosition(int n, vector<vector<int>>& edges, int t, int target) {
		vector<vector<int>> graph(n + 1, vector<int>(n + 1, 0));
		for (int i = 0; i < edges.size(); ++i) {
			graph[edges[i][0]][edges[i][1]] = 1;
			graph[edges[i][1]][edges[i][0]] = 1;
		}
		vector<int> visited(n + 1, 0);
		double prob = 1.0;
		int time = 0;

		queue<Point> q;
		q.push(Point(1, 0, 1.0));
		while (true) {
			Point cur = q.front();
			q.pop();
			if (cur.id == target) {
				prob = cur.prob;
				time = cur.time;
				break;
			}
			visited[cur.id] = 1;
			int child = 0;
			for (int i = 1; i <= n; ++i) {
				if (visited[i] == 0 && graph[cur.id][i] == 1) {
					++child;
				}
			}
			for (int i = 1; i <= n; ++i) {
				if (visited[i] == 0 && graph[cur.id][i] == 1) {
					Point next(i, cur.time + 1, cur.prob / child);
					q.push(next);
				}
			}
		}

		if (t < time)return 0;
		else if (t == time)return prob;
		else {
			int child = 0;
			for (int i = 1; i <= n; ++i) {
				if (visited[i] == 0 && graph[target][i] == 1) {
					++child;
				}
			}
			if (child == 0)return prob;
			else return 0;
		}

	}
};

本代码提交AC,用时40MS。

LeetCode Time Needed to Inform All Employees

1376. Time Needed to Inform All Employees

A company has n employees with a unique ID for each employee from 0 to n - 1. The head of the company has is the one with headID.

Each employee has one direct manager given in the manager array where manager[i] is the direct manager of the i-th employee, manager[headID] = -1. Also it’s guaranteed that the subordination relationships have a tree structure.

The head of the company wants to inform all the employees of the company of an urgent piece of news. He will inform his direct subordinates and they will inform their subordinates and so on until all employees know about the urgent news.

The i-th employee needs informTime[i] minutes to inform all of his direct subordinates (i.e After informTime[i] minutes, all his direct subordinates can start spreading the news).

Return the number of minutes needed to inform all the employees about the urgent news.

Example 1:

Input: n = 1, headID = 0, manager = [-1], informTime = [0]
Output: 0
Explanation: The head of the company is the only employee in the company.

Example 2:

Input: n = 6, headID = 2, manager = [2,2,-1,2,2,2], informTime = [0,0,1,0,0,0]
Output: 1
Explanation: The head of the company with id = 2 is the direct manager of all the employees in the company and needs 1 minute to inform them all.
The tree structure of the employees in the company is shown.

Example 3:

Input: n = 7, headID = 6, manager = [1,2,3,4,5,6,-1], informTime = [0,6,5,4,3,2,1]
Output: 21
Explanation: The head has id = 6. He will inform employee with id = 5 in 1 minute.
The employee with id = 5 will inform the employee with id = 4 in 2 minutes.
The employee with id = 4 will inform the employee with id = 3 in 3 minutes.
The employee with id = 3 will inform the employee with id = 2 in 4 minutes.
The employee with id = 2 will inform the employee with id = 1 in 5 minutes.
The employee with id = 1 will inform the employee with id = 0 in 6 minutes.
Needed time = 1 + 2 + 3 + 4 + 5 + 6 = 21.

Example 4:

Input: n = 15, headID = 0, manager = [-1,0,0,1,1,2,2,3,3,4,4,5,5,6,6], informTime = [1,1,1,1,1,1,1,0,0,0,0,0,0,0,0]
Output: 3
Explanation: The first minute the head will inform employees 1 and 2.
The second minute they will inform employees 3, 4, 5 and 6.
The third minute they will inform the rest of employees.

Example 5:

Input: n = 4, headID = 2, manager = [3,3,-1,2], informTime = [0,0,162,914]
Output: 1076

Constraints:

  • 1 <= n <= 10^5
  • 0 <= headID < n
  • manager.length == n
  • 0 <= manager[i] < n
  • manager[headID] == -1
  • informTime.length == n
  • 0 <= informTime[i] <= 1000
  • informTime[i] == 0 if employee i has no subordinates.
  • It is guaranteed that all the employees can be informed.

一个公司有n个工人,每个工人有一个直属上级,公司最大的老板没有直属上级。当老板要下发通知时,他会通知他的直属下级,直属下级又一级一级往下通知。每个上级通知其所有下级的用时不同。问老板的通知要过多久能传达给所有工人。

这是一题多叉树的问题。本质上是求从根节点到所有叶子节点中的最长路径是多少。

想到树的问题一定要想到递归解法。对于任意一棵子树,【它到所有叶子节点的最长路径】等于【它到所有直属下级的长度】加上【所有直属下级到它们各自的所有叶子节点的最长路径的最大值】之和。

完整代码如下:

class Solution {
public:
	int maxTime(int headID, map<int, vector<int>>& employees, vector<int>& informTime) {
		if (employees.find(headID) == employees.end())return 0;

		int cur_time = informTime[headID];
		int child_max = 0;
		for (int i = 0; i < employees[headID].size(); ++i) {
			int cur_child = maxTime(employees[headID][i], employees, informTime);
			child_max = max(child_max, cur_child);
		}
		return cur_time + child_max;
	}
	int numOfMinutes(int n, int headID, vector<int>& manager, vector<int>& informTime) {
		if (n == 1)return informTime[0];
		map<int, vector<int>> employees;
		for (int i = 0; i < n; ++i) {
			employees[manager[i]].push_back(i);
		}
		return maxTime(headID, employees, informTime);
	}
};

本代码提交AC,用时1548MS。

LeetCode Bulb Switcher III

1375. Bulb Switcher III

There is a room with n bulbs, numbered from 1 to n, arranged in a row from left to right. Initially, all the bulbs are turned off.

At moment k (for k from 0 to n - 1), we turn on the light[k] bulb. A bulb change color to blue only if it is on and all the previous bulbs (to the left) are turned on too.

Return the number of moments in which all turned on bulbs are blue.

Example 1:

Input: light = [2,1,3,5,4]
Output: 3
Explanation: All bulbs turned on, are blue at the moment 1, 2 and 4.

Example 2:

Input: light = [3,2,4,1,5]
Output: 2
Explanation: All bulbs turned on, are blue at the moment 3, and 4 (index-0).

Example 3:

Input: light = [4,1,2,3]
Output: 1
Explanation: All bulbs turned on, are blue at the moment 3 (index-0).
Bulb 4th changes to blue at the moment 3.

Example 4:

Input: light = [2,1,4,3,6,5]
Output: 3

Example 5:

Input: light = [1,2,3,4,5,6]
Output: 6

Constraints:

  • n == light.length
  • 1 <= n <= 5 * 10^4
  • light is a permutation of  [1, 2, ..., n]

Discuss


这道题有点意思。给定n个灯泡,如果某个灯泡及其左边的所有灯泡都亮着,则这个灯泡会变成蓝色。刚开始时所有灯泡都是关着的,现在会进行n次开灯的操作,但开灯的顺序不确定。问有多少次开灯操作会使得亮着的所有灯都是蓝色的。

描述起来有点绕,大家看看题目中的示意图就很清楚了。

暴力解法是每开一次灯,都遍历其左边所有灯是否都亮着,时间复杂度是O(n^2),我估计会TLE,我没试过。

我的O(n)的方法如下。在开灯的过程中,记录当前开过的灯的最大编号max_id,同时记录目前已经亮着的灯的数目n。如果max_id==n,则此次开灯操作能使得亮着的灯都变成蓝色。举个例子,如果max_id==n==5,说明目前亮着5盏灯,且这5盏灯的最大编号是5,那说明其余亮着的灯的编号肯定是1,2,3,4,所以这5盏灯都会变成蓝色。

完整代码如下:

class Solution {
public:
	int numTimesAllBlue(vector<int>& light) {
		int ans = 0;
		int curmax = 0;
		for (int i = 0; i < light.size(); ++i) {
			curmax = max(curmax, light[i]);
			if (curmax == i + 1)++ans;
		}
		return  ans;
	}
};

本代码提交AC,用时112MS。

LeetCode Generate a String With Characters That Have Odd Counts

1374. Generate a String With Characters That Have Odd Counts

Given an integer nreturn a string with n characters such that each character in such string occurs an odd number of times.

The returned string must contain only lowercase English letters. If there are multiples valid strings, return any of them.  

Example 1:

Input: n = 4
Output: "pppz"
Explanation: "pppz" is a valid string since the character 'p' occurs three times and the character 'z' occurs once. Note that there are many other valid strings such as "ohhh" and "love".

Example 2:

Input: n = 2
Output: "xy"
Explanation: "xy" is a valid string since the characters 'x' and 'y' occur once. Note that there are many other valid strings such as "ag" and "ur".

Example 3:

Input: n = 7
Output: "holasss"

Constraints:

  • 1 <= n <= 500

简单题。要求构造一个长度为n的字符串,使得字符串中所有字符出现的频率都为奇数。不要被样例迷惑了。如果n为奇数,直接重复一个字符n次即可;如果n为偶数,则重复一个字符n-1次,外加另一个字符。

完整代码如下:

class Solution {
public:
	string generateTheString(int n) {
		if (n % 2 == 0) {
			return string(n - 1, 'a') + "b";
		}
		else {
			return string(n, 'a');
		}
	}
};

本代码提交AC,用时0MS。

LeetCode Print Binary Tree

655. Print Binary Tree

Print a binary tree in an m*n 2D string array following these rules:

  1. The row number m should be equal to the height of the given binary tree.
  2. The column number n should always be an odd number.
  3. The root node’s value (in string format) should be put in the exactly middle of the first row it can be put. The column and the row where the root node belongs will separate the rest space into two parts (left-bottom part and right-bottom part). You should print the left subtree in the left-bottom part and print the right subtree in the right-bottom part. The left-bottom part and the right-bottom part should have the same size. Even if one subtree is none while the other is not, you don’t need to print anything for the none subtree but still need to leave the space as large as that for the other subtree. However, if two subtrees are none, then you don’t need to leave space for both of them.
  4. Each unused space should contain an empty string "".
  5. Print the subtrees following the same rules.

Example 1:

Input:
     1
    /
   2
Output:
[["", "1", ""],
 ["2", "", ""]]

Example 2:

Input:
     1
    / \
   2   3
    \
     4
Output:
[["", "", "", "1", "", "", ""],
 ["", "2", "", "", "", "3", ""],
 ["", "", "4", "", "", "", ""]]

Example 3:

Input:
      1
     / \
    2   5
   / 
  3 
 / 
4 
Output:

[["",  "",  "", "",  "", "", "", "1", "",  "",  "",  "",  "", "", ""]
 ["",  "",  "", "2", "", "", "", "",  "",  "",  "",  "5", "", "", ""]
 ["",  "3", "", "",  "", "", "", "",  "",  "",  "",  "",  "", "", ""]
 ["4", "",  "", "",  "", "", "", "",  "",  "",  "",  "",  "", "", ""]]

Note: The height of binary tree is in the range of [1, 10].


很有意思的一个题,考察了二叉树的多个知识点。

给定一棵二叉树,要求逐层打印出每一层节点的值,而且子节点的位置要在父节点位置的下一层的左边。根据样例可以看到,打印出来的二维数组,如果把每层节点连起来的话,就是原来二叉树的形状。

观察样例可以发现这样一些规律:二叉树的高度就是二维数组的行数;假设高度为h,则二维数组的列数就是$2^h-1$。所以,首先根据LeetCode Maximum Depth of Binary Tree求出二叉树的高度,然后构造好目标大小的二维数组。

接着就是根据二叉树填充数组了。树的题目一般都是用递归来解决,这题也不例外。我们设计一个work函数,每次传入当前需要填充的树节点,二维数组的行号(层数),该节点所在的列数范围。当填充好这个节点后,递归的填充其左节点和右节点,层数加一,同时更新列数范围。

完整代码如下:

class Solution {
public:
	int height(TreeNode* root) {
		if (root == NULL)return 0;
		if (root->left == NULL && root->right == NULL)return 1;
		int lh = height(root->left), rh = height(root->right);
		return (lh > rh ? lh : rh) + 1;
	}
	void work(TreeNode* root, vector<vector<string>>& strTree,int layer,int left,int right) {
		if (root == NULL)return;
		int mid = (left + right) / 2;
		strTree[layer][mid] = to_string(root->val);
		work(root->left, strTree, layer + 1, left, mid - 1);
		work(root->right, strTree, layer + 1, mid + 1, right);
	}
	vector<vector<string>> printTree(TreeNode* root) {
		int h = height(root);
		vector<vector<string>> strTree(h, vector<string>((1 << h) - 1, ""));
		work(root, strTree, 0, 0, strTree[0].size() - 1);
		return strTree;
	}
};

本代码提交AC,用时0MS,性能还是不错的。

LeetCode Search in a Binary Search Tree

700. Search in a Binary Search Tree

Given the root node of a binary search tree (BST) and a value. You need to find the node in the BST that the node’s value equals the given value. Return the subtree rooted with that node. If such node doesn’t exist, you should return NULL.

For example, 

Given the tree:
        4
       / \
      2   7
     / \
    1   3

And the value to search: 2

You should return this subtree:

      2     
     / \   
    1   3

In the example above, if we want to search the value 5, since there is no node with value 5, we should return NULL.

Note that an empty tree is represented by NULL, therefore you would see the expected output (serialized tree format) as [], not null.


给定一个二叉搜索树,要求从树中搜索一个等于val的节点。二叉搜索树是左子树节点小于根节点,右子树节点大于根节点的树。所以搜索时只需要和根节点比较大小,递归的在左子树或右子树中搜索即可。

完整代码如下:

class Solution {
public:
	TreeNode* searchBST(TreeNode* root, int val) {
		if (root == NULL)return NULL;
		if (root->val == val)return root;
		if (val < root->val)return searchBST(root->left, val);
		else return searchBST(root->right, val);
	}
};

本代码提交AC,用时52MS

LeetCode Maximum Binary Tree

654. Maximum Binary Tree

Given an integer array with no duplicates. A maximum tree building on this array is defined as follow:

  1. The root is the maximum number in the array.
  2. The left subtree is the maximum tree constructed from left part subarray divided by the maximum number.
  3. The right subtree is the maximum tree constructed from right part subarray divided by the maximum number.

Construct the maximum tree by the given array and output the root node of this tree.

Example 1:

Input: [3,2,1,6,0,5]
Output: return the tree root node representing the following tree:

      6
    /   \
   3     5
    \    / 
     2  0   
       \
        1

Note:

  1. The size of the given array will be in the range [1,1000].

给定一个数组,要求用数组元素创建一个最大树。最大树的含义是,根节点是数组中的最大元素,左子树是以数组中最大元素划分的左子数组递归构造最大树,右子树以右子数组递归构造最大树。

简单题,设计一个子数组makeTree(nums,left,right),每次从区间[left,right]中找到最大元素,然后构造根节点,最后递归在左子数组和右子数组中构造最大树。

完整代码如下:

const int MINVAL = -999999999;

class Solution {
public:
	TreeNode* makeTree(vector<int>& nums, int left, int right) {
		if (left > right)return NULL;
		if (left == right)return new TreeNode(nums[left]);
		int maxid = -1, maxval = MINVAL;
		for (int i = left; i <= right; ++i) {
			if (nums[i] > maxval) {
				maxval = nums[i];
				maxid = i;
			}
		}
		TreeNode* root = new TreeNode(nums[maxid]);
		root->left = makeTree(nums, left, maxid - 1);
		root->right = makeTree(nums, maxid + 1, right);
		return root;
	}
	TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
		return makeTree(nums, 0, nums.size() - 1);
	}
};

本代码提交AC,用时100MS。

我原本以为可以先求出前k项的最大值和后k项的最大值,后续构造最大树的时候只需要查找这两个最大值就可以,事实上这是不行的,因为用最大值划分之后,求最大值和最小值的区间就不是前k项或者后k项了,有可能是某个中间的子区间,所以行不通,只能用上述暴力方法了。

LeetCode Minimum Cost to Make at Least One Valid Path in a Grid

1368. Minimum Cost to Make at Least One Valid Path in a Grid

Given a m x ngrid. Each cell of the grid has a sign pointing to the next cell you should visit if you are currently in this cell. The sign of grid[i][j] can be:

  • 1 which means go to the cell to the right. (i.e go from grid[i][j] to grid[i][j + 1])
  • 2 which means go to the cell to the left. (i.e go from grid[i][j] to grid[i][j - 1])
  • 3 which means go to the lower cell. (i.e go from grid[i][j] to grid[i + 1][j])
  • 4 which means go to the upper cell. (i.e go from grid[i][j] to grid[i - 1][j])

Notice that there could be some invalid signs on the cells of the grid which points outside the grid.

You will initially start at the upper left cell (0,0). A valid path in the grid is a path which starts from the upper left cell (0,0) and ends at the bottom-right cell (m - 1, n - 1) following the signs on the grid. The valid path doesn’t have to be the shortest.

You can modify the sign on a cell with cost = 1. You can modify the sign on a cell one time only.

Return the minimum cost to make the grid have at least one valid path.

Example 1:

Input: grid = [[1,1,1,1],[2,2,2,2],[1,1,1,1],[2,2,2,2]]
Output: 3
Explanation: You will start at point (0, 0).
The path to (3, 3) is as follows. (0, 0) --> (0, 1) --> (0, 2) --> (0, 3) change the arrow to down with cost = 1 --> (1, 3) --> (1, 2) --> (1, 1) --> (1, 0) change the arrow to down with cost = 1 --> (2, 0) --> (2, 1) --> (2, 2) --> (2, 3) change the arrow to down with cost = 1 --> (3, 3)
The total cost = 3.

Example 2:

Input: grid = [[1,1,3],[3,2,2],[1,1,4]]
Output: 0
Explanation: You can follow the path from (0, 0) to (2, 2).

Example 3:

Input: grid = [[1,2],[4,3]]
Output: 1

Example 4:

Input: grid = [[2,2,2],[2,2,2]]
Output: 3

Example 5:

Input: grid = [[4]]
Output: 0

Constraints:

  • m == grid.length
  • n == grid[i].length
  • 1 <= m, n <= 100

这个题目路有点难,我想了一下午,不过想明白之后也就不难了。

给定一个网格地图,每个网格标明了从该位置可以到达的下一个位置。现在要从地图的左上角走到右下角,有可能根据图标不能到达,问最少改变几个图标可以到达。每个图标仅可改变一次,可将其改为上下左右任意一个方向。

解法1。事实上,对于网格中的每个点,它都可以走到其上下左右的4个点,只不过按图标走费用为0,不按图标走费用为1。所以可以构造一个长和宽都是m*n的图,总元素即为(m*n)*(m*n),新的图中的值就是老图中相邻两个点之间的费用。然后在新的图中执行Dijkstra算法,寻找最短路径,即为原图的最小消费。

解法1太复杂了,忘记怎么写Dijkstra了,放弃。

解法2。不转换为新的图,直接在原图中执行算法。设置一个和原图同样大小的cost二维数组,cost[i][j]表示从左上角走到该位置的最小费用。对原图从左上角开始执行BFS,如果从当前点走到下一个节点的累加费用小于下一个节点的当前费用,则更新下一个节点的费用,并且BFS走到下一个节点。最终右下角的费用就是最小费用。

完整代码如下:

const int MAXCOST = 1000000;
struct Node {
	int x, y;
	Node() :x(0), y(0) {};
	Node(int x_, int y_) :x(x_), y(y_){};
};
class Solution {
public:

	int minCost(vector<vector<int>>& grid) {
		vector<vector<int>> dirs = { {0, 1}, {0, -1},{1, 0},{-1, 0} };//右左下上
		int m = grid.size(), n = grid[0].size();
		vector<vector<int>> cost(m, vector<int>(n, MAXCOST));
		cost[0][0] = 0;
		queue<Node> q;
		q.push(Node());
		while (!q.empty()) {
			Node cur = q.front();
			q.pop();
			for (int i = 0; i < dirs.size(); ++i) {
				int x = cur.x + dirs[i][0], y = cur.y + dirs[i][1];
				if (x < 0 || x >= m || y < 0 || y >= n)continue;
				if (i + 1 == grid[cur.x][cur.y] && cost[cur.x][cur.y] < cost[x][y]) {
					cost[x][y] = cost[cur.x][cur.y];
					q.push(Node(x, y));
				}
				else if (i + 1 != grid[cur.x][cur.y] && (cost[cur.x][cur.y] + 1 < cost[x][y])) {
					cost[x][y] = cost[cur.x][cur.y] + 1;
					q.push(Node(x, y));
				}
			}
		}
		return cost[m - 1][n - 1];
	}
};

本代码提交AC,用时76MS。

该解法没有设置visited数组标记走过的节点,因为没必要。一方面,以题目中的Example 2的图为例(下标从1开始),其(2,1)位置有可能从(1,1)直接下来,也有可能(1,1)先走右边下来再走左边到达(2,1),而第二条路径的费用低于第一条路径,如果设置visited数组的话,第二条路径就没法走了。另一方面,也不用担心不设置visited数组会导致程序来回来反复跑而无法终止,因为只有当走到该点的费用小于该点的当前费用时,才会将其加入队列,如果来回跑的话,费用肯定大于改点当前费用,也就不会加入队列了。所以无需设置visited数组,程序也能正常结束并得到正确结果。

解法3。参考讨论区题解,和解法2类似,但比解法2效率更高。BFS的时候,使用双端队列存储需要访问的节点,而且每次插入队列时,先插入满足原图方向标识的节点,而且是插入到队列开头,因为满足方向标识的路径费用为0,插入到队列开头之后,下次BFS时也先从队列开头取节点。对于不满足方向标识的节点,插入到队列尾部。这个策略相当于优先走费用为0的路径,只有当这些路径无法到达时,才会走费用为1的路径。

完整代码如下:

struct Node {
	int x, y, cost;
	Node() :x(0), y(0), cost(0) {};
	Node(int x_, int y_, int cost_) :x(x_), y(y_), cost(cost_) {};
};
class Solution {
public:

	int minCost(vector<vector<int>>& grid) {
		vector<vector<int>> dirs = { {0, 1}, {0, -1},{1, 0},{-1, 0} };//右左下上
		int m = grid.size(), n = grid[0].size();
		vector<vector<int>> flag(m, vector<int>(n, 0));
		deque<Node> q;
		q.push_back(Node());
		int ans = 0;
		while (!q.empty()) {
			Node cur = q.front();
			q.pop_front();
			flag[cur.x][cur.y] = 1;
			if (cur.x == m - 1 && cur.y == n - 1) {
				ans = cur.cost;
				break;
			}
			for (int i = 0; i < dirs.size(); ++i) {
				int x = cur.x + dirs[i][0], y = cur.y + dirs[i][1];
				if (x < 0 || x >= m || y < 0 || y >= n)continue;
				if (flag[x][y] == 1)continue;
				if (i + 1 == grid[cur.x][cur.y]) {
					q.push_front(Node(x, y, cur.cost));
				}
				else {
					q.push_back(Node(x, y, cur.cost + 1));
				}
			}
		}
		return ans;
	}
};

本代码提交AC,用时36MS,比解法2效率更高。