• 人生也要动态规划


    人生像一场旅行,参考别人,也能映射出以后的自己,虽然凡事都有意外,但是毕竟意外概率极小,还是要居安思危,规划好每个年龄段该做的事。

    乔治·桑塔亚纳说过,“那些遗忘过去的人注定要重蹈覆辙。”这句话放在问题求解过程中也同样适用。不懂动态规划的人会在解决过的问题上再次浪费时间,懂的人则会事半功倍。那么什么是动态规划?这种算法有何神奇之处?

    动态规划的好处在于存储以前计算过得值,不重复计算。

    1、什么是动态规划?

    动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推(或者说分治)的方式去解决。
    动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

    2、什么时候要用动态规划?
    如果要求一个问题的最优解(通常是最大值或者最小值),而且该问题能够分解成若干个子问题,并且小问题之间也存在重叠的子问题,则考虑采用动态规划。

    3、怎么使用动态规划?
    我把下面称为动态规划五部曲:
    1. 判题题意是否为找出一个问题的最优解
    2. 从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题
    3. 从下往上分析问题 ,找出这些问题之间的关联(状态转移方程)
    4. 讨论底层的边界问题
    5. 解决问题(通常使用数组进行迭代求出最优解)

    烤几个栗子778:
    例子1:
    剑指Offer:剪绳子
    给你一根长度为n的绳子,请把绳子剪成m段 (m和n都是整数,n>1并且m>1)每段绳子的长度记为k[0],k[1],…,k[m].请问k[0]k[1]…*k[m]可能的最大乘积是多少?

    例如,当绳子的长度为8时,我们把它剪成长度分别为2,3,3的三段,此时得到的最大乘积是18.
    看完题目,我们按照上面提到的“动态规划五部”解决问题
    1、判题题意是否为找出一个问题的最优解
    看到字眼是“可能的最大乘积是多少”,判断是求最优解问题,可以用动态规划解决;

    2、从上往下分析问题,大问题可以分解为子问题,子问题中还有更小的子问题
    题目中举了个例子:当绳子的长度为8时,我们把它剪成长度分别为2,3,3的三段,此时得到的最大乘积是18;我们可以从这里开始突破,把长度为8绳子的最大乘积分解为数个子问题,长度为8我们可以把它看成长度为1和7的绳子的和,或者长度 为2和6的绳子的和,或者长度为3和5的绳子的和and so on!
    到这里,相信大家已经看到一丝真理了吧?

    3. 从下往上分析问题 ,找出这些问题之间的关联(状态转移方程)
    在第二点时,我们已经从上到下分析问题了,现在我们要从下往上分析问题了。分析可知,
    f(8) 的值就是f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)它们之中的最小值,即f(8) = Max{f(1)*f(7),f(2)*f(6),f(3)*f(5),f(4)*f(4)}
    只要知道f(1)到f(7)的值就能求出f(8);对于f(7),只要知道f(1)到f(6)的值就能求出f(6);对于f(6),只要知道f(1)到f(5)的值就能求出f(6);以些类推,我们只要知道前几个边界的值,就能一步步迭代出后续的结果!
    状态转移方程: f(n)=Max{f(n-i)*f(i)} i={1,2,3,…,n/2}

    4. 讨论底层的边界问题
    底层的边界问题说的就是最小的前几个数值的f(n)的值,本题中就是f(0)、f(1)、f(2)、f(3)的值
    对于f(0),长度为0的绳子,没办法剪,没有意义
    对于f(1),长度为1的绳子,没办法剪,设为1
    对于f(2),长度为2的绳子,只有一种剪法,剪成两段长度为1的绳子,但剪后的乘积为1,比自身更小;如果不是求自身的值,要求乘积最大值的话就没必要剪。
    对于f(3),长度为3的绳子,只有一种剪法,剪成两段长度为1和2的绳子,但剪后的乘积为2,比自身更小;如果不是求自身的值,要求乘积最大值的话也没必要剪。
    5、解决问题
    这一部就是写代码了

    public static int cutting(int n) {
            //长度小于等等于1没办法剪
            if(n <= 1)
                return 0;
            //对于f(2),长度为2的绳子,只有一种剪法,剪成两段长度为1的绳子,剪后的乘积为1
            if(n == 2)
                return 1;
            //对于f(3),长度为3的绳子,只有一种剪法,剪成两段长度为1和2的绳子,但剪后的乘积为2
            if(n == 3)
                return 2;
            //数组用于存储绳子乘积最大值
            int value[] = new int[n + 1];
            value[0] = 0;
            value[1] = 1;
            //剪后的乘积为1,比自身更小;如果不是求自身的值,要求乘积最大值的话就没必要剪
            value[2] = 2;
            //剪后的乘积为2,比自身更小;如果不是求自身的值,要求乘积最大值的话也没必要剪
            value[3] = 3;
            //从f(4)开始迭代
            for(int i = 4;i <= n; i++) {
                max = 0;
                for(int j = 1;j <= i/2; j++) {
                    int val = value[j] * value[i - j];
                    max = val > max ? val : max;
                }
                value[i] = max;
            }
            return value[n];
    }

    max 就是在存储上一步计算的值放到数组里面,给下一步使用

    案列二

    在聊一下java中存储值

    java中对于基本数据类型外,其他都是引用类型,引用类型的值指向对象的引用,

    Node nodeA = new Node();

    Node nodeB = nodeA;

    那么操作nodeB 就是操作了NodeA

    那么这有什么用呢?比如单向循环链表

    public class ListTest {
        public static void main(String[] args) {
    
            Node frist = new Node(0);
            Node cur = new Node(-1);
            for(int i = 0; i<5; i++){
                Node boy = new Node(i);
                if(i==0)                  {
                    frist = boy;
                    frist.setNext(frist);
                    cur = frist;
                }
                else {
                    cur.setNext(boy);
                    boy.setNext(frist);
                    cur= boy;
                }
    
            }
            Node s = frist;
            while (s.getNext()!=null){
                System.out.println(s.getNext().getNo());
                s = s.getNext();
                if(s.getNext()==frist){
                    break;
                }
            }
        }
    
    
    }
    class Node{
    
        private   int no;
        private   Node next;
    
        public int getNo() {
            return no;
        }
    
        public void setNo(int no) {
            this.no = no;
        }
    
        public Node getNext() {
            return next;
        }
    
        public void setNext(Node next) {
            this.next = next;
        }
        public Node(int i){
            this.no = i;
        }
    }

    其中 cur 就是地址引用,将frist固定在头结点,是插入节点时更加方便,只需要cur.setNext(boy); boy.setNext(frist)形成环状即可,然后cur = boy,指向最后的节点 

  • 相关阅读:
    FastDFS介绍
    SwiftUI 中使用SDWebImageSwiftUI加载网络图片
    SwiftUI 中使用BBSwiftUIKit开源库实现上拉加载和下拉刷新
    SwiftUI 中使用ScrollView+LazyVStack代替List
    SwiftUI 动画
    SwiftUI 中实现省市区选择器
    SwiftUI 中Slider的使用
    SwiftUI 中Stepper的使用
    SwiftUI 中通过Toggle实现单选框和复选框效果
    SwiftUI 中加载bundle中的图片
  • 原文地址:https://www.cnblogs.com/xqhv587/p/12557947.html
Copyright © 2020-2023  润新知