精华内容
下载资源
问答
  • 近三年前的篇旧文,虽然在浏览器和相关技术标准方面有过时之处,但总体上对概念的理解、开发的思路和代码样例仍然有参考价值。背景当Javascript被Netscape公司发明出来时,它被用来做一些琐细的事情,比如校验表单...
    展开全文
  • Hibernate一级缓存和二级缓存详解

    万次阅读 多人点赞 2014-10-14 09:02:38
    一、一级缓存二级缓存的概念解释 (1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个 session(一定要同一个session)又做了同一个操作,...

    一、一级缓存二级缓存的概念解释

    (1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个

    session(一定要同一个session)又做了同一个操作,那么hibernate直接从一级缓存中拿,而不会再去连数据库,取数据;

    (2)二级缓存就是SessionFactory级别的缓存,顾名思义,就是查询的时候会把查询结果缓存到二级缓存中,如果同一个sessionFactory

    创建的某个session执行了相同的操作,hibernate就会从二级缓存中拿结果,而不会再去连接数据库;

    (3)Hibernate中提供了两级Cache,第一级别的缓存是Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理

    的,一般情况下无需进行干预;第二级别的缓存是SessionFactory级别的缓存,它是属于进程范围或群集范围的缓存。这一级别的缓

    存可以进行配置和更改,并且可以动态加载和卸载。 Hibernate还为查询结果提供了一个查询缓存,它依赖于第二级缓存;


    二、一级缓存和二级缓存的比较


    (1)第一级缓存 第二级缓存 存放数据的形式相互关联的持久化对象 对象的散装数据 缓存的范围事务范围,每个事务都有单独的第一级

    缓存进程范围或集群范围,缓存被同一个进程或集群范围内的所有事务共享并发访问策略由于每个事务都拥有单独的第一级缓存,不

    会出现并发问题,无需提供并发访问策略由于多个事务会同时访问第二级缓存中相同数据,因此必须提供适当的并发访问策略,来保

    证特定的事务隔离级别数据过期策略没有提供数据过期策略。

    (2)处于一级缓存中的对象永远不会过期,除非应用程序显式清空缓存或者

    清除特定的对象必须提供数据过期策略,如基于内存的缓存中的对象的最大数目,允许对象处于缓存中的最长时间,以及允许对象处

    于缓存中的最长空闲时间物理存储介质内存内存和硬盘。

    (3)对象的散装数据首先存放在基于内存的缓存中,当内存中对象的数目达到数

    据过期策略中指定上限时,就会把其余的对象写入基于硬盘的缓存中。

    (4)缓存的软件实现在Hibernate的Session的实现中包含了缓存的

    实现由第三方提供,Hibernate仅提供了缓存适配器(CacheProvider)。用于把特定的缓存插件集成到Hibernate中。

    (5)启用缓存的方式

    只要应用程序通过Session接口来执行保存、更新、删除、加载和查询数据库数据的操作,Hibernate就会启用第一级缓存,把数据库

    中的数据以对象的形式拷贝到缓存中,对于批量更新和批量删除操作,如果不希望启用第一级缓存,可以绕过Hibernate API,直接

    通过JDBC API来执行指操作。

    (6)用户可以在单个类或类的单个集合的粒度上配置第二级缓存。如果类的实例被经常读但很少被修改,就

    可以考虑使用第二级缓存。

    (7)只有为某个类或集合配置了第二级缓存,Hibernate在运行时才会把它的实例加入到第二级缓存中。

    用户管理缓存的方式第一级缓存的物理介质为内存,由于内存容量有限,必须通过恰当的检索策略和检索方式来限制加载对象的数目。

    Session的 evit()方法可以显式清空缓存中特定对象,但这种方法不值得推荐。第二级缓存的物理介质可以是内存和硬盘,因此第二

    级缓存可以存放大量的数据,数据过期策略的maxElementsInMemory属性值可以控制内存中的对象数目。

    (8)管理第二级缓存主要包括两个方面:选择需要使用第二级缓存的持久类,设置合适的并发访问策略:选择缓存适配器,设置合适的数据过期策略。


    三、 一级缓存的管理


    (1)当应用程序调用Session的save()、update()、savaeOrUpdate()、get()或load(),以及调用查询接口的 list()、iterate()或

    filter()方法时,如果在Session缓存中还不存在相应的对象,Hibernate就会把该对象加入到第一级缓存中。当清理缓存时,

    Hibernate会根据缓存中对象的状态变化来同步更新数据库。 Session为应用程序提供了两个管理缓存的方法: evict(Object obj)

    :从缓存中清除参数指定的持久化对象。 clear():清空缓存中所有持久化对象。

    (2)save、update、saveOrupdate、load、list、iterate、lock会向一级缓存存放数据;

    save 案例:
    	//添加一个学生
    			Student student=new Student();
    			student.setName("小东");
    			
    			s.save(student);//放入一级缓存
    			
    			//我马上查询
    			Student stu2=(Student) s.get(Student.class, student.getId()); //select
    			System.out.println("你刚刚加入的学生名字是"+stu2.getName());

    (3)什么操作会从一级缓存取数据get、load、list

     

    get / load 会首先从一级缓存中取,如没有.再有不同的操作[get 会立即向数据库发请求,而load 会返回一个代理对象,直到用户真的去使用数据,才会向数据库发请求

    //查询45号学生
    		
    			Student stu=(Student) s.get(Student.class, 45);
    			
    			System.out.println("|||||||||||||||||||");
    			
    			String hql="from Student where id=45";
    			
    			
    			Student stu2=(Student) s.createQuery(hql).uniqueResult();
    			
    			System.out.println(stu2.getName());

    从上面的案例,我们看出 query.list() query.uniueResut() 不会从一级缓取数据但是query.list 或者query.uniqueRestu() 会向一级缓存放数据的.

    注意:

    ① 一级缓存不需要配置,就可以使用,它本身没有保护机制,所以我们程序员要考虑这个问题,我们可以同 evict 或者 clear来清除session缓存中对象. evict 是清除一个对象,clear是清除所有的sesion缓存对象

    ② session级缓存中对象的生命周期session关闭后,就自动销毁.

    ③ 我们自己用HashMap来模拟一个Session缓存,加深对缓存的深入.

    四、Hibernate二级缓存的管理


    1. Hibernate二级缓存策略的一般过程如下:

    1) 条件查询的时候,总是发出一条select * from table_name where …. (选择所有字段)这样的SQL语句查询数据库,一次获得所有的数据对象。

    2) 把获得的所有数据对象根据ID放入到第二级缓存中。

    3) 当Hibernate根据ID访问数据对象的时候,首先从Session一级缓存中查;查不到,如果配置了二级缓存,那么从二级缓存中查;查不到,再查询数据库,把结果按照ID放入到缓存。

    4) 删除、更新、增加数据的时候,同时更新缓存。

    Hibernate二级缓存策略,是针对于ID查询的缓存策略,对于条件查询则毫无作用。为此,Hibernate提供了针对条件查询的Query Cache。

    5) 二级缓存的对象可能放在内存,也可能放在磁盘.


    2. 什么样的数据适合存放到第二级缓存中? 

    1) 很少被修改的数据 

    2) 不是很重要的数据,允许出现偶尔并发的数据 

    3) 不会被并发访问的数据 

    4) 参考数据,指的是供应用参考的常量数据,它的实例数目有限,它的实例会被许多其他类的实例引用,实例极少或者从来不会被修改。

    3. 不适合存放到第二级缓存的数据? 

    1) 经常被修改的数据 

    2) 财务数据,绝对不允许出现并发 

    3) 与其他应用共享的数据。

    4. 常用的缓存插件 Hibernater二级缓存是一个插件,下面是几种常用的缓存插件:

    ◆EhCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,对Hibernate的查询缓存提供了支持。

    ◆OSCache:可作为进程范围的缓存,存放数据的物理介质可以是内存或硬盘,提供了丰富的缓存数据过期策略,对Hibernate的查询

    缓存提供了支持。

    ◆SwarmCache:可作为群集范围内的缓存,但不支持Hibernate的查询缓存。

    ◆JBossCache:可作为群集范围内的缓存,支持事务型并发访问策略,对Hibernate的查询缓存提供了支持。


    5. 配置Hibernate二级缓存的主要步骤:

    1) 选择需要使用二级缓存的持久化类,设置它的命名缓存的并发访问策略。这是最值得认真考虑的步骤。

    2) 选择合适的缓存插件,然后编辑该插件的配置文件。

    <property name="hbm2ddl.auto">update</property>
    	<!-- 启动二级缓存 -->
    	<property name="cache.use_second_level_cache">true</property>
    	<!-- 指定使用哪种二级缓存 -->
    	<property name="cache.provider_class">org.hibernate.cache.OSCacheProvider</property>
    	<mapping resource="com/hsp/domain/Department.hbm.xml" />
    	<mapping resource="com/hsp/domain/Student.hbm.xml" />
    	<!-- 指定哪个domain启用二级缓存 
    	特别说明二级缓存策略:
    	1. read-only
    	2. read-write
    	3. nonstrict-read-write
    	4. transcational
    	-->
    	<class-cache class="com.hsp.domain.Student" usage="read-write"/>

    3)可以把oscache.properties文件放在 src目录下,这样你可以指定放入二级缓存的对象capacity 大小默认1000

    6.使用二级缓存:

    // TODO Auto-generated method stub
    		//通过获取一个sesion,让hibernate框架运行(config->加载hibernate.cfg.xml)
    		Session s=null;
    		Transaction tx=null;
    		
    		try {
    			//我们使用基础模板来讲解.
    			s=HibernateUtil.openSession();
    			tx=s.beginTransaction();
    			
    			//查询45号学生
    		
    			Student stu1=(Student) s.get(Student.class, 45);//45->一级缓存		
    			System.out.println(stu1.getName());
    			
    			
    			tx.commit();
    			
    		} catch (Exception e) {
    			e.printStackTrace();
    			if(tx!=null){
    				tx.rollback();
    			}
    		}finally{
    			
    			if(s!=null && s.isOpen()){
    				s.close();
    			}
    		}
    		
    		System.out.println("*********************************");
    		try {
    			//我们使用基础模板来讲解.
    			s=HibernateUtil.openSession();
    			tx=s.beginTransaction();
    			
    			//查询45号学生
    		
    			Student stu1=(Student) s.get(Student.class, 45);	
    			System.out.println(stu1.getName());
    			
    			Student stu3=(Student) s.get(Student.class, 46);	
    			System.out.println(stu3.getName());
    				tx.commit();
    			
    		} catch (Exception e) {
    			e.printStackTrace();
    			if(tx!=null){
    				tx.rollback();
    			}
    		}finally{
    			
    			if(s!=null && s.isOpen()){
    				s.close();
    			}
    		}
    		
    		//完成一个统计,统计的信息在Sessfactory
    		//SessionFactory对象.
    		Statistics statistics= HibernateUtil.getSessionFactory().getStatistics();
    		System.out.println(statistics);
    		System.out.println("放入"+statistics.getSecondLevelCachePutCount());
    		System.out.println("命中"+statistics.getSecondLevelCacheHitCount());
    		System.out.println("错过"+statistics.getSecondLevelCacheMissCount());

    在配置了二级缓存后,请大家要注意可以通过 Statistics,查看你的配置命中率高不高



    展开全文
  • 说实话,我觉得很难,特别是对于初学者来说,我当时入门动态规划的时候,是看 0-1 背包问题,当时真的是脸懵逼。后来,我遇到动态规划的题,看的懂答案,但就是自己不会做,不知道怎么下手。就像做递归的题,看的...

    动态规划难吗?说实话,我觉得很难,特别是对于初学者来说,我当时入门动态规划的时候,是看 0-1 背包问题,当时真的是一脸懵逼。后来,我遇到动态规划的题,看的懂答案,但就是自己不会做,不知道怎么下手。就像做递归的题,看的懂答案,但下不了手,关于递归的,我之前也写过一篇套路的文章,如果对递归不大懂的,强烈建议看一看:为什么你学不会递归,告别递归,谈谈我的经验

    对于动态规划,春招秋招时好多题都会用到动态规划,一气之下,再 leetcode 连续刷了几十道题

    在这里插入图片描述

    之后,豁然开朗 ,感觉动态规划也不是很难,今天,我就来跟大家讲一讲,我是怎么做动态规划的题的,以及从中学到的一些套路。相信你看完一定有所收获

    如果你对动态规划感兴趣,或者你看的懂动态规划,但却不知道怎么下手,那么我建议你好好看以下,这篇文章的写法,和之前那篇讲递归的写法,是差不多一样的,将会举大量的例子。如果一次性看不完,建议收藏,同时别忘了素质三连

    为了兼顾初学者,我会从最简单的题讲起,后面会越来越难,最后面还会讲解,该如何优化。因为 80% 的动规都是可以进行优化的。不过我得说,如果你连动态规划是什么都没听过,可能这篇文章你也会压力山大。

    一、动态规划的三大步骤

    动态规划,无非就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。下面我们先来讲下做动态规划题很重要的三个步骤,

    如果你听不懂,也没关系,下面会有很多例题讲解,估计你就懂了。之所以不配合例题来讲这些步骤,也是为了怕你们脑袋乱了

    第一步骤:定义数组元素的含义,上面说了,我们会用一个数组,来保存历史数组,假设用一维数组 dp[] 吧。这个时候有一个非常非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思?

    第二步骤:找出数组元素之间的关系式,我觉得动态规划,还是有一点类似于我们高中学习时的归纳法的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2]…dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步,后面我会讲几种类型的题来说。

    学过动态规划的可能都经常听到最优子结构,把大的问题拆分成小的问题,说时候,最开始的时候,我是对最优子结构一梦懵逼的。估计你们也听多了,所以这一次,我将换一种形式来讲,不再是各种子问题,各种最优子结构。所以大佬可别喷我再乱讲,因为我说了,这是我自己平时做题的套路。

    第三步骤:找出初始值。学过数学归纳法的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但是,我们得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我们必须要能够直接获得 dp[2] 和 dp[1] 的值,而这,就是所谓的初始值

    由了初始值,并且有了数组元素之间的关系式,那么我们就可以得到 dp[n] 的值了,而 dp[n] 的含义是由你来定义的,你想求什么,就定义它是什么,这样,这道题也就解出来了。

    不懂?没事,我们来看三四道例题,我讲严格按这个步骤来给大家讲解。

    二、案例详解

    案例一、简单的一维 DP

    问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

    (1)、定义数组元素的含义

    按我上面的步骤说的,首先我们来定义 dp[i] 的含义,我们的问题是要求青蛙跳上 n 级的台阶总共由多少种跳法,那我们就定义 dp[i] 的含义为:跳上一个 i 级的台阶总共有 dp[i] 种跳法。这样,如果我们能够算出 dp[n],不就是我们要求的答案吗?所以第一步定义完成。

    (2)、找出数组元素间的关系式

    我们的目的是要求 dp[n],动态规划的题,如你们经常听说的那样,就是把一个规模比较大的问题分成几个规模比较小的问题,然后由小的问题推导出大的问题。也就是说,dp[n] 的规模为 n,比它规模小的是 n-1, n-2, n-3… 也就是说,dp[n] 一定会和 dp[n-1], dp[n-2]…存在某种关系的。我们要找出他们的关系。

    那么问题来了,怎么找?

    这个怎么找,是最核心最难的一个,我们必须回到问题本身来了,来寻找他们的关系式,dp[n] 究竟会等于什么呢?

    对于这道题,由于情况可以选择跳一级,也可以选择跳两级,所以青蛙到达第 n 级的台阶有两种方式

    一种是从第 n-1 级跳上来

    一种是从第 n-2 级跳上来

    由于我们是要算所有可能的跳法的,所以有 dp[n] = dp[n-1] + dp[n-2]。

    (3)、找出初始条件

    当 n = 1 时,dp[1] = dp[0] + dp[-1],而我们是数组是不允许下标为负数的,所以对于 dp[1],我们必须要直接给出它的数值,相当于初始值,显然,dp[1] = 1。一样,dp[0] = 0.(因为 0 个台阶,那肯定是 0 种跳法了)。于是得出初始值:

    dp[0] = 0.
    dp[1] = 1.
    即 n <= 1 时,dp[n] = n.

    三个步骤都做出来了,那么我们就来写代码吧,代码会详细注释滴。

    int f( int n ){
    	if(n <= 1)
    	return n;
    	// 先创建一个数组来保存历史数据
    	int[] dp = new int[n+1];
    	// 给出初始值
    	dp[0] = 0;
    	dp[1] = 1;
    	// 通过关系式来计算出 dp[n]
    	for(int i = 2; i <= n; i++){
    		dp[i] = dp[i-1] + dp[-2];
    	}
    	// 把最终结果返回
    	return dp[n];
    }
    
    (4)、再说初始化

    大家先想以下,你觉得,上面的代码有没有问题?

    答是有问题的,还是错的,错在对初始值的寻找不够严谨,这也是我故意这样弄的,意在告诉你们,关于初始值的严谨性。例如对于上面的题,当 n = 2 时,dp[2] = dp[1] + dp[0] = 1。这显然是错误的,你可以模拟一下,应该是 dp[2] = 2。

    也就是说,在寻找初始值的时候,一定要注意不要找漏了,dp[2] 也算是一个初始值,不能通过公式计算得出。有人可能会说,我想不到怎么办?这个很好办,多做几道题就可以了。

    下面我再列举三道不同的例题,并且,再在未来的文章中,我也会持续按照这个步骤,给大家找几道有难度且类型不同的题。下面这几道例题,不会讲的特性详细哈。实际上 ,上面的一维数组是可以把空间优化成更小的,不过我们现在先不讲优化的事,下面的题也是,不讲优化版本。

    案例二:二维数组的 DP

    我做了几十道 DP 的算法题,可以说,80% 的题,都是要用二维数组的,所以下面的题主要以二维数组为主,当然有人可能会说,要用一维还是二维,我怎么知道?这个问题不大,接着往下看。

    问题描述

    一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

    机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

    问总共有多少条不同的路径?

    在这里插入图片描述

    这是 leetcode 的 62 号题:https://leetcode-cn.com/problems/unique-paths/

    还是老样子,三个步骤来解决。

    步骤一、定义数组元素的含义

    由于我们的目的是从左上角到右下角一共有多少种路径,那我们就定义 dp[i] [j]的含义为:当机器人从左上角走到(i, j) 这个位置时,一共有 dp[i] [j] 种路径。那么,dp[m-1] [n-1] 就是我们要的答案了。

    注意,这个网格相当于一个二维数组,数组是从下标为 0 开始算起的,所以 右下角的位置是 (m-1, n - 1),所以 dp[m-1] [n-1] 就是我们要找的答案。

    步骤二:找出关系数组元素间的关系式

    想象以下,机器人要怎么样才能到达 (i, j) 这个位置?由于机器人可以向下走或者向右走,所以有两种方式到达

    一种是从 (i-1, j) 这个位置走一步到达

    一种是从(i, j - 1) 这个位置走一步到达

    因为是计算所有可能的步骤,所以是把所有可能走的路径都加起来,所以关系式是 dp[i] [j] = dp[i-1] [j] + dp[i] [j-1]。

    步骤三、找出初始值

    显然,当 dp[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [0….n-1] 和所有的 dp[0….m-1] [0]。这个还是非常容易计算的,相当于计算机图中的最上面一行和左边一列。因此初始值如下:

    dp[0] [0….n-1] = 1; // 相当于最上面一行,机器人只能一直往左走

    dp[0…m-1] [0] = 1; // 相当于最左面一列,机器人只能一直往下走

    撸代码

    三个步骤都写出来了,直接看代码

    public static int uniquePaths(int m, int n) {
        if (m <= 0 || n <= 0) {
            return 0;
        }
    
        int[][] dp = new int[m][n]; // 
      	// 初始化
      	for(int i = 0; i < m; i++){
          dp[i][0] = 1;
        }
      	for(int i = 0; i < n; i++){
          dp[0][i] = 1;
        }
    		// 推导出 dp[m-1][n-1]
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
    

    O(n*m) 的空间复杂度可以优化成 O(min(n, m)) 的空间复杂度的,不过这里先不讲

    案例三、二维数组 DP

    写到这里,有点累了,,但还是得写下去,所以看的小伙伴,你们可得继续看呀。下面这道题也不难,比上面的难一丢丢,不过也是非常类似

    问题描述

    给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

    **说明:**每次只能向下或者向右移动一步。

    举例:
    输入:
    arr = [
      [1,3,1],
      [1,5,1],
      [4,2,1]
    ]
    输出: 7
    解释: 因为路径 1→3→1→1→1 的总和最小。
    

    和上面的差不多,不过是算最优路径和,这是 leetcode 的第64题:https://leetcode-cn.com/problems/minimum-path-sum/

    还是老样子,可能有些人都看烦了,哈哈,但我还是要按照步骤来写,让那些不大懂的加深理解。有人可能觉得,这些题太简单了吧,别慌,小白先入门,这些属于 medium 级别的,后面在给几道 hard 级别的。

    步骤一、定义数组元素的含义

    由于我们的目的是从左上角到右下角,最小路径和是多少,那我们就定义 dp[i] [j]的含义为:当机器人从左上角走到(i, j) 这个位置时,最下的路径和是 dp[i] [j]。那么,dp[m-1] [n-1] 就是我们要的答案了。

    注意,这个网格相当于一个二维数组,数组是从下标为 0 开始算起的,所以 由下角的位置是 (m-1, n - 1),所以 dp[m-1] [n-1] 就是我们要走的答案。

    步骤二:找出关系数组元素间的关系式

    想象以下,机器人要怎么样才能到达 (i, j) 这个位置?由于机器人可以向下走或者向右走,所以有两种方式到达

    一种是从 (i-1, j) 这个位置走一步到达

    一种是从(i, j - 1) 这个位置走一步到达

    不过这次不是计算所有可能路径,而是计算哪一个路径和是最小的,那么我们要从这两种方式中,选择一种,使得dp[i] [j] 的值是最小的,显然有

    dp[i] [j] = min(dp[i-1][j],dp[i][j-1]) + arr[i][j];// arr[i][j] 表示网格种的值
    
    步骤三、找出初始值

    显然,当 dp[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [0….n-1] 和所有的 dp[0….m-1] [0]。这个还是非常容易计算的,相当于计算机图中的最上面一行和左边一列。因此初始值如下:

    dp[0] [j] = arr[0] [j] + dp[0] [j-1]; // 相当于最上面一行,机器人只能一直往左走

    dp[i] [0] = arr[i] [0] + dp[i] [0]; // 相当于最左面一列,机器人只能一直往下走

    代码如下
    public static int uniquePaths(int[][] arr) {
      	int m = arr.length;
      	int n = arr[0].length;
        if (m <= 0 || n <= 0) {
            return 0;
        }
    
        int[][] dp = new int[m][n]; // 
      	// 初始化
      	dp[0][0] = arr[0][0];
      	// 初始化最左边的列
      	for(int i = 1; i < m; i++){
          dp[i][0] = dp[i-1][0] + arr[i][0];
        }
      	// 初始化最上边的行
      	for(int i = 1; i < n; i++){
          dp[0][i] = dp[0][i-1] + arr[0][i];
        }
    		// 推导出 dp[m-1][n-1]
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + arr[i][j];
            }
        }
        return dp[m-1][n-1];
    }
    

    O(n*m) 的空间复杂度可以优化成 O(min(n, m)) 的空间复杂度的,不过这里先不讲

    案例 4:编辑距离

    这次给的这道题比上面的难一些,在 leetcdoe 的定位是 hard 级别。好像是 leetcode 的第 72 号题。

    问题描述

    给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。

    你可以对一个单词进行如下三种操作:

    插入一个字符
    删除一个字符
    替换一个字符

    输入: word1 = "horse", word2 = "ros"
    输出: 3
    解释: 
    horse -> rorse (将 'h' 替换为 'r')
    rorse -> rose (删除 'r')
    rose -> ros (删除 'e')
    

    解答

    还是老样子,按照上面三个步骤来,并且我这里可以告诉你,90% 的字符串问题都可以用动态规划解决,并且90%是采用二维数组。

    步骤一、定义数组元素的含义

    由于我们的目的求将 word1 转换成 word2 所使用的最少操作数 。那我们就定义 dp[i] [j]的含义为:当字符串 word1 的长度为 i,字符串 word2 的长度为 j 时,将 word1 转化为 word2 所使用的最少操作次数为 dp[i] [j]

    有时候,数组的含义并不容易找,所以还是那句话,我给你们一个套路,剩下的还得看你们去领悟。

    步骤二:找出关系数组元素间的关系式

    接下来我们就要找 dp[i] [j] 元素之间的关系了,比起其他题,这道题相对比较难找一点,但是,不管多难找,大部分情况下,dp[i] [j] 和 dp[i-1] [j]、dp[i] [j-1]、dp[i-1] [j-1] 肯定存在某种关系。因为我们的目标就是,从规模小的,通过一些操作,推导出规模大的。对于这道题,我们可以对 word1 进行三种操作

    插入一个字符
    删除一个字符
    替换一个字符

    由于我们是要让操作的次数最小,所以我们要寻找最佳操作。那么有如下关系式:

    一、如果我们 word1[i] 与 word2 [j] 相等,这个时候不需要进行任何操作,显然有 dp[i] [j] = dp[i-1] [j-1]。(别忘了 dp[i] [j] 的含义哈)。

    二、如果我们 word1[i] 与 word2 [j] 不相等,这个时候我们就必须进行调整,而调整的操作有 3 种,我们要选择一种。三种操作对应的关系试如下(注意字符串与字符的区别):

    (1)、如果把字符 word1[i] 替换成与 word2[j] 相等,则有 dp[i] [j] = dp[i-1] [j-1] + 1;

    (2)、如果在字符串 word1末尾插入一个与 word2[j] 相等的字符,则有 dp[i] [j] = dp[i] [j-1] + 1;

    (3)、如果把字符 word1[i] 删除,则有 dp[i] [j] = dp[i-1] [j] + 1;

    那么我们应该选择一种操作,使得 dp[i] [j] 的值最小,显然有

    dp[i] [j] = min(dp[i-1] [j-1],dp[i] [j-1],dp[[i-1] [j]]) + 1;

    于是,我们的关系式就推出来了,

    步骤三、找出初始值

    显然,当 dp[i] [j] 中,如果 i 或者 j 有一个为 0,那么还能使用关系式吗?答是不能的,因为这个时候把 i - 1 或者 j - 1,就变成负数了,数组就会出问题了,所以我们的初始值是计算出所有的 dp[0] [0….n] 和所有的 dp[0….m] [0]。这个还是非常容易计算的,因为当有一个字符串的长度为 0 时,转化为另外一个字符串,那就只能一直进行插入或者删除操作了。

    代码如下
    public int minDistance(String word1, String word2) {
        int n1 = word1.length();
        int n2 = word2.length();
        int[][] dp = new int[n1 + 1][n2 + 1];
        // dp[0][0...n2]的初始值
        for (int j = 1; j <= n2; j++) 
        	dp[0][j] = dp[0][j - 1] + 1;
        // dp[0...n1][0] 的初始值
        for (int i = 1; i <= n1; i++) dp[i][0] = dp[i - 1][0] + 1;
    		// 通过公式推出 dp[n1][n2]
        for (int i = 1; i <= n1; i++) {
            for (int j = 1; j <= n2; j++) {
              	// 如果 word1[i] 与 word2[j] 相等。第 i 个字符对应下标是 i-1
                if (word1.charAt(i - 1) == word2.charAt(j - 1)){
                	p[i][j] = dp[i - 1][j - 1];
                }else {
                   dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]), dp[i - 1][j]) + 1;
                }         
            }
        }
        return dp[n1][n2];  
    }
    

    最后说下,如果你要练习,可以去 leetcode,选择动态规划专题,然后连续刷几十道,保证你以后再也不怕动态规划了。当然,遇到很难的,咱还是得挂。

    Leetcode 动态规划直达:https://leetcode-cn.com/tag/dynamic-programming/

    三、总结

    上面的这些题,基本都是不怎么难的入门题,除了最后一道相对难一点,本来是要在写几道难一点,并且讲如何优化的,不过看了下字数,文章有点长了,关于如何优化的,后面再讲吧,在之后的文章中,我也会按照这个步骤,在给大家讲四五道动态规划 hard 级别的题,会放在每天推文的第二条给大家学习。如果大家感兴趣,文章看的人多,那么优化篇很快就会撸出来,大家想要第一时间观看我的文章的,可以关注我的个人微信公众号『苦逼的码农』,不过感兴趣的人很少的话,动力比较少,可能就会慢一些,所以各位小伙伴,如果觉得有收获,不放三连走起来,嘻嘻。

    有收获?希望老铁们来个三连击,给更多的人看到这篇文章

    1、给俺点个赞呗,可以让更多的人看到这篇文章,顺便激励下我,嘻嘻。

    2、老铁们,关注我的原创微信公众号「帅地玩编程」,专注于写算法 + 计算机基础知识(计算机网络+ 操作系统+数据库+Linux)。

    保存让你看完有所收获,不信你打我。后台回复『电子书』送你一份精选电子书大礼包,包含各类技能的优质电子书。

    展开全文
  • 最小编辑距离问题:递归与动态规划#106 Open youngwindopened this issueon 14 Jun 2017· 13 comments Comments Assignees No one assigned Labels 算法 Projects None yet Milestone No milestone 8 ...

    最小编辑距离问题:递归与动态规划 #106

     Open

    youngwind opened this issue on 14 Jun 2017 · 13 comments

    Comments

    Assignees

    No one assigned

    Labels

    算法

    Projects

    None yet

    Milestone

    No milestone

    8 participants

    @youngwind@shadeofgod@MrErHu@wangning0@jiajianrong@wanhmr@demonbibi@liduo1997love

    @youngwind

    Owner

    youngwind commented on 14 Jun 2017 • 

    edited 

    继上一篇 #104 ,本来是在研究虚拟 DOM 算法的,看到了 livoras 的这篇文章,觉得写得很好!珠玉在前,我便不再赘述了。该文章中提到:列表更新本质上是一个最小编辑距离问题,不过并未就此详细展开。今天,我们来具体看看这个问题。

    问题

    给定两个字符串 a 和 b,只允许以下三种操作:

    1. 插入一个字符;
    2. 删除一个字符;
    3. 替换一个字符。

    求:把 a 转换成 b 的最小操作次数,也就是所谓的最小编辑距离。
    举例: "xy" => "xz",只需要把 y 替换成 z,因此,最小编辑距离为 1。
    "xyz" => "xy",只需要删除 z ,因此,最小编辑距离为 1。

    求解这个问题,一般有两种思路:递归和动态规划。

    递归

    所谓递归,便是把大问题”分治“成类似的小问题。
    假设,a 的长度是 m,b 的长度是 n,要求 a[1]a[2]...a[m] => b[1]b[2]...b[n] 的最小编辑距离,记为 d[m][n]。

    1. 如果 a[m] === b[n],那么问题转化为求解:a[1]a[2]...a[m-1] => b[1]b[2]...b[n-1] 的最小编辑距离,因此 d[m][n] === d[m-1][n-1]。比如,"xyz" => "pqz" 的最小编辑距离等于 "xy" => "pq" 的最小编辑距离。
    2. 如果 a[m] !== b[n],又分为三种情况:
      1. 比如,"xyz" => "efg" 的最小编辑距离等于 "xy" => "efg" 的最小编辑距离 + 1(因为允许插入操作,插入一个 "z"),抽象的描述便是 d[m][n] === d[m-1][n] + 1。
      2. 比如,"xyz" => "efg" 的最小编辑距离等于 "xyzg" => "efg" 的最小编辑距离 + 1,且因为最后一个字符都是 "g",根据第一个判断条件,可以再等于 "xyz" => "ef" 的最小编辑距离 + 1,因此,得到结论:"xyz" => "efg" 的最小编辑距离等于 "xyz" => "ef" 的最小编辑距离 + 1,抽象的描述便是:d[m][n] === d[m][n-1] + 1。
      3. 比如,"xyz" => "efg" 的最小编辑距离等于 "xyg" => "efg" 的最小编辑距离 + 1(因为允许替换操作,可以把 "g" 换成 "z"),再等于 "xy" => "ef" 的编辑距离 + 1(根据第一个判断条件),抽象的描述便是: d[m][n] === d[m-1][n-1] + 1。
      4. 上述三种情况都有可能出现,因此,取其中的最小值便是整体上的最小编辑距离。
    3. 如果 a 的长度为 0,那么 a => b 的最小编辑距离为 b 的长度;反过来,如果 b 的长度为 0,那么 a => b 的最小编辑距离为 a 的长度。

    代码实现

    /**
     * 递归算法
     * @param {string} a
     * @param {string} b
     * @param {number} i 字符串 a 的长度
     * @param {number} j 字符串 b 的长度
     * @returns {number} 从 a → b 的最小编辑距离
     */
    function recursion(a, b, i, j) {
        if (j === 0) {
            return i;
        } else if (i === 0) {
            return j;
        } else if (a[i - 1] === b [j - 1]) {
            return recursion(a, b, i - 1, j - 1);
        } else {
            let m1 = recursion(a, b, i - 1, j) + 1;
            let m2 = recursion(a, b, i, j - 1) + 1;
            let m3 = recursion(a, b, i - 1, j - 1) + 1;
            return Math.min(m1, m2, m3);
        }
    }

    动态规划

    动态规划看起来跟递归很像,不过推理逻辑正好是反过来的。递归的逻辑是:“要求得 d[m][n],先要求得 d[m-1][n-1]……”,动态规划的逻辑是:“先求得 d[m-1][n-1],再求 d[m][n]……”这是它们的主要区别。
    举个例子,在已知 d[0][0],d[0][1],d[1][0] 的前提下,要求 d[1][1]:

    1. 如果 a[1] === b[1],那么 d[1][1] 等于 d[0][0],也就是 0;
    2. 如果 a[1] !== b[1],那么 d[1][1] 等于 d[0][1]、d[1][0] 和 d[0][0] 三者中的最小值 + 1,也就是 1。

    接着用同样的方式,可以求得 d[1][2]、d[1][3]、……、d[1][n],然后继续求得 d[2][1]、d[2][2]、……、d[2][n],一直到 d[m][n]。代码实现如下:

    /**
     * 动态规划算法
     * @param {string} a
     * @param {string} b
     * @returns {number} 从 a → b 的最小编辑距离
     */
    function dynamicPlanning(a, b) {
        let lenA = a.length;
        let lenB = b.length;
        let d = [];
        d[0] = [];
    
        for (let j = 0; j <= lenB; j++) {
            d[0].push(j);
        }
    
        for (let i = 0; i <= lenA; i++) {
            if (d[i]) {
                d[i][0] = i;
            } else {
                d[i] = [];
                d[i][0] = i;
            }
        }
    
        for (let i = 1; i <= lenA; i++) {
            for (let j = 1; j <= lenB; j++) {
                if (a[i - 1] === b[j - 1]) {
                    d[i][j] = d[i - 1][j - 1];
                } else {
                    let m1 = d[i - 1][j] + 1;
                    let m2 = d[i][j - 1] + 1;
                    let m3 = d[i - 1][j - 1] + 1;
                    d[i][j] = Math.min(m1, m2, m3);
                }
            }
        }
    
        return d[lenA][lenB];
    }

    对比与实证

    这两种算法哪一个更快呢?
    我不知道如何从理论上去计算其时间复杂度和空间复杂度,因为对于复杂度的计算,我只能应付那种简单的,比如 n 重循环,我知道其时间复杂度是 n 次方。但是,对于这种稍微复杂一些的程序,我便不知所措了。
    怎么办呢? → 我决定从实证的角度去得到答案,也就是说,通过实际测量程序的运行时间,来衡量其时间复杂度。
    我使用 jsPerf,建了一个 Test Case,从链接进去点击“Run Tests”,稍等片刻,便能看到程序运行的速度(指标 ops/sec 为 1s 内程序重复执行次数,数值越高代表程序运行越快),具体执行结果如下图所示。
    result
    从图中我们可以看到:递归的时间复杂度是指数级的,动态规划的时间复杂度是线性级的。
    为什么动态规划快那么多呢? → 从代码上我们能猜测一二:**动态规划需要存储一个矩阵d[m][n],随着规模的增大,矩阵变得越来越大,对内存的占用也是越来越多的。这本质上是在用空间换时间。**当然,也是有办法可以优化其占用空间的,具体的可以看参考资料中明无梦的文章。另外,目前我还不知道该如何实际测量程序的空间复杂度,如果有知道的朋友,还望不吝赐教。

    参考资料

    1. 编辑距离, By 明无梦
    2. 动态规划求编辑距离, By 残阳似血
    展开全文
  • 对于动态规划,春招秋招时好多题都会用到动态规划,气之下,再 leetcode 连续刷了几十道动态规划的题在这里插入图片描述之后,豁然开朗 ,感觉动态规划也不是很难,今天...
  • 谈UE4高级渲染:动态光照迭代快

    千次阅读 2017-03-15 16:54:34
    这是个256平方公里的开放大世界,伴随很多真实照片的物件以及全动态的光影。还有一些非常高精度的植被和石头之类的效果。 当我们想要做这样个Demo,挑选了苏格兰的Scotland。因为我们想要做个看起来真实的...
  • Emacs,最强编辑器,没有之

    万次阅读 多人点赞 2018-12-08 12:57:39
    小编知道,此标题出,肯定会遭受广大群众“诟病”,说不好还会被其他编辑器的粉丝暗地里“干掉”。 比如,Vim,Sublime,Nano编辑器的粉丝可能就会来“踢馆”,VS或Eclipse的粉丝也兴许会“群起而攻”,但是小...
  • AServer接入网关承载整个阿里集团的入口流量,负责亿用户的长链保活,支持上万路由策略转发,是连接上亿用户与后端几十万服务节点的桥梁,在今年双十一需要支撑亿在线用户、千万QPS、...
  • 详解编辑距离算法-Levenshtein Distance

    万次阅读 2020-02-06 18:19:03
    最简单的方法是检查所有可能的编辑序列,从中找出最短的条。但这个序列总数可能达到指数,但完全不需要这么多,因为我们只要找到距离最短的那条而不是所有可能的序列。这个概念是由俄罗斯科学家Vladimir ...
  • 随着 Web 的迅速发展,这种轻巧而灵活的语言被委以越来越多的任务,动态地修改页面内容,一致地处理事件,甚至无刷新地和服务器交互。然而,与传统的客户端编程相比,JavaScript 操作的对象限制在 DOM 模型之内,...
  • 动态规划

    千次阅读 多人点赞 2017-08-30 16:15:42
    动态规划前几天被阿里校招笔试一道装箱问题的编程题吓懵逼了,遂决定好好看看动态规划的东西,结合在牛客网上的课程,总结一下基础动态规划的知识。动态规划的关键点在于解决冗余和记忆化搜索。当遇到一道需要暴力...
  • 这是个256平方公里的开放大世界,伴随很多真实照片的物件以及全动态的光影。还有一些非常高精度的植被和石头之类的效果。 当我们想要做这样个Demo,挑选了苏格兰的Scotland。因为我们想要做个看起来真实的...
  • 这是作者网络安全自学教程系列,主要...这篇文章将讲解逆向分析OllyDbg动态调试工具的基本用法,包括界面介绍、常用快捷键和TraceMe案例分析。基础性文章,希望对您有所帮助,如果存在错误或不足之处,还望告知,加油!
  • 国家一级B选择题答案

    万次阅读 2009-07-29 13:57:00
    网络部分1) 有域名为bit.edu.cn,根据域名代码的规定,此域名表示A) 政府机关 B) 商业组织C) 军事部门 D) 教育机构2) 计算机网络分局域网、城域网和广域网,属于局域网的是A)ChinaDDN网 B)Novell网C) ...
  • 今日快讯2017年9月15日,饿了么母公司拉扎斯集团发内部邮件...而百度外卖原CEO巩振兵担任百度外卖董事,负责集团及百度外卖战略布局及关键业务梳理;百度外卖其他管理层人员任用保持不变。作者简介短暂的休息回到了周
  • 一年成为Emacs高手(像神一样使用编辑器)

    万次阅读 多人点赞 2012-01-31 16:35:12
    世界大师 Steve Purcell 的 Emacs 配置 . 警告,Purcell 总爱试用最新的 Web 开发的新技术,对他而言稳定性不是第位的,如果你有热情和能力,愿意一起折腾,那么水平会提高很快. 这个如果是很重要的前提,当我上...
  • Excel图表—条形的高级做法

    千次阅读 2019-08-17 11:48:57
    本篇制作图表如下: 、如何为不同条件的数据设立不同的格式? 二、如何添加均线? 三、如何将堆积和簇状柱形组合? 四、对于分类类别
  • wpf多级动态右键菜单ContextmMenu

    千次阅读 2016-09-21 17:54:38
    搞这个右键菜单用了挺的时间,是网络上面资料比较少,二是里面有坑,坑就是众所周知,c#程序有两种窗口,种是Form窗口,另种是wpf窗口。网上的资料是这两种混合,开始我并没有意识到这两种有多大的差别,...
  • 目录:简介二:算法定义1:定义2:a small case3:算法的上下界限三:应用场景1:数据对齐2:拼写纠错四:其他的编辑距离算法五:算法实现1:递归实现2:动态规划实现 总结句话:编辑距离就是从个字符串变到...
  • DayDayUp:Markdown编辑器的简介、入门、使用方法(Markdown编辑器撰写博客) ... Markdown是种轻量的「标记语言」,Markdown是种可以使用普通文本编辑器编写的标记语言,通过简单的标记语法,它可以...
  • 安卓高级xml布局()高级输入框EditText设计转载请...大家先看一下效果当用户输入时动态出现删除按钮 现在先罗列一下技术点: 1.如何使用圆角输入框和按钮背景 2.如何实现“手机号”、“密码”后面的竖线 3.
  • 动态规划算法经典案例

    千次阅读 2019-04-29 19:20:10
    动态规划算法是从暴力搜索算法优化过来的,如果我们不清楚暴力搜索的过程,就难以理解动态规划的实现,当我们了解了动态规划算法的基本原理的文字概述,实现条件之后,这时可能并不是太理解这种思想,去面对实际问题...
  • 动态规划算法解决流水作业调度

    千次阅读 多人点赞 2020-07-06 11:45:29
    这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入段漂亮的代码片生成个适合你的列表创建个表格设定内容居中、居左、...
  • 例2:只青蛙次可以跳上1台阶,也可以跳上2。求该青蛙跳上个n的台阶总共有多少种跳法。 依旧是找递推关系: 1)跳一阶,就种方法 2)跳两阶,它可以次跳两个,也可以个跳,所以有两种 3)三个...
  • 篇文章分享了IDA Pro反汇编工具的基础用法,并简单讲解个EXE逆向工程解密实战方法;本篇文章将讲解动态分析OllyDbg工具的基础用法,主要是结合两个Crakeme案例逆向破解。希望对初学者有帮助,大神请飘过,谢谢...
  • (自适应动态规划综述)

    千次阅读 2020-01-08 09:36:49
    摘要:自适应动态规划(Adaptive/Approximate Dynamic Programming,ADP)是最优控制领域新兴起的种近似最优方法,它在人工智能领域、强化学习、人工神经网络、模糊系统、演化计算等方面蓬勃发展,为求解非线性系统...
  • QT界面编辑

    千次阅读 2019-05-22 15:22:03
    QT界面编辑 ** QT建立游戏登录界面 1、新建个界面(app)工程 点击 “欢迎” 选项,点击 “New Project” 弹出新建项目界面,在项目中选择 Application -> QT Widgets Application 选项,再点击 choose ,在弹...
  • 0 成功。 操作成功。当 SQL 语句成功地执行时,数据库服务器把这个 SQLCODE 值返回给应用 ...使用这个 SQLCODE值决定个语句什么时候到达数据结束。...1204 你的系统遇到了个未知...如果你在安装以后,已经编辑了...
  • 基于WEB的HTML 编辑器,WYSIWYG所见即所得的编辑器,或是个富文本的编辑器,是我们在开发WEB应用时接收用户输入时必需要考虑的问题。下面是一些开源的WEB在线的WYSWIG编辑器。 1. FCKeditor FCKeditor 这些在线...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 82,911
精华内容 33,164
关键字:

一级动态太长怎么编辑