这次contest对我来说难度比较大,一些问题只会暴力求解(TLE),从思考到弄懂题目花了我挺多时间。
一、1071. Greatest Common Divisor of Strings
这题我用的暴力求解的方法。
由于是要找到一个最长的字符串使得给定的两个字符串S1和S2都是由该字符串组成,所以我们要找的那个字符串的最大长度一定不会超过S1和S2中较小的那个长度。
因此我假定要找的字符串的长度len=min(S1.size,S2.size),然后从此长度递减,寻找满足要求的字符串。
每一次我都判断选取的长度是否可能被S1.size和S2.size整除,不可以的话直接跳过,算是做了一个优化。
class Solution {
public:
string gcdOfStrings(string str1, string str2) {
if(str1==str2)
return str1;
int len1=str1.size(),len2=str2.size();
if(len1>len2)
swap(str1,str2),swap(len1,len2);
for(int len=len1;len>0;len--)
{
if(len2%len||len1%len)
continue;
string sub=str1.substr(0,len);
string s1,s2;
int i=len1/len,j=len2/len;
while(i--)
s1+=sub;
while(j--)
s2+=sub;
if(s1==str1&&s2==str2)
return sub;
}
return "";
}
};
二、1072. Flip Columns For Maximum Number of Equal Rows
这道题我看了discuss区大多数的人的解法后感觉像是一道智力题...,好像跟我知道的算法不一样。
因为每次都是一整列的翻转,所以一次翻转会影响所有的行,那么如果我们最终做的所有翻转会对大多数的行造成相同影响且使得它们在最后变为全0或者全1,则这些行的数量就是我们要找的答案。
延续上面的想法,记录每一行的相对顺序(相对于行首元素的值,相同则记录为1否则记录为0),即如果有两行分别是:
1 0 0 1 0,则我们记录的相对顺序为 1 0 0 1 0;
0 1 1 0 1,则我们记录的相对顺序为 1 0 0 1 0;
可以看到这两行对应列的元素是相反的,我们对其中一些列做翻转最后会使得这两行同时满足要求,而他们的相对顺序又是相等的,所i有只要将所有的行用相对顺序的方式表示,然后求相对顺序相同的最大行数就是答案。
class Solution {
public:
int maxEqualRowsAfterFlips(vector<vector<int>>& matrix) {
unordered_map<string,int> order;
int row=matrix.size(),col=matrix[0].size();
for(int r=0;r<row;++r)
{
string row; //用来记录每一行的相对顺序
for(int c=0;c<col;++c)
{
if(matrix[r][c]==matrix[r][0])
row+='1'; //1 代表当前的元素与该行第一个元素相等
else
row+='0';
}
++order[row];
}
int ans=0;
for(auto ele:order)
ans=max(ans,ele.second);
return ans;
}
};
这题算是向我引入了相对顺序这么一种思想以及表示方式,希望以后碰到类似的题目的时候我能联系起来。
三、1073. Adding Two Negabinary Numbers
求两个以-2为基部的数字的和。这题在contest期间我是没有什么头绪的,想过取偶数位和奇数位分别相加然后再怎么操作一番,但想不出来,赛后看了discuss区域经常看到的一位大神的解法,还得花时间理解一下,很巧妙。
主要的思想是抵消。由于是-2为基部,所以当幂次为偶数时(-2)^k是正数,当幂次为奇数时(-2)^k是负数,正负数交替出现,所以当有进位时,需要将进位与更高位做抵消,因为进位与相邻的下一位更高的位的计算出来的符号是相反的。
class Solution {
public:
vector<int> addNegabinary(vector<int>& arr1, vector<int>& arr2) {
int n1=arr1.size(),n2=arr2.size();
int i=n1-1,j=n2-1,carry=0;
vector<int> ans;
//从低位往高位相加
while(i>=0||j>=0||carry)
{
if(i>=0)
carry+=arr1[i--];
if(j>=0)
carry+=arr2[j--];
ans.push_back(carry&1);
carry=-(carry>>1); //关键就在这一句
/*
结合“抵消”的思想可以理解为什么是“=-”,但正常的话判断进位应该是carry/2吧?为什么会是carry>>1?
首先比较容易知道的是carry的值可能为0,1,2,3;
当carry=0,1时,没进位,下一次计算时carry仍然为0;
当carry=2,3时,有进位,下一次计算时carry设置为-1,可以抵消下一次的计算结果;
所以carry又有了-1的状态,如果下一次计算时未能抵消,即carry仍为-1,则说明
上一次运算结果想要抵消却无可抵消,只能进位。设当前的位为K,下一位为K+1,
即2*(-2)^k[进位]用(-2)^(k+2)+(-2)^(k+1)表示,
(-2)^(k+2)+(-2)^(k+1)=4*(-2)^k-2*(-2)^k
所以需要将当前的位置为1,进位也置为1;
采用carry>>1和carry&1完美表示了上面不同情况的做法:-1>>1=-1,-1&1=1.
*/
}
while(!ans.back()&&ans.size()>1)
ans.pop_back();
reverse(ans.begin(),ans.end());
return ans;
}
};
四、1074. Number of Submatrices That Sum to Target
给定一个目标数字和一个矩阵,问该矩阵有多少子矩阵的元素总和等于该目标数字。
这题我也是暴力搜索,意料之中的TLE。用了前缀和以及使用积分图求某个区域的的和的方法。
正确的解法也用了前缀和,但是结合另一种思想,复杂度为O(N^3)或者O(N^3*logN),后面的logN取决于我们用什么数据结构来记录某个前缀出现的次数。
思想是把矩阵(二维)转为数组(一维)去求解。一维的题目是这一道560. Subarray Sum Equals K,一样的思想。
所以先讲一维的:给定一个数组和一个目标数字K,求该数组有多少连续子序列的总和等于目标数字。
通过求前缀和可以得到一个数组[0,i]范围内的所有元素的和,那如果我们的返回是[a,b]呢?那就用[0,b]-[0,a-1]就可以了。所以我们可以用前缀和求得数组任意连续子序列的总和。
这样的话我们每次算得以i结尾的前缀和后,记录该前缀和出现的次数,同时判断在[0,i]这个区间之内,有没有哪个区间的元素总和为K,相当于判断在此之前我们是否有记录和为prefix[i]-K的区间出现的次数(prefix[i]为[0,i]内所有元素之和)。
如果之前有记录过prefix[i]-k,则说明[0,i]区间中包含了和为K的连续子区间。(注意我们记录的是不同的前缀和)
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
int n=nums.size(),ans=0,cursum=0;
unordered_map<int,int> cnt;
cnt[0]=1;
for(int i=0;i<n;++i)
{
cursum+=nums[i];
if(cnt.count(cursum-k))
ans+=cnt[cursum-k];
++cnt[cursum];
}
return ans;
}
};
回到矩阵这一题,我们可以将矩阵的每一行对应的同一列加在一起,相当于把多行压缩成一行,这就把问题转为一维去求解了。
如上图,把一个矩阵化为多个一维数组,这些数组涵盖了所有子矩阵可能的行的组合。
class Solution {
public:
int numSubmatrixSumTarget(vector<vector<int>>& matrix, int target) {
int row=matrix.size(),col=matrix[0].size();
int sum[col+1];
int ans=0;
for(int i=0;i<row;++i)
{
memset(sum,0,sizeof(sum));
for(int r=i;r<row;++r)
{
for(int c=0;c<col;++c)
sum[c]+=matrix[r][c];
//下面的同一维的解法
unordered_map<int,int> cnt;
cnt[0]=1;
int cursum=0;
for(int c=0;c<col;++c)
{
cursum+=sum[c];
if(cnt.count(cursum-target))
ans+=cnt[cursum-target];
++cnt[cursum];
}
}
}
return ans;
}
};
这题contest虽然从做题到学习前前后后花了比较多时间,但还是挺有收获的。