动态规划

刷题方法

告别动态规划,连刷 40 道题,我总结了这些套路,看不懂你打我 - 知乎

北美算法面试的题目分类,按类型和规律刷题

九章的DP分类

九章DP分类

重点250

题目分类

一维dp

矩阵型DP

House Robber : 一维dp

方法一:自定向下 + memo

  • 这种方法是从问题整体出发,向子问题方向分解求值,同时将子问题结果缓存下来。
  • 一般来说这种方法在分析出,子问题与父问题关系后,能直接想到。
  • 注:如果去掉memo,那就是递归解问题的方法了,可以用在子问题解不重叠的场景。
class Solution {
Map<Integer,Integer> m = new HashMap<>();
int helper(int[] nums, int right){
if(m.containsKey(right)){
return m.get(right);
} if(right == 0){
m.put(right, nums[0]);
return nums[0];
}
else if(right == 1){
int i = Math.max(nums[0], nums[1]);
m.put(right, i);
return i;
} int i = Math.max(helper(nums, right - 2) + nums[right], helper(nums, right-1));
m.put(right, i);
return i;
}
public int rob(int[] nums) {
if(nums.length == 0) return 0;
return helper(nums, nums.length - 1);
}
}

方法二:自底向上 + memo

  • 由小问题向大问题推导,这个思路就必须有memo缓存了,不过可以考虑进一步优化缓存.
  • 这个效率已经要比方法一快了.
class Solution {
public int rob(int[] nums) {
if(nums.length == 0) return 0; int[] memo = new int[nums.length];
for(int i=0; i<nums.length; i++){
if(i == 0 ) memo[i] = nums[i];
else if(i == 1){
memo[i] = Math.max(nums[0], nums[1]);
}
else{
memo[i] = Math.max(memo[i-1], memo[i-2] + nums[i]);
}
} return memo[nums.length - 1];
}
}

方法三:自底向上 + memo optimised

class Solution {
public int rob(int[] nums) {
if(nums.length == 0) return 0;
if(nums.length == 1) return nums[0]; int n_1 = nums[0], n_2 = 0;
for(int i=1; i<nums.length; i++){
int t = Math.max(n_1, n_2 + nums[i]);
n_2 = n_1; n_1 = t;
} return n_1;
}
}

House Robber II : 一维dp

方法:同上

class Solution {
public int rob(int[] nums) {
if(nums.length == 0) return 0;
if(nums.length == 1) return nums[0];
if(nums.length == 2) return Math.max(nums[0], nums[1]);
int x = 0;
// 1. drop the last num
int n2 = nums[0], n1 = Math.max(nums[0], nums[1]);
for(int i = 2; i < nums.length -1; i ++){
int t = Math.max(n2 + nums[i], n1);
n2 = n1;
n1 = t;
}
x = n1; n2 = 0;
n1 = nums[1];
for(int i = 2; i < nums.length; i ++){
int t = Math.max(n2 + nums[i], n1);
n2 = n1;
n1 = t;
} return Math.max(x, n1);
}
}

House Robber III:一维DP

方法一: 自顶向下 + memo

这种方式写起来容易出错,效率也比较低

/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
Map<TreeNode, Integer> m = new HashMap<TreeNode, Integer>();
Map<TreeNode, Integer> m2 = new HashMap<TreeNode, Integer>(); int helper(TreeNode t, boolean parentUsed){
if(t == null) return 0; if(parentUsed){
if(m.containsKey(t)){
return m.get(t);
}
int x = helper(t.left, false) + helper(t.right, false);
m.put(t, x);
return x;
}
else{
if(m2.containsKey(t)){
return m2.get(t);
} int notUse = helper(t.left, false) + helper(t.right, false); int use = t.val + helper(t.left, true) + helper(t.right, true); int x = Math.max(use, notUse); m2.put(t, x);
return x;
}
}
public int rob(TreeNode root) {
if(root == null) return 0; return Math.max(root.val +
helper(root.left, true) + helper(root.right,true),
helper(root.left, false) + helper(root.right, false));
}
}

方法二:自顶向下 + memo

虽然也是自顶向下,下面的方法就简洁很多;特别注意标important那行容易出错

/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution { int[] helper(TreeNode t){
int[] re = new int[2];
if(null == t){
re[0] = 0;
re[1] = 0;
return re;
} int[] left = helper(t.left);
int[] right = helper(t.right); re[0] = t.val + left[1] + right[1];
re[1] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]); // important
return re;
}
public int rob(TreeNode root) {
if(root == null) return 0; int[] re = helper(root); return Math.max(re[0], re[1]);
}
}

Decode Ways : 一维DP,求方法总数

解法一: 自顶向下,递归 + no memo, time limit exceed

这道题真有点不太好想

class Solution {
int helper(char[] str, int right){
int sum1 = 0, sum2 = 0; if(right < 0) return 1;
if(right == 0){
return (str[right] != '0') ? 1 : 0;
} if(str[right] != '0'){
sum1 = helper(str, right-1);
} if(str[right-1] != '0'){
Integer x = Integer.valueOf(new String(str, right - 1, 2));
if(x >= 1 && x <= 26){
sum2 = helper(str, right-2);
}
} return sum1 + sum2;
} public int numDecodings(String s) {
return helper(s.toCharArray(), s.length() - 1);
}
}

解法二: 自顶向下,递归 + memo

class Solution {
Map<Integer, Integer> m = new HashMap<>(); int helper(char[] str, int right){
int sum1 = 0, sum2 = 0; if(m.containsKey(right)){
return m.get(right);
} if(right < 0) return 1;
if(right == 0){
return (str[right] != '0') ? 1 : 0;
} if(str[right] != '0'){
sum1 = helper(str, right-1);
} if(str[right-1] != '0'){
Integer x = Integer.valueOf(new String(str, right - 1, 2));
if(x >= 1 && x <= 26){
sum2 = helper(str, right-2);
}
} m.put(right, sum1+sum2);
return sum1 + sum2;
} public int numDecodings(String s) {
return helper(s.toCharArray(), s.length() - 1);
}
}

方法三:自底向上 + 加 O(n) memo

class Solution {
public int numDecodings(String s) {
if(s.length() == 0 || s.charAt(0) == '0') return 0; int[] memo = new int[s.length() + 1];
memo[0] = 1; memo[1] = 1; char[] str = s.toCharArray(); for(int i=1; i<s.length(); i++){ int t1 = 0, t2 = 0;
if(str[i] != '0'){
t1 = memo[i];
} if(str[i-1] != '0'){
Integer x = Integer.valueOf(new String(str, i-1, 2));
if(x >= 10 && x <= 26){
t2 = memo[i-1];
}
} memo[i+1] = t1 + t2;
} return memo[s.length()];
}
}

Word Break : 一维DP,求是否可行

方法一 : 自顶向下

递归 + memo

class Solution {
Map<Integer, Boolean> m = new HashMap<>(); boolean helper(char[] str, int right, Set<String> dict){
if(right >= str.length){
return true;
} if(m.containsKey(right)){
return m.get(right);
} for(int j = 1; j < str.length - right + 1; j++){
if(dict.contains(new String(str, right, j))){
if(helper(str, right + j, dict)){
return true;
}
}
} m.put(right, false);
return false;
} public boolean wordBreak(String s, List<String> wordDict) {
Set<String> dict = new HashSet<>(); for(String r : wordDict){
dict.add(r);
} return helper(s.toCharArray(), 0, dict);
}
}

方法二:自底向上

递推 + memo

(ps:这道题并不像经典dp,它当前状态不仅依赖于上阶段的状态,而是依赖于过去所有阶段的状态,看看grandyang的解说

class Solution {
public boolean wordBreak(String s, List<String> wordDict) {
Set<String> dict = new HashSet<>();
int maxi = 0;
for(String r : wordDict){
dict.add(r);
maxi = Math.max(maxi, r.length());
} char[] arr = s.toCharArray();
boolean[] can = new boolean[s.length() + 1];
can[0] = true; for(int i = 0; i < s.length(); i++){ boolean c = false;
for(int j = i; j >= 0 && j >= (i - maxi); j--){ // j >= (i - maxi)是优化部分
if(can[j] && dict.contains(new String(arr, j, i+1 - j))){
c = true;break;
}
}
can[i+1] = c;
} return can[s.length()];
}
}

Maximum Product Subarray : 求最大最小值

方法一 : 自底向上

递推 + memo

这道题稍微加了一个弯,因为有正负数,我们必须记录上一阶段求得的最大最小值,否则,就不能直接根据上一阶段的结果,直接推导本阶段值。

class Solution {
class Maxi{
int maxi;
int mini; Maxi(){
maxi = 1;
mini = 1;
}
} public int maxProduct(int[] nums) { Maxi[] memo = new Maxi[nums.length + 1];
memo[0] = new Maxi(); for(int i=0; i<nums.length; i++){
memo[i+1] = new Maxi(); if(nums[i] > 0){
int x = Math.max(memo[i].maxi, memo[i].mini);
memo[i+1].maxi = (x > 0) ? x * nums[i] : nums[i]; int y = Math.min(memo[i].maxi, memo[i].mini);
memo[i+1].mini = (y <= 0) ? y * nums[i] : nums[i];
}
else if(nums[i] < 0){
int x = Math.min(memo[i].maxi, memo[i].mini);
memo[i+1].maxi = (x <= 0) ? x * nums[i] : nums[i]; int y = Math.max(memo[i].maxi, memo[i].mini);
memo[i+1].mini = (y > 0) ? y * nums[i] : nums[i];
}
else {
memo[i+1].maxi = memo[i+1].mini = 0;
}
} int maxi = Integer.MIN_VALUE;
for(int i = 1; i < nums.length + 1; i++){
maxi = Math.max(maxi, memo[i].maxi);
}
return maxi;
}
}

Ugly Number II

方法一: 递推

这个解法的归并排序很经典

class Solution {
public int nthUglyNumber(int n) {
if(n <= 5) return n; List<Integer> li = new ArrayList<>();
li.add(1); int idx2 = 0;
int idx3 = 0;
int idx5 = 0;
while(li.size() < n){
int v2 = 2 * li.get(idx2);
int v3 = 3 * li.get(idx3);
int v5 = 5 * li.get(idx5); int mini = Math.min(v2, Math.min(v3, v5));
li.add(mini); if(mini == v2) idx2++;
if(mini == v3) idx3++;
if(mini == v5) idx5++; } return li.get(n-1);
}
}

方法二 : 最小堆

这种写法效率低点,但是是个思路;另外,需搞懂1. Comparator这种写法是什么语法; 2. Priority, Iterator的用法

class Solution {
public int nthUglyNumber(int n) {
if(n <= 5) return n; PriorityQueue<Long> q = new PriorityQueue<>(
new Comparator<Long>(){
public int compare(Long a, Long b){
if(a < b){
return -1;
}
else if(a > b){
return 1;
}
else {
return 0;
}
}
}); q.add((long)1); for(int i = 1; i < n; i++){
long x = q.remove(); Iterator<Long> it = q.iterator();
while(it.hasNext()){
if(x == it.next()) it.remove();
} q.add( (long)(x * 2) );
q.add( (long)(x * 3) );
q.add( (long)(x * 5) );
} return q.peek().intValue();
}
}

Is Subsquence

方法一: 自底向上, 递推 + memo

可以将O(N)空间优化为常数

class Solution {
public boolean isSubsequence(String s, String t) {
int lenS = s.length();
int lenT = t.length();
if(lenS > lenT) return false; int memo = new int[lenS + 1];
memo[0] = -1; char[] arrS = s.toCharArray();
char[] arrT = t.toCharArray(); for(int i = 0; i < lenS; i ++){
memo[i + 1] = lenT;
for(int j = memo[i] + 1; j < lenT; j ++){
if(arrT[j] == arrS[i]){
memo[i + 1] = j;
break;
}
}
} return memo[lenS] != lenT;
}
}

413. Arithmetic Slices

方法一 :参考grandyang

方法二 : memo + 递推

dp[i]表示以位置i上的数字结尾的Arithmetic Slice的数目

这道题主要要搞清楚,为啥dp[i] = dp[i-1] + 1

class Solution {
public int numberOfArithmeticSlices(int[] A) { if( A.length < 3 ) {
return 0;
} int cnt = 0;
int[] dp = new int[A.length]; for( int i=2; i < A.length; i++ ) {
if(A[i] - A[i-1] == A[i-1] - A[i-2]) {
dp[i] = dp[i-1] + 1;
} cnt += dp[i];
} return cnt;
}
}

leetcode动态规划笔记一---一维DP的更多相关文章

  1. leetcode动态规划笔记二

    动态规划 题目分类 一维dp 矩阵型DP Unique Paths II : 矩阵型DP,求所有方法总数 Minimum Path Sum:矩阵型,求最大最小值 Triangle : 矩阵型,求最大最 ...

  2. leetcode动态规划笔记三---单序列型

    单序列型DP 相比一维DP,这种类型状态转移与过去每个阶段的状态都有关. Longest Increasing Subsequence : 求最大最小值 Perfect Squares : 求某个规模 ...

  3. leetcode算法笔记:二叉树,动态规划和回溯法

    在二叉树中增加一行 题目描述 给定一个二叉树,根节点为第1层,深度为 1.在其第 d 层追加一行值为 v 的节点. 添加规则:给定一个深度值 d (正整数),针对深度为 d-1 层的每一非空节点 N, ...

  4. LEETCODE —— Maximum Subarray [一维DP]

    Maximum Subarray Find the contiguous subarray within an array (containing at least one number) which ...

  5. 快速上手leetcode动态规划题

    快速上手leetcode动态规划题 我现在是初学的状态,在此来记录我的刷题过程,便于以后复习巩固. 我leetcode从动态规划开始刷,语言用的java. 一.了解动态规划 我上网查了一下动态规划,了 ...

  6. Leetcode学习笔记(4)

    题目1 ID121 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格. 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润. 注意你不能在买入股 ...

  7. Dynamic Programming - leetcode [动态规划]

    115. Distinct Subsequences 96. Unique Binary Search Trees 120. Triangle 123. Best Time to Buy and Se ...

  8. [Leetcode][动态规划] 零钱兑换

    一.题目描述 给定不同面额的硬币 coins 和一个总金额 amount.编写一个函数来计算可以凑成总金额所需的最少的硬币个数.如果没有任何一种硬币组合能组成总金额,返回 -1. 示例 1: 输入: ...

  9. 动态规划(Dynamic Programming, DP)---- 最大连续子序列和

    动态规划(Dynamic Programming, DP)是一种用来解决一类最优化问题的算法思想,简单来使,动态规划是将一个复杂的问题分解成若干个子问题,或者说若干个阶段,下一个阶段通过上一个阶段的结 ...

随机推荐

  1. Reactive Extensions (Rx) 入门(2) —— 安装 Reactive Extensions

    译文:https://blog.csdn.net/fangxing80/article/details/7581937 原文:http://www.atmarkit.co.jp/fdotnet/int ...

  2. A1137 | 录数据查询模拟

    这应该是比较简单的一个模拟题,但是考试的时候花了较长的时间,并且最后一个case没过,丢了6分.这题的通过率不高,可见最后一个case还是有挑战性的. 考试的时候想的是在录数据的时候建立一个[ID]到 ...

  3. 洛谷/Codeforces CF865D 题解

    若想要深入学习反悔贪心,传送门. Description: 已知接下来 \(n\) 天的股票价格,每天可以买入当天的股票,卖出已有的股票,或者什么都不做,求 \(n\) 天之后最大的利润. Metho ...

  4. 驱动中遍历模块,以及获取ntoskrnl.exe基址

    方法是基于PsLoadModuleList方式 驱动中遍历模块 一丶简介 简介: 进入内核了.遍历内核中使用的模块该怎么办. 其实在驱动中.我们的DriverEntry入口位置. 提供了两个参数. 一 ...

  5. lintcode-828. 字模式

    题目描述: 828.字模式 给定一个模式和一个字符串str,查找str是否遵循相同的模式.这里遵循的意思是一个完整的匹配,在一个字母的模式和一个非空的单词str之间有一个双向连接的模式对应. 样例 给 ...

  6. Asp.net 与 Core .net 用法区别

    1.  定义一个类 如下,注意int?这里 public class A{ public int? num{get;set;} } 2. 如果传递的参数不能转换成int类型,则core里面接受不了参数 ...

  7. js逆向分析之acorn和escodegen的使用

    替换之前的d形如 d("77696669") 执行代码 const fs = require('fs'); const acorn = require('acorn'); cons ...

  8. 聊聊Mysql索引和redis跳表 ---redis的有序集合zset数据结构底层采用了跳表原理 时间复杂度O(logn)(阿里)

    redis使用跳表不用B+数的原因是:redis是内存数据库,而B+树纯粹是为了mysql这种IO数据库准备的.B+树的每个节点的数量都是一个mysql分区页的大小(阿里面试) 还有个几个姊妹篇:介绍 ...

  9. convmv 解决GBK 迁移到 UTF-8 ,中文 文件名乱码

    yum install convmv 命令: convmv -f GBK -t UTF-8 -r --nosmart --notest <目标目录> -f from -t to --nos ...

  10. Unity制作棋牌手游之斗地主

    目录 大小7.2GB,MP4格式 扫码时备注或说明中留下邮箱 付款后如未回复请至https://shop135452397.taobao.com/ 联系店主