文章目录
- [一. 力扣 [713. 乘积小于 K 的子数组](https://leetcode.cn/problems/subarray-product-less-than-k/description/)](#一. 力扣 713. 乘积小于 K 的子数组)
-
- [1. 题目解析](#1. 题目解析)
- [2. 滑动窗口算法原理](#2. 滑动窗口算法原理)
- [3. 二分+对数+前缀和+双指针](#3. 二分+对数+前缀和+双指针)
- [3. 代码](#3. 代码)
- [二. 2024秦皇岛-Problem D](#二. 2024秦皇岛-Problem D)
-
- [1. 题目解析](#1. 题目解析)
- [2. 算法原理](#2. 算法原理)
- [3. 代码](#3. 代码)
一. 力扣 713. 乘积小于 K 的子数组
1. 题目解析
题意简单明了, 成绩小于指定的k, 子数组指的是连续的区间
2. 滑动窗口算法原理
3. 二分+对数+前缀和+双指针
3. 代码
滑动窗口代码👇
java
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
int l = 0, r = 0, mul = 1, ret = 0;
// k为1, 可以直接返回0, 因为这里乘积最小值为1, 且numd[i]最小值也是1
if (k <= 1) return 0;
while (r < nums.length) {
mul *= nums[r];
while (mul >= k) {
mul /= nums[l];
l++;
}
ret += r - l + 1;
r++;
}
return ret;
}
}
二分+双指针代码👇
java
class Solution {
public int numSubarrayProductLessThanK(int[] nums, int k) {
if (k <= 1) {
return 0;
}
int n = nums.length;
double[] dp = new double[n + 1];
for (int i = 1; i <= n; i++) {
dp[i] = dp[i - 1] + Math.log(nums[i - 1]);
}
double logk = Math.log(k);
int ret = 0;
for (int j = 0; j < n; j++) {
int l = 0; // l左端点可能为0
int r = j; // r最大就是右端点
int i = j + 1;// 左端点的最坏情况,nums[j]本身也不满足
double t = dp[j + 1] - logk + 1e-10;
while (l <= r) {
int mid = l + (r - l) / 2;
if (dp[mid] > t) {
i = mid;
r = mid - 1;
}else {
l = mid + 1;
}
}
ret += j - i + 1;
}
return ret;
}
}
二. 2024秦皇岛-Problem D
小编总结了两天的超详细解析
1. 题目解析
- 题目很好理解, 重要的是对于示例的分析, 这道题的示例给的可以说很恶心
- 猫条可以类比为修改次数(交换次数)
2. 算法原理
这道题用到的算法思想还是较多的, 动态规划 + 正难则反 + 贡献计数
- 首先是步骤一: 贡献计数, 即如何给定字符串中的优美子序列
- 步骤二: 将问题转化, 反向思考, 在构建字符串过程中统计优美子序列
- 重头戏: 动态规划, 要理解其中 i , j, k 分别对应的含义
- 兄弟们可能有疑惑的地方, 为什么要从0遍历k
3. 代码
java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.Arrays;
import java.util.StringTokenizer;
public class My_Main {
// 封装从左面挑选两个的方法
static int select2(int left) {
return (left) * (left - 1) / 2;
}
public static void main(String[] args) throws Exception {
// 读取输入
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st = new StringTokenizer(br.readLine());
char[] cc = st.nextToken().toCharArray();
st = new StringTokenizer(br.readLine());
int m = Integer.valueOf(st.nextToken());
int n = cc.length;
// 统计字符c的数量 和 记录c的下标
int C = 0;
for (char ch : cc) if (ch == 'c') C++;
int P = n - C;
int idx = 1;
int[] pos = new int[C + 1];
//我们要填的是下一个数, 即i+1这个数, 因此i从0开始遍历,
for (int i = 0; i < n; i++) {
if (cc[i] == 'c') {
pos[idx++] = i + 1; // 让index从1开始, 同时每个index绑定的下标也右移一位, 即原本cc[0]位置为c, 对应的就是pos[1] = 1
}
}
// 建立dp表
int[][][] dp = new int[n + 1][C + 1][m + 1];
// 初始化
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= C; j++) {
Arrays.fill(dp[i][j], -1);
}
}
dp[0][0][0] = 0;
for (int i = 0; i < n; i++) {
// j 表示的是c的个数,超过i或者C都没有意义, 因此选择较小的那一个
for (int j = 0; j <= Math.min(i, C); j++) {
for (int k = 0; k <= m; k++) {
// 当前位置j和k不能满足,满足的话会被提前填写
if (dp[i][j][k] < 0) continue;
// 放p,先判断是否有剩余
if (i - j < P) {
int addP = select2(j) * (C - j);
dp[i + 1][j][k] = Math.max(dp[i + 1][j][k], dp[i][j][k] + addP);
}
// 放c, j<C才有意义, 才可以放c
if (j < C) {
int addC = select2(i - j) * (P - (i - j));
// 求总代价
int cost = k + Math.abs(i + 1 - pos[j + 1]);
if (cost <= m) {
dp[i + 1][j + 1][cost] = Math.max(dp[i + 1][j + 1][cost], dp[i][j][k] + addC);
}
}
}
}
}
int ret = 0;
for (int k = m; k >= 0; k -= 2) {
ret = Math.max(dp[n][C][k], ret);
}
System.out.println(ret);
}
}







