204场周赛
5500. 乘积为正数的最长子数组长度
解题思路:
我们使用两个数组来进行动态规划,分别为p_dp[i]:以 i 结尾乘积为正数得最长子数组长度 n_dp[i]:以 i 结尾乘积为负数得最长子数组长度。当nums[i]为不同情况时,每个数组得不同操作如下:
- nums[i] > 0时,p_dp[i] = p_dp[i - 1] + 1即可。n_dp[i] 要根据n_dp[i - 1] 是否存在负数得最长子数组,若存在则乘上一个正数还是负数所以n_dp[i] = n_dp[i - 1] + 1,若不存在则n_dp[i] = 0
- nums[i] < 0时,p_dp[i]要根据n_dp[i - 1],因为若以i - 1结尾乘积为负数得最长子数组长度乘上一个负数,则肯定为以 i 结尾乘积为正数得最长子数组长度。同样得道理,n_dp[i] 根据 p_dp[i - 1]乘上一个负数得到最长子数组长度。
- nums[i] == 0时,p_dp和n_dp都不会选择得,所以全部重置为0即可
代码:
class Solution {
public:
int getMaxLen(vector<int>& nums) {
int sz = nums.size();
vector<int> p_dp(sz);
vector<int> n_dp(sz);
p_dp[0] = nums[0] > 0 ? 1 : 0;
n_dp[0] = nums[0] < 0 ? 1 : 0;
int res = p_dp[0];
for(int i = 1; i < sz; i++) {
if(nums[i] > 0) {
p_dp[i] = p_dp[i - 1] + 1;
n_dp[i] = n_dp[i - 1] > 0 ? n_dp[i - 1] + 1 : 0;
} else if(nums[i] < 0) {
p_dp[i] = n_dp[i - 1] > 0 ? n_dp[i - 1] + 1 : 0;
n_dp[i] = p_dp[i - 1] + 1;
} else {
p_dp[i] = 0;
n_dp[i] = 0;
}
res = max(res , p_dp[i]);
}
return res;
}
};
总结:
- 这里得dp数组每个都是局部求解,因为在遇到0时,我们全部重置了,重置后得结果可能不是最优解,所以我们不能直接返回p_dp[sz - 1],要使用res单独存储起来,每次都更新。
- 这题得难点不在于动态规划得思想,在于两个不同状态得切换求值,当nums[i] > 0 或者nums[i] < 0时,如果通过另一个状态来求得该状态得值。我们只是把两种状态使用数组表示而已
1568. 使陆地分离的最少天数
解题思路:
设置check函数判断是否只存在一个岛屿,我们只要检查是否删除一个陆地后都都会存在一个岛屿,那么我们只要找到一个相邻有4条边得陆地,删除以它和它得对角一个陆地,即可分离出两个岛屿了。
代码:
class Solution {
public:
int dx[4] = {1 , 0 , -1 , 0};
int dy[4] = {0 , 1 , 0 , -1};
bool isInside(const vector<vector<int>>& grid , int x , int y) {
return x >= 0 && x < grid.size() && y >= 0 && y < grid[0].size();
}
bool check(const vector<vector<int>>& grid) {
int x , y , count = 0;
for(int i = 0; i < grid.size(); i++) {
for(int j = 0; j < grid[i].size(); j++) {
if(grid[i][j] == 0) continue;
count++;
x = i;
y = j;
}
}
queue<pair<int , int>> q;
bool mark[30][30] = {0};
q.push(make_pair(x , y));
mark[x][y] = true;
count--;
while(!q.empty()) {
auto f = q.front();
q.pop();
for(int i = 0; i < 4; i++) {
int nx = f.first + dx[i] , ny = f.second + dy[i];
if(isInside(grid , nx , ny) && grid[nx][ny] == 1) {
if(mark[nx][ny]) continue;
mark[nx][ny] = true;
q.push(make_pair(nx , ny));
count--;
}
}
}
return count != 0;
}
int minDays(vector<vector<int>>& grid) {
if(check(grid)) return 0;
for(int i = 0; i < grid.size(); i++) {
for(int j = 0; j < grid[i].size(); j++) {
if(grid[i][j] == 0) continue;
grid[i][j] = 0;
if(check(grid)) return 1;
grid[i][j] = 1;
}
}
return 2;
}
};
总结:
这题更像是一道思维题,当发现最多只要2步操作得时候,这题就AC了。这道题得时间复杂度为 O(n^4)
1569. 将子数组重新排序得到同一个二叉查找树的方案数
代码:
class Solution {
public:
const long long mod = 1000000007;
long long com[1001][1001];
long long combine(int a , int b) {
return com[a][b];
}
void dfs(vector<int>& nums , int L , int R , long long &mul) {
if(R - L + 1 <= 2) return;
vector<int> less , greater;
for(int i = L + 1; i <= R; i++) {
if(nums[i] < nums[L]) less.push_back(nums[i]);
else greater.push_back(nums[i]);
}
mul *= combine(less.size() + greater.size() , greater.size());
if(mul >= mod) mul %= mod;
dfs(less , 0 , less.size() - 1 , mul);
dfs(greater , 0 , greater.size() - 1 , mul);
}
int numOfWays(vector<int>& nums) {
com[1][0] = com[1][1] = 1;
for(int i = 2; i <= 1000; i++) {
com[i][0] = 1;
for(int j = 1; j <= i; j++) {
com[i][j] = (com[i - 1][j] + com[i - 1][j - 1]) % mod;
}
}
long long mul = 1;
dfs(nums , 0 , nums.size() - 1, mul);
return (mul - 1 + mod) % mod;
}
};
34场双周赛
5492. 分割字符串的方案数
解题思路:
对于没有1的字符串,就是排列组合问题,一共有 种可能。
对于有1的字符串,若1的数量不能被3整除时,直接返回0。否则就是计算第一个字符串结尾和第二个字符开头有多少个0字符串再加1,再计算第二个字符串结尾和第三个字符串开发之间有多少个0字符串再加1。两数相乘即可得到答案
代码:
class Solution {
public:
const int mod = 1000000007;
int numWays(string s) {
bool flag = true;
long long res = 0;
int num = 0;
for(int i = 0; i < s.size(); i++) {
if(s[i] == '1') {
flag = false;
num++;
}
}
if(flag) {
long long sz = s.size();
res = (sz - 1) * (sz - 2) / 2;
return res % mod;
}
if(num % 3 != 0) {
return 0;
}
int both = num / 3;
long long temp1 = 1 , temp2 = 1 , temp = 0;
for(int i = 0; i < s.size(); i++) {
if(s[i] == '1' && both) {
both--;
} else if(s[i] == '0' && !both) {
temp++;
} else if(s[i] == '1' && !both) {
if(temp1 == 1) temp1 = temp + 1;
else if(temp2 == 1) temp2 = temp + 1;
temp = 0;
both = num / 3 - 1;
}
}
res = temp1 * temp2;
res %= mod;
return res;
}
};
总结:
这题在我比赛时,死在了没有1的情况下,不知道如何算出,我直接使用加法进行计算。
5493. 删除最短的子数组使剩余数组有序
解题思路:
- 找到两端的有序子数组
- 合并两个数组成为一个新的有序数组,计算影响成为有序数组数据的数量
- 计算无序数组的数量
- 返回影响成为有序数组数据的数量+无序数组的数量
代码:
class Solution {
public:
int findLengthOfShortestSubarray(vector<int>& arr) {
int n = arr.size();
int i = 0;
for(; i < n - 1; i++) {
if(arr[i] > arr[i + 1]) break;
}
int left = i;
if(left == n - 1) return 0;
i = n - 1;
for(; i > 0; i--) {
if(arr[i] < arr[i - 1]) break;
}
int right = i;
if(right - left == n - 1) {
if(arr[right] >= arr[left]) return n - 2;
return n - 1;
}
if(arr[left] <= arr[right]) return right - left - 1;
int l = 0 , r = right , cnt = 0;
while(l < left + 1 && r < n) {
if(arr[l] > arr[r]) {
cnt++;
l++;
r++;
} else {
l++;
}
}
return right - left - 1 + cnt;
}
};
总结:
- left == n - 1,证明数组本身就是个有序数组,直接返回0
- right - left == n - 1,证明数组除开头尾就是一个反序数组。然后判断arr[right]和arr[left]
- 若arr[left]和arr[right]能组成一个有序数组,则返回n - 2
- 否则返回n - 1
- 当arr[left] <= arr[right],证明左端有序数组的结尾与右端有序数组的开头能组成有序数组,直接删除中间的无需数组即可
5494. 统计所有可行路径
解题思路:
每种求路径数量的问题都可以使用动态规划来解决。问题就是如何设置dp数组和函数逻辑。
dp[i][j]
表示当前起点为i
,剩余油量j
的总方案数。
因为这个路径可以重复走无数次,所以我们的结束条件就是不重复计算即可。
代码:
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 res = 0;
if(start == finish) res++;
int len = locations.size();
for(int i = 0; i < len; i++) {
int diff = abs(locations[i] - locations[start]);
if(start == i) continue;
if(fuel - diff >= 0) {
res = (res + dfs(locations , i , finish , fuel - diff)) % mod;
}
}
dp[start][fuel] = res % 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);
}
};
总结:
- 必须要在类内初始化一下,如果不初始化,默认数组中的数就为0,但是dp[start][fuel] == 0,可能会出现重复计算的情况
这题其实不难,或者说动态规划的题大部分都不难,主要是找到dp数组的参数设置,然后就是空间和时间的优化。这些都是动态规划中涉及的。
第205场周赛
1577. 数的平方等于两数乘积的方法数
解题思路:
直接用两个哈希表来存储数组中每个数的平方值的个数,最后根据nums1[i] * nums1[j]的值为索引值找到个数,nums2同理可得
代码:
class Solution {
public:
int numTriplets(vector<int>& nums1, vector<int>& nums2) {
map<long long , int> mli1 , mli2;
int res = 0;
for(auto i : nums1) mli1[(long long) 1 * i * i]++;
for(auto i : nums2) mli2[(long long) 1 * i * i]++;
for(int i = 0; i < nums1.size(); i++) {
for(int j = i + 1; j < nums1.size(); j++) {
res += mli2[(long long) 1 * nums1[i] * nums1[j]];
}
}
for(int i = 0; i < nums2.size(); i++) {
for(int j = i + 1; j < nums2.size(); j++) {
res += mli1[(long long) 1 * nums2[i] * nums2[j]];
}
}
return res;
}
};
总结:
这题使用哈希表存储平方数值的个数方法解决,比赛时想到用的是递归方法,想得非常的复杂,其实是道水题
1578. 避免重复字母的最小删除成本
解题思路:
当遇到重复字母时,我们使用贪心思想,选择当前两个中的较小成本的字母进行”删除“。当“删除”的字母是i + 1时,因为i + 1可能会影响到后面的成本计算,所以把i + 1和 i 的成本交换一下,相当于删除的是永远是前面一个重复数,并且是成本较小的。
代码:
class Solution {
public:
int minCost(string s, vector<int>& cost) {
int res = 0 , n = s.size();
for(int i = 0; i < n - 1; i++) {
if(s[i] == s[i + 1]) {
res += min(cost[i] , cost[i + 1]);
if(cost[i] > cost[i + 1]) swap(cost[i] , cost[i + 1]);
}
}
return res;
}
};
总结:
这里的贪心思想如何转换成对于全局都适用的规律呢,就是swap(cost[i] , cost[i + 1])。当我们遇到3个或者多个相同的数时,我总是比较当前的两个,无法想到如何顾及到后面较小的成本值。这就是解决方法。