[算法分析与设计] leetcode 每周一题: 494. Target Sum


题目链接: 

494. Target Sum




题目大意:

给定一个由非负整数组成的长度为 n 的数组 nums 和一个目标整数 S, 需要对数组 nums 中的各个整数执行 + 或 - 的运算操作, 求出 使 nums 中的所有整数的运算结果等于 S 的 运算模式 的数目 ;

假设 1 <= n <= 20, 且 nums 中的数的和不大于 1000 ; 同时, 输出(符合要求的运算模式的数目) 保证可以用 32位整型表示 ;


例如: 给定: nums = [1, 1, 1, 1, 1], S = 3, 则输出应为: 5 ; (原因如下)

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3




解题过程:

(1) 最直接的思路便是 穷举式 的做法, 且题目已经对输入数据规模做了限制, 可以知道开销再大也大不到哪儿去, 于是直接考虑原始的穷举, 也即针对每个数, 逐次记录所有可能的运算操作的和, 我当时直接用 逐次双倍增长 的 vector, 最坏的情况下显然便是增长到 2^20 个结果了, 然后统计结果输出即可 ;

(2) 然后发现超时, 于是考虑稍微进行优化, 比如, 一次性分配好空间 等, 还是超时, 应该是因为测试样例颇多, 且这种做法确实慢 ... 然后 leetcode 可能算的是总的时间 ... ;

(3) 考虑到 无论何种运算模式, 最后的结果必然落在 各数之和的正负区间 之间, 显然计算过程中会有相同的和 (比如: 1+2=2+1=3), 而随着规模的不断翻倍, 这种浪费会爆炸开来的 ... , 故而可以考虑用 map 之类的结构及时收敛, 即, 每次只记录已处理的数的可能的和的出现次数, 则到最后便同样可以得到结果 ;


(*) 这种题目, 也有巧妙的解法, 关键是找出数学规律 ;  比如这个问题里的关系可以直观地转化为: 将 数组 nums 分成两个不相交的子集 a, b, 而 S = sum(a) - sum(b), 然后慢慢推导出其他的规律, 具体的直接搜索就可以找到分享了 ;




代码如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        using Ways = unordered_map<int, int>;
        
        Ways ways;
        ways[0] = 1;
        for (int n : nums) {
            Ways newWays;
            for (auto w : ways) {
                auto sum = w.first;
                auto count = w.second;
                newWays[sum + n] += count;
                newWays[sum - n] += count;
            }
            ways = newWays;
        }
        return ways[S];
    }
};



Runtime: 116 ms


题目链接: 

494. Target Sum




题目大意:

给定一个由非负整数组成的长度为 n 的数组 nums 和一个目标整数 S, 需要对数组 nums 中的各个整数执行 + 或 - 的运算操作, 求出 使 nums 中的所有整数的运算结果等于 S 的 运算模式 的数目 ;

假设 1 <= n <= 20, 且 nums 中的数的和不大于 1000 ; 同时, 输出(符合要求的运算模式的数目) 保证可以用 32位整型表示 ;


例如: 给定: nums = [1, 1, 1, 1, 1], S = 3, 则输出应为: 5 ; (原因如下)

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3




解题过程:

(1) 最直接的思路便是 穷举式 的做法, 且题目已经对输入数据规模做了限制, 可以知道开销再大也大不到哪儿去, 于是直接考虑原始的穷举, 也即针对每个数, 逐次记录所有可能的运算操作的和, 我当时直接用 逐次双倍增长 的 vector, 最坏的情况下显然便是增长到 2^20 个结果了, 然后统计结果输出即可 ;

(2) 然后发现超时, 于是考虑稍微进行优化, 比如, 一次性分配好空间 等, 还是超时, 应该是因为测试样例颇多, 且这种做法确实慢 ... 然后 leetcode 可能算的是总的时间 ... ;

(3) 考虑到 无论何种运算模式, 最后的结果必然落在 各数之和的正负区间 之间, 显然计算过程中会有相同的和 (比如: 1+2=2+1=3), 而随着规模的不断翻倍, 这种浪费会爆炸开来的 ... , 故而可以考虑用 map 之类的结构及时收敛, 即, 每次只记录已处理的数的可能的和的出现次数, 则到最后便同样可以得到结果 ;


(*) 这种题目, 也有巧妙的解法, 关键是找出数学规律 ;  比如这个问题里的关系可以直观地转化为: 将 数组 nums 分成两个不相交的子集 a, b, 而 S = sum(a) - sum(b), 然后慢慢推导出其他的规律, 具体的直接搜索就可以找到分享了 ;




代码如下:

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int S) {
        using Ways = unordered_map<int, int>;
        
        Ways ways;
        ways[0] = 1;
        for (int n : nums) {
            Ways newWays;
            for (auto w : ways) {
                auto sum = w.first;
                auto count = w.second;
                newWays[sum + n] += count;
                newWays[sum - n] += count;
            }
            ways = newWays;
        }
        return ways[S];
    }
};



Runtime: 116 ms


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值