算法系列之动态规划

动态规划(Dynamic Programming,简称DP)是一种用于解决复杂问题的算法设计技术。它通过将问题分解为更小的子问题,并存储这些子问题的解来避免重复计算,从而提高算法的效率。本文将介绍动态规划的基本概念、适用场景、复杂度分析,并通过Java代码实现经典的动态规划问题。

动态规划介绍

动态规划的核心思想是将一个复杂的问题分解为若干个相互重叠的子问题,通过解决这些子问题来构建原问题的解。动态规划通常适用于具有以下两个性质的问题:

  • 最优子结构:问题的最优解包含其子问题的最优解。

  • 重叠子问题:在求解过程中,相同的子问题会被多次计算。

动态规划算法的实现方式:

  • 自底向上(Bottom-Up):通过迭代的方式从最小的子问题开始,逐步构建更大的子问题的解,直到解决原问题。

复杂度分析

动态规划的时间复杂度和空间复杂度取决于问题的规模和子问题的数量。假设问题的规模为n,子问题的数量为m,则:

  • 时间复杂度:通常为O(m * n),其中m是子问题的数量,n是每个子问题的计算复杂度。

  • 空间复杂度:通常为O(m),用于存储子问题的解。

Java实现斐波那契数列

斐波那契数列:斐波那契数列的定义是从0和1开始,后续的每一项都是前两项的和,即0、1、1、2、3、5、8、13、21、34......这个数列在数学、自然界以及日常生活中都有着广泛的应用和意义。‌

我们通过动态规划和递归两种方式实现输入一个正整数 n ,输出斐波那契数列的第 n 项。

java 复制代码
/**
 * 斐波那契数列
 *
 * 斐波那契数列:斐波那契数列的定义是从0和1开始,后续的每一项都是前两项的和,即0、1、1、2、3、5、8、13、21、34......这个数列在数学、自然界以及日常生活中都有着广泛的应用和意义。‌
 *
 * 输入一个正整数 n ,输出斐波那契数列的第 n 项。
 */
public class FibonacciSequence {

    /**
     *动态规划解法
     * @param n
     * @return
     */

    public static int fibonacciDP(int n) {
        // 边界条件
        if (n <= 1) {
            return n;
        }
        //定义初始发f(n-2)的值
        int prev2 = 0;
        //定义初始发f(n-1)的值
        int prev1 = 1;
        //定义初始发f(n)的值
        int current = 0;
        // 循环计算斐波那契数列的第 n 项(f(n) = f(n-1)+f(n-2))
        for (int i = 2; i <= n; i++) {
            current = prev1 + prev2;
            prev2 = prev1;
            prev1 = current;
        }
        return current;
    }

    /**
     *递归解法
     * @param n
     * @return
     */

    public static int fibonacciRec(int n) {
        // 边界条件
        if (n <= 1) {
            return n;
        }
        //递归求解
        return fibonacciRec(n-1) +fibonacciRec(n-2);
    }
    public static void main(String[] args) {
        System.out.println(fibonacciDP(10));
        System.out.println(fibonacciRec(10));
    }

}

⼯作安排问题

  • 问题描述:

⼩明每周上班都会拿到⾃⼰的⼯作清单,⼯作清单内包含 n 项⼯作,每项⼯作都有对应的耗时时⻓(单位h)和报酬,⼯作的总报酬为所有已完成⼯作的报酬之和。那么请你帮⼩明安排⼀下⼯作,保证⼩明在指定的⼯作时间内⼯作收⼊最⼤化。

  • 输入描述:

输⼊的第⼀⾏为两个正整数 T , n 。 T 代表⼯作时⻓(单位h, 0 < T < 100000 ) , n 代表⼯作数量( 1 < n < 3000 )

接下来是 n ⾏,每⾏包含两个整数 t , w 。 t 代表该项⼯作消耗的时⻓(单位h, t > 0 ) , w 代表该项⼯作的报酬。

  • 输出描述

输出⼩明指定⼯作时⻓内⼯作可获得的最⼤报酬。

  • 示例

示例1

输入:

复制代码
40 3
20 10
20 20
20 5

输出:

复制代码
30

示例2

输入:

复制代码
100 3
50 10
20 30
50 20

输出:

复制代码
50
  • 代码实现
java 复制代码
public class WorkArrangement {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 输⼊最⼤⼯作时间max_time和⼯作数量N
        int max_time = scanner.nextInt();
        // ⼯作数量N
        int N = scanner.nextInt();
        //工作时常数组
        int[] times = new int[N];
        //工资数组
        int[] wages = new int[N];

        // 输⼊N份⼯作的时间和报酬
        for (int i = 0; i < N; i++) {
            times[i] = scanner.nextInt();
            wages[i] = scanner.nextInt();
        }

        Map<Integer,Integer> dpMap = new HashMap<>();
        //初始化工作时长和工资
        dpMap.put(0,0);

        for(int i = 0; i< N; i++){
            // 复制当前的dpMap
            Map<Integer, Integer> currentDP = new HashMap<>(dpMap);
            //当前工作的时长和工资
            int time = times[i];
            int wage = wages[i];
            // 遍历当前currentDP哈希表中,所有的时间和报酬
            for(Map.Entry<Integer, Integer> entry: currentDP.entrySet()){
                int preTime = entry.getKey();
                int preWage = entry.getValue();
                // 当前的结束时间
                int endTime = preTime + time;
                // 当前结束时间对应的工资
                int endWage = preWage + wage;
                // 如果结束时间不超过最大时间,则更新dpMap
                if(endTime <= max_time){
                    dpMap.put(endTime, Math.max(dpMap.getOrDefault(endTime,0),endWage));
                }
            }

        }

        // 输出dpMap哈希表中的最⼤值
        int maxWage = 0;
        for (int wage : dpMap.values()) {
            maxWage = Math.max(maxWage, wage);
        }

        System.out.println(maxWage);

    }
}

总结

动态规划是一种强大的算法设计技术,适用于解决具有最优子结构和重叠子问题性质的问题。通过合理地分解问题和存储子问题的解,动态规划可以显著提高算法的效率。本文通过斐波那契数列和工作安排问题展示了动态规划的基本思想和Java实现。希望本文能帮助你更好地理解和应用动态规划算法。

相关推荐
এ᭄画画的北北43 分钟前
力扣-142.环形链表II
算法·leetcode
朝九晚五ฺ1 小时前
【算法学习】递归、搜索与回溯算法(二)
数据结构·学习·算法·深度优先
短尾黑猫1 小时前
【LeetCode 42】接雨水(单调栈、DP、双指针)
算法·leetcode
月亮被咬碎成星星1 小时前
LeetCode[226] 翻转二叉树
算法·leetcode
KingQian20181 小时前
单片机嵌入式滤波算法库
单片机·嵌入式硬件·算法
瑞雪兆丰年兮2 小时前
数学实验(Matlab语言环境和线性代数实验)
算法·数学建模·matlab·数学实验
烨然若神人~3 小时前
算法训练营第十一天|150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素
算法
天天爱吃肉82183 小时前
机器学习经典算法:用决策树原理优化新能源汽车续航能力
算法·决策树·机器学习
金融小师妹3 小时前
AI技术视角:美联储信号与黄金动态的量化研究——基于多模态数据分析框架
大数据·人工智能·算法
dot to one3 小时前
C++ 深入解析 数据结构中的 AVL树的插入 涉及的旋转规则
数据结构·c++·算法·visual studio·avl树