【leetcode】dp---中等(1)413. 等差数列划分_dp序列组成(2)**416. 分割等和子集_dp_01背包_or(3)**464. 我能赢吗_dp博弈_状态压缩_minmax

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

例如,以下数列为等差数列:

1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9

以下数列不是等差数列。

1, 1, 2, 5, 7

数组 A 包含 N 个数,且索引从0开始。数组 A 的一个子数组划分为数组 (P, Q),P 与 Q 是整数且满足 0<=P<Q<N 。

如果满足以下条件,则称子数组(P, Q)为等差数组:

元素 A[P], A[p + 1], ..., A[Q - 1], A[Q] 是等差的。并且 P + 1 < Q 。

函数要返回数组 A 中所有为等差数组的子数组个数。

示例:

A = [1, 2, 3, 4]

返回: 3, A 中有三个子等差数组: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。

 dp[i]:以i为下标的最长等差数列长度
 状态转移:若buf[i] == buf[i-1]即i属于前一个等差行列,dp[i] = dp[i-1] + 1;
                   若buf[i] != buf[i-1]即i单独等差,dp[i] = 1;

// dp[i]:以i为下标的最长等差数列长度
// 状态转移r:若buf[i] == buf[i-1]即i属于前一个等差行列,dp[i] = dp[i-1] + 1;
//           若buf[i] != buf[i-1]即i单独等差,dp[i] = 1;
class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) { //(p,q)至少包含3个数
        int n = A.size();
        if(n < 3) return 0;
        vector<int> buf(n, 0); // 存i和前一个数的差值
        for(int i = 1; i < n; i++) buf[i] = A[i] - A[i-1];
        vector<int> dp(n, 0);   
        dp[0] = 0;
        for(int i = 1; i < n; i++){
            if(dp[i-1] >= 2 && buf[i] == buf[i-1]) dp[i] = dp[i-1] + 1;
            else if(buf[i] != buf[i-1]) dp[i] = 1;
            else if(dp[i-1] == 1 && buf[i] == buf[i-1]) dp[i-1] = 2, dp[i] = 3; 
            else dp[i] = 2;
        }
        int ans = 0;
        for(int i = 0; i < n; i++){
            //printf("dp[%d]:%d ", i, dp[i]);
            if(dp[i] < 3) continue;
            ans += dp[i] - 2; 
        }
        return ans;
    }
};

结果:

执行用时:4 ms, 在所有 C++ 提交中击败了70.55% 的用户

内存消耗:7.4 MB, 在所有 C++ 提交中击败了61.21% 的用户

 

修改:

 dp[i]:以i结尾的等差数列个数

 状态转换:A[i] - A[i-1] == A[i-1] - A[i-2]时 dp[i] = dp[i-1] + 1;

// dp[i]:以i结尾的等差数列个数
// 状态转换:A[i] - A[i-1] == A[i-1] - A[i-2]时 dp[i] = dp[i-1] + 1;
class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) { //(p,q)至少包含3个数
        int n = A.size();
        if(n < 3) return 0;
        int ans = 0;
        vector<int> dp(n, 0);   
        for(int i = 2; i < n; i++){
            if(A[i] - A[i-1] == A[i-1] - A[i-2]){
                dp[i] = dp[i-1] + 1;
                ans += dp[i];
            }
        }
        return ans;
    }
};

结果:

执行用时:4 ms, 在所有 C++ 提交中击败了70.55% 的用户

内存消耗:7.7 MB, 在所有 C++ 提交中击败了5.17% 的用户

 

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

注意:

  1.     每个数组中的元素不会超过 100
  2.     数组的大小不会超过 200

示例 1:

输入: [1, 5, 11, 5]

输出: true

解释: 数组可以分割成 [1, 5, 5] 和 [11].

特殊情况:输入:[2] 输出:false  输入:[1,3,4,4],[1,2,3,4,5,6,7](数组的选择无序,所有想到背包问题)

01背包,每个物品只能选择一次,使得和等于target

 dp[i][j]: 前i个数字,其和为j

 状态转移:1)前i-1和为j, dp[i-1][j];

                   2)前i-1个需加上第i个,和为j: dp[i-1][j-nums[i]];

// dp[i][j]: 前i个数字,其和为j
// 状态转移:1)前i-1和为j, dp[i-1][j];
//          2)前i-1个需加上第i个,和为j: dp[i-1][j-nums[i]];
// dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]
class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int sum = 0, n = nums.size();
        if(n < 2) return false;
        for(int i = 0; i < n; i++) sum += nums[i];
        if(sum % 2) return false;
        sum /= 2;

        vector<vector<int>> dp(n, vector(sum + 1, 0));
        if(nums[0] <= sum) dp[0][nums[0]] = 1;
        for(int i = 1; i < n; i++){
            if(nums[i] == sum) return true;
            for(int j = 0; j <= sum; j++){
                if(j - nums[i] <= 0) dp[i][j] = dp[i-1][j];
                else if(dp[i-1][j] == 1 || dp[i-1][j-nums[i]] == 1) dp[i][j] = 1;
            }
            if(dp[i][sum]) return true;
        }
        return false;
    }
};

结果:

执行用时:92 ms, 在所有 C++ 提交中击败了83.39% 的用户

内存消耗:60.7 MB, 在所有 C++ 提交中击败了5.68% 的用户

 

464、在 "100 game" 这个游戏中,两名玩家轮流选择从 1 到 10 的任意整数,累计整数和,先使得累计整数和达到 100 的玩家,即为胜者。

如果我们将游戏规则改为 “玩家不能重复使用整数” 呢?

例如,两个玩家可以轮流从公共整数池中抽取从 1 到 15 的整数(不放回),直到累计整数和 >= 100。

给定一个整数 maxChoosableInteger (整数池中可选择的最大数)和另一个整数 desiredTotal(累计和),判断先出手的玩家是否能稳赢(假设两位玩家游戏时都表现最佳)?

你可以假设 maxChoosableInteger 不会大于 20, desiredTotal 不会大于 300。

示例:

输入:
maxChoosableInteger = 10
desiredTotal = 11

输出:
false

解释:
无论第一个玩家选择哪个整数,他都会失败。
第一个玩家可以选择从 1 到 10 的整数。
如果第一个玩家选择 1,那么第二个玩家只能选择从 2 到 10 的整数。
第二个玩家可以通过选择整数 10(那么累积和为 11 >= desiredTotal),从而取得胜利.
同样地,第一个玩家选择任意其他整数,第二个玩家都会赢。

状态压缩dp

dp(1<<maxInt-1,-1) 将每种整数的抽取情况用集合表示,抽取该数放入集合:比如抽取数1,2,9:{1,2,9}

1<<maxInt,表示集合{},{0},{0,1},{0,1,2},...,{0,1,2,...,maxInt-1}

实际呈现为十进制数,1<<maxInt = 2^maxInt。可以理解为二进制位大小maxInt,对每个位0,1选择,表示状态。、

state | 1 << (i - 1),将元素i-1插入集合state中

// 状态压缩dp
class Solution {
public:
    vector<int> dp; // 维护每种整数抽取情况所导致的先手玩家输赢情况.d
    bool dfs(int state, int maxInt, int total){ // state:表示当前的整数抽取情况
        if(dp[state] != -1) return dp[state];
        dp[state] = 0; // 假设玩家输
        for(int i = 1; i <= maxInt; i++){ // 玩家选择下一个要抽取的整数
            if(state >> (i - 1) & 1) continue; //该整数i已经被抽取了,包含在state状态中
            if(i >= total) {dp[state] = 1; break;} // 玩家抽取数i,累计整数和达到要求,获胜
            bool B = dfs(state | 1 << (i - 1), maxInt, total - i); // 将抽取整数的状态更新,将累计和更新,轮到对方玩游戏
            if(!B) {dp[state] = 1; break;} // A抽取i后,B无论怎么抽取都输了,则A胜利
        }
        return dp[state];
    }

    bool canIWin(int maxInt, int total) {
        if((1 + maxInt) * (maxInt) / 2 < total)return false;// 特判,总和不能达到total,输
        dp.resize(1 << maxInt, -1); // 状态压缩,对候选的整数的抽取方式进行枚举,抽取的则出现在集合中,1<<maxInt:{},{0},{1},{0,1},{0,1,2},{0,1,2,3}...,{0,1,2,n-1}。1<<maxInt-1,排除空集。方便表示,转化为整数。大小为2^maxInt.
        return dfs(0, maxInt, total); // state = 0 表示空集,什么都没有抽取
    }
};

结果:

执行用时:20 ms, 在所有 C++ 提交中击败了91.80% 的用户

内存消耗:40.8 MB, 在所有 C++ 提交中击败了22.72% 的用户

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值