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

news2024/11/19 9:41:11

题目: 力扣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%胜率,也还行。

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

官方代码胜率:

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【网络原理】TCP 协议中比较重要的一些特性(一)

目录 1、TCP 协议 2、确认应答 2.1、确认序号 3、超时重传 4、连接管理 4.1、建立连接&#xff08;三次握手&#xff09; 4.2、断开连接&#xff08;四次挥手&#xff09; 1、TCP 协议 TCP 是工作中最常用到的协议&#xff0c;也是面试中最常考的协议&#xff0c;具有面…

spring-data-elasticsearch官方文档解读(部分)

Spring Data Elasticsearch 这里主要学习的是4.4.16版本的文档 1. 版本 下表显示了 Spring Data 发行版系列使用的 Elasticsearch 版本和其中包含的 Spring Data Elasticsearch 版本&#xff0c;以及引用该特定 Spring Data 发行版系列的 Spring Boot 版本。给出的 Elastics…

关于yolov8的DFL模块(pytorch以及tensorrt)

可以参考我改的项目&#xff0c;不过目前推理结果不对&#xff0c;还在修复&#xff1a; https://github.com/lindsayshuo/yolov8-cls-tensorrtx先看代码 class DFL(nn.Module):"""Integral module of Distribution Focal Loss (DFL).Proposed in Generalized…

视频占用内存太大了怎么办 如何快速又无损的压缩视频 快来学习吧

视频文件太大是很多人在使用电脑或移动设备时经常遇到的问题。如果视频文件过大&#xff0c;不仅会占用过多的存储空间&#xff0c;还会让播放和传输变得困难。为了解决这个问题&#xff0c;我们需要学会如何缩小视频文件大小。那么如何缩小储存视频的大小呢&#xff1f;下面给…

【JAVA】CSS2:样式、选择器、伪类、颜色、字体、边框、列表、背景、盒子、布局、浮动

本文介绍了CSS样式、选择器、伪类、像素、颜色、字体、边框、列表、表格属性、背景、盒子、布局与浮动 1.样式 1.1 行内样式 <h1 style"color: aqua;font-size: large;">123</h1> 1.2 内部样式 <style>h1{color: red;font: 100;}</style>…

高速电路顶级会议DesignCon 2019年会议总结和论文资料分享

会议基本介绍 DesignCon 2019年是一场专注于电子设计和信号完整性的国际会议&#xff0c;于在美国加利福尼亚州举行。 主题丰富&#xff1a;DesignCon 2019年聚焦于电子设计和信号完整性的多个领域&#xff0c;包括高速串行链接、射频和微波设计、功率完整性、模拟设计、信号…

Unity3d Shader篇(十三)— 透明度混合(AlphaBlend)

文章目录 前言一、什么是透明度混合&#xff1f;1. 透明度混合原理2. 透明度混合优缺点优点&#xff1a;缺点&#xff1a; 3. 纹理图 二、使用步骤1. Shader 属性定义2. SubShader 设置3. 渲染 Pass4. 定义结构体和顶点着色器函数5. 片元着色器函数 三、效果四、总结 前言 在计…

ArmSoM Rockchip系列产品 通用教程 之 PCIe 使用

1. PCIe 简介​ PCIe&#xff08;Peripheral Component Interconnect Express&#xff09;是一种用于连接主板和外部设备的高速串行接口标准。它是 PCI 技术的后继者&#xff0c;旨在提供更高的带宽和更好的性能。 高速传输&#xff1a; PCIe接口提供了高速的数据传输通道&am…

【STA】SRAM / DDR SDRAM 接口时序约束学习记录

1. SRAM接口 相比于DDR SDRAM&#xff0c;SRAM接口数据与控制信号共享同一时钟。在用户逻辑&#xff08;这里记作DUA&#xff08;Design Under Analysis&#xff09;&#xff09;将数据写到SRAM中去的写周期中&#xff0c;数据和地址从DUA传送到SRAM中&#xff0c;并都在有效时…

使用QEMU来模拟运行Linux系统

第一步&#xff1a;安装 执行命令 假设我们呢开发板需要arm64架构的Ubuntu 得通过apt-file去找&#xff0c;可以找到qemu-system-arm 所以直接按照qemu-system-arm就行了 apt-file list会列举这个软件包里所有的文件 这个命令可以列举出所有安装好的包名&#xff0c;有点像pip…

什么是PLC远程控制模块?

随着工业自动化的不断发展&#xff0c;可编程逻辑控制器&#xff08;PLC&#xff09;已成为现代工业设备中不可或缺的核心组件。然而&#xff0c;传统的PLC管理方式往往受限于现场操作和维护&#xff0c;难以满足日益复杂的工业需求。在这一背景下&#xff0c;PLC远程控制模块应…

【数据库系统概论】第2章:关系数据库

文章目录 0. 前言2.1 关系数据结构及形式化定义2.1.1关系2.1.2 关系模式 2.2 关系操作2.3 关系的完整性2.4 关系代数 0. 前言 关系数据库系统是支持关系模型的数据库系统。第一章初步介绍了关系模型及其基本术语。本章将深入介绍关系模型。 按照数据模型的三个要素&#xff0c;…

基础刷题50之五(重复的子字符串)

文章目录 前言一、题目二、力扣官方解释1、枚举2、字符串匹配 三、文心一言解释1、枚举2、字符串匹配 总结 前言 刚上研一&#xff0c;有人劝我好好学C&#xff0c;当时用的不多就没学&#xff0c;现在毕业上班了。在此亡羊补牢了 在此感谢力扣和文心一言 一、题目 给定一个…

Python图像处理【22】基于卷积神经网络的图像去雾

基于卷积神经网络的图像去雾 0. 前言1. 渐进特征融合网络2. 图像去雾2.1 网络构建2.2 模型测试 小结系列链接 0. 前言 单图像去雾 (dehazing) 是一个具有挑战性的图像恢复问题。为了解决这个问题&#xff0c;大多数算法都采用经典的大气散射模型&#xff0c;该模型是一种基于单…

ECharts饼图图例消失踩的坑

在使用Echarts的饼图时&#xff0c;当时做法是在图例数小于8时显示全部的图例&#xff0c;在大于8的时候显示前8个图例。于是用了两种不同的方式处理。导致出现切换时间后图例不显示的情况。 错误过程&#xff1a; 在进行图例生成时采用了两种不同的方式&#xff1a; ①如果…

Redis底层源码分析系列(前提准备)

文章目录 一、 面试题二、 源码分析1. 源码导入2. 源码核心部分 一、 面试题 1. redis跳跃列表了解吗&#xff1f;这个数据结构有什么缺点&#xff1f; 2. redis项目里面怎么用&#xff1f; redis的数据结构都了解哪些&#xff1f; 3. redis的zset底层实现&#xff1f; redi…

深入理解Servlet

目录&#xff1a; ServletWeb开发历史Servlet简介Servlet技术特点Servlet在应用程序中的位置Tomcat运行过程Servlet继承结构Servlet生命周期Servlet处理请求的原理Servlet的作用HttpServletRequest对象HttpServletResponse对象ServletContext对象ServletConfig对象Cookie对象与…

Constrained Iterative LQR 自动驾驶中使用的经典控制算法

Motion planning 运动规划在自动驾驶领域是一个比较有挑战的部分。它既要接受来自上层的行为理解和决策的输出,也要考虑一个包含道路结构和感知所检测到的所有障碍物状态的动态世界模型。最终生成一个满足安全性和可行性约束并且具有理想驾驶体验的轨迹。 通常,motion plann…

微信小程序开发系列(二十八)·小程序API如何发送网络请求以及网络请求失败后的解决方法

目录 1. 小程序API介绍 2. 网络请求 2.1 网络请求失败解决方法 2.2 如何跳过域名校验 1. 小程序API介绍 小程序开发框架提供丰富的微信原生API&#xff0c;可以方便的调起微信提供的能力&#xff0c;例如&#xff1a;获取用户信息、微信登录、微信支付等&#xff0c;小…

“antd“: Unknown word.cSpell

你遇到的问题是 VS Code 的 Code Spell Checker 插件在检查拼写时&#xff0c;将 "antd" 标记为未知单词。"antd" 是 Ant Design 的缩写&#xff0c;是一个流行的 React UI 库&#xff0c;不是一个英语单词&#xff0c;所以 Spell Checker 会将其标记为错误…