1562. 查找大小为 M 的最新分组
考点:双链表/集合/数组
1、双链表解法
算法思想:每次当某个位置 i 设置为1后就会出现以下情况
①i的左边有1右边无1
②i的右边有1左边无1
③i的左右两边都有1
设定m个1的数组的个数为ans,步骤数为cnt
第一种情况先检测i的左边1的个数如果是m则ans-1,则i的的左指针设置为最左边1的下标,再次检测当前更新后1的个数如果是m则ans+1;第二种情况i的的右指针设置为最右边1的下标,则i的的右指针设置为最右边1的下标,再次检测当前更新后1的个数如果是m则ans+1;第三种情况监测左右两边1的个数,有m的则ans-1,更新i位置左指针为最左边1的下标,i位置右指针为最右边1的下标,再次检测更新后1的个数如果是m则ans+1,ans>0的情况才更新cnt,最终返回cnt。
时间复杂度:O(n)
class Solution {
int N = 100010;
int[] l = new int[N], r = new int[N];
int get(int x){
return r[x] - x + 1;
}
public int findLatestStep(int[] arr, int m) {
int n = arr.length;
int res = -1, cnt = 0;
for(int i = 0; i < n; ++i){
int c = arr[i];
if(l[c - 1] != 0 && r[c + 1] != 0){
if(get(l[c - 1]) == m)cnt--;
if(get(c + 1) == m)cnt--;
l[r[c + 1]] = l[c - 1];
r[l[c - 1]] = r[c + 1];
if(get(l[c - 1]) == m)cnt++;
}else if(l[c - 1] != 0){
if(get(l[c - 1]) == m)cnt--;
r[l[c - 1]] = c;
l[c] = l[c - 1];
if(get(l[c - 1]) == m)cnt++;
}else if(r[c + 1] != 0){
if(get(c + 1) == m)cnt--;
r[c] = r[c + 1];
l[r[c + 1]] = c;
if(get(c) == m)cnt++;
}else{
l[c] = r[c] = c;
if(m == 1)cnt++;
}
if(cnt != 0)res = i + 1;
}
return res;
}
}
2、集合做法
算法思想:
这题如果使用正向遍历,就需要考虑数组融合,这样会很麻烦,它既然求的是一个长度为 m 的一组 1 的最后步骤,其实等价于逆序出现第一个长度为 m 的一组 1 的步骤,例 11101 变为 11111,题目找的是 11101 这个步骤,不就相当于找 11111 变为 11101 吗。
那么这题可以这么来做,逆序遍历,每次遍历可以看作将那个位置的 1 变为 0(因为最终状态为全 1,所以反向相当于 1 变为 0)。这里我们以找到一个长度为 2 的一组 1 为例,当前为 110111,我们已经在下标为 2 的地方将 1 变为了 0(默认下标 0 开始),如果接下来下标为 5 的地方将 1 变为了 0,即变为 110110,这时则存在长度为 2 的一组 1,即为所求。
算法流程:
添加边界 0,n+1;
逆序遍历数组,查找当前值左右两边第一个已经被遍历到的数(相当于上例中,值 5 找值 2,因为他们都已经被遍历了,对应下标的地方为 0),如果存在长度为m的地方,return true;不然,将值存放在 set 中
重复步骤 2
作者:wwssxxu
链接:https://leetcode-cn.com/problems/find-latest-group-of-size-m/solution/fan-xiang-qjing-wei-shen-sui-ran-wo-yun-xing-shi-j/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间复杂度:O(nlogn)
class Solution {
public int findLatestStep(int[] arr, int m) {
TreeSet set=new TreeSet<>();
set.add(0);//首位哨兵
set.add(arr.length+1);//末尾哨兵
if(arr.length==m) return arr.length;
int n=arr.length;
for (int i = n-1; i >=0; i--) {
int index=arr[i];//当前插入1的位置
int a=set.lower(index);//当前位置紧邻的前一个0的位置
int b=set.higher(index);//当前位置紧邻的后一个0的位置
if(index-a-1==m||b-index-1==m){//计算当前位置左右区间内1的个数为m则返回当前步骤
return i;
}
set.add(index);//出现0则存储当前位置
}
return -1;
}
}
3、数组做法(很妙的解法)
1定义边界数组 b,b[i] = x 代表以位置 i 为边界,一组 1 的长度为 x
2定义数量数组 c,c[j] = y 代表一组长度为 j 的 1 的数量有 y 个
3遍历 arr 数组,当前位置为 i,值为 a,a 也是二进制字符串的位置
4查看边界数组,二进制字符串位置 a 的左右位置即 a-1 和 a+1,将左右区间一组 1 的长度记为 left, right
5那么在位置 a 上添加 1后,区间合并后一组 1 长度为 left+right+1,更新位置 a 和区间左右边界位置上边界数组的值
6更新数量数组 c,由于合并区间,导致原来长度为 left 和 right 的数量均减 1,新的长度 new 的数量加 1
7如果当前有长度为 m 的一组 1,那么更新 ans,注意题目的要求是下标从 1 开始
作者:elevenxx
链接:https://leetcode-cn.com/problems/find-latest-group-of-size-m/solution/shi-yong-shu-zu-jin-xing-qu-jian-he-bing-by-eleven/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
时间复杂度:O(n)
class Solution:
def findLatestStep(self, arr: List[int], m: int) -> int:
b = [0] * (len(arr) + 2)
c = [0] * (len(arr) + 1)
ans = -1
for i, a in enumerate(arr):
left, right = b[a-1], b[a+1] ##得到当前位置前后边界位置紧邻的1的个数
new = left + right + 1 ##加上当前位置的1计算新的1的个数
b[a] = b[a-left] = b[a+right] = new
##此时当前位置并入,边界发生变化不是之前的两个边界a - left, a - 1; a + 1, a + right,而是合并为1个边界分别为a - left 和 a + right将更新后1的个数重新更新到边界位置
c[left] -= 1; c[right] -= 1; c[new] += 1 ##重新更新长度为left right和new的组数
if c[m] > 0: ##m长度的组数大于0则更新步骤数
ans = i + 1
return ans
1563. 石子游戏 V
考点:dfs/动态规划
1、dfs解法
算法思想:
设 f(L,R) 为在 stoneValue[L:R] 这一排石子上可获得最大分数;
显然,f(1,N) 即为答案。那么 f(1,N) 咋求呢?递归呀~
终止条件:显然 L == R 时,可直接获得答案,为 0。那就把 L == R 作为终止条件。
递进阶段:从 L 到 R 枚举分割策略,然后根据题意保留 f(L,i) + sum(L,i),或者 f(i+1,R) + sum(i+1, R)中的最大值。
回归阶段:利用递进阶段获得的最优解,更新f(L,R)。
另外,因为求解过程中,会产生重复的子问题,所以需要通过记忆化的方法避免重复计算。
作者:Time-Limit
链接:https://leetcode-cn.com/problems/stone-game-v/solution/di-gui-ji-yi-hua-by-time-limit/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public:
int64_t dp[501][501]; // 记忆化数组,用于避免重复计算
int64_t sum[501];
int64_t dfs(int L, int R) {
if(dp[L][R] != -1) { //已经计算过该子问题了,直接范围答案
return dp[L][R];
}
if(L == R) { // 终止条件,直接获得答案
dp[L][R] = 0;
} else {
//递进阶段,根据题意,求解最大值;
int64_t val = 0;
for(int i = L; i< R; i++) {
int64_t s1 = sum[i] - sum[L-1];
int64_t s2 = sum[R] - sum[i];
if(s1 < s2) { // 根据题意,只能取后半段
val = max(val, s1 + dfs(L, i));
} else if(s1 > s2){ // 根据题意,只能取前半段
val = max(val, s2 + dfs(i+1, R));
} else { // 相等时,可任意选择~
val = max(val, max(dfs(L, i), dfs(i+1, R)) + s1);
}
}
//回归阶段,更新答案
dp[L][R] = val;
}
return dp[L][R];
}
int stoneGameV(vector& stoneValue) {
memset(dp, -1, sizeof(dp));
//出来一下前缀和
sum[0] = 0;
for(int i = 0; i < stoneValue.size(); i++) {
sum[i+1] = sum[i] + stoneValue[i];
}
return dfs(1, stoneValue.size());
}
};
2、动态规划
敬请稍候