栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

【面试算法题总结12】动态规划算法

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

【面试算法题总结12】动态规划算法

动态规划算法:

动态规划算法和回溯算法很像,需要明确basecase、状态、选择。
动态规划的暴力穷举求解阶段就是回溯算法。只是有的问题可以构造最优子结构,找到重叠子问题,可以用dp table优化,将递归树大幅剪枝。

一方面,动态规划问题的一般形式就是求最值。核心是穷举,但其存在“重叠子问题”,如果暴力穷举效率会极其低下,所以需要使用dp数组来优化穷举过程,避免不必要的计算。
另一方面,动态规划问题一定会具备“最优子结构”(子问题必须相互独立),这样才能通过子问题的最值得到原问题的最值。
最后,只有列出正确的“状态转移方程”,才能正确的穷举。写出正确的状态转移方程,一定要思考basecase、状态、选择、dp数组的定义(表示状态和选择)

思考的流程:
自顶向下的递归 (已经需要basecase、状态、选择。“状态”用函数参数存储,“选择”需要遍历)
-》 自顶向下的递归+备忘录(空间换时间)
-》 自下向上的循环+dp数组(和自顶向下复杂度差不多,状态用dp数组下标存储)
-》 dp数组的缩小(状态压缩)

 

例题1:斐波那契数列
class Solution {
    public int fib(int n) {
        int[] dp=new int[n+1];
        dp[0]=0;
        if(n==0){
            return 0;
        }
        dp[1]=1;
        for(int i=2;i<=n;++i){
            dp[i]=(dp[i-1]+dp[i-2])%1000000007;
        }
        return dp[n];
    }
}

状态压缩后:

class Solution {
    public int fib(int n) {
        int dp1=0,dp2=1;
        if(n==0||n==1){
            return n;
        }
        for(int i=2;i<=n;++i){
            int temp=(dp1+dp2)%1000000007;
            dp1=dp2;
            dp2=temp;
        }
        return dp2;
    }
}

一、01背包
import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        int n=in.nextInt();
        int V=in.nextInt();
        int[] v=new int[n+1]; //volume体积
        int[] c=new int[n+1]; //cost价值
        for(int i=0;i=v[i];--j){
                dp[j]= Math.max(dp[j],dp[j-v[i]]+c[i]);
            }
        }
        System.out.println(dp[V]);
    }

    //若背包恰好装满,求至多能装多大价值的物品。dp[i][v]表示前i个物品 恰好 装入容量为v的背包中能获得最大价值。就是初始化的不一样。
    static void justF(int n,int V,int[] v,int[] c){
        int[] dp=new int[V+1];
        Arrays.fill(dp,Integer.MIN_VALUE);
        dp[0]=0;
        for(int i=1;i<=n;++i){
            for(int j=V;j>=v[i];--j){
                dp[j]= Math.max(dp[j],dp[j-v[i]]+c[i]);
            }
        }
        int result=dp[V]<0?0:dp[V];
        System.out.println(result);
    }
}
例题1: 分割等和子集 例题2: 一和零 例题3: 目标和 解法1:回溯算法

时间复杂度太高

class Solution {
    int count=0;
    public int findTargetSumWays(int[] nums, int target) {
        dfs(nums,0,target,0);
        return count;
    }
    void dfs(int[] nums,int index,int target,int sum){
        if(index>=nums.length){
            if(sum==target){
                ++count;
            }
            return;
        }
        dfs(nums,index+1,target,sum+nums[index]);
        dfs(nums,index+1,target,sum-nums[index]);
    }
}
解法2:动态规划算法

1 我们要消除target可能为负数的影响,所以要进行转换,将原题变为01背包问题,而不是现在的这个-11背包问题
这里实际上找的是nums[]选哪些数字可以让其和为(sum-target)/2,推导如下:
设被减去的数字和为neg,所有数字和为sum。有sum-2neg=target,则neg=(sum-target)/2
2 由于 nums[i]可以等于0 ,而且+0,-0算两种方法,所以不能让dp[i][0]都为1,而只能让dp[0][0]为1

class Solution {
    public int findTargetSumWays(int[] nums, int target) {
        //真正的目标数字realTarget,这样才是01背包问题
        int sum=0;
        for(int i=0;i=nums[i-1]){
                    dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]];
                }else{
                    dp[i][j]=dp[i-1][j];
                }
            }
        }
        return dp[dp.length-1][dp[0].length-1];
    }
}

例题4: 盈利计划 例题5: 最后一块石头的重量 II

 

二、完全背包

大佬题解中的程序猿大队长

import java.util.*;

public class Main{
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        int n=in.nextInt();
        int V=in.nextInt();
        int[] v=new int[n+1]; //volume体积
        int[] c=new int[n+1]; //cost价值
        for(int i=0;i 
例题1: 数位成本和为目标值的最大数字 
例题2:零钱兑换 

贪心是不行的,比如coins=[1,51,100],target=102用贪心就是错的
状态是目标金额(dp的下标),所需结果是目标金额的最少硬币数(dp的内容),选择是每种硬币的面值

class Solution {
    public int coinChange(int[] coins, int amount) {
        //dp下标表示状态:目标金额;dp内容需要的最少硬币个数
        int[] dp=new int[amount+1];
        //初始化base case(基本情况)
        dp[0]=0;
        for(int i=1;i<=amount;++i){
            dp[i]=(amount+1);
        }
        //i用来遍历状态
        for(int i=1;i<=amount;++i){
            //coin用来遍历选择
            for(int coin:coins){
                int temp=i-coin;
                if(temp<0){
                    continue;
                }
                dp[i]=Math.min(dp[temp]+1,dp[i]);
            }
        }
        return dp[amount]==(amount+1)?-1:dp[amount];
    }
}
例题3:零钱兑换 II 例题4:完全平方数

 

三、分组背包 例题1:掷骰子的N种方法

 

四、其他 例题1:最长回文子串

很明显求解出所有的字符串自然能够找到最大的,与例题2做好比较。大佬题解

解法1:动态规划法
class Solution {
    public String longestPalindrome(String s) {
        boolean[][] dp=new boolean[s.length()][s.length()];
        int left=-1,right=-2;
        for(int j=0;j 
解法2:中心扩散双指针 
class Solution {
    public String longestPalindrome(String s) {
        int needLeft=-1,needRight=-2;
        for (int center = 0; center< 2*s.length()-1; center++) {
            int left=center/2;
            int right=left+center%2;
            while(left>=0&&rightneedRight-needLeft){
                    needRight=right;
                    needLeft=left;
                }
                --left;
                ++right;
            }
        }
        return s.substring(needLeft,needRight+1);
    }
}

 

例题2:回文子串

大佬题解

解法1:动态规划法

状态:dp[i][j] 表示字符串s在[i,j]区间的子串是否是一个回文串。

class Solution {
    public int countSubstrings(String s) {
        // 动态规划
        boolean[][] dp = new boolean[s.length()][s.length()];
        int result = 0;

        for (int j = 0; j < s.length(); j++) {      //一定要把列放在外层
            for (int i = 0; i <= j; i++) {
                if (s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[i + 1][j - 1])) {
                    dp[i][j] = true;
                    result++;
                }
            }
        }

        return result;
    }
}
解法2:中心扩散双指针
class Solution {
    public int countSubstrings(String s) {
        int result = 0;
        for (int center = 0; center< 2*s.length()-1; center++) {
            int left=center/2;
            int right=left+center%2;
            while(left>=0&&right 

 

例题3:最少回文分割

dp[i]的含义为字符串长度从0到i,最少可以分为几个回文串

class Solution {
    public int minCut(String s) {
        int n = s.length();
        int[] dp = new int[n+1];
        dp[0]=0;
        for(int i=1;i<=n;i++){
            dp[i]=i;
            for(int j=0;j 

 

例题4:连续子数组的最大和

状态:dp[i]表示以A[i]作为末尾的连续序列的最大和。

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        while(in.hasNextInt()){
            int n=in.nextInt();
            int[] arr=new int[n];
            for(int i=0;i 

 

例题5:最长上升子序列

给定一个长度为 n 的数组 arr,求它的最长严格上升子序列的长度。

解法1:动态规划
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        int n=in.nextInt();
        int[] arr=new int[n];
        for(int i=0;idp[i]){
                    dp[i]=dp[j]+1;
                }
            }
            result=Math.max(result,dp[i]);
        }
        System.out.println(result);
    }
}
解法2:动态规划+二分搜索算法
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner in=new Scanner(System.in);
        int n=in.nextInt();
        int[] arr=new int[n];
        for(int i=0;i value[maxLength]) {
                // 大于目前最大长度的子序列的最后一位,给value[]后边续上
                maxLength++;
                value[maxLength] = arr[i];
            } else {
                // 小于目前最大长度的子序列的最后一位,查找前边部分第一个大于自身的位置
                // 更新它
                int t = find(value, maxLength, arr[i]);
                value[t] = arr[i];
            }
        }
 
        System.out.println(maxLength);
    }
        // 二分查找
    private static int find(int[] value, int maxindex, int i) {
        int l = 1, r = maxindex;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (i > value[mid]) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return l;
    }
}

 

例题6:最长上升子序列扩展

小强现在有个物品,每个物品有两种属性和.他想要从中挑出尽可能多的物品满足以下条件:对于任意两个物品和,满足或者.问最多能挑出多少物品.
输入例子1:
2
3
1 3 2
0 2 3
4
1 5 4 2
10 32 19 21

输出例子1:
2
3

import java.util.*;

class Node implements Comparable{       //不能直接在Arrays.sort()里面用lamda函数,会超时。必须要类实现Comparable接口,重写compareTo()
    int x;
    int y;
    public Node(int x,int y) {
        this.x = x;
        this.y = y;
    }

    @Override
    public int compareTo(Object o) {
        Node node = (Node) o;
        return this.x==node.x?node.y-this.y:this.x-node.x;
    }
}


public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int num = sc.nextInt();
        for (int i = 0; i < num; i++) {
            int goodsNum = sc.nextInt();
            int[] linearr1 = new int[goodsNum];
            int[] linearr2 = new int[goodsNum];
            for (int j = 0; j < linearr1.length; j++) {
                linearr1[j] = sc.nextInt();
            }
            for (int j = 0; j < linearr2.length; j++) {
                linearr2[j] = sc.nextInt();
            }
            Node[] list = new Node[goodsNum];
            for (int j = 0; j < list.length; j++) {
                list[j] = new Node(linearr1[j], linearr2[j]);
            }
            Arrays.sort(list);
            //动态规划+二分法
            binarySearch(list);
        }
    }

    private static void binarySearch(Node[] arr) {
        int[] value = new int[arr.length +1];
        int maxLength = 1;
        value[1] = arr[0].y;
        for (int i = 1; i < arr.length; i++) {
            if (arr[i].y > value[maxLength]) {
                value[++maxLength] = arr[i].y;
            } else {
                int t = find(value, maxLength, arr[i].y);
                value[t] = arr[i].y;
            }
        }

        System.out.println(maxLength);
    }

    // 二分查找
    private static int find(int[] value, int maxindex, int i) {
        int l = 1, r = maxindex;
        while (l <= r) {
            int mid = (l + r) / 2;
            if (i > value[mid]) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return l;
    }
}

 

例题7:最长上升子序列扩展 解法1:动态规划

空间复杂度 O(mn),时间复杂度 O(mn)

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        int n=in.nextInt();
        int m=in.nextInt();
        String a=in.next();
        String b=in.next();

        //逻辑开始
        int[][] dp=new int[n+1][m+1];
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                if(a.charAt(i-1)==b.charAt(j-1)){
                    dp[i][j]=dp[i-1][j-1]+1;
                }else{
                    dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        System.out.println(dp[n][m]);
    }
}
解法2:优化空间复杂度

空间复杂度 O(min(m,n)),时间复杂度 O(mn)

import java.util.*;

public class Main {
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        int n=in.nextInt();
        int m=in.nextInt();
        String a=in.next();
        String b=in.next();

        //逻辑开始
        if(n 

 

例题8:DAG最长路

DAG是有向无环图。

(1)求整个DAG中的最长路径

dp[i]表示从i号顶点出发能获得的最长路径长度。图用邻接矩阵存储。

class Solution {
    int DP(int i){
        if(dp[i]>0)return dp[i];
        for(int j=0;jdp[i]){ //可以获得更长的路径
                    dp[i]=temp; //覆盖dp[i]
                    choice[i]=j;    //i号顶点的后继顶点是j
                }
            }
        }
        return dp[i];
    }

    //调用printPath前需要先得到最大的dp[i],然后将i作为路径起点传入
    void printPath(int i){
        System.out.print(i);
        while(choice[i]!=-1){   //choice数组初始化为-1
            i=choice[i];
            System.out.println("->"+i);
        }
    }
}
(2)固定终点,求DAG的最长路径

dp[i]表示从i号顶点出发到达重点T能获得的最长路径长度。图用邻接矩阵存储。

class Solution {
    int DP(int i){
        if(vis[i])return dp[i];
        vis[i]=true;
        for(int j=0;j 

 

例题9:数塔问题
import java.util.*;

public class T5 {
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        int n=in.nextInt();
        int[][] num=new int[n+1][n+1];
        int[][] dp=new int[n+1][n+1];
        for(int i=1;i<=n;++i){
            for(int j=1;j<=i;++j){
                num[i][j]=in.nextInt();
            }
        }
        //逻辑开始
        for(int j=1;j<=n;++j){
            dp[n][j]=num[n][j];
        }
        for(int i=n-1;i>=1;--i){
            for(int j=1;j<=i;++j){
                dp[i][j]=Math.max(dp[i+1][j],dp[i+1][j+1])+num[i][j];
            }
        }
        System.out.println(dp[1][1]);
    }
}
例题10:二叉树方案数
import java.util.*;

public class T9 {
    public static void main(String[] args) {
        Scanner in =new Scanner(System.in);
        int n=in.nextInt();
        int m=in.nextInt();

        //逻辑开始
        //[i][j]表示i个节点最大深度为j的树数量
        int mod= (int)Math.pow(10,9)+7;
        long[][] dp=new long[n+1][m+1];     //必须是long,不然只能通过部分用例
        Arrays.fill(dp[0], 1);
        for(int i=1;i<=n;++i){
            for(int j=1;j<=m;++j){
                for(int k=0;k 
例题11:正确比例下的最大乘积 
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/759222.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号