算法篇------动态规划1

动态规划的概念和理解

对动态规划的经验:
1,抽象(状态数组下标的控制,状态数组下标位置的值的意义)。
2, 代码短小精悍,思维复杂庞大。
3, 必须画图。
4, 感悟。

这一类题的求解核心:
首先,不会照搬照抄状态定义,状态转移方程,,,,等等来写,而是自己的感悟过程
1,将一个大问题划分成一个个子问题。
2,每一步都要求解子问题,因为下一步必须要用到子问题的结果。

题目1—斐波那契数列

我们知道,求解斐波那契数列的过程就是一个典型的动态规划。
0,1,1,2,3,5,8,13,21,34,

这里以一个青蛙跳台阶的例子来说明情况,
大家都知道青蛙跳台阶是一个斐波那契数列,可是为什么是斐波那契数列呢?

牛课网的青蛙跳台阶问题
在这里插入图片描述
代码1:递归
时间复杂度: O(n ^ 2) 画出图的时候类似于一颗二叉树,执行的次数取最坏
空间复杂度:O(n) 这里为什么是O(n)?函数栈帧是不断在复用的,举个例子f(5) = f(4) + f(3)
调用f(4)创建相应的栈帧,调用完成然后销毁。 之后才会去调用f(3)

int fib(int n)
{
    if(n == 1)
      return 1;
    else if(n == 2)
      return 2;
    else
      return fib(n - 1) + fib(n - 2);
}

代码2: 用数组存放起来
空间复杂度 : O(N)
时间复杂度:O(N)

    int arr[41] = {0, 1, 2,};
    for(int i = 3; i < 41; i++)
    {
        arr[i] = arr[i - 1] + arr[i - 2];
    }

代码3:直接用一个值表示每一次计算的结果
空间复杂度: O(1)
时间复杂度:O(n)

 int f1 = 1, f2 = 2;
    int ret = 0;  //结果
    for(int i = 2; i < n; i++)
    {
        ret = f1 + f2;
        f1 = f2;
        f2 = ret;
    }

题目2-------三角形(中等)

牛客网:三角形

我先吐槽一下一波题目:真的坑坏本宝宝了
它从上向下走的时候,只能:直接向下 或者 右斜向下
也就是(i, j) ---- > (i + 1, j) 或者(i + 1, j + 1)。

我理解的时候是还可以向斜向左, 也就是 k可以到(i + 1, j -1 )。因为题目说的是走到下一行的相邻位置
搞的我把题目想的的复杂,然后巨难受。
在这里插入图片描述

思路1代码:
时间复杂度 : O(n ^ 2)
空间复杂度: O(n ^ 2)

class Solution {
public:
    int minimumTotal(vector<vector<int> > &triangle) 
    {
        int row = triangle.size();
        //最后一行元素的大小
        int col = triangle.back().size();
        vector<vector<int>> vv(triangle);

        for(int i = 1; i < row; i++)
        {
           for(int j = 0; j <= i; j++)
           {
               if(j == 0)
                   vv[i][j] = vv[i-1][j] + triangle[i][j];
               else if(i == j)
                   vv[i][j] = vv[i-1][j - 1] + triangle[i][j];
               else
                   vv[i][j] = min(vv[i-1][j-1], vv[i-1][j]) + triangle[i][j];
           }      
        }
        
        //遍历最后一排求解
        int min = INT_MAX;
        for(int i = 0; i < col; i++)
        {
            if(min > vv[row-1][i])
               min = vv[row-1][i];
        }

        return min;
    }
};

思路2代码:
时间复杂度: O(n ^ 2)
空间复杂度: O(n)

class Solution {
public:
    int minimumTotal(vector<vector<int> > &triangle) 
    {
        vector<int> v(triangle.back());
        int row = triangle.size();

        for(int i = row - 2; i >= 0; i--)
        {
            for(int j = 0; j <= i; j++)
            {
                v[j] = min(v[j], v[j + 1]) + triangle[i][j];
            }
        }
        
        return v[0];
    }
};

题目3----拆分词句(较难)

牛客网:拆分词句

题目描述:
给定一个字符串s和一组单词dict,判断s是否可以用空格分割成一个单词序列,使得单词序列中所有的单词都是dict中的单词(序列可以包含一个或多个单词)。
例如:
给定s=“nowcode”;
dict=[“now”, “code”].
返回true,因为"nowcode"可以被分割成"now code".
在这里插入图片描述
代码:
时间复杂度: O(n ^ 2)
空间复杂度: O(n)

class Solution {
public:
    bool wordBreak(string s, unordered_set<string> &dict) 
    {
        int len = s.size() + 1;

        //can_break[i]的值表示:前i个字符是否可以被分割
        vector<bool> can_break(len + 1, false);

        //初始状态:可以理解为0个字符就是可以分割的(可在词典中找到)
        can_break[0] = true;   

        for(int i = 1; i < len + 1; i++)
        {
            
            for(int j = i - 1; j >= 0; j--)
            {
                //前j个字符可以分割, 然后数组中 j + 1到第 j个字符可以分割
                //数组下标的索引 - 1,  另外的话搞不清下标,可以想极限情况
                if(can_break[j] && dict.find(s.substr(j, i - j)) != dict.end())
                {
                    can_break[i] = true;
                    break;
                }
            }
        }
        
        return can_break[len];
    }
};

题目4-----路径总数(简单)

牛客网:路径总数
有一个坑: 只有一行一列的时候(起点和终点在一起的时候居然结果为1)。离大谱。
在这里插入图片描述

代码:
时间复杂度:O(m * n)
空间复杂度: O(m * n)

class Solution {
public:
    int uniquePaths(int m, int n) 
    {
        //避免那个坑,全部给成1算了
        vector<vector<int>>  vv(m, vector<int>(n, 1));

        for(int i = 1; i < m; i++)
        {
            for(int j = 1; j < n; j++)
            {
                vv[i][j] = vv[i-1][j] + vv[i][j-1];
            }
        }

        return vv[m-1][n-1];
    }
};

题目5------最小路径和(中等)

牛客网:带权值的最小路径和

在这里插入图片描述

代码:
时间复杂度: O(m * n)
空间复杂度: O(m * n)

class Solution {
public:
    int minPathSum(vector<vector<int> >& grid) 
    {
        int row = grid.size();
        int col = grid[0].size();

        vector<vector<int>> vv(row, vector<int>(col,0));
        vv[0][0] = grid[0][0];

        for(int i = 1; i < row; i++)
           vv[i][0] = vv[i-1][0] + grid[i][0];
        
        for(int i = 1; i < col; i++)
           vv[0][i] = vv[0][i-1] + grid[0][i];

        for(int i = 1; i < row; i++)
        {
            for(int j = 1; j < col; j++)
            {
                vv[i][j] = min(vv[i-1][j], vv[i][j-1]) + grid[i][j];
            }
        }
        
        return vv[row-1][col-1];
    }
};

题目6-----背包问题

练码:背包问题2

这个背包问题你一定会有很多疑惑:
例如:放不放的下? 放的下要不要放?
然后就是放的下,要不要遍历拿出某一个物品来比较价值。

核心:画图 + 感悟。
行标 i : 物品个数
列标j : 背包大小
数组下标表示什么呢? 在背包大小为j的情况下,放i个物品的最大价值。

并且要多开一行一列: 表示没有物品的时候,或者背包大小为0的时候的价值 0 。
为什么要多开一行一列??? 解释困难-----> 需要感悟!!!!
在这里插入图片描述

代码:
时间复杂度:O(n * m)
空间复杂度: O(n * m)

class Solution {
public:
    int backPackII(int m, vector<int> &a, vector<int> &v)
    {
        int row = a.size() + 1;
        int col = m + 1;
        vector<vector<int>> vv(row,vector<int>(col,0));

        for(int i = 1; i < row; i++)
        {
            for(int j = 1; j < col; j++)
            {
                if(a[i-1] > j)
                {
                    vv[i][j] = vv[i-1][j];
                }
                else
                {
                    int take_pack = vv[i-1][j-a[i-1]] + v[i - 1];
                    vv[i][j] = max(take_pack, vv[i-1][j]);
                }
            }
        }
        return vv[row-1][col-1];
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

通过全部用例

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值