当前位置: 首页 > news >正文

算法50:动态规划专练(力扣514题:自由之路-----4种写法)

题目: 力扣514 : 自由之路  . - 力扣(LeetCode)

题目的详细描述,直接打开力扣看就是了,下面说一下我对题目的理解:

事例1:

输入: ring = "godding", key = "gd"
输出: 4.

1. ring的第一个字符默认是指向12点方向的,这一点很重要

2. key的第一个字符为g,而ring中首字符和末尾字符都为g。因此,必然存在选择首字符的g还是末尾字符g的问题。

3. 即使ring中第一个字符为g,也还存在存在顺时针旋转和逆时针旋转的问题。(当然,当前案例比较极端,如果第一个字符不为g,理解起来更合适)

4. 这一题要求的是旋转的最小步数。因此,最终的值必然是获取最小值的。

5. 那么整体串起来分析:

        ring = "godding", key = "gd"

       

字符godding
下标0123456

a1. 如果ring的第一个g旋转,顺时针步数为0;逆时针步数为7;算上最终确认的1步,因此就               是在 1 和 8中选取最小值,选取1

a2. 接着a1继续分析,既然key的第一个字符已经确定了。那么key的第二个字符d又该选择                   了。我们到底是用下标为2的d呢,还是使用下标为3的d呢?

选择1: 下标为2的d,从a1的g顺时针旋转只需要2步,逆时针旋转需要5步,。算上确认的1步,就是在3和6中选取最小值,选取3

选择2: 下标为3的d, 从a1的g顺时针旋转只需要3步,逆时针旋转需要4步。算上确认的1步,就是在4和5中选取最小值. 选取 4

如果g使用的是ring中第一个字符,针对以上2种选择,最好的选择就是使用下标为2的d,顺时针旋转2步,即3步。  那么,总的代价就是 1 + 3 = 4。

开头,我们就说过,ring中有开头和结尾都存在g,因此存在选择的问题。

b1. 如果我们使用ring中下标为6的g最为key的开头。顺时针旋转需要1步,逆时针旋转需要6步,算上最终确认的1步,就是2步和7步。选取最小值2.

b2. 接着b1继续分析,既然key的第一个字符已经确定了。那么key的第二个字符d又该选择        了。我们到底是用下标为2的d呢,还是使用下标为3的d呢?

选择1: 下标为2的d,从b1的g顺时针旋转只需要4步,逆时针旋转需要3步,。算上确认的1步,就是在5和4中选取最小值,选取4

选择2: 下标为3的d, 从b1的g顺时针旋转只需要3步,逆时针旋转需要4步。算上确认的1步,就是在4和5中选取最小值. 选取4

如果g使用的是ring中最后一个字符,针对以上2种选择,最好都为4。  那么,总的代价就是 2 + 4 = 6。

最终,如果以ring中第一个g作为旋转选择,最小的步数为4;  以ring中最后一个g作为旋转选择,那么最小步数为6;  因此,当前案例最小步数为 Math.min(4, 6).

递归代码:

package 刷题.第三天;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 力扣514: 自由之路* https://leetcode.cn/problems/freedom-trail/*/
public class C2_FreeDomTtail_leet514_递归 {//递归版本public int findRotateSteps(String ring, String key) {if (ring == null || ring.length() == 0 || key == null || key.length() == 0) {return 0;}char[] source = ring.toCharArray();char[] target = key.toCharArray();//记录下每个字符的位置,有可能存在重复值的情况HashMap<Character, List> map = new HashMap<Character, List>();for (int i = 0; i < ring.length(); i++) {if (map.containsKey(source[i])) {//放入下标的位置map.get(source[i]).add(i);}else {List list = new ArrayList();list.add(i);map.put(source[i], list);}}return process(map, source, 0, target, 0);}public int process (Map<Character, List> map,char[] source, int sourceStartIndex,char[] target, int targetIndex) {if (targetIndex == target.length) {return 0;}List<Integer> ops = map.get(target[targetIndex]);int minStep = Integer.MAX_VALUE;for (int i = 0; i < ops.size(); i++) {//从sourceStartIndex 到 ops.get(i) 最下步数; +1是确认按钮耗费的1步int step = getMinSteps(sourceStartIndex, ops.get(i), source.length) + 1;//深度优先遍历; 此时source的的开始位置为 ops.get(i)minStep = Math.min(minStep, step + process(map, source, ops.get(i), target, targetIndex + 1));}return minStep;}//获取从最小长度public int getMinSteps(int start, int end, int size){//如果start < end, 则是顺时针; 反之, 逆时针int step1 = Math.abs(start - end);//如果step1是顺时针,那么step则是逆时针; 反之,顺时针int step2 = size - step1;return Math.min(step1, step2);}public static void main(String[] args) {C2_FreeDomTtail_leet514_递归 ss = new C2_FreeDomTtail_leet514_递归();String source = "godding";String target = "gd";System.out.println(ss.findRotateSteps(source, target));}
}

测试结果:

超时是好事情,说明整体逻辑大概率是没有问题的。超时,说明递归计算的次数有问题。上方的分析过程中,a2和b2 都是针对d进行逻辑判断的,明显存在重复的过程。那么,就需要在递归的基础之上添加缓存了,俗称记忆化搜索。

我之前在说动态规划的时候就说过,如果表结构依赖不严格,或者说即使依赖严格表结构,但是没有优化的空间。  递归 + 缓存 == 动态规划。

递归+缓存版本:

package 刷题.第三天;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 力扣514: 自由之路* https://leetcode.cn/problems/freedom-trail/*/
public class C2_FreeDomTtail_leet514_递归_缓存 {//递归 + 缓存public int findRotateSteps(String ring, String key) {if (ring == null || ring.length() == 0 || key == null || key.length() == 0) {return 0;}char[] source = ring.toCharArray();char[] target = key.toCharArray();//记录下每个字符的位置,有可能存在重复值的情况HashMap<Character, List> map = new HashMap<Character, List>();for (int i = 0; i < ring.length(); i++) {if (map.containsKey(source[i])) {//放入下标的位置map.get(source[i]).add(i);}else {List list = new ArrayList();list.add(i);map.put(source[i], list);}}int[][] dp = new int[source.length][target.length];for (int i = 0; i < source.length; i++) {for (int j = 0; j < target.length; j++) {//代表没算过dp[i][j] = -1;}}return process(map, source, 0, target, 0, dp);}public int process (Map<Character, List> map,char[] source, int sourceStartIndex,char[] target, int targetIndex,int[][] dp) {if (targetIndex == target.length) {return 0;}//缓存if (dp[sourceStartIndex][targetIndex] != -1) {return dp[sourceStartIndex][targetIndex];}List<Integer> ops = map.get(target[targetIndex]);int minStep = Integer.MAX_VALUE;for (int i = 0; i < ops.size(); i++) {//从sourceStartIndex 到 ops.get(i) 最下步数; +1是确认按钮耗费的1步int step = getMinSteps(sourceStartIndex, ops.get(i), source.length) + 1;//深度优先遍历; 此时source的的开始位置为 ops.get(i)minStep = Math.min(minStep, step + process(map, source, ops.get(i), target, targetIndex + 1, dp));dp[sourceStartIndex][targetIndex] = minStep;}return minStep;}//获取从最小长度public int getMinSteps(int start, int end, int size){//如果start < end, 则是顺时针; 反之, 逆时针int step1 = Math.abs(start - end);//如果step1是顺时针,那么step则是逆时针; 反之,顺时针int step2 = size - step1;return Math.min(step1, step2);}public static void main(String[] args) {C2_FreeDomTtail_leet514_递归_缓存 ss = new C2_FreeDomTtail_leet514_递归_缓存();String source = "godding";String target = "gd";System.out.println(ss.findRotateSteps(source, target));}
}

测试结果:

84%的胜率,8毫秒,已经相当优秀了。其实,这就是最优解

第三版本:纯动态规划

动态规划,那就需要分析递归的逻辑了。下面以ring作为行,以key作为列

第一步:

0 (g)1 (d)
0 (g)

下标0的g: 

顺时针:1步

逆时针:8步

选取1步

1 (o)
2 (d)

3 (d)

4 (i)
5 (n)
6 (g)

下标6的g :

顺时针:2步

逆时针:7步

选取2步

第二步:

0 (g)1 (d)
0 (g)

下标0的g: 1步

1 (o)
2 (d)

下标为2的d:

从下标为0的g过来,

顺时针2步,逆时针5步,

算上确认的1步,

就是3步和6步,选取小值,即3步

从下标为6的g过来,

顺时针3步,逆时针4步,

算上确认的1步,

就是4步和5步,选取小值,即4步

最终值就是:

1+3 和 2 +4选取小的。即4步

1是下标为0的g耗费的1步

2是下标为6的g耗费的2步

3 (d)

下标为3的d:

从下标为0的g过来,

顺时针3步,逆时针4步,

算上确认的1步,

就是4步和5步,选取小值,即4步

从下标为6的g过来,

顺时针4步,逆时针3步,

算上确认的1步,

就是5步和4步,选取小值,即4步

最终值就是:

1+4 和 2 +4选取小的。即5步

1是下标为0的g耗费的1步

2是下标为6的g耗费的2步

4 (i)
5 (n)
6 (g)下标6的g : 2步

因此,最终最小的步数就是当key遍历完最后一个字符得到,即 1 + 3 = 4步;

纯动态规划

package 刷题.第三天;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;/*** 力扣514: 自由之路* https://leetcode.cn/problems/freedom-trail/*/
public class C2_FreeDomTtail_leet514_动态规划 {//纯动态规划public int findRotateSteps(String ring, String key) {if (ring == null || ring.length() == 0 || key == null || key.length() == 0) {return 0;}char[] source = ring.toCharArray();char[] target = key.toCharArray();//记录下每个字符的位置,有可能存在重复值的情况HashMap<Character, List> map = new HashMap<Character, List>();for (int i = 0; i < ring.length(); i++) {if (map.containsKey(source[i])) {//放入下标的位置map.get(source[i]).add(i);}else {List list = new ArrayList();list.add(i);map.put(source[i], list);}}int[][] dp = new int[source.length][target.length + 1];//最终返回的最小值int finalMinStep = Integer.MAX_VALUE;//第一列List<Integer> ops = map.get(target[0]);for (int index : ops) {dp[index][0] = getMinSteps(0, index, source.length) + 1;//如果要拼接的key只有一个字符,直接获取最小值即可finalMinStep = Math.min(finalMinStep,  dp[index][0]);}//如果要拼接的字符长度超过1,那么finalMinStep的值需要//等到 target的最后一列才能确定if (target.length > 1) {finalMinStep = Integer.MAX_VALUE;}//列遍历,从第二列开始往后遍历for (int i = 1; i < target.length ; i++){//当前列对应的行信息List<Integer> ops2 = map.get(target[i]);//当前列前一列对应的行信息List<Integer> ops3 = map.get(target[i-1]);for (int j : ops2)  //结束{//j行i列的默认最小值int minStep = Integer.MAX_VALUE;for(int m : ops3) //开始{//从m行到j行的步数int curStep = getMinSteps(m, j, source.length) + 1;//dp[m][i-1] : 从0到m累计步数//dp[j][i-1] + curStep : 代表从0行到j行累计步数int steps = dp[m][i-1] + curStep;//更新j行i列的最小值minStep = Math.min(minStep, steps);dp[j][i] = minStep;}//要拼接字符串的最后一个字符,也就是说可以//已经全部拼接完了if (i == target.length - 1) {finalMinStep = Math.min(finalMinStep, dp[j][i]);}}}return finalMinStep;}//获取从最小长度public int getMinSteps(int start, int end, int size){//如果start < end, 则是顺时针; 反之, 逆时针int step1 = Math.abs(start - end);//如果step1是顺时针,那么step则是逆时针; 反之,顺时针int step2 = size - step1;return Math.min(step1, step2);}public static void main(String[] args) {C2_FreeDomTtail_leet514_动态规划 ss = new C2_FreeDomTtail_leet514_动态规划();/*String source = "godding";String target = "gd";*/String source = "eh";String target = "h";System.out.println(ss.findRotateSteps(source, target));}
}

测试结果:

10毫秒,76%胜率,也还行。

第四种解法,即官方解法。因为胜率没有我写的两个版本的高,我就不说了。

官方代码胜率:

相关文章:

算法50:动态规划专练(力扣514题:自由之路-----4种写法)

题目: 力扣514 &#xff1a; 自由之路 . - 力扣&#xff08;LeetCode&#xff09; 题目的详细描述&#xff0c;直接打开力扣看就是了&#xff0c;下面说一下我对题目的理解: 事例1&#xff1a; 输入: ring "godding", key "gd" 输出: 4. 1. ring的第…...

重学SpringBoot3-集成Thymeleaf

更多SpringBoot3内容请关注我的专栏&#xff1a;《SpringBoot3》 期待您的点赞&#x1f44d;收藏⭐评论✍ 重学SpringBoot3-集成Thymeleaf 1. 添加Thymeleaf依赖2. 配置Thymeleaf属性&#xff08;可选&#xff09;3. 创建Thymeleaf模板4. 创建一个Controller5. 运行应用并访问页…...

【数据可视化】Echarts最常用图表

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 前言2. 准备工作3. 柱状图3.1 绘制堆积柱状图3.2 绘制标准条形图3.3 绘制瀑布图 4. 折线图4.1 绘制堆积面积图和堆积折线图4.2 绘制阶梯图 5. 饼图5.1 绘制标准饼图5.2 绘制圆环图5.2 绘制嵌套饼图5.3 绘制南丁格尔…...

flink:通过table api把文件中读取的数据写入MySQL

当写入数据到外部数据库时&#xff0c;Flink 会使用 DDL 中定义的主键。如果定义了主键&#xff0c;则连接器将以 upsert 模式工作&#xff0c;否则连接器将以 append 模式工作 package cn.edu.tju.demo2;import org.apache.flink.streaming.api.environment.StreamExecutionE…...

【Java 多线程 哈希表】 HashTable, HashMap, ConcurrentHashMap 之间的区别

HashTable、HashMap和ConcurrentHashMap都是Java中用于存储键值对的集合框架的一部分&#xff0c;但它们之间存在一些重要的联系和区别。 联系 键值对存储&#xff1a;它们都用于存储键值对&#xff0c;并允许你根据键来检索值。基于哈希&#xff1a;它们内部都使用了哈希表来…...

有趣之matlab-烟花

待整合1 2 3 动态 有趣编程之11 静态 逼真 3 .m文件路径下放back1.jpg back4.jpg…背景照片 点击screen 就会有小白点升起&#xff0c;爆炸 function yanhuamoban()clear all;%定义全局变量global ah ;%坐标轴句柄global styleNum ;%爆炸图案样式global multiColor; %多颜色变换…...

C语言指针与数组(不适合初学者版):一篇文章带你深入了解指针与数组!

&#x1f388;个人主页&#xff1a;JAMES别扣了 &#x1f495;在校大学生一枚。对IT有着极其浓厚的兴趣 ✨系列专栏目前为C语言初阶、后续会更新c语言的学习方法以及c题目分享. &#x1f60d;希望我的文章对大家有着不一样的帮助&#xff0c;欢迎大家关注我&#xff0c;我也会回…...

springboot Mongo大数据查询优化方案

前言 因为项目需要把传感器的数据保存起来&#xff0c;当时设计的时是mongo来存储&#xff0c;后期需要从mongo DB里查询传感器的数据记录。由于传感器每秒都会像mongo数据库存500条左右的数据&#xff0c;1天就有4320万条数据&#xff0c;要想按照时间条件去查询&#xff0c;…...

Ollama管理本地开源大模型,用Open WebUI访问Ollama接口

现在开源大模型一个接一个的&#xff0c;而且各个都说自己的性能非常厉害&#xff0c;但是对于我们这些使用者&#xff0c;用起来就比较尴尬了。因为一个模型一个调用的方式&#xff0c;先得下载模型&#xff0c;下完模型&#xff0c;写加载代码&#xff0c;麻烦得很。 对于程…...

Linux--基本知识入门

一.几个基本知识 终端: CtrlAltT 或者桌面/文件夹右键,打开终端切换为管理员: sudo su 退出:exit查看内核版本号: uname -a内核版本号含义: 5 代表主版本号;13代表次版本号;0代表修订版本号;30代表修订版本的第几次微调;数字越大表示内核越新. 二.目录…...

基于springboot+vue实现的大学计算机课程管理平台的设计与实现(全套资料)

一、系统架构 前端&#xff1a;vue | antv 后端&#xff1a;springboot | mybatis-plus 环境&#xff1a;jdk17 | mysql | maven | node | redis 二、代码及数据库 三、功能介绍 01. 登录页 02. 首页 03. 系统基础模块-用户管理 04. 系统基础模块-部门…...

LeetCode2115. 从给定原材料中找到所有可以做出的菜

拓扑排序 题面 题目链接&#xff1a;2115. 从给定原材料中找到所有可以做出的菜 - 力扣&#xff08;LeetCode&#xff09; 你有 n 道不同菜的信息。给你一个字符串数组 recipes 和一个二维字符串数组 ingredients 。第 i 道菜的名字为 recipes[i] &#xff0c;如果你有它 所有…...

项目性能优化—性能优化的指标、目标

项目性能优化—性能优化的指标、目标 性能优化的终极目标是什么 性能优化的目标实际上是为了更好的用户体验&#xff1a; 一般我们认为用户体验是下面的公式&#xff1a; 用户体验 产品设计&#xff08;非技术&#xff09; 系统性能 ≈ 系统性能 快 那什么样的体验叫快呢…...

蓝桥杯刷题(三)

一、P8752 [蓝桥杯 2021 省 B2] 特殊年份&#xff08;洛谷&#xff09; 题目描述 今年是 2021 年&#xff0c;2021 这个数字非常特殊, 它的千位和十位相等, 个位比百位大 1&#xff0c;我们称满足这样条件的年份为特殊年份。 输入 5 个年份&#xff0c;请计算这里面有多少个…...

20240312-算法复习打卡day21||● 530.二叉搜索树的最小绝对差 ● 501.二叉搜索树中的众数 ● 236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差 1.中序遍历得到升序数组 class Solution { private:vector<int> vec;void traversal(TreeNode* root) {if (root NULL) return;if (root->left) traversal(root->left);vec.push_back(root->val);if (root->right) traversal(r…...

今天我们来学习一下关于MySQL数据库

目录 前言: 1.MySQL定义&#xff1a; 1.1基础概念&#xff1a; 1.1.1数据库&#xff08;Database&#xff09;&#xff1a; 1.1.2表&#xff08;Table&#xff09;&#xff1a; 1.1.3记录&#xff08;Record&#xff09;与字段&#xff08;Field&#xff09;&#xff1a; …...

长期护理保险可改善老年人心理健康 | CHARLS CLHLS CFPS 公共数据库周报(3.6)...

欢迎报名2024年“真实世界临床研究”课程&#xff01; 本周郑老师开讲&#xff1a;“真实世界临床研究”培训班&#xff0c;3月16-17日两天&#xff0c;欢迎报名&#xff01; CHARLS公共数据库‍ CHARLS数据库简介中国健康与养老追踪调查(China Health and Retirement Longitud…...

49、C++/友元、常成员函数和常对象、运算符重载学习20240314

一、封装类 用其成员函数实现&#xff08;对该类的&#xff09;数学运算符的重载&#xff08;加法&#xff09;&#xff0c;并封装一个全局函数实现&#xff08;对该类的&#xff09;数学运算符的重载&#xff08;减法&#xff09;。 代码&#xff1a; #include <iostream…...

SQL Server错误:15404

执行维护计划失败&#xff0c;提示SQL Server Error 15404 无法获取有关... 异常如下图&#xff1a; 原因&#xff1a;数据库用户名与计算机名称不一致 解决办法&#xff1a;1.重名称数据库用户名 将前缀改成计算机名 2.重启SQL Server代理...

Halcon文件操作

1、Region读写操作 region&#xff08;区域&#xff09;是一种重要的数据类型&#xff0c;用于表示图像中的特定区域。这些区域可以代表图像中的目标、感兴趣的区域、边缘、形状等等 read_image (Image, printer_chip/printer_chip_01) dev_open_window (0, 0, 512, 512, black…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

智慧工地云平台源码,基于微服务架构+Java+Spring Cloud +UniApp +MySql

智慧工地管理云平台系统&#xff0c;智慧工地全套源码&#xff0c;java版智慧工地源码&#xff0c;支持PC端、大屏端、移动端。 智慧工地聚焦建筑行业的市场需求&#xff0c;提供“平台网络终端”的整体解决方案&#xff0c;提供劳务管理、视频管理、智能监测、绿色施工、安全管…...

相机Camera日志实例分析之二:相机Camx【专业模式开启直方图拍照】单帧流程日志详解

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 目录 一、场景操作步骤 二、日志基础关键字分级如下 三、场景日志如下&#xff1a; 一、场景操作步骤 操作步…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

UDP(Echoserver)

网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法&#xff1a;netstat [选项] 功能&#xff1a;查看网络状态 常用选项&#xff1a; n 拒绝显示别名&#…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

【HTTP三个基础问题】

面试官您好&#xff01;HTTP是超文本传输协议&#xff0c;是互联网上客户端和服务器之间传输超文本数据&#xff08;比如文字、图片、音频、视频等&#xff09;的核心协议&#xff0c;当前互联网应用最广泛的版本是HTTP1.1&#xff0c;它基于经典的C/S模型&#xff0c;也就是客…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

AI+无人机如何守护濒危物种?YOLOv8实现95%精准识别

【导读】 野生动物监测在理解和保护生态系统中发挥着至关重要的作用。然而&#xff0c;传统的野生动物观察方法往往耗时耗力、成本高昂且范围有限。无人机的出现为野生动物监测提供了有前景的替代方案&#xff0c;能够实现大范围覆盖并远程采集数据。尽管具备这些优势&#xf…...