面试_动态规划

01背包

剑指 Offer II 101. 分割等和子集

给定一个非空的正整数数组 nums ,请判断能否将这些数字分成元素和相等的两部分。

示例 1:

输入:nums = [1,5,11,5]
输出:true
解释:nums 可以分割成 [1, 5, 5] 和 [11] 。
示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:nums 不可以分为和相等的两部分

提示:

1 <= nums.length <= 200
1 <= nums[i] <= 100
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        //nums = [1,5,11,5] -> [1, 11],[5,5] -> true
        int nlen = nums.size();
        if (nlen < 2) {
            return false;
        }
        int total_sum = accumulate(nums.begin(), nums.end(), 0);
        // 和为奇数,肯定不可以拆分
        if (total_sum % 2) {
            return false;
        }
        //找最大值
        int max_sum = *max_element(nums.begin(), nums.end());
        int part_sum = total_sum / 2;
        if (max_sum > part_sum) {
            return false;
        }
        //1. dp代表部分和为i
        // vector<vector<int> > dp(nlen, vector<int>(part_sum + 1, 0));
        // for (int i = 0; i < nlen; ++i) {
        //     dp[i][0] = true;
        // }
        // //第0个数可以构成num[0]的部分和
        // dp[0][nums[0]] = true;
        // for (int i = 1; i < nlen; ++i)
        // {
        //     int num = nums[i];
        //     for (int j = 1; j <= part_sum; ++j)
        //     {
        //         //当前数小于部分和, 不加 | 加当前数
        //         if (nums[i] <= j) {
        //             dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];
        //         } else {
        //             dp[i][j] = dp[i - 1][j];
        //         }
        //     }
        // }
        // return dp[nlen - 1][part_sum];
        
        //2. 优化
        vector<int> dp(part_sum + 1, 0);
        dp[0] = true;
        for (int i = 0; i < nlen; ++i)
        {
            int num = nums[i];
            for (int j = part_sum; j >= num; --j)
            {
                //不加 | 加
                dp[j] = dp[j] | dp[j - num];
            }
        }
        return dp[part_sum];
    }
};

完全背包

322. 零钱兑换

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

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

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

示例 1:

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

输入:coins = [2], amount = 3
输出:-1
示例 3:

输入:coins = [1], amount = 0
输出:0

提示:

1 <= coins.length <= 12
1 <= coins[i] <= 231 - 1
0 <= amount <= 104

法一:动态规划

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        if amount == 0:
            return 0
        clen = len(coins)
        Max = amount + 1
        dp = [Max for _ in range(amount + 1)]
        dp[0] = 0
        for i in range(1, amount + 1):
            for j in range(0, clen):
                if coins[j] <= i:
                    dp[i] = min(dp[i], dp[i - coins[j]] + 1)
        
        return dp[amount] if dp[amount] <= amount else -1

法二:记忆化搜索

class Solution {
   vector<int> count;
   int dp(vector<int>& coins, int rest_amount)
   {
        if (rest_amount < 0) return -1;
        if (rest_amount == 0) return 0;
        //如果下次还要计算这个问题的值直接从数组中取出返回即可,这样能保证每个子问题最多只被计算一次
        if (count[rest_amount - 1] != 0) return count[rest_amount - 1];
        int Min = INT_MAX;
        for (int coin : coins)
        {
            //重复选
            int res = dp(coins, rest_amount - coin);
            if (res >= 0 && res < Min) {
                Min = res + 1;
            }
        }
        count[rest_amount - 1] = (Min == INT_MAX ? -1 : Min);
        return count[rest_amount - 1];
   }
public:
    int coinChange(vector<int>& coins, int amount) {
        if (amount < 1) return 0;
        //count为0~amount所需要的最小硬币数
        count.resize(amount);
        return dp(coins, amount);
    }
};

python写法:使用 @functools.lru_cache(max_len)

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
        # 2. 记忆化搜索
        @functools.lru_cache(amount)
        def dp(rest_amount):
            if rest_amount < 0: return -1
            if rest_amount == 0: return 0
            mini = int(1e9)
            for coin in coins:
                res = dp(rest_amount - coin)
                if res >= 0 and res < mini:
                    mini = res + 1
            return mini if mini < int(1e9) else -1
        
        if amount < 1: return 0
        return dp(amount)

一、简单DP

62. 不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

示例 1:

输入:m = 3, n = 7
输出:28
示例 2:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。

  1. 向右 -> 向下 -> 向下
  2. 向下 -> 向下 -> 向右
  3. 向下 -> 向右 -> 向下
    示例 3:

输入:m = 7, n = 3
输出:28
示例 4:

输入:m = 3, n = 3
输出:6

提示:

1 <= m, n <= 100
题目数据保证答案小于等于 2 * 109
class Solution {
    int ans;
public:
    // 1. dfs:超时
    // void dfs(int i, int j, int m, int n)
    // {
    //     if (i < 0 || i == m || j < 0 || j == n) {
    //         return;
    //     }
    //     if (i == m - 1 && j == n - 1) {
    //         ans += 1;
    //         return;
    //     }

    //     int dirs[][2] = {
   {0, 1}, {1, 0}};
    //     for (int k = 0; k < 2; k++)
    //     {
    //         int x = i + dirs[k][0], y = j + dirs[k][1];
    //         if (x < 0 || x == m || y < 0 || y == n) {
    //             continue;
    //         }
    //         dfs(x, y, m, n);
    //     }
    // }
    // int uniquePaths(int m, int n) {
    //     dfs(0, 0, m, n);
    //     return ans;
    // }
    //2. 动态规划: O(n^2)
    // int uniquePaths(int m, int n)
    // {
    //     vector<vector<int> > dp(m + 1, vector<int>(n + 1));
    //     for (int i = 0; i < m; ++i) {
    //         dp[i][0] = 1;
    //     }
    //     for (int j = 0; j < n; ++j) {
    //         dp[0][j] = 1;
    //     }
    //     for (int i = 1; i < m; ++i)
    //     {
    //         for (int j = 1; j < n; ++j)
    //         {
    //             dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
    //         }
    //     }
    //     return dp[m - 1][n - 1];
    // }

    // O(m)
    int uniquePaths(int m, int n)
    {
        // C(m + n - 2, m - 1): m + n - 2次移动,有m-1次向下,n-1次向右。因此路径总数
        long long ans = 1;
        for (int x = n, y = 1; y < m; ++x, ++y)
        {
            ans = ans * x / y;
        }
        return ans;
    }
};

动态规划:时间复杂度O(n2),空间复杂度O(n2)
组合数:时间复杂度O(m), 空间O(1)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值