5492. 分割字符串的方案数 #组合公式 #乘法原理 #区间分割
题意
给定01二进制串s,可将s分割为三个非空 字符串s1,s2,s3,即(s1+s2+s3=s)。现要你求出分割s的方案数,保证s1,s2,s3中字符1的数目相同(对1e9+7取模),他们的长度不一定相等
分析
举个例子,01100011000101串,可以知道三个子串必须包含2个'1',我们观察到左子串的界限,可以如下划分:011∣00011000101、0110∣0011000101、01100∣011000101、011000∣11000101,共四种划分方案;同理对于右子串,共有四种划分方案。当我们将左右子串由于中间子串的间隔,使得左右子串的确定不会相互影响,同时中间子串也相应确定下来,通过乘法原理,总方案数=左子串划分方案数×右子串划分方案数=16。
然而,别忘了诸如00000等只包含'0'的串,三子串的划分确定会相互影响,比如0∣000∣0、000∣0∣0等,此时我们可以用高中所学排列组合的“挡板法”确定下来,即串长为n,有n−1空位,可以插入两个挡板,即Cn−12。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| typedef long long ll; class Solution { private: int sum[100005]; const int MOD = 1e9 + 7; public: int numWays(string s) { int n = s.size(); for (int i = 0; i < n; i++) sum[i + 1] = sum[i + 1 - 1] + (s[i] == '1'); if(sum[n] == 0){ ll ans = (ll)(n - 1) * (ll)(n - 2) / (ll)2; return ans % MOD; } if(sum[n] % 3 != 0) return 0; else{ int diff = sum[n] / 3; ll a = 0, b = 0; for (int i = 1; i <= n - 2; i++) if(sum[i] == diff) a++; for (int i = n; i >= 2; i--) if(sum[n] - sum[i - 1] == diff) b++; ll ans = (a % MOD) * (b % MOD); return ans % MOD; } } };
|
5493. 删除最短的子数组使剩余数组有序 #归并排序 #区间分割
题意
给定整数数组arr ,你需要删除一个子数组(原数组中连续的一个子序列,可以为空),使得arr中剩下的元素是 非递减的。现要你求出满足题目要求的最短子数组的长度。
分析
感谢@Liuyh思路。
题目要求只能删除一个子数组,也就说子数组内部存在一小段是非递减的,但也必须要删去。一般地,我们一定是从数组中间部分进行删除(只删除左部分或只删除右部分属于退化情况),即[....|XXXX|....](X表示要删除的部分)。 删除中间的子数组(并不代表最终删除子数组),意味着剩余前后两个子数组都符合非递减,这一操作需要前后两个指针,分别将两数组边界确定下来。代码中左子数组为[0, lo],而右子数组为[hi, len-1]。比如原数组[1,2,3,10,4,2,3,5],确定出左子数组 [1,2,3,10],lo指向10,右子数组[2, 3, 5]。中间数组删除长度为hi−lo+1
现在我们要合并上述两个子数组,需要用到归并排序的思想,一旦发现右子数组比左子数组还大的元素,其中一个元素给舍弃掉(无需考虑是删除左数组的还是右数组的)。举个例子:
[1,2,3,10]、[2,3,5]- 由于
1<2,此时[2,3,10]、[2,3,5]; - 由于
2<=2,此时[3,10]、[2,3,5] - 由于
3>2,舍弃右子数组的2,左子数组跳过3,ans++,此时[10]、[3,5] - 由于
10>3,舍弃10,得到 []、[5],算法结束。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public: int findLengthOfShortestSubarray(vector<int>& arr) { int lo = 0, len = arr.size(), hi = arr.size() - 1; if(len == 1) return 0; while(lo + 1 < len && arr[lo] <= arr[lo + 1]) lo++; if(lo + 1 == len) return 0; while(lo < hi && arr[hi - 1] <= arr[hi]) hi--; if(hi == lo && hi == 0) return len - 1;
int ans = hi - lo - 1; int i = 0, j = hi;
while(i <= lo && j < len){ if(arr[i] <= arr[j]) i++; else if(arr[i] > arr[j]){ i++; j++; ans++; } } return ans; } };
|
5494. 统计所有可行路径 #记忆化搜索
题意
有若干个城市,第i个城市有location[i]值。给定你城市出发起点start、目的地城市finish、初始汽油总量fuel。每当你从城市j移动到城市i时,消耗的油量为|location[j] - location[i]|。你要保证剩余的油量非负,方能移动到其他城市。同时,你可以经过任意城市超过一次(包括start和finish)。现要你求出从start到finish可能路径的数目(需取模)
分析
观察到数据范围fuel≤200及locations.len≤100,直接记忆化搜索即可。但注意,尽管你此时到达了finish城市,只要你油量足够,仍能够移动到其他城市。
dp[i][j]表示当前起点为i,剩余油量j的总方案数。
要注意,我直接把dp数组放到全局变量的时候,出锅了不少qaq,别忘了在类内要对这个全局变量初始化一下,同时记忆化搜索一般赋值为-1较为稳妥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| typedef long long ll; int dp[105][205]; const int MOD = 1e9 + 7; class Solution { public: int dfs(vector<int>& locations, int start, int finish, int fuel){ if(dp[start][fuel] != -1) return dp[start][fuel]; ll ans = 0; int len = locations.size(); if(start == finish) ans++; for (int i = 0; i < len; i++){ int diff = abs(locations[i] - locations[start]); if(start == i) continue; if(fuel - diff >= 0){ ans = (ans + dfs(locations, i, finish, fuel - diff)) % MOD; } } dp[start][fuel] = (ans % MOD); return dp[start][fuel]; } int countRoutes(vector<int>& locations, int start, int finish, int fuel) { memset(dp, -1, sizeof(dp)); return dfs(locations, start, finish, fuel); } };
|