算法奇妙屋(五十)-二分与双指针的结合 + 2024秦皇岛-Problem D

文章目录

  • [一. 力扣 [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);
	}
}
相关推荐
love在水一方7 小时前
【Voxel-SLAM】 体素地图与Bundle Adjustment算法深度分析(四)
人工智能·算法·机器学习
阿Y加油吧7 小时前
二刷 LeetCode:198. 打家劫舍 & 279. 完全平方数 复盘笔记
笔记·算法·leetcode
承渊政道8 小时前
【动态规划算法】(子序列问题解题框架与典型案例)
数据结构·c++·学习·算法·leetcode·macos·动态规划
_F_y8 小时前
仿RabbitMQ实现消息队列-服务端核心模块实现(3)
c++·算法·rabbitmq
m0_629494738 小时前
LeetCode 热题 100-----15.轮转数组
数据结构·算法·leetcode
AI科技星8 小时前
从180°旋转定值π、e论证时空宿命与未来可预测性—全域数学视角
人工智能·算法·机器学习·数学建模·数据挖掘
WL_Aurora8 小时前
Python 算法基础篇之栈和队列
python·算法
艺术电影节8 小时前
惊喜映后 | 伍迪·艾伦经典修复澳门首映
算法·推荐算法·电视
Brilliantwxx8 小时前
【C++】初步认识STL(3)
开发语言·c++·笔记·算法