LeedCode第 244 场周赛(二)
5776. 判断矩阵经轮转后是否一致
题目链接:https://leetcode-cn.com/problems/determine-whether-matrix-can-be-obtained-by-rotation/
给你两个大小为 n x n 的二进制矩阵 mat 和 target 。现 以 90 度顺时针轮转 矩阵 mat 中的元素 若干次 ,如果能够使 mat 与 target 一致,返回 true ;否则,返回 false 。
题目思路:
主要给出转换过程中每个点的横纵坐标的变化表示,比如,设置矩阵中任意一点A的坐标是(i ,j),则顺时针旋转90度后的坐标是(j , n-1-i),继续顺时针旋转90度后的坐标是(n-1-i.n-1-j),接着再顺时针选择90度后的坐标是(n-1-j , i),最后顺时针旋转90度后,回到起点(i ,j)。
C++代码:
1 class Solution { 2 public: 3 bool findRotation(vector<vector<int>>& mat, vector<vector<int>>& target) { 4 int n = mat.size(); 5 int m = target.size(); 6 int sum=0; 7 int sum1=0; 8 int sum2=0; 9 int sum3=0; 10 if(m==n) 11 { 12 for(int i=0; i<n ;i++) 13 { 14 for(int j=0 ;j<n; j++) 15 { 16 if(mat[i][j]==target[i][j]) 17 { 18 // printf("%d %d\n",i,j); 19 sum++; 20 } 21 } 22 } 23 24 for(int i=0; i<n ;i++) 25 { 26 for(int j=0 ;j<n; j++) 27 { 28 if(mat[i][j]==target[j][n-1-i]) 29 { 30 // printf("%d %d\n",i,j); 31 sum1++; 32 } 33 } 34 } 35 36 for(int i=0; i<n ;i++) 37 { 38 for(int j=0 ;j<n; j++) 39 { 40 if(mat[i][j]==target[n-1-i][n-1-j]) 41 { 42 // printf("%d %d\n",i,j); 43 sum2++; 44 } 45 } 46 } 47 48 for(int i=0; i<n ;i++) 49 { 50 for(int j=0 ;j<n; j++) 51 { 52 if(mat[i][j]==target[n-1-j][i]) 53 { 54 // printf("%d %d\n",i,j); 55 sum3++; 56 } 57 } 58 } 59 60 //if(sum2==sum3) 61 // printf("sxiangtong %d %d\n",sum2,sum3); 62 int ans=n*n; 63 // printf("sum : %d ans: %d\n",sum,ans); 64 if((sum==ans) || (sum1==ans) || (sum2==ans) || (sum3==ans)) 65 { 66 return true; 67 } 68 else 69 { 70 return false; 71 } 72 } 73 74 else 75 return false; 76 } 77 };
5777. 使数组元素相等的减少操作次数
题目链接:https://leetcode-cn.com/problems/reduction-operations-to-make-the-array-elements-equal/
给你一个整数数组 nums ,你的目标是令 nums 中的所有元素相等。完成一次减少操作需要遵照下面的几个步骤:
找出 nums 中的 最大 值。记这个值为 largest 并取其下标 i (下标从 0 开始计数)。如果有多个元素都是最大值,则取最小的 i 。
找出 nums 中的 下一个最大 值,这个值 严格小于 largest ,记为 nextLargest 。
将 nums[i] 减少到 nextLargest 。
返回使 nums 中的所有元素相等的操作次数。
解题思路:
先sort快排,然后采用前缀的思想,也可以理解为后一个数值和相邻的前一个数值有关,并把操作的次数记录下来。
C++代码:
1 class Solution { 2 public: 3 int reductionOperations(vector<int>& nums) { 4 int n=nums.size(); 5 int ans=0; 6 int sum[50005]; 7 memset(sum, 0, sizeof(sum)); 8 sort(nums.begin(),nums.end()); 9 for(int i=0;i<n-1;i++) 10 { 11 if(nums[i+1]!=nums[i]) 12 { 13 sum[i+1]=sum[i]+1; 14 } 15 else 16 { 17 sum[i+1]=sum[i]; 18 } 19 } 20 for(int i=0 ; i<n ; i++) 21 { 22 ans = ans + sum[i]; 23 } 24 return ans; 25 } 26 };
5778. 使二进制字符串字符交替的最少反转次数
题目链接:https://leetcode-cn.com/problems/minimum-number-of-flips-to-make-the-binary-string-alternating/
给你一个二进制字符串 s 。你可以按任意顺序执行以下两种操作任意次:
类型 1 :删除 字符串 s 的第一个字符并将它 添加 到字符串结尾。
类型 2 :选择 字符串 s 中任意一个字符并将该字符 反转 ,也就是如果值为 '0' ,则反转得到 '1' ,反之亦然。
请你返回使 s 变成 交替 字符串的前提下, 类型 2 的 最少 操作次数 。
我们称一个字符串是 交替 的,需要满足任意相邻字符都不同。
比方说,字符串 "010" 和 "1010" 都是交替的,但是字符串 "0100" 不是。
解题思路1:
滑动窗口的方法。
对字符串 s 变成2倍的s。
然后我们用01
和10
交替创建两个相同长度的不同字符串。例如:s =11100
-
- s =
1110011100
- s1 =
1010101010
- s2=
0101010101
- s =
- 最后,使用滑动窗口(大小 n)将 s 与 s1 和 s2 进行比较。
- 为什么我们可以将 s 加倍来完成第一个操作,让我们看一下相同的示例 s =
11100
:`- 双 s:
1110011100
- 大小为n的窗口: |11100|11100 ==> 1|11001|1100 ==> 11|10011|100 以此类推,当我们移动滑动窗口一步时,同样从头开始追加1个元素。
- 双 s:
参考链接:https://leetcode.com/problems/minimum-number-of-flips-to-make-the-binary-string-alternating/discuss/1253874/C++-Solution-sliding-window.-O(N)
C++代码:
1 class Solution { 2 public: 3 int minFlips(string s) { 4 int n = s.size(); 5 s += s; 6 string s1, s2; 7 8 for(int i = 0; i < s.size(); i++) { 9 s1 += i % 2 ? '0' : '1'; 10 s2 += i % 2 ? '1' : '0'; 11 } 12 int ans1 = 0, ans2 = 0, ans = INT_MAX; 13 for(int i = 0; i < s.size(); i++) { 14 if(s1[i] != s[i]) ++ans1; 15 if(s2[i] != s[i]) ++ans2; 16 if(i >= n) { //the most left element is outside of sliding window, we need to subtract the ans if we did `flip` before. 17 if(s1[i - n] != s[i - n]) --ans1; 18 if(s2[i - n] != s[i - n]) --ans2; 19 } 20 if(i >= n - 1) 21 ans = min({ans1, ans2, ans}); 22 } 23 return ans; 24 } 25 };
解题思路2:
分析+前缀和+后缀和
提示 1
我们可以将所有类型 2的操作安排在类型 1 的操作之前。
提示 1 解释
由于类型 2 的操作是反转任意一个字符,而类型 1 的操作只会改变字符的相对顺序,不会改变字符的值。因此如果我们想要修改某个字符,随时都可以修改。这样我们就可以把所有类型 2 的操作安排到最前面。
提示 2
设字符串 ss 的长度为 n。
如果 n 是偶数,那么在所有类型 2 的操作完成后,s 已经是一个交替字符串了。
提示 2 解释
当 n 是偶数时,交替字符串只可能为 010101⋯01 或者101010⋯10 的形式。对这两个字符串进行类型 2 的操作,只会在它们之间相互转换。
类型 2 的操作是可逆的,这说明交替字符串只可能由交替字符串通过类型 2 的操作得来。因此,在所有类型 2 的操作完成后,s 必须是一个交替字符串。
提示 3
如果 n 是奇数,那么交替字符串为 0100101⋯010 或者 1011010⋯101 的形式。
我们首先考虑 0100101⋯010,如果在所有类型 2 的操作完成后,s 可以通过类型 2 的操作得到该字符串,那么:
要么 s 就是 0100101⋯010;
要么 s 是 0101⋯010∣01⋯01 的形式,或者是 01010⋯10∣01⋯010 的形式。这里我们用竖线 | 标注了类型 2 的操作,在 | 左侧的字符通过类型 2 的操作被移动到字符串的末尾,最终可以得到 0100101⋯010。
因此,s 要么是一个交替字符串(即 0100101⋯010),要么由两个交替字符串拼接而成,其中左侧的交替字符串以 0 结尾,右侧的交替字符串以 0 开头。
同理,如果我们考虑 1011010⋯101,那么 s 要么就是 1011010⋯101,要么由两个交替字符串拼接而成,其中左侧的交替字符串以 1 结尾,右侧的交替字符串以 1 开头。
我们用pre[i][j] 表示对于字符串的前缀 s[0..i]s[0..i],如果我们希望通过类型 2 的操作修改成「以 j 结尾的交替字符串」,那么最少需要的操作次数。这里 j 的取值为 0 或 1。根据定义,有递推式:
pre[i][0]=pre[i−1][1]+I(s[i],1)
pre[i][1]=pre[i−1][0]+I(s[i],0)
其中 I(x,y) 为示性函数,如果 x=y,那么函数值为 1,否则为 0。例如 I(s[i],1) 就表示:如果s[i] 为 1,那么我们需要通过类型 2 的操作将其修改为 0,否则无需操作。
同理,我们用suf[i][j] 表示对于字符串的后缀 s[0..i]s[0..i],如果我们希望通过类型 2 的操作修改成「以 j 开头的交替字符串」,那么最少需要的操作次数。这里 j 的取值为 0 或 1,同样有递推式:
suf[i][0]=suf[i+1][1]+I(s[i],1)
suf[i][1]=suf[i+1][0]+I(s[i],0)
在求解完数组 pre 和suf 后:
答案可以为 pre[n−1][0] 或者 pre[n−1][1],对应着将 s 本身变为一个交替字符串;
如果 n 是奇数,那么答案还可以为 pre[i][0]+suf[i+1][0] 以及pre[i][1]+suf[i+1][1],对应着将 s 变为两个交替字符串的拼接。
所有可供选择的答案中的最小值即为类型 2 的操作的最少次数。
细节:如果 n 是偶数,我们无需求出 suf 。
参考链接:https://leetcode-cn.com/problems/minimum-number-of-flips-to-make-the-binary-string-alternating/solution/shi-er-jin-zhi-zi-fu-chuan-zi-fu-jiao-ti-i52p/
C++代码:
1 class Solution { 2 public: 3 int minFlips(string s) { 4 //示性函数 5 auto I = [](char ch, int x) -> int { 6 return ch - '0' == x; 7 }; 8 9 int n = s.size(); 10 vector<vector<int>> pre(n, vector<int>(2)); 11 12 //注意 i=0 的边界情况 13 for(int i=0 ; i<n ;++i) 14 { 15 pre[i][0]=(i==0 ? 0 : pre[i-1][1])+I(s[i],1); 16 pre[i][1]=(i==0 ? 0 : pre[i-1][0])+I(s[i],0); 17 } 18 int ans = min(pre[n - 1][0], pre[n - 1][1]); 19 20 //如果n是奇数,还需要求出suf 21 if (n % 2 == 1) { 22 // 如果 n 是奇数,还需要求出 suf 23 vector<vector<int>> suf(n, vector<int>(2)); 24 // 注意 i=n-1 的边界情况 25 for (int i = n - 1; i >= 0; --i) { 26 suf[i][0] = (i == n - 1 ? 0 : suf[i + 1][1]) + I(s[i], 1); 27 suf[i][1] = (i == n - 1 ? 0 : suf[i + 1][0]) + I(s[i], 0); 28 } 29 for (int i = 0; i + 1 < n; ++i) { 30 ans = min(ans, pre[i][0] + suf[i + 1][0]); 31 ans = min(ans, pre[i][1] + suf[i + 1][1]); 32 } 33 } 34 35 return ans; 36 } 37 };