第一题-6369. 左右元素和的差值
类似题目:剑指 Offer 66. 构建乘积数组
【前缀和+后缀和思想 & 递推】
- 方法1:
- 求出元素的前缀和、后缀和(都不包含该元素),二者相减取绝对值即为答案
- 两次遍历:前缀和一次正向遍历(存放的数组作为答案数组),后缀和一次逆向遍历(变量tmp动态记录当前后缀和的值,
tmp += b[i+1];
)并同步计算答案(b[i] = abs(b[i] - tmp);
) - 复杂度
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)(答案数组不算)
vector<int> leftRigthDifference(vector<int>& nums) {
vector<int> b(nums.size(), 0);
for(int i = 1; i< nums.size(); i++) b[i] = b[i-1] + nums[i-1];
int tmp = 0;
for(int i = nums.size()-2; i>= 0; i--){
tmp += nums[i+1];
b[i] = abs(b[i] - tmp);
}
return b;
}
- 方法2:思维,推导
- 可得:
leftSum[i] = leftSum[i-1] + nums[i-1];
rightSum[i] = rightSum[i-1] - nums[i];
- 所以:
- 两个变量left,right分别动态维护leftSum[i]和rightSum[i]即可
- 初始:
left(0) = 0,right(0) = sum{nums[1~(n-1)]}
- 也可以只开一个变量动态维护leftSum[i]-rightSum[i]
- 可得:
vector<int> leftRigthDifference(vector<int>& nums) {
int n = nums.size();
vector<int> res(n, 0);
int left = 0, right = 0;
for(int i = 1; i < n; i++) right += nums[i];
res[0] = abs(left - right);
for(int i = 1; i< n; i++){
left += nums[i-1];
right -= nums[i];
res[i] = abs(left - right);
}
return res;
}
第二题-6368. 找出字符串的可整除数组
【数学,递推(模运算的性质)】
题干:前缀word[0,...i]
组成的数nums(i)
能否整除m,即:nums(i)
对m取模是否为0。
- 该题是判断所有的前缀
word[0,...i]
是否满足条件,考虑word[0,...i]
与word[0,...(i-1)]
的联系 - 对于
word[0,...(i-1)]
所组成的数nums(i-1)
,word[0,...i]
组成的数nums(i) = nums(i-1)*10 + int(word[i])
;- 而要求的是
nums(i)
对m取模是否为0,不妨设nums(i)
模m的余数为mod(i)
,则:mod(i) = nums(i)%m
= (nums(i-1) * 10 + int(word[i])) %m
= (nums(i-1) * 10 %m) + int(word[i]) %m
= (nums(i-1) %m) * (10 %m) + int(word[i]) %m
= mod(i-1) * 10 %m + int(word[i])%m
(由mod(i) = nums(i)%m
得num(i-1) %m = mod(i-1)
)
= (mod(i-1)*10 + int(word[i])) %m
- 利用的是模运算的性质:
-(a+b)%m = a%m + b%m
-(a*b)%m = a%m * b%m
- 所以
mod(i) = (mod(i-1)*10 + int(word[i])) %m
- 根据这个递推式,可直接正向遍历计算出当前mod得到答案
- 而要求的是
- 复杂度
- 时间复杂度: O ( n ) O(n) O(n)
- 空间复杂度: O ( 1 ) O(1) O(1)
vector<int> divisibilityArray(string word, int m) {
int n = word.size();
vector<int> div(n,0);
long long mod = 0;
for(int i = 0; i< word.size(); i++){
mod = (10ll*mod + (word[i] - '0')) % m;
div[i] = (!mod);
}
return div;
}
第三题-6367. 求出最多标记下标
【贪心+双指针 & 二分】
题干:数组nums中满足 2 * nums[i] <= nums[j]
(且i!=j
)的元素可以配对,配对后就不能再和其它元素配对了,求出nums中最多可以配对的对数(该题答案是对数*2)。
- 方法1:贪心,双指针
- 将数组升序排序,对于前面的元素
nums[i]
,应该尽可能配对>=2*nums[i]
且最小的元素,这样能保证在nums[i]
后面比它大的元素也尽可能被配对- 但这样会导致这样一种情况:
- [3,6,8,12],3配对了6,但8无法配对12;应当是3配对8,6配对12
- 原因是3配对了(能配对别的元素的)6,导致6无法再配对别的元素
- 所以,对于一个要主动匹配别的元素的元素
nums[i]
,它的候选配对列表里的元素,要满足:不可能作为主动方,去匹配别的元素- 很显然,n个元素,至多有n/2对配对,排序后数组的前半段元素都可能作为主动方匹配别的元素
- 但这样会导致这样一种情况:
- 如何计数匹配对数:
- 双指针i、j,i指向前半段元素开头,j指向后半段元素开头(n个元素,至多有n/2对配对,所以n为奇数时,显然右半段要更大)
- 若
2*nums[i]<=nums[j]
:i++,j++,cnt++;
- 否则:
j++
; - 直到
i==mid+1
(主动方都匹配完了)或j==n
(没有可匹配的了)为止
- 否则:
- 若
- 答案就是
cnt << 1
。
- 双指针i、j,i指向前半段元素开头,j指向后半段元素开头(n个元素,至多有n/2对配对,所以n为奇数时,显然右半段要更大)
- 复杂度:
- 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)(排序 O ( n l o g n ) O(nlogn) O(nlogn),双指针遍历 O ( n ) O(n) O(n))
- 空间复杂度: O ( 1 ) O(1) O(1)
- 将数组升序排序,对于前面的元素
int maxNumOfMarkedIndices(vector<int>& nums) {
sort(nums.begin(), nums.end());
int n = nums.size();
int cnt = 0;
int i = 0 ,j = ((n+1)>>1);
int mid = j-1;
while(i <= mid && j < n){
if(2*nums[i] <= nums[j]){
i++;
j++;
cnt++;
}
else j++;
}
return (cnt << 1);
}
- 方法2:二分
- 将数组升序排序。对于有n个元素的数组,至多匹配n/2对
- 设最大匹配对数为k
- 显然:
<k
的对数,也能满足都匹配(为true)>k
的对数,不能满足都匹配(为false)
- 以k作为分界点,满足单调性:<=k为1,>k为0
- 所以可以二分答案转化为判定
- 显然:
- 二分:
- 设x为尝试匹配的对数,f(x)为该数组能否匹配x对
- 随着x增大,f(x)在一个分界点从1变到0
- 分界点即为所求
- 即:求f(x) == 1的右边界
- 随着x增大,f(x)在一个分界点从1变到0
- 设x为尝试匹配的对数,f(x)为该数组能否匹配x对
- 初始化:x最小为0,最大为n/2,取值范围是[0,n/2],所以left = 0,right = n/2
- 设最大匹配对数为k
- 复杂度
- 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)(排序 O ( n l o g n ) O(nlogn) O(nlogn),二分 O ( n l o g n ) O(nlogn) O(nlogn))
- 空间复杂度: O ( 1 ) O(1) O(1)
- 将数组升序排序。对于有n个元素的数组,至多匹配n/2对
int maxNumOfMarkedIndices(vector<int>& nums){
sort(nums.begin(), nums.end());
int n = nums.size();
int left = 0, right = (n>>1);
int ans = 0;
while(left <= right){
int mid = left + ((right - left) >>1);
if(check(nums, mid)){ //寻找check(mid) == 1的右边界
ans = mid;
left = mid+1;
}
else right = mid-1;
}
return (ans << 1);
}
bool check(vector<int>& nums, int k){
for(int i = 0; i < k; i++){
int j = (nums.size()-1+1-k)+i;
if(2*nums[i] > nums[j]) return false;
}
return true;
}
第四题-6366. 在网格图中访问一个格子的最少时间
【最短路,Dijkstra算法】
题干:m*n的网格,0时刻从(0,0)出发,目的地是(m-1,n-1)。对于每个位置(x,y),都标明了该位置允许最早到达的时间grid[x][y]
(早于这个时间不让到达),要求每个时刻都要动不能原地踏步,问最早到达(m-1,n-1)的时间(不能到达(m-1,n-1)返回-1)。
- 要求不能原地踏步,所以在t不满足
>=grid[x][y]
时,至少得有2个格子能够来回倒腾耗时间。所以0时刻从(0,0)出发后,必须得到达(0,1)或(1,0)这两个格子的一个- 只要能够到达(0,1)或(1,0),那么最终一定能到达(m-1,n-1)
- 所以,若
gird[0][1] > 1 && gird[1][0] > 1
,意味着(m-1,n-1)不可达,直接返回-1
- 对于m*n的矩阵可以抽象成一张图,从一个位置走到其相邻位置,花费的时间为1
- 求最早到达(m-1,n-1)的时间,即求从(0,0)到(m-1,n-1)花费的最小时间
- 这是个最短路问题(且无负权边),可用Dijkstra算法
- 但是还有限制:到达(x,y)的时间不能早于
grid[x][y]
- 即从(0,0)到(x,y),花费的时间不能少于
grid[x][y]
- 就是说,从一个点(x,y)到相邻的点(x’,y’),必须满足:
- 当前点(x,y)花费的时间+1>=相邻点(x’,y’)允许到达的最小花费时间
grid[x'][y']
- 若不满足,可以通过来回倒腾(每次+2),直到满足条件
- 倒腾的次数为
(grid[x'][y']+1 - (d[x][y]+1))/2
- 当前点(x,y)花费的时间+1>=相邻点(x’,y’)允许到达的最小花费时间
- 所以使用Dijkstra算法,用点(x,y)更新点(x’, y’)=(x+dx[i], y+dy[i])的
d[x'][y']
时,需要先使上述的条件满足- 用来与
d[x'][y']
比较的t
等于:d[x][y]+1
(d[x][y]+1>=grid[x'][y']
,不需要倒腾)- 或
d[x][y]+1 + (grid[x'][y']+1 - (d[x][y]+1))/2 * 2
(d[x][y]+1<grid[x'][y']
,需要倒腾)
- 其实还可以转化成奇偶一致性判断
- 因为每次倒腾时间都+2,与完全不倒腾花费时间的奇偶性是一致的
- 偶数时间从(0,0)出发,所以对于点(x,y),若x+y为奇数,则它是奇数时间可达;若x+y为偶数,则它是偶数时间可达
- 所以
t = max(d[x][y]+1,grid[x'][y'])
- 但
grid[x'][y']
,仅表明允许到达的最早时间(最小花费时间),不能保证这个时间就能到达- 通过
(grid[x'][y'] - (x+y))&1
判断grid[x'][y']
与(x+y)
的奇偶性是否一致来判断grid[x'][y']
这个时间能否到达 - 不能达到(即不一致),dis要+1
- 通过
- 所以
- 用来与
- 就是说,从一个点(x,y)到相邻的点(x’,y’),必须满足:
- 即从(0,0)到(x,y),花费的时间不能少于
- 求最早到达(m-1,n-1)的时间,即求从(0,0)到(m-1,n-1)花费的最小时间
- 复杂度:
- 时间复杂度: O ( ( m n ) l o g ( m n ) ) O((mn)log(mn)) O((mn)log(mn))
- 空间复杂度: O ( m n ) O(mn) O(mn)
- 细节:
- 可达性判断
- 条件满足判断(奇偶一致性判断)
int minimumTime(vector<vector<int>>& grid) {
int m = grid.size(), n = grid[0].size(); //题目保证m,n>=2
if(grid[0][1] >1 && grid[1][0] > 1) return -1;
vector<vector<int>> d(m, vector<int>(n, INT_MAX));
priority_queue<node> q;
d[0][0] = 0;
q.push(node{0,0,0});
while(!q.empty()){
node p = q.top();
q.pop();
if(p.t > d[p.x][p.y]) continue;
for(int i = 0; i < 4; i++){
int x = p.x + dx[i];
int y = p.y + dy[i];
if(x < 0 || y < 0 || x >=m || y >= n) continue;
// int t = max(d[p.x][p.y]+1, grid[x][y]); //d[p.x][p.y]与p.t的值是一样的
// t += ((t - (x+y))&1); //奇偶一致性判断
int t = d[p.x][p.y]+1 + ((d[p.x][p.y] + 1 >= grid[x][y] )? 0 : (grid[x][y]+1 - (d[p.x][p.y]+1))/2*2);
if(t < d[x][y]){ //是小于
d[x][y] = t;
q.push(node{x, y, t});
}
}
}
return d[m-1][n-1];
}
int dx[4] = {-1,1,0,0};
int dy[4] = {0,0,-1,1};
struct node{
int x, y;
int t;
node(int x, int y, int t=0): x(x), y(y), t(t){}
friend bool operator<(const node& a, const node& b){
return a.t > b.t;
}
};