活跃农民
- 积分
- 428
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2018-8-4
- 最后登录
- 1970-1-1
|
2.9 做题
279. Perfect Squares. 这是一个DP题目,要求给定一个数字,看看它最少可以由几个完全平方数相加而构成,完全平方数就是1,4,9,16,25这种;比如5这个数字,可以是1 + 1 + 1 + 1组成,这样就是5个,但是当然也可以是4 + 1组成,这样就是两个;又比如27这个数字,可以是1 + 1 + ... + 1组成,这样就是27个,当然也可以是16 + 9 + 1 + 1组成,这样就是4个,当然也可以是9 + 9 + 9组成,这样就是3个;所以一个数字可以有多种完全平方数组合的方式;那么具体来说使用DP的方法,其实就是dp就是可以组成i的最少方式,对于任意一个j来说,只要i >= j * j,那么都可以有dp = dp[i - j * j] + 1;这是因为,i - j * j肯定是一个比i小的数字,比如如果i是27的话,那么j是2,那么i - j * j就是23;如果现在知道了组成23的完全平方数最小数量,那么只要在这个基础上再加一个2 * 2 = 4就可以构成27了,也就是说dp[27] = dp[23] + 1,这代表着加“一个”4即可;当然这种情况还会有很多,因为比i小的、加上一个完全平方数就可以构成i的数不止这一个,而这些数的组成方式数量,只要再加1(加一个完全平方数)就可以构成i;所以对于j从1开始到i的平方根进行逐个递增,对于每一个dp[i - j * j] + 1都比较一下,找出最小的那个,就是最后确定的dp了;因此base case就是dp[0] = 0和dp[1] = 1,然后i从2开始一直循环到n;对于每一个i循环内部,都去循环j,j从1开始只要满足i >= j * j就一直递增,对于每一个j都去让dp和dp[i - j * j] + 1进行比较,谁小取谁,最后assign到dp上
139. Word Break. 这个题目使用DP的方法,对于整个word进行一步一步往上检查;假设dp表示对于给定String从0开始一直到i的这一段substring是否能够break成功,是由从0开始到j的这一段substring,和从j开始到i的这一段substring,是由这两段substring共同决定的;而(0, j)这一段其实就是dp[j],而(j, i)这一段则可以在wordDict当中去寻找并检验;因此dp = dp[j] && set.contains(s.substring(j, i));具体来说,首先对i从1开始到s的长度循环,然后内部就是对DP的构建过程;dp代表从0开始到i是否能够break成功,那么对于每一个dp,内部都要进行这种两段式拆分,也就是内部也要有一个循环,这个循环其实就是循环j了,这个j就是拆分0到i这一段,然后看看代表0到j的dp[j]和j到i这一段是否存在于wordDict当中;这里有一个小技巧,对于j的循环,如果从i往0循环的话,速度要比从0往i循环更快
375. Guess Number Higher or Lower II. 这个题目的理解方式是这样,对于从1到n的这个范围之内,target的数字是任意未知的,而每次如果猜错了的话,会有提示target是更大还是更小,从而缩小下一次猜的范围,同时要交纳猜错的数字相同的钱;那么如果猜的话,从1到n这范围内的数字每一个都有可能被猜到,如果猜到了一个数字x且1 <= x <= n的话,这个x如果猜错了,那么首先要缴纳x数值的钱,同时会提示是比x更大还是比x更小;如果target比x更大的话,下一个猜的范围自然就是从x + 1到n,而如果target比x更小的话,下一个猜的范围自然就是从1到x - 1;这样其实就完成了把一个大问题拆分成小问题的过程,也就是在i到j范围之内,如果猜了x的话,那么要交的钱实际上就是x + Max((i, x - 1), (x + 1, j))这样,因为并不确定到底是比x更大还是比x更小,因此如果这一次猜了x的话,那么必须要有足够的钱,去应付下一个范围,而现在有两个可能的范围,所以就必须准备好这两个范围所需要的钱的更多的那个;因为每个范围要求至少需要准备的钱其实是确定的,那么对于这两个范围来说,在不确定的究竟是哪个范围的时候,肯定要准备更多的那个才能保证万无一失;那么回到一开始的从1到n这个最终范围,其实1 <= x <= n的这个x可以从1取到n的,也就是说这里如何取这个x其实是有最优策略的,可以通过选择猜合适的x,使得后续不断缩小范围的过程中,所需要支付的钱是最少的;因此就把x从1到n全部都尝试一遍,需要钱最少的对应的x,就是从1到n所需要准备交纳的钱;这里取最小是因为这里是在选择最优的策略,而之前取最大是因为之前的范围是不可知的
322. Coin Change. 这个题目是给定了一个数组,数组内部包含了硬币的面值,假设每个面值的硬币可提供的数量有无限多个,而又给了一个target,看看能不能通过给定数组当中的硬币(只能使用给定面值的硬币,但是同一种面值的硬币可以使用多次)来组成target;这个题目很类似之前的combination sum,所以首先想到用DFS的方法,也就是对于排好序的给定数组进行循环,对于每一个面值,都调用recursion,然后把当前target扣除当前index对应的面值,然后记录count + 1,如果在recursion的过程中发现target正好等于0的话,就说明这个count是有效的,就可以和全局最小值进行比较;类似的recursion方法还有一种,也就是在函数中,直接对所有的coin面值数组进行循环,对于每一个面值,都进行recursive调用原本给定的函数,只不过传入的参数是原有amount减去当前的coin,如果这个recursion返回的数值不是-1(也就意味着amount减去当前的coin面值剩下的amount可以被这个数组内的面值硬币所组成),那么这个amount的组成硬币数量,就相当于返回的数值基础上再加1(就是加上一个当前面值);那么只需要维护一个global的min,然后对于对coins循环当中的所有的这种组成硬币数量,取一个全局最小就可以了;但是这种方法额问题就在于重复计算太多了,因为在原本的coins循环时,可能amount减去某个coin面值后的recursion路径中已经算出来过了以后的值,因此就可以维护一个Map,对于每一次算出来的某个amount对应的硬币数值都put进去,这样就可以作为recursion提前终止的条件,避免重复计算;那么最简单的就是DP方法了,这里是bottom up的DP,也就是当算出来amount为i时的组成硬币数量以后,再去考虑amount为i + 1的时候的组成硬币数量应该怎么由更小的dp去组成;这里其实也很简单,首先对i循环,这就是构建DP的过程;而对于每一个i的循环内部,都去算对于这个i的组成硬币最小值是多少,那么在循环内部,再针对这个i去对coins数组进行循环;对于每一个面值coin,如果这个面值不比i大,并且i减去这个coin以后剩下的amount,在DP数组中检查后发现并不是-1(也就是说i - coin也是在之前的步骤中算出来是可行的),那么这个i其实就可以由dp[i - coin] + 1来表示dp;当然这里对coins数组进行循环,可能不止一个coin都满足这个条件,所以就在内循环中对min进行更新;循环结束以后,如果min有更新,就证明是可以组成的,从而dp就确定下来了,否则dp就是-1,说明不能由这些面值的硬币所组成
312. Burst Balloons. 这个题目我看的这个https://leetcode.com/problems/bu ... siest-Java-Solution;思路就是,对于一个范围start到end,内部的任何一个气球i,如果希望扎它的话,假设从start到i - 1和从i + 1到end这两个区间都扎完毕了,那么现在就只剩下了start - 1、i、end + 1这三个了,那么如果扎i的话,也就是他们三个的数组元素相乘,这样就相当于是扎完了;那么对i进行循环一下,找出最大的那个就可以了;而对于那两个已经扎完毕的区间,从start到i - 1和从i + 1到end,就可以用recursion方法进行求解;这里我没用来得及用DP,但是也同样是可以用DP进行bottom up求解的;那么对于recursion的方法,可以有一个dp数组,dp[start][end]去进行memory记录即可
|
|