贪心算法和动态规划

人们认识事物的方法有三种:通过概念(即对事物的基本认识)、通过判断(即对事物的加深认识)、和推理(对事物的深层认识)。其中,推理又包含归纳法和演绎法。(这些从初中高中一直到大学我们都是一直在学习的,关键是理解)

归纳法是从特殊到一般,属于发散思维;(如:苏格拉底会死;张三会死;李四会死;王五会死……,他们都是人。所以,人都会死。)

演绎法是从一般到特殊,属于汇聚思维。(如:人都会死的;苏格拉底是人。所以,苏格拉底会死。)

贪心:

贪心法:是指从问题的初始状态出发,通过若干次的贪心选择而得出最优值(或较优值)的一种解题方法。贪心策略总是做出在当前看来是最优的选择,也就是说贪心策略并不是从整体上加以考虑,它所作出的选择只是在某种意义上的局部最优解。(贪心在很多情况下往往是不合适的,所以对于刷题我并不建议使用弹性算法,用动态规划就挺好)

零钱兑换:

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

输入:coins = [1, 2, 5], amount = 11
输出:3 
解释:11 = 5 + 5 + 1

如果我们用贪心思想,每次取最大的数额的硬币,那么6,1,1,1,1,5,5,5凑成10,我们可能可能会陷入一种局部最优,但是并不是我们想要的结果 

public class coinChange {
    /**
     * 问题最好硬币数组成一个数
     * 硬币面额一定,数量不一定
     * f(要组成的面额)=最小组成总数的硬币数量
     *假设我们知道 F(S) ,即组成金额 S 最少的硬币数,最后一枚硬币的面值是 C
     *F(S)=F(S−C)+1
     *那问题就转化成了求F(S-C)
     * 由于我们不知道c是多少,所以我们要枚举枚举每个硬币面额值,并选择其中的最小值min(F(S-ci))
     * @param coins
     * @param amount
     * @return
     */
    public int coinChange(int[] coins, int amount) {
        if(amount == 0) return 0;
        if(amount<0){
            return  -1;
        }
        int[] dp = new int[amount+1];//最多的硬币情况是全部是1元,共有amount个硬币.其实这里不一定是+1,+多少都可以。只要dp不可能取都值即可
        Arrays.fill(dp, amount+1);//必须将所有的dp赋最大值,因为要找最小值
        dp[0] = 0;//自底向上,金额为0,最小硬币数为0。当输入当金额是0当说话,肯定是0;
        for(int am = 1; am <= amount; am++) {//自底向上
            for (int coin : coins) {//遍历coins的金额
                if (am >= coin) {//只有总金额大于当前当面值当时候才继续
                    dp[am] = Math.min(dp[am], dp[am-coin] + 1);//金额为11的最小硬币数 和 金额为(11-一个面值)的最小硬币数+1 比较最小值
                
                }
            }
        }
        return dp[amount]>amount? -1: dp[amount];//
        }
}

打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

示例 1:

输入:[1,2,3,1]
输出:4
解释:偷窃 1 号房屋 (金额 = 1) ,然后偷窃 3 号房屋 (金额 = 3)。
     偷窃到的最高金额 = 1 + 3 = 4 。
示例 2:

输入:[2,7,9,3,1]
输出:12
解释:偷窃 1 号房屋 (金额 = 2), 偷窃 3 号房屋 (金额 = 9),接着偷窃 5 号房屋 (金额 = 1)。
     偷窃到的最高金额 = 2 + 9 + 1 = 12 。

/**
 * 输入是每个房间的金额
 * 问题是求能偷到到最大值
 * 约束是不能偷相邻的方便
 * f(0)=0
 * f(1)=nums[1]
 * f(2)=max(f(1),f(0)+nums[1])
 * f(3)=max(f(2),f(1)+nums[2])
 * f(4)=max(f(3),f(2)+nums[3])
 * 那么我们可以得出一个转移方程,这个看起来可以用递归,但是递归会有很多重复计算 不如直接做
 */
public class rob {
    
    public int rob(int[] nums) {
        int [] dp=new int[nums.length+1];
        
        if(nums==null){
            return 0;
        }
        if (nums.length==1){
            return nums[0];
        }
        dp[0] = nums[0];
        dp[1] = Math.max(nums[0], nums[1]);
        for (int i = 2; i <nums.length ; i++) {
            dp[i] = Math.max(dp[i - 2] + nums[i], dp[i - 1]);
        }
    
        return dp[nums.length - 1];
    
    }
}

做几道之后发现动态规划好想还是不是很难哈,但是问题是要怎么识别这是一个动态规划能解决的问题。

来来来再看一道

最长有效括号

给你一个只包含 '(' 和 ')' 的字符串,找出最长有效(格式正确且连续)括号子串的长度。

示例 1:

输入:s = "(()"
输出:2
解释:最长有效括号子串是 "()"
示例 2:

输入:s = ")()())"
输出:4
解释:最长有效括号子串是 "()()"
示例 3:

输入:s = ""
输出:0

/*
   看到这个题目的第一印象,,用栈
   然后就是我们要保存前面的遍历结果。
   最先想到的当然暴力法,每个都遍历一边,比较最大。
   我压根没想到这居然也是一道动态规划的问题
 */
   public int longestValidParentheses(String s) {
        Deque<Integer> stack = new LinkedList<>();
        int max = 0;
        //为了保持一致性,避免边界条件处理
        stack.push(-1);
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i)=='('){
                //入栈
                stack.push(i);
            }else {
                //出栈
                stack.pop();
                if (stack.isEmpty()) {
                    stack.push(i);
                }else {
                    max =Math.max(max,i-stack.peek());
                }
            }
            
        }
        return max;
    }

/**
动态规划解法,虽然这道题我肯定不会有动态规划去解决,因为什么? 想不到哈哈哈

*/
class Solution {
    public int longestValidParentheses2(String s) {
        int maxans = 0;
        int[] dp = new int[s.length()];
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) == ')') {
                if (s.charAt(i - 1) == '(') {
                    dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
                } else if (i - dp[i - 1] > 0 && s.charAt(i - dp[i - 1] - 1) == '(') {
                    dp[i] = dp[i - 1] + ((i - dp[i - 1]) >= 2 ? dp[i - dp[i - 1] - 2] : 0) + 2;
                }
                maxans = Math.max(maxans, dp[i]);
            }
        }
        return maxans;
    }
}

剪绳子(同差分整数):https://leetcode-cn.com/problems/jian-sheng-zi-lcof

给你一根长度为 n 的绳子,请把绳子剪成整数长度的 m 段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1] 。请问 k[0]*k[1]*...*k[m-1] 可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
 

 /**
     * 一个是j * (i - j) 直接相乘。
     *
     * 一个是j * dp[i - j],相当于是拆分(i - j),对这个拆分不理解的话,可以回想dp数组的定义。
     *
     * 那有同学问了,j怎么就不拆分呢?
     *
     * j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过了。那么从1遍历j,比较(i - j) * j和dp[i - j] * j 取最大的。递推公式:dp[i] = max(dp[i], max((i - j) * j, dp[i - j] * j));
     *
     * 也可以这么理解,j * (i - j) 是单纯的把整数拆分为两个数相乘,而j * dp[i - j]是拆分成两个以及两个以上的个数相乘。
     *
     * 如果定义dp[i - j] * dp[j] 也是默认将一个数强制拆成4份以及4份以上了。
     *
     * 所以递推公式:dp[i] = max({dp[i], (i - j) * j, dp[i - j] * j});
     *
     * 那么在取最大值的时候,为什么还要比较dp[i]呢?
     *
     * 因为在递推公式推导的过程中,每次计算dp[i],取最大的而已。
     * @param n
     * @return
     */
    public int cuttingRope(int n) {
        int dp[]=new int[n+1];
        dp[2] =2;
        for (int i = 2; i <n; i++) {
            for (int j = 2; j <i; j++) {
                dp[i] =Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
            }
        }
        return dp[n];
    }

贪心算法动态规划都是解决最优化问题的算法,但它们的思想和实现方式有所不同。 贪心算法是一种贪心选择策略的算法,它总是做出当前最优的选择,并希望通过这种选择能够得到全局最优解。贪心算法通常适用于问题具有最优子结构性质的情况,即问题的最优解可以通过子问题的最优解来构造。贪心算法的时间复杂度通常比较低,但是它不能保证得到全局最优解。 动态规划算法则是一种将问题分解成子问题并将子问题的解缓存起来的算法动态规划算法通常适用于问题具有重叠子问题和最优子结构性质的情况,即问题的最优解可以通过子问题的最优解来构造,并且子问题之间存在重叠。动态规划算法的时间复杂度通常比较高,但是它可以保证得到全局最优解。 下面是一个使用贪心算法动态规划算法解决背包问题的例子: 假设有一个背包,它的容量为C,有n个物品,每个物品有一个重量w和一个价值v。现在需要选择一些物品放入背包中,使得它们的总重量不超过C,且总价值最大。 使用贪心算法,我们可以按照每个物品的单位价值(即价值/重量)从大到小排序,然后依次将单位价值最大的物品放入背包中,直到背包无法再放入物品为止。 使用动态规划算法,我们可以定义一个二维数组dp[i][j],其中dp[i][j]表示在前i个物品中选择一些物品放入容量为j的背包中所能获得的最大价值。然后我们可以根据以下递推式来计算dp数组: dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]) 其中w[i]和v[i]分别表示第i个物品的重量和价值。最终的答案即为dp[n][C]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值