【算法】动态规划合集

12 篇文章 0 订阅

由于我是动态规划界菜鸡中的菜鸡,所以这里列的都是很基础的示例

剑指 Offer 10- I:斐波那契数列

关于为什么需要动态规划,还要从经典的斐波那契数列说起(力扣力扣

递归

这种题当然难不倒我,大一C语言就教了好吗?

class Solution {

    /**
     * @param Integer $n
     * @return Integer
     */
    function fib($n) {
        if($n<=1){
            return $n;
        }
        return $this->fib($n-1)+$this->fib($n-2);
    }
}

在测试用例$n=39时,超出运行时间限制了...

本例中的递归解法差在哪了(只是本例,不是所有递归一概而论的)

如图:它有大量的重复计算,算f(n)前需要先算f(n-1)、f(n-2),再依次向下算...

Picture0.png

 map图

有一些改良的方法,就是在缓存或者增加数组来存储f(0)、f(1)...f(n-2)这些值,以避免重复计算,虽然会额外增加内存空间,但是思路还是不错的

class Solution {

	public static $map = [];
    /**
     * @param Integer $n
     * @return Integer
     */
    function fib($n) {
    	if($n<=1){
    		self::$map[$n] = $n;
    		return $n;
    	}else{
    		$a = isset(self::$map[$n-1]) ? self::$map[$n-1] : $this->fib($n-1);
    		$b = isset(self::$map[$n-2]) ? self::$map[$n-2] : $this->fib($n-2);

            //因为$n只与最邻近两个元素相关,所以可以删除前面的以减少内存占用开销,使得空间复杂度为常量O(1)
            if(isset(self::$map[$n-3])){ unset(self::$map[$n-3]); }

    		self::$map[$n] = ($a+$b)%1000000007;
    		return self::$map[$n];
    	}
    }
}

因为$map随着元素增加而线性递增,空间复杂度o(n)。

也可以按注释中类似方法删除不需要部分,空间复杂度o(1)。

动态规划

动态规划和递归有一点相似,但是动态规划往往要求总结和显式指出规律,在处理具有重叠子问题时相对递归的优势就非常明显。

f(n)只跟f(n-1)和f(n-2)有关,那么可以只记录更新f(n-1)、f(n-2)

class Solution {

    /**
     * @param Integer $n
     * @return Integer
     */
    function fib($n) {
    	if($n<=1){
    		return $n;
    	}
    	$a = 0;
    	$b = 1;
    	for($i=2;$i<=$n;$i++){
    		$sum = ($a+$b)%1000000007;
    		$a = $b;
    		$b = $sum;
    	}
    	return $sum;
    }
}

GO:

package main

func main()  {
	f := fei(6)
	println(f)
}

func fei(n int) int{

	var a int = 0
	var b int = 1
	var sum int
	if n<=1{
		return n
	}
	for i :=2;i<=n;i++{
		sum = a+b
		a = b
		b = sum
	}
	return sum
}

 

剑指 Offer 10- II. 青蛙跳台阶问题

与斐波那契数列几乎相同的还有青蛙跳台问题

力扣

递归

class Solution {
 
    /**
     * @param Integer $n
     * @return Integer
     */
    function numWays($n) {
        if($n<=1){
            return 1;
        }
        return $this->numWays($n-1)+$this->numWays($n-2);
    }
}

气不气~ 

map

存一下过往数据

class Solution {

	public static $map=[];
    /**
     * @param Integer $n
     * @return Integer
     */
	function numWays($n) {
	    if($n<=1){
	    	self::$map[$n]=1;
			return 1;
		}else{
			$a = isset(self::$map[$n-1]) ? self::$map[$n-1] : $this->numWays($n-1);
    		$b = isset(self::$map[$n-2]) ? self::$map[$n-2] : $this->numWays($n-2);
			self::$map[$n]=($a + $b)%1000000007;
			return self::$map[$n];
		}
	}
}

还有与其几乎完全一样的爬楼梯问题:力扣

动态规划

class Solution {

    /**
     * @param Integer $n
     * @return Integer
     */
    function numWays($n) {
    	if($n<=1){
    		return 1;
    	}
    	$a = $b = 1;
    	for($i=2;$i<=$n;$i++){
    		$sum = ($a+$b)%1000000007;
    		$a = $b;
    		$b = $sum;
    	}
    	return $sum;
    }
}

 时间空间复杂度都是常量级的O(1)

力扣198题:打家劫舍

当然这是动态规划非常基础的用法,还有非常经典的打家劫舍问题

力扣

网上分析很多,其实偷窃最大金额就在隔一间前的最大金额+最后一间的总金额与一间前的最大金额的对比结果。

动态规划1

非常经典的dp数组开辟额外存储空间,很好理解

class Solution {

    /**
     * @param Integer[] $nums
     * @return Integer
     */
    function rob($nums) {

        if(count($nums)==1){
            return $nums[0];
        }elseif (count($nums)==2) {
        	return max($nums[0],$nums[1]);
        }
        $dp=[];
        $dp[0] = $nums[0];
        $dp[1] = max($nums[0],$nums[1]);
        for($i=2;$i<count($nums);$i++){
        	$dp[$i] = max($dp[$i-2]+$nums[$i],$dp[$i-1]);
            //确定不用后删除,内存占用变为常量级
        	unset($dp[$i-2]);
        }
        //因为最后执行的$i++,实际$i的值会=count($nums),需要-1以获取数组最后一个元素
        return $dp[$i-1];
    }
}

动态规划2

当然如果理解,其实这也是非常经典的辗转交换,不必开辟了空间再删除

class Solution {

    /**
     * @param Integer[] $nums
     * @return Integer
     */
    function rob($nums) {

        if(count($nums)==1){
            return $nums[0];
        }elseif (count($nums)==2) {
        	return max($nums[0],$nums[1]);
        }
        $a = $nums[0];
        $b = max($nums[0],$nums[1]);
        for($i=2;$i<count($nums);$i++){
        	$c = max($a+$nums[$i],$b);
        	$a = $b;
        	$b = $c;
        }
        return $c;
    }
}

不清楚abc具体为啥这样交换的话,参考上例中dp数组,就会发现它是一个平移

 

 时间复杂度O(n),空间复杂度O(1)

种花问题

力扣

每块地种花的条件是:前后自己都没种过花,种了花的地需要标记为1,第一个和最后一个位置特殊情况单独考虑。

动态规划

class Solution {

    /**
     * @param Integer[] $flowerbed
     * @param Integer $n
     * @return Boolean
     */
    function canPlaceFlowers($flowerbed, $n) {

    	if(count($flowerbed) == 1){
    		return intval($flowerbed[0] == 0) >= $n;
    	}else{
    		$nums = 0;
    		$start = 1;
    		if($flowerbed[0] == 0 && $flowerbed[1] == 0){
    			$nums ++;
                $flowerbed[0] = 1;
    		}
    		$count = count($flowerbed);
    		for($i=$start;$i<$count-1;$i++){
    			if($flowerbed[$i-1] == 0 && $flowerbed[$i] == 0 && $flowerbed[$i+1] == 0){
    				$nums++;
    				$flowerbed[$i] = 1;
    			}
    		}
            if($nums >= $n){
    			return true;
    		}
    		if($flowerbed[$count-2] == 0 && $flowerbed[$count-1] == 0){
    			$nums++;
    		}
    	}
    	return $nums >= $n;
    }
}

最大子序和

力扣

动态规划

当前最大值$max和之前最大值有关,也和当前能累积到的最大“助力”$pre有关,当$nums[$i]加上自己能累积的“助力” > 之前最大值$max时,将取代之。

    function maxSubArray($nums) {
    	$pre = 0;
    	$max = $nums[0];
    	for($i=0;$i<count($nums);$i++){
    		//$pre是与$nums[$i]连续后的最大值
    		$pre = max($pre+$nums[$i],$nums[$i]);
            //当前最大值
    		$max = max($pre,$max);
    	}
    	return $max;
    }

时间复杂度O(n),空间复杂度O(1) 

使用最小花费爬楼梯

力扣

动态规划

所谓的爬一阶实际是到达相邻元素,爬两阶是到达隔一个的元素,当楼梯数大于等于3之后,可知f(n) =  min(f(n-2)+cost[n-2],f(n-1)+cost[n-1]).动态规划的难点就在推导规律,可以先枚举再总结。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值