今天的比賽又鴿掉了,晚上回來才把四道題做出來,感覺這次的題目難度適中,偏向數學題。
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
只,那麼兔子中至少有 種對應兔子數量爲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;
}
};