今天的比赛又鸽掉了,晚上回来才把四道题做出来,感觉这次的题目难度适中,偏向数学题。
Minimum Distance Between BST Nodes
问题
求给出的二叉搜索树中任意两个元素最小的差值。
思路
由于二叉搜索树的特点为左子树的元素小于根的元素并且右子树的元素大于根的元素,通过中序遍历可以得到有序数列,任意两个元素最小的差值必定为有序数列中相邻元素的差值,因此逐个比较相邻两个元素的差值取其中的最小值。本来我觉得这个方法有点麻烦,不过看到答案也是这样做的,应该没有更优的选择了。
代码
/**
* 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:
int minDiffInBST(TreeNode* root) {
int ans = INT_MAX;
vector<int> v;
addVec(root, v); //递归中序遍历得到有序数列
for (int i = 1; i < v.size(); ++i) {
ans = min(ans, v[i]-v[i-1]); //取最小差值
}
return ans;
}
void addVec(TreeNode* root, vector<int> &v) {
if (root == NULL) return;
addVec(root->left, v);
v.push_back(root->val);
addVec(root->right, v);
}
};
Rabbits in Forest
问题
假设森林中每只兔子都有各自的颜色,每只兔子都会说出其它与自己有相同颜色的兔子的数量,已知森林中一部分兔子的回答,求森林中至少有多少只兔子。
思路
一开始我想得很简单,假设有n
只某颜色的兔子,那么这种颜色的兔子的回答就是n-1
,如果有一部分兔子的回答相同,那么当这些兔子的颜色相同时兔子数量最小,因此只需要将每一种不同的回答对应的兔子数量加起来就可以得到结果了。后来想到回答为n-1
的兔子最多只能有n
只为相同颜色,因此要保证每一种不同的回答对应的兔子数量尽可能少,假设回答为n-1
的兔子有m
只,那么兔子中至少有
⌈
m
/
n
⌉
\lceil m/n \rceil
⌈m/n⌉ 种对应兔子数量为n
的颜色。
代码
class Solution {
public:
int numRabbits(vector<int>& answers) {
unordered_map<int,int> m;
int ans = 0;
for (int i = 0; i < answers.size(); ++i) {
++m[answers[i]]; //记录每一种不同回答的兔子数量
}
for (auto iter: m) {
ans += ceil(iter.second * 1.0 / (iter.first+1)) * (iter.first+1);
}
return ans;
}
};
Reaching Points
问题
给出源坐标(x, y)
,每次操作更新坐标为(x+y, y
或(x, x+y)
,求源坐标是否能通过若干次操作得到目标坐标。
思路
这道题非常有意思,每一次操作都有两种可能的结果,用BFS时间复杂度为2tx+ty,很有可能超时。其实如果将操作反过来,得到的结果就很容易确定了:
x > y: (x-y, y) -> (x, y) ⇒ (x, y) -> (x-y, y)
x < y: (x, y-x) -> (x, y) ⇒ (x, y) -> (x, y-x)
由于x > 0 && y > 0
,对于操作得到的坐标(x, y)
,x = y
不可能成立,如果x > y
,那么最后一次操作之前必然为(x-y, y)
;如果x < y
,那么最后一次操作之前必然为(x, y-x)
,因此只需要判断目标坐标在若干次操作之前是否为源坐标就可以了。后来在提交的时候还发现一个可以优化的地方,如果每次只是进行减法操作,最后还是会超时,因此要用取模操作减少时间复杂度。
代码
class Solution {
public:
bool reachingPoints(int sx, int sy, int tx, int ty) {
while (max(tx, ty) > max(sx, sy)) {
if (tx == ty) break; //不可能通过操作得到的坐标,因此无需反向操作
if (tx > ty) {
tx -= ty; //得到最后一次操作前的坐标
if (tx > ty) tx -= (tx - max(ty, max(sx, sy))) / ty * ty; //减少时间复杂度逼近最后一次tx -= ty的操作
} else {
ty -= tx; //得到最后一次操作前的坐标
if (ty > tx) ty -= (ty - max(ty, max(sx, sy))) / tx * tx; //减少时间复杂度逼近最后一次ty -= tx的操作
}
}
return tx == sx && ty == sy;
}
};
Transform to Chessboard
问题
有一个矩阵,每个元素不是0
就是1
,每次操作可以交换两行或两列,目标矩阵中与0
相邻的元素均为1
并且与1
相邻的元素均为0
,求至少经过多少次操作可以得到目标矩阵,如果不可能则为-1
。
思路
这道题非常难,我觉得是一道数学题,最后猜到做法但没能给出证明。首先我就放弃暴力解,判断是否为目标矩阵很麻烦而且交换的可能太多,然后我发现目标矩阵必定是像国际象棋棋盘那样的矩阵,因为每次操作不改变每一行和每一列中0
和1
的数量,所以初始矩阵必须要满足每一行和每一列中0
和1
的数量相差不超过1才有可能得到目标矩阵,并且我猜这个是充要条件,这是最关键的假设。基于这个充要条件可以排除不可能的结果,剩下的矩阵都可以变成目标矩阵,既然已经确定一个矩阵可以变成目标矩阵,那么列交换对每一行的元素位置影响都一样,只需要取其中一行分析就可以了,行交换同理。取任意一行,确定奇数位置和偶数位置1
的数量,如果行元素数量为偶数,那么将奇数位置和偶数位置中1
较少的位置中的1
交换到1
较多的位置;如果行元素数量为奇数,那么根据1
的总数量确定交换到的位置,1
比0
多则交换到奇数位置,1
比0
少则交换到偶数位置。最后将列交换和行交换的次数相加得到结果。
代码
class Solution {
public:
int movesToChessboard(vector<vector<int>>& board) {
if (board.size() == 0) return -1;
int ans = 0;
int rows = board.size();
int cols = board[0].size();
vector<int> v(2, 0); //记录奇数位置和偶数位置的1的数量
for (int i = 0; i < rows; ++i) {
v[0] = v[1] = 0;
for (int j = 0; j < cols; ++j) {
v[j % 2] += board[i][j];
}
int ones = v[0] + v[1];
if (cols % 2 == 0 && ones != cols / 2) return -1; //1的数量不正确可以排除
if (cols % 2 == 1 && ones != cols / 2 && ones != cols / 2 + 1) return -1; //1的数量不正确可以排除
}
//确定列交换次数
if (cols % 2 == 0) ans += min(v[0], v[1]);
else if (v[0] + v[1] == cols / 2) ans += v[0];
else ans += v[1];
for (int j = 0; j < cols; ++j) {
v[0] = v[1] = 0;
for (int i = 0; i < rows; ++i) {
v[i % 2] += board[i][j];
}
int ones = v[0] + v[1];
if (rows % 2 == 0 && ones != rows / 2) return -1; //1的数量不正确可以排除
if (rows % 2 == 1 && ones != rows / 2 && ones != rows / 2 + 1) return -1; //1的数量不正确可以排除
}
//确定行交换次数
if (rows % 2 == 0) ans += min(v[0], v[1]);
else if (v[0] + v[1] == rows / 2) ans += v[0];
else ans += v[1];
return ans;
}
};