LeetCode 446. 等差数列划分 II - 子序列(动态规划)/ 416. 分割等和子集 (背包问题)/322. 零钱兑换(完全背包)

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 446. 等差数列划分 II - 子序列(动态规划)/ 416. 分割等和子集 (背包问题)/322. 零钱兑换(完全背包)相关的知识,希望对你有一定的参考价值。

446. 等差数列划分 II - 子序列

2021.8.11 每日一题

题目描述

给你一个整数数组 nums ,返回 nums 中所有 等差子序列 的数目。

如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差序列。

例如,[1, 3, 5, 7, 9]、[7, 7, 7, 7] 和 [3, -1, -5, -9] 都是等差序列。
再例如,[1, 1, 2, 5, 7] 不是等差序列。
数组中的子序列是从数组中删除一些元素(也可能不删除)得到的一个序列。

例如,[2,5,10] 是 [1,2,1,2,4,1,5,10] 的一个子序列。
题目数据保证答案是一个 32-bit 整数。

示例 1:

输入:nums = [2,4,6,8,10]
输出:7
解释:所有的等差子序列为:
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]

示例 2:

输入:nums = [7,7,7,7,7]
输出:16
解释:数组中的任意子序列都是等差子序列。

提示:

1 <= nums.length <= 1000
-2^31 <= nums[i] <= 2^31 - 1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/arithmetic-slices-ii-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

动规:很容易想到要记录每个位置的每个公差对应的序列长度
但是如何开始这个动规是个问题,看题解后发现是把当前数和之前所有数的差都记录下来
注意数的范围,做差可能会越界,所以要转换成long来处理

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        //想想怎么动规
        //其实我有个思路,就是用哈希表记录每个数字出现的位置
        //然后遍历,记录每个位置,以它为结尾,并且公差是t的等差数列的长度;如果后面还有跟他公差相同的,那么就把这个序列加1

        //其实往往就差临门一脚
        //我没有想好这个动规该怎么开始,所以去看了题解,weiwei哥写的太好了
        //发现是记录和前面每个数的差值,并且记录这个差值下出现的元素个数
        //然后这个差值和元素个数是用一个哈希表数组存放的

        int l = nums.length;
        //哈希数组,存放差值和对应个数
        Map<Long, Integer>[] map = new HashMap[l];

        for(int i = 0; i < l; i++){
            map[i] = new HashMap<>();
        }


        int res = 0;
        //动规
        for(int i = 1; i < l; i++){
            Map<Long, Integer> curr = map[i];
            for(int j = 0; j < i; j++){
                //两个数的差值
                long diff = (long)nums[i] - (long)nums[j];
                Map<Long, Integer> temp = map[j];
                //如果差值在temp中有,那么就可以把这个差值的序列加1,如果没有,那么就赋值为1
                //这里有个问题,就是对于示例2来说,每个位置差都相同,那么就提示我们,不应该仅仅加之前temp中的结果
                //还应该把当前已存在的结果加上
                curr.put(diff, curr.getOrDefault(diff, 0) + temp.getOrDefault(diff, 0) + 1);
                //对于结果的贡献,加了已当前数为结尾的几个等差序列
                res += temp.getOrDefault(diff, 0);
            }
        }
        return res;
    }
}

//[0,2000000000,-294967296]错了
//为啥呢,越界了

416. 分割等和子集

题目描述

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

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

示例 2:

输入:nums = [1,2,3,5]
输出:false
解释:数组不能分割成两个元素和相等的子集。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

这应该是我刚开始做动规时候遇到的题,转眼一年快过去了

首先要想分成两个和等的子集,那么和为sum的一半
如果和是奇数,那么不可以;如果最大元素大于sum的一半,也不行
然后要找的就是数组中的元素,能否求得和为sum/2
典型的背包问题

class Solution {
    public boolean canPartition(int[] nums) {
        int l = nums.length;
        int sum = 0;
        int max = 0;
        for(int num : nums){
            sum += num;
            max = Math.max(max, num);
        }
        if(sum % 2 == 1)
            return false;
        int target = sum / 2;
        if(max > target)
            return false;
        boolean[][] dp = new boolean[l + 1][target + 1];
        for(int i = 0; i <= l; i++){
            dp[i][0] = true;
        }
        for(int i = 1; i <= l; i++){
            for(int j = 1; j <= target; j++){
                dp[i][j] = dp[i - 1][j];
                if(j >= nums[i - 1])
                    dp[i][j] |= dp[i - 1][j - nums[i - 1]];
            }
        }
        return dp[l][target];
    }
}

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

示例 4:

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

示例 5:

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

提示:

1 <= coins.length <= 12
1 <= coins[i] <= 2^31 - 1
0 <= amount <= 10^4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

完全背包问题

class Solution {
    public int coinChange(int[] coins, int amount) {
        //每个硬币用多次,完全背包问题
        int l = coins.length;
        int[] dp = new int[amount + 1];
        Arrays.fill(dp, Integer.MAX_VALUE / 2);
        dp[0] = 0;
        for(int i = 1; i <= amount; i++){
            for(int j = 0; j < l; j++){
                int temp = coins[j];
                if(i >= temp)
                    dp[i] = Math.min(dp[i], dp[i - temp] + 1);
            }
        }
        return dp[amount] >= Integer.MAX_VALUE / 2 ? -1 : dp[amount];

    }
}

以上是关于LeetCode 446. 等差数列划分 II - 子序列(动态规划)/ 416. 分割等和子集 (背包问题)/322. 零钱兑换(完全背包)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 313. 超级丑数(最小堆动态规划)/413. 等差数列划分/167. 两数之和 II - 输入有序数组

LeetCode 446. Arithmetic Slices II - Subsequence

LeetCode446. Arithmetic Slices II - Subsequence

446 Arithmetic Slices II - Subsequence 算数切片之二 - 子序列

数据结构与算法之深入解析“等差数列划分II”的求解思路与算法示例

Leetcode 413.等差数列划分