leetcode 高效制胜 4 动态规划

53. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:

输入:nums = [1]
输出:1
示例 3:

输入:nums = [0]
输出:0
示例 4:

输入:nums = [-1]
输出:-1
示例 5:

输入:nums = [-100000]
输出:-100000

提示:

1 <= nums.length <= 3 * 104
-105 <= nums[i] <= 105

动态规划

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size();
        int ans;
        vector<int>sum(n,0);
        sum[0]=nums[0];
        ans=sum[0];
        for(int i=1;i<n;i++){
            if(sum[i-1]>0){
                sum[i]=sum[i-1]+nums[i];
            }else{
                sum[i]=nums[i];
            }
            if(sum[i]>ans)ans=sum[i];
        }
        return ans;
    }
};
  1. sum[i]是以nums[i]为结尾的最大和
  2. O(n)

分治法:

class Solution {
public:
    int ans;

    void merge(int l,int r,vector<int>& nums){
        //cout<<l<<r<<endl;
        if(l>r)return;
        if(l==r){
            if(nums[l]>ans)ans=nums[l];
            return;
        }
        int mid=l+(r-l)/2;
        
        merge(l,mid,nums);
        merge(mid+1,r,nums);
        
        int leftSum=nums[mid];
        int rightSum=nums[mid+1];
        int sum=0;
        for(int i=mid;i>=l;i--){
            sum+=nums[i];
            if(sum>leftSum)leftSum=sum;
        }
        sum=0;
        for(int i=mid+1;i<=r;i++){
            sum+=nums[i];
            if(sum>rightSum)rightSum=sum;
        }

        if(rightSum+leftSum>ans)ans=rightSum+leftSum;

    }

    int maxSubArray(vector<int>& nums) {
        int n=nums.size();
        ans=nums[0];
        merge(0,n-1,nums);
        return ans;
    }
};
  1. 分治,类似归并排序
  2. 时间复杂度O(nlogn)

416. 分割等和子集

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

示例 1:

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

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

提示:

1 <= nums.length <= 200
1 <= nums[i] <= 100

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n=nums.size();
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=nums[i];
        }
        if(sum&1)return false;
        vector<bool> dp(sum/2+1,0);
        dp[0]=1;
        for(int i=0;i<n;i++){
            for(int j=sum/2-nums[i];j>=0;j--)
            if(dp[j]){
                dp[j+nums[i]]=1;
            }
        }
        return dp[sum/2];
    }
};
  1. 这个题很巧妙,第一感觉有点想背包,对于每个数是选或者不选
  2. 由于子集和相等,所以一定有一个子集和是总和的一半
  3. 注意第二层循环要倒着写,因为假设目前数是1,0+1=1,所以dp[1]=1,然后dp[2]=2…
  4. 从大数加到小数的话,因为都是正整数,所以加完的数肯定更大,不会在这层被加
class Solution:
	def canPartition(self, nums):
		# 求和
		total = sum(nums)
		# 是否奇数
		if total & 1:
			return False
		# 初始化dp和target(total/2)
		dp, target = 1, total >> 1
		for i in nums:
			# 状态转移(转换为二进制去理解)
			dp |= dp << i
			# 是否找到解(转换为二进制去理解)
			if dp >> target & 1 == 1:
				return True
		return False

作者:englishvillage
链接:https://leetcode-cn.com/problems/partition-equal-subset-sum/solution/dpde-ji-zhi-xie-fa-wei-yun-suan-by-engli-qv6j/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  1. dp<<i,就是dp左移i位,若dp中某些位是1,则那些位的下标+i也是1了
  2. 在|dp,就是不加i的结果

https://blog.csdn.net/qll125596718/article/details/6901935

#include <iostream>
#include <bitset>
using namespace std;
 
int main(){
	//bitset 使用整数初始化bitset
	bitset<3> bs(7);
	//输出bs各个位的值
	cout<<"bs[0] is "<<bs[0]<<endl;
	cout<<"bs[1] is "<<bs[1]<<endl;
	cout<<"bs[2] is "<<bs[2]<<endl;
	//下面的语句会抛出outofindexexception
	//cout<<"bs[3] is "<<bs[3]<<endl;
 
	//使用字符串初始化bitset
	//注意:使用string初始化时从右向左处理,如下初始化的各个位的值将是110,而非011
	string strVal("011");
	bitset<3> bs1(strVal);
	//输出各位	
	cout<<"bs1[0] is "<<bs1[0]<<endl;
	cout<<"bs1[1] is "<<bs1[1]<<endl;
	cout<<"bs1[2] is "<<bs1[2]<<endl;
	//cout输出时也是从右边向左边输出
	cout<<bs1<<endl;
 
	//bitset的方法
	//any()方法如果有一位为1,则返回1
	cout<<"bs1.any() = "<<bs1.any()<<endl;
 
	//none()方法,如果有一个为1none则返回0,如果全为0则返回1
	bitset<3> bsNone;
	cout<<"bsNone.none() = " <<bsNone.none()<<endl;
 
	//count()返回几个位为1
	cout<<"bs1.count() = "<<bs1.count()<<endl;
 
	//size()返回位数
	cout<<"bs1.size() = "<<bs1.size()<<endl;
 
	//test()返回某一位是否为1
	//flip()诸位取反
	bitset<3> bsFlip = bs1.flip();
	cout<<"bsFlip = "<<bsFlip<<endl;
 
	//to_ulong
	unsigned long val = bs1.to_ulong();
	cout<<val;
}

c++版

class Solution {
public:
    bool canPartition(vector<int>& nums) {
        int n=nums.size();
        int sum=0;
        for(int i=0;i<n;i++){
            sum+=nums[i];
        }
        if(sum&1)return false;
        bitset<10001> bit(1);
        for(int i=0;i<n;i++){
            bit|=bit<<nums[i];
        }
        return bit[sum>>1];
    }
};

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] <= 231 - 1
0 <= amount <= 104

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,10001);
        int n=coins.size();
    

        dp[0]=0;
       
        for(int i=1;i<=amount;i++){
            for(int j=0;j<n;j++){
                if(i>=coins[j]){
                    dp[i]=min(dp[i],dp[i-coins[j]]+1);
                }
            }
        }
        return dp[amount]==10001?-1:dp[amount];
    }
};
  1. 不知道我为啥想的时候非得把coins放在第一层循环…
class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1,10001);
        map<int,int> newCoins;
        int n=coins.size();
        for(int i=0;i<n;i++){
            int sum=0;
            int j=1;
            int res=coins[i];
            while(sum<amount){
                if(newCoins.find(res)!=newCoins.end()){
                    if(newCoins[res]>j)newCoins[res]=j;
                }else{
                    newCoins[res]=j;
                }
                sum+=res;
                res<<=1;
                j<<=1;
            }
        }

        dp[0]=0;
        map<int,int>::iterator it;
        for(it=newCoins.begin();it!=newCoins.end();it++){
            for(int j=amount;j>=it->first;j--){
                dp[j]=min(dp[j],dp[j-it->first]+it->second);
            }
        }
        return dp[amount]==10001?-1:dp[amount];
    }
};

983. 最低票价

在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days 的数组给出。每一项是一个从 1 到 365 的整数。

火车票有三种不同的销售方式:

一张为期一天的通行证售价为 costs[0] 美元;
一张为期七天的通行证售价为 costs[1] 美元;
一张为期三十天的通行证售价为 costs[2] 美元。
通行证允许数天无限制的旅行。 例如,如果我们在第 2 天获得一张为期 7 天的通行证,那么我们可以连着旅行 7 天:第 2 天、第 3 天、第 4 天、第 5 天、第 6 天、第 7 天和第 8 天。

返回你想要完成在给定的列表 days 中列出的每一天的旅行所需要的最低消费。

示例 1:

输入:days = [1,4,6,7,8,20], costs = [2,7,15]
输出:11
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。
在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, …, 9 天生效。
在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。
你总共花了 $11,并完成了你计划的每一天旅行。
示例 2:

输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]
输出:17
解释:
例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:
在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, …, 30 天生效。
在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。
你总共花了 $17,并完成了你计划的每一天旅行。

提示:

1 <= days.length <= 365
1 <= days[i] <= 365
days 按顺序严格递增
costs.length == 3
1 <= costs[i] <= 1000

class Solution {
public:
    int mincostTickets(vector<int>& days, vector<int>& costs) {
        int n=days.size();
        int m=costs.size();
        vector<int> dp(366,0);


        int now=days[0];
        int i=0;
        for(;now<=365&&i<n;now++){
            if(now==days[i]){
                dp[now]=dp[now-1]+costs[0];
                if(now>=7)dp[now]=min(dp[now-7]+costs[1],dp[now]);
                else dp[now]=min(costs[1],dp[now]);
                if(now>=30)dp[now]=min(dp[now-30]+costs[2],dp[now]);
                else dp[now]=min(costs[2],dp[now]);
                i++;
            }else{
                dp[now]=dp[now-1];
            }
        }
        return dp[days[n-1]];
    }
};
  1. 我发现自己现在好不严谨,只有now>=7才能用cost[1]吗?判断是为了下标不溢出,而不是筛选。:)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值