日常鸽比赛,不过做完这周的题后感觉挺有意思的,记一下思路。
Valid Tic-Tac-Toe State
问题
给出一个井字棋的棋盘,求是否合理。
思路
首先,井字棋先手为X
,后手为O
,因此合理的棋盘中X
数量比O
数量多1
或两者相等,如果先手获胜,则X
数量必然比O
数量多1
;如果后手获胜,则X
数量必然和O
数量相等。其次,一旦有一方获胜,即任意一行或一列或对角线均为X
或均为O
,则游戏结束,因此棋盘不能同时满足双方的胜利条件。我一开始还想到一种情况,就是一方获胜后继续游戏,导致棋盘中只有一方胜利,这种情况满足前面的条件,但实际上不合理,所以要特别考虑。后来我发现要满足这种情况至少要有六个相同的符号:
XOO
XXO
XXX
此时棋盘中只有X
获胜,但事实上无论第六个X
放在哪个位置,前五个X
已经满足胜利条件了,因此这种情况是不合理的,如果仅根据胜利条件来判断棋盘是否合理会造成误判。不过,如果考虑X
和O
的数量关系就会得到正确的结果,因此不需要再当作特殊情况对待。
代码
class Solution {
public:
bool validTicTacToe(vector<string>& board) {
int xCount = 0, oCount = 0; //X和O的数量
bool x3 = false, o3 = false; //X和O的胜利条件
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
if (board[i][j] == 'X') ++xCount;
else if (board[i][j] == 'O') ++oCount;
}
if (board[i][0] == 'X' && board[i][1] == 'X' && board[i][2] == 'X') x3 = true;
if (board[0][i] == 'X' && board[1][i] == 'X' && board[2][i] == 'X') x3 = true;
if (board[i][0] == 'O' && board[i][1] == 'O' && board[i][2] == 'O') o3 = true;
if (board[0][i] == 'O' && board[1][i] == 'O' && board[2][i] == 'O') o3 = true;
}
if (board[0][0] == 'X' && board[1][1] == 'X' && board[2][2] == 'X') x3 = true;
if (board[0][2] == 'X' && board[1][1] == 'X' && board[2][0] == 'X') x3 = true;
if (board[0][0] == 'O' && board[1][1] == 'O' && board[2][2] == 'O') o3 = true;
if (board[0][2] == 'O' && board[1][1] == 'O' && board[2][0] == 'O') o3 = true;
if (o3 && x3) return false; //双方同时获胜
if (xCount > oCount + 1 || xCount < oCount) return false; //X和O数量关系不合理
else if (xCount == oCount + 1 && o3) return false; //X数量比O数量多1不可能后手获胜
else if (xCount == oCount && x3) return false; //X数量和O数量相等不可能先手获胜
return true;
}
};
Number of Matching Subsequences
问题
给出一个字符串和一组序列,求字符串的子序列数量。
思路
求一个序列是否为字符串的子序列很简单,只需要遍历字符串就可以了,假设字符串长度n
,序列长度m
,那么时间复杂度为O(n)
,已经达到最优。但如果对每一个序列都遍历字符串,假设序列数为l
,那么时间复杂度为O(l*n)
,这是最差的时间复杂度,很有可能超时。我一开始想用动态规划,记录字符串中每一个字母的下一个任意字母的最近位置,然后时间复杂度为O(l*m)
,这是最优的时间复杂度,但空间复杂度为O(26*n)
,不符合题目要求。后来我用26
个队列记录每个序列的匹配情况,只需要遍历一次字符串就可以判断子序列的数量了,时间复杂度为O(l*m)
,具体步骤如下:
1.取每个序列第一个字母对应队列,记录当前序列以及当前位置并插入队列。
2.取字符串第一个字母对应队列,取出队列中所有元素视为匹配成功,记录对应每个序列的下一个位置并插入下一个字母的对应队列,即让每个匹配当前字母成功的序列等待下一次匹配。
3.遍历字符串所有字母,判断字符串的子序列数量。
代码
class Solution {
public:
int numMatchingSubseq(string S, vector<string>& words) {
int ans = 0;
vector<queue<pair<int, int>>> v(26);
for (int i = 0; i < words.size(); ++i) {
v[words[i][0]-'a'].push({i, 1});
}
for (int i = 0; i < S.size(); ++i) {
int s = v[S[i]-'a'].size();
for (int j = 0; j < s; ++j) {
auto p = v[S[i]-'a'].front();
v[S[i]-'a'].pop();
if (p.second == words[p.first].size()) ++ans;
else v[words[p.first][p.second]-'a'].push({p.first, p.second+1});
}
}
return ans;
}
};
Number of Subarrays with Bounded Maximum
问题
给出一个序列和上下界,求最大项在上下界之间的连续的子序列数量。
思路
遍历序列,计算以每个元素结尾的符合要求的子序列数量并相加。如果当前元素大于上界,那么以当前元素结尾的子序列数量为0
;如果当前元素在上下界之间,那么任意以当前元素结尾且不包含大于上界元素的子序列均符合要求,即从上一个大于上界元素的下一个元素开始到当前元素的数量;如果当前元素小于下界,那么任意以当前元素结尾且不包含大于上界元素且包含在上下界之间元素的子序列均符合要求,即从上一个大于上界元素的下一个元素开始到上一个在上下界之间元素的数量。
代码
class Solution {
public:
int numSubarrayBoundedMax(vector<int>& A, int L, int R) {
int ans = 0;
int a = 0; //以当前元素结尾且符合要求的子序列数量
int b = 0; //连续的小于下界元素的数量
for (int i = 0; i < A.size(); ++i) {
if (A[i] > R) a = b = 0;
else if (A[i] < L) ++b;
else {
a += b+1;
b = 0;
}
ans += a;
}
return ans;
}
};
Preimage Size of Factorial Zeroes Function
问题
n!
末尾的0
的数量为K
,求符合要求的n
的数量。
思路
n! = 1*2*3*···*n
,因此n!
末尾的0
的数量与质因子2
和5
的数量有关。每有一个质因子2
和一个质因子5
,n!
末尾就会有一个0
,由于质因子2
的数量大于质因子5
的数量,所以n!
末尾的0
的数量等于质因子5
的数量。假设m >= 0
,那么(5*m+1)!,(5*m+2)!,(5*m+3)!,(5*m+4)!,(5*m+5)!
末尾的0
的数量相同,所以符合要求的n
的数量要么为0
,要么为5
。当K = 1,2,3,4,6,···
时n
的数量为5
,当K = 5,···
时n
的数量为0
,因为n!
的第1
个含质因子5
的因子为5
,第2
个因子为10
,第3
个因子为15
,第4
个因子为20
,第5
个因子为25
,由于25
有两个质因子5
,所以不存在n!
有且仅有5
个质因子5
。以此类推,不存在n!
有且仅有30
个质因子5
,因为第25
个因子为125
,125
有三个质因子5
。将K
作类似于进制的转换,我发现对于权1,6,31,···
,如果有任意一个的系数为5
,那么对应的n!
不存在。
代码
class Solution {
public:
int preimageSizeFZF(int K) {
vector<int> v(15);
v[0] = 1;
for (int i = 1; i < 14; ++i) {
v[i] = 5*v[i-1] + 1; //获得每一个权
}
for (int i = 13; i >= 0; --i) {
if (K / v[i] == 5) return 0;
else K %= v[i];
}
return 5;
}
};
总结
这次的题目非常有意思,我通过找规律得到了解题方法,但其中的原理并不能完全领会,还有待证明。