class083 动态规划中用观察优化枚举的技巧-下【算法】

class083 动态规划中用观察优化枚举的技巧-下【算法】

算法讲解083【必备】动态规划中用观察优化枚举的技巧-下

在这里插入图片描述

code1 1235. 规划兼职工作

// 规划兼职工作
// 你打算利用空闲时间来做兼职工作赚些零花钱,这里有n份兼职工作
// 每份工作预计从startTime[i]开始、endTime[i]结束,报酬为profit[i]
// 返回可以获得的最大报酬
// 注意,时间上出现重叠的 2 份工作不能同时进行
// 如果你选择的工作在时间X结束,那么你可以立刻进行在时间X开始的下一份工作
// 测试链接 : https://leetcode.cn/problems/maximum-profit-in-job-scheduling/

利用观察单调性 + 二分搜索的方式优化枚举,最优解时间复杂度O(n*logn)

dp[i]:[0…i]的最大报酬
不需要i位置的工作:dp[i-1]
需要i位置的工作:profit[i]+dp[j]:j是结束时间不超过startTime[i]的最右的

package class083;

import java.util.Arrays;

// 规划兼职工作
// 你打算利用空闲时间来做兼职工作赚些零花钱,这里有n份兼职工作
// 每份工作预计从startTime[i]开始、endTime[i]结束,报酬为profit[i]
// 返回可以获得的最大报酬
// 注意,时间上出现重叠的 2 份工作不能同时进行
// 如果你选择的工作在时间X结束,那么你可以立刻进行在时间X开始的下一份工作
// 测试链接 : https://leetcode.cn/problems/maximum-profit-in-job-scheduling/
public class Code01_MaximumProfitInJobScheduling {

	public static int MAXN = 50001;

	public static int[][] jobs = new int[MAXN][3];

	public static int[] dp = new int[MAXN];

	public static int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
		int n = startTime.length;
		for (int i = 0; i < n; i++) {
			jobs[i][0] = startTime[i];
			jobs[i][1] = endTime[i];
			jobs[i][2] = profit[i];
		}
		// 工作按照结束时间从小到大排序
		Arrays.sort(jobs, 0, n, (a, b) -> a[1] - b[1]);
		dp[0] = jobs[0][2];
		for (int i = 1, start; i < n; i++) {
			start = jobs[i][0];
			dp[i] = jobs[i][2];
			if (jobs[0][1] <= start) {
				dp[i] += dp[find(i - 1, start)];
			}
			dp[i] = Math.max(dp[i], dp[i - 1]);
		}
		return dp[n - 1];
	}

	// job[0...i]范围上,找到结束时间 <= start,最右的下标
	public static int find(int i, int start) {
		int ans = 0;
		int l = 0;
		int r = i;
		int m;
		while (l <= r) {
			m = (l + r) / 2;
			if (jobs[m][1] <= start) {
				ans = m;
				l = m + 1;
			} else {
				r = m - 1;
			}
		}
		return ans;
	}

}

code2 629. K 个逆序对数组

// K个逆序对数组
// 逆序对的定义如下:
// 对于数组nums的第i个和第j个元素
// 如果满足0<=i<j<nums.length 且 nums[i]>nums[j],则为一个逆序对
// 给你两个整数n和k,找出所有包含从1到n的数字
// 且恰好拥有k个逆序对的不同的数组的个数
// 由于答案可能很大,只需要返回对10^9+7取余的结果
// 测试链接 : https://leetcode.cn/problems/k-inverse-pairs-array/

最优解 利用观察 + 构造窗口累加和,已经进入 观察并设计高效的查询结构 的范畴了
只不过这个结构就仅存在于概念,并用一个int类型的变量维护而已

package class083;

// K个逆序对数组
// 逆序对的定义如下:
// 对于数组nums的第i个和第j个元素
// 如果满足0<=i<j<nums.length 且 nums[i]>nums[j],则为一个逆序对
// 给你两个整数n和k,找出所有包含从1到n的数字
// 且恰好拥有k个逆序对的不同的数组的个数
// 由于答案可能很大,只需要返回对10^9+7取余的结果
// 测试链接 : https://leetcode.cn/problems/k-inverse-pairs-array/
public class Code02_KInversePairsArray {

	// 最普通的动态规划
	// 不优化枚举
	public static int kInversePairs1(int n, int k) {
		int mod = 1000000007;
		// dp[i][j] : 1、2、3...i这些数字,形成的排列一定要有j个逆序对,请问这样的排列有几种
		int[][] dp = new int[n + 1][k + 1];
		dp[0][0] = 1;
		for (int i = 1; i <= n; i++) {
			dp[i][0] = 1;
			for (int j = 1; j <= k; j++) {
				if (i > j) {
					for (int p = 0; p <= j; p++) {
						dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod;
					}
				} else {
					// i <= j
					for (int p = j - i + 1; p <= j; p++) {
						dp[i][j] = (dp[i][j] + dp[i - 1][p]) % mod;
					}
				}
			}
		}
		return dp[n][k];
	}

	// 根据观察方法1优化枚举
	// 最优解
	// 其实可以进一步空间压缩
	// 有兴趣的同学自己试试吧
	public static int kInversePairs2(int n, int k) {
		int mod = 1000000007;
		int[][] dp = new int[n + 1][k + 1];
		dp[0][0] = 1;
		// window : 窗口的累加和
		for (int i = 1, window; i <= n; i++) {
			dp[i][0] = 1;
			window = 1;
			for (int j = 1; j <= k; j++) {
				if (i > j) {
					window = (window + dp[i - 1][j]) % mod;
				} else {
					// i <= j
					window = ((window + dp[i - 1][j]) % mod - dp[i - 1][j - i] + mod) % mod;
				}
				dp[i][j] = window;
			}
		}
		return dp[n][k];
	}

}

code3 514. 自由之路

// 自由之路
// 题目描述比较多,打开链接查看
// 测试链接 : https://leetcode.cn/problems/freedom-trail/

优化枚举的核心是贪心策略:
下一步做枚举时,不需要枚举所有可能性,只需要枚举 顺时针的最近、逆时针的最近 两种可能性即可

贪心当然会有专题讲述!【必备】课程的动态规划专题结束了,就会开始贪心的专题

package class083;

// 自由之路
// 题目描述比较多,打开链接查看
// 测试链接 : https://leetcode.cn/problems/freedom-trail/
public class Code03_FreedomTrail {

	// 为了让所有语言的同学都可以理解
	// 不会使用任何java语言自带的数据结构
	// 只使用最简单的数组结构
	public static int MAXN = 101;

	public static int MAXC = 26;

	public static int[] ring = new int[MAXN];

	public static int[] key = new int[MAXN];

	public static int[] size = new int[MAXC];

	public static int[][] where = new int[MAXC][MAXN];

	public static int[][] dp = new int[MAXN][MAXN];

	public static int n, m;

	public static void build(String r, String k) {
		for (int i = 0; i < MAXC; i++) {
			size[i] = 0;
		}
		n = r.length();
		m = k.length();
		for (int i = 0, v; i < n; i++) {
			v = r.charAt(i) - 'a';
			where[v][size[v]++] = i;
			ring[i] = v;
		}
		for (int i = 0; i < m; i++) {
			key[i] = k.charAt(i) - 'a';
		}
		for (int i = 0; i < n; i++) {
			for (int j = 0; j < m; j++) {
				dp[i][j] = -1;
			}
		}
	}

	public static int findRotateSteps(String r, String k) {
		build(r, k);
		return f(0, 0);
	}

	// 指针当前指着轮盘i位置的字符,要搞定key[j....]所有字符,最小代价返回
	public static int f(int i, int j) {
		if (j == m) {
			// key长度是m
			// 都搞定
			return 0;
		}
		if (dp[i][j] != -1) {
			return dp[i][j];
		}
		int ans;
		if (ring[i] == key[j]) {
			// ring b
			//      i
			// key  b
			//      j
			ans = 1 + f(i, j + 1);
		} else {
			// 轮盘处在i位置,ring[i] != key[j]
			// jump1 : 顺时针找到最近的key[j]字符在轮盘的什么位置
			// distance1 : 从i顺时针走向jump1有多远
			int jump1 = clock(i, key[j]);
			int distance1 = (jump1 > i ? (jump1 - i) : (n - i + jump1));
			// jump2 : 逆时针找到最近的key[j]字符在轮盘的什么位置
			// distance2 : 从i逆时针走向jump2有多远
			int jump2 = counterClock(i, key[j]);
			int distance2 = (i > jump2 ? (i - jump2) : (i + n - jump2));
			ans = Math.min(distance1 + f(jump1, j), distance2 + f(jump2, j));
		}
		dp[i][j] = ans;
		return ans;
	}

	// 从i开始,顺时针找到最近的v在轮盘的什么位置
	public static int clock(int i, int v) {
		int l = 0;
		// size[v] : 属于v这个字符的下标有几个
		int r = size[v] - 1, m;
		// sorted[0...size[v]-1]收集了所有的下标,并且有序
		int[] sorted = where[v];
		int find = -1;
		// 有序数组中,找>i尽量靠左的下标
		while (l <= r) {
			m = (l + r) / 2;
			if (sorted[m] > i) {
				find = m;
				r = m - 1;
			} else {
				l = m + 1;
			}
		}
		// 找到了就返回
		// 没找到,那i顺指针一定先走到最小的下标
		return find != -1 ? sorted[find] : sorted[0];
	}

	public static int counterClock(int i, int v) {
		int l = 0;
		int r = size[v] - 1, m;
		int[] sorted = where[v];
		int find = -1;
		// 有序数组中,找<i尽量靠右的下标
		while (l <= r) {
			m = (l + r) / 2;
			if (sorted[m] < i) {
				find = m;
				l = m + 1;
			} else {
				r = m - 1;
			}
		}
		// 找到了就返回
		// 没找到,那i逆指针一定先走到最大的下标
		return find != -1 ? sorted[find] : sorted[size[v] - 1];
	}

}

code4 未排序数组中累加和小于或等于给定值的最长子数组长度

// 累加和不大于k的最长子数组
// 给定一个无序数组arr,长度为n,其中元素可能是正、负、0
// 给定一个整数k,求arr所有的子数组中累加和不大于k的最长子数组长度
// 要求时间复杂度为O(n)
// 测试链接 : https://www.nowcoder.com/practice/3473e545d6924077a4f7cbc850408ade
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过

强烈推荐先看一下讲解046

利用构造单调数组 + 二分搜索的解不是最优解,时间复杂度O(n*logn)

最优解中包含的贪心思想(窗口的加速建立、可能性的淘汰),是这个题的重点,时间复杂度O(n)

贪心当然会有专题讲述!【必备】课程的动态规划专题结束了,就会开始贪心的专题

package class083;

// 累加和不大于k的最长子数组
// 给定一个无序数组arr,长度为n,其中元素可能是正、负、0
// 给定一个整数k,求arr所有的子数组中累加和不大于k的最长子数组长度
// 要求时间复杂度为O(n)
// 测试链接 : https://www.nowcoder.com/practice/3473e545d6924077a4f7cbc850408ade
// 请同学们务必参考如下代码中关于输入、输出的处理
// 这是输入输出处理效率很高的写法
// 提交以下的code,提交时请把类名改成"Main",可以直接通过

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StreamTokenizer;

// 至今的最优解,全网题解几乎都是我几年前讲的方法
public class Code04_LongestSubarraySumNoMoreK {

	public static int MAXN = 100001;

	public static int[] nums = new int[MAXN];

	public static int[] minSums = new int[MAXN];

	public static int[] minSumEnds = new int[MAXN];

	public static int n, k;

	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		StreamTokenizer in = new StreamTokenizer(br);
		PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
		while (in.nextToken() != StreamTokenizer.TT_EOF) {
			n = (int) in.nval;
			in.nextToken();
			k = (int) in.nval;
			for (int i = 0; i < n; i++) {
				in.nextToken();
				nums[i] = (int) in.nval;
			}
			out.println(compute2());
		}
		out.flush();
		out.close();
		br.close();
	}

	public static int compute1() {
		int[] sums = new int[n + 1];
		for (int i = 0, sum = 0; i < n; i++) {
			// sum : 0...i范围上,这前i+1个数字的累加和
			sum += nums[i];
			// sums[i + 1] : 前i+1个,包括一个数字也没有的时候,所有前缀和中的最大值
			sums[i + 1] = Math.max(sum, sums[i]);
		}
		int ans = 0;
		for (int i = 0, sum = 0, pre, len; i < n; i++) {
			sum += nums[i];
			pre = find(sums, sum - k);
			len = pre == -1 ? 0 : i - pre + 1;
			ans = Math.max(ans, len);
		}
		return ans;
	}

	public static int find(int[] sums, int num) {
		int l = 0;
		int r = n;
		int m = 0;
		int ans = -1;
		while (l <= r) {
			m = (l + r) / 2;
			if (sums[m] >= num) {
				ans = m;
				r = m - 1;
			} else {
				l = m + 1;
			}
		}
		return ans;
	}

	public static int compute2() {
		minSums[n - 1] = nums[n - 1];
		minSumEnds[n - 1] = n - 1;
		for (int i = n - 2; i >= 0; i--) {
			if (minSums[i + 1] < 0) {
				minSums[i] = nums[i] + minSums[i + 1];
				minSumEnds[i] = minSumEnds[i + 1];
			} else {
				minSums[i] = nums[i];
				minSumEnds[i] = i;
			}
		}
		int ans = 0;
		for (int i = 0, sum = 0, end = 0; i < n; i++) {
			while (end < n && sum + minSums[end] <= k) {
				sum += minSums[end];
				end = minSumEnds[end] + 1;
			}
			if (end > i) {
				// 如果end > i,
				// 窗口范围:i...end-1,那么窗口有效
				ans = Math.max(ans, end - i);
				sum -= nums[i];
			} else {
				// 如果end == i,那么说明窗口根本没扩出来,代表窗口无效
				// end来到i+1位置,然后i++了
				// 继续以新的i位置做开头去扩窗口
				end = i + 1;
			}
		}
		return ans;
	}

}

2023-12-10 12:31:44

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/246941.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

安装2023最新版Java SE 21.0.1来开发Java应用程序

安装2023最新版Java SE 21.0.1来开发Java应用程序 Install the latest version of Java SE 21.01 to Develop Java Applications By JacksonML 本文简要介绍如何下载和安装2023年最新版Java Development Kit (简称JDK&#xff0c;即Java开发工具包标准版&#xff09;21.0.1&…

“一键调整尺寸,轻松完成视频批量剪辑:批量放大视频尺寸“

你是否曾经遇到过需要批量调整视频尺寸的情况&#xff1f;无论是为了适应不同的播放平台&#xff0c;还是为了满足客户的特定需求&#xff0c;批量调整视频尺寸都是一项繁琐而耗时的工作。但是&#xff0c;现在有一种方法可以让你轻松完成这项任务&#xff0c;那就是使用我们的…

[已解决]HttpMessageNotReadableException: JSON parse error: Unexpected character:解析JSON时出现异常的问题分析与解决方案

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

linux的定时任务Corntab

安装crontab # yum安装crontab yum install -y crontab# 开机自启crond服务并现在启动 systemctl enable --now crondcron系统任务调度 系统任务调度&#xff1a; 系统周期性所要执行的工作&#xff0c;比如写缓存数据到硬盘、日志清理等。 在/etc/crontab文件&#xff0c;这…

【IEEE】2区SCI,接收领域广,稳定检索47年!

重点 本期推荐 区块链是一种新兴技术&#xff0c;很多行业和领域都以创新方式采用了此技术&#xff0c;如能源、金融、媒体和娱乐以及零售等。此外&#xff0c;区块链作为一门新兴的交叉学科, 涉及密码学应用&#xff08;加密&#xff0c;隐私等&#xff09;&#xff0c; 分布式…

圈子社交文化系统,了解生活,更了解你!APP小程序H5三端源码交付,支持二开!

在这个快节奏的时代&#xff0c;圈子社交系统成为了我生活中不可或缺的一部分。通过这个系统&#xff0c;我不仅可以结识到志同道合的朋友&#xff0c;还可以参与各种有趣的活动和发布自己的心情和见解。在这个圈子里&#xff0c;我感受到了无限的可能性和温暖的人性。 首先&am…

MySQL如何进行Sql优化

&#xff08;1&#xff09;客户端发送一条查询语句到服务器&#xff1b; &#xff08;2&#xff09;服务器先查询缓存&#xff0c;如果命中缓存&#xff0c;则立即返回存储在缓存中的数据&#xff1b; &#xff08;3&#xff09;未命中缓存后&#xff0c;MySQL通过关键字将SQ…

leetcode 69. x 的平方根(优质解法)

代码&#xff1a; class Solution {public int mySqrt(int x) {long left0;long rightx;while (left<right){long midleft(right-left1)/2;//注意乘法操作和加法操作都很容易发生溢出if(mid*mid<x){leftmid;}else {rightmid-1;}}return (int)left;} } 题解&#xff1a;…

FM30H12G N通道沟槽电源MOS管 封装形式PDFN5*6

FM30H12G 是一款 N通道沟槽电源的场效应管&#xff08;MOS管&#xff09;&#xff0c;封装形式&#xff1a;PDFN5*6。 来百度APP畅享高清图片 FM30H12G应用&#xff1a; 1、液晶电视 2、笔记本 3、电梯 4、感应加热 5、电动工具

基于JAVA的校园电子商城系统论文

摘 要 网络技术和计算机技术发展至今&#xff0c;已经拥有了深厚的理论基础&#xff0c;并在现实中进行了充分运用&#xff0c;尤其是基于计算机运行的软件更是受到各界的关注。加上现在人们已经步入信息时代&#xff0c;所以对于信息的宣传和管理就很关键。因此校园购物信息的…

Linux 非阻塞网络IO模式

非阻塞网络IO模式介绍 当用户线程发起一个 read 操作后&#xff0c;并不需要等待&#xff0c;而是马上就得到了一个结果。如果结果是一个 error 时&#xff0c;它就知道数据还没有准备好&#xff0c;于是它可以再次发送 read 操作。一旦内核中的数据准备好了&#xff0c;并且又…

如何用CHAT写启发感想

问CHAT&#xff1a;写篇劳动教育课程给我带来的启发和影响 CHAT回复&#xff1a;自从我参加了学校的劳动教育课程&#xff0c;我深深地意识到劳动的重要性。这门课程通过让我们体验和认识各种劳动形式&#xff0c;提醒我去珍惜每一份来之不易的成果。接下来我想分享几个显著的观…

IntelliJ IDEA无公网环境远程访问Linux服务器进行开发

文章目录 1. 检查Linux SSH服务2. 本地连接测试3. Linux 安装Cpolar4. 创建远程连接公网地址5. 公网远程连接测试6. 固定连接公网地址7. 固定地址连接测试 本文主要介绍如何在IDEA中设置远程连接服务器开发环境&#xff0c;并结合Cpolar内网穿透工具实现无公网远程连接&#xf…

C++数据类型以及函数设计学习记录

一、C类型转换原则 当表达式中出现了多种类型数据的混合运算时&#xff0c;往往需要进行类型转换。表达式中的类型转换分为两种&#xff1a;隐式转换和显式转换&#xff0c;但此处仅对隐式转换进行总结。 隐式转换分为算术转换和其它隐式类型转换两大类&#xff0c;其它隐式类型…

智慧港口解决方案:PPT全文69页,附下载

关键词&#xff1a;智慧港口解决方案&#xff0c;数字化港口&#xff0c;智慧港口发展现状与展望&#xff0c;智慧码头&#xff0c;智慧港口发展趋势 一、智慧港口建设背景 随着数字经济、智慧交通发展&#xff0c;强调“要大力发展智慧交通和智慧物流”“努力打造世界一流的…

京东方将取代三星显示,引领可折叠面板市场 | 百能云芯

在智能手机市场蓬勃扩张的浪潮中&#xff0c;中国面板制造巨头京东方将在2023年第4季超越三星显示&#xff0c;引领可折叠面板市场。 据市调公司DSCC数据显示&#xff0c;今年下半年&#xff0c;原本独占鳌头的三星电子在可折叠手机市场的份额将从去年同期的86%降至72%&#xf…

一些好用的VSCode扩展

可以在扩展这里直接搜索需要的扩展&#xff0c;点击安装即可。 1.Chinese 中文扩展&#xff0c;就是说虽然咱们懂点英语&#xff0c;但还是中文看着方便 2.Auto Rename Tag 当你重命名一个HTML 标签时&#xff0c;会自动重命名与他配对的HTML 标签 当你选择h4这个标签时&…

PPT插件-好用的插件-放映笔、绘图板-大珩助手

放映笔 幻灯片放映时&#xff0c;工具在幻灯片的左下方&#xff0c;本工具在幻灯片的右侧&#xff0c;可以移动&#xff0c;可以方便在右侧讲课时候使用 绘图板 可在绘图板上写签名、绘制图画、写字等等&#xff0c;点画笔切换橡皮擦&#xff0c;点插入绘图&#xff0c;将背景…

用什么样的开源流程表单实现办公流程化?

近日&#xff0c;有不少热心网友询问道&#xff1a;如果要实现流程化办公&#xff0c;让整个办公效率火速提升上来&#xff0c;可以用什么样的开源流程表单工具&#xff1f;大伙都知道&#xff0c;随着低代码开发平台的盛行&#xff0c;办公效率也得到很大的提升&#xff0c;它…

【LeetCode: 2415. 反转二叉树的奇数层 | BFS + DFS】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…