LeetCode494. 目标和

news2024/11/15 11:12:27

494. 目标和

文章目录

    • [494. 目标和](https://leetcode.cn/problems/target-sum/)
      • 一、题目
      • 二、题解
        • 方法一:目标和路径计数算法
        • 方法二:01背包
        • 方法三:01背包一维数组


一、题目

给你一个非负整数数组 nums 和一个整数 target

向数组中的每个整数前添加 '+''-' ,然后串联起所有整数,可以构造一个 表达式

  • 例如,nums = [2, 1] ,可以在 2 之前添加 '+' ,在 1 之前添加 '-' ,然后串联起来得到表达式 "+2-1"

返回可以通过上述方法构造的、运算结果等于 target 的不同 表达式 的数目。

示例 1:

输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 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 + 1 + 1 + 1 - 1 = 3

示例 2:

输入:nums = [1], target = 1
输出:1

提示:

  • 1 <= nums.length <= 20
  • 0 <= nums[i] <= 1000
  • 0 <= sum(nums[i]) <= 1000
  • -1000 <= target <= 1000

二、题解

方法一:目标和路径计数算法

思考过程可以分为以下几步:

  1. 问题拆解: 首先,将问题拆解为更小的子问题。在这个问题中,我们可以将目标和问题拆解为在给定数组中选择一些数字,使得它们的和等于目标值。

  2. 状态定义: 确定动态规划的状态。在这个问题中,我们可以考虑使用二维数组 dp[i][j] 表示在前 i 个数字中,和为 j 的表达式的数量。

  3. 状态转移方程: 找到状态之间的转移关系。这是动态规划问题的核心。在这个问题中,对于每个数字 nums[i],我们有两种选择:添加正号或者添加负号。因此,状态转移方程可以表示为:dp[i][j] = dp[i-1][j-nums[i]] + dp[i-1][j+nums[i]],即前 i-1 个数字和为 j-nums[i] 的表达式数量加上前 i-1 个数字和为 j+nums[i] 的表达式数量,最后的数字是dp[i][j] = dp[i-1][abs(j-nums[i])] + dp[i-1][j+nums[i]],因为如果j < nums[i],我们是可以通过dp[i-1][nums[i]-j]得到dp[i][j]。举个例子,如图,为什么dp[2][0] = 2?图中已经说明白了。

在这里插入图片描述

  1. 边界条件: 根据问题的实际情况,确定边界条件。在这个问题中,我们可以从第一个数字开始考虑,初始化 dp[0][j],表示只有一个数字时,和为 j 的表达式数量。

  2. 问题求解: 根据状态转移方程和边界条件,从小问题逐步求解到原问题。填充二维数组 dp,最终 dp[nums.size()-1][abs(target)] 就是我们要的答案,表示在所有数字中,和为 target 的表达式数量。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        // 定义一个二维数组 dp,大小为 (nums.size(), 2001),其中 dp[i][j] 表示在前 i 个数字中,和为 j 的表达式数量
        vector<vector<int>> dp(nums.size(), vector<int>(2001, 0));
        
        // 初始化:处理只有一个数字的情况
        for(int i = 0; i <= 1000; i++){
            if(nums[0] == i){
                if(nums[0] == 0) dp[0][i] = 2;  // 对于数字 0,可以有正号或负号
                else dp[0][i] = 1;
            }
        }
        
        // 填写 dp 数组
        for(int i = 1; i < nums.size(); i++){
            for(int j = 0; j <= 1000; j++){
                dp[i][j] = dp[i-1][abs(j-nums[i])] + dp[i-1][j+nums[i]];  // 根据状态转移方程计算 dp[i][j]
            }
        }
        
        return dp[nums.size()-1][abs(target)];  // 返回在所有数字中,和为 target 的表达式数量
    }
};

方法二:01背包

01背包问题通常是这样的:给定一组物品,每个物品有重量和价值,我们要选择一些物品放入一个容量有限的背包中,使得背包中物品的总重量不超过背包的容量,同时最大化物品的总价值。

在这道题中,我们可以将正数部分和负数部分(前面是加号的和前面是减号的)的数字分别看作两组物品。正数部分的数字相当于具有正的价值,负数部分的数字相当于具有负的价值。

具体步骤如下:

  1. 将数组 nums 拆分成两个子数组:addminusadd 包含所有正数部分的数字,minus 包含所有负数部分的数字。

    我们可以得到add-minus = targetadd+minus = sum,从而根据这两个式子得出(sum+target)/2 = add

  2. 使用01背包的思想:这个问题就转化成了所有数字凑成add这个数的方法。即背包容量是add,物品是nums数组里的所有元素。

  3. 使用动态规划来解决问题:我们创建一个二维数组 dp,其中 dp[i][j] 表示在考虑前 i 个物品时,总和等于 j 的方法数。

    • 初始化 dp[0][0] 为 1,因为当没有物品可选时,总和为 0 是唯一的一种方式;但如果nums[0] = 0,还有一种方式就是只取nums[0],则有两种方式。
    • 对于其它物品 nums[i],我们根据以下规则更新 dp[i][j]
      • 如果 j >= nums[i],那么我们可以选择是否将 nums[i] 放入背包,dp[i][j]等于不放入背包和放入背包方式的总和,即 dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]]
      • 如果 j < nums[i],则nums[i]不可以放入背包dp[i][j] = dp[i-1][j]
  4. 最终的答案是 dp[nums.size()-1][add],表示在考虑所有物品后,总和等于 add 的方法数。

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) {
        int sum = 0;
        // 计算数组nums的总和
        for(int i = 0; i < nums.size(); i++){
            sum += nums[i];
        }
        // 如果总和与目标值之和为奇数,或者总和小于目标值的绝对值,则无解,返回0
        if((sum + target) % 2 == 1 || sum < abs(target)){
            return 0;
        }
        // 计算要构造的正数部分和,这是01背包问题中的背包容量
        int add = (sum + target) / 2;
        // 创建二维动态规划数组dp
        vector<vector<int>> dp(nums.size(),vector<int>(add+1, 0));
        
        // 初始化dp数组
        for(int i = 0; i <= add; i++){
            // 如果i等于第一个数字nums[0],表示可以用第一个数字构造和为i的方式有1种
            if(i == nums[0]){
                dp[0][i] = 1;
            }
        } 
        // 特殊情况处理
        if(nums[0] == 0) dp[0][0] = 2;
        // 普通情况:当没有物品可选时,总和为 0 是唯一的一种方式
        else dp[0][0] = 1;
        
        // 填写dp数组
        for(int i = 1; i < nums.size(); i++){
            for(int j = 0; j <= add; j++){
                // 如果当前数字nums[i]大于目标和j,无法用当前数字构造
                if(nums[i] > j) dp[i][j] = dp[i-1][j];
                // 选择是否将 `nums[i]` 放入背包,`dp[i][j]`等于不放入背包和放入背包方式的总和
                else dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i]];
            }
        }
        // 返回最终的结果,即在考虑所有数字后,构造和为add的方法数
        return dp[nums.size()-1][add];
    }
};

方法三:01背包一维数组

具体细节就是初始化这里,这个dp[0] = 1相当于是按照结论推初始化(因为按照二维的初始化方式是错误的),最好还是去理解一下二维的吧……

class Solution {
public:
    int findTargetSumWays(vector<int>& nums, int target) { 
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) sum += nums[i];
        if (abs(target) > sum) return 0; 
        if ((target + sum) % 2 == 1) return 0; 
        int add = (target + sum) / 2; 
        vector<int> dp(add + 1, 0); 
        dp[0] = 1;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = add; j >= nums[i]; j--) { 
                dp[j] += dp[j - nums[i]];
            }
        }
        return dp[add]; 
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/964118.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IPC进程间通信及示例代码

一. 什么是进程通信 进程通信&#xff08; InterProcess Communication&#xff0c;IPC&#xff09;就是指进程之间的信息交换。实际上&#xff0c;进程的同步与互斥本质上也是一种进程通信&#xff08;这也就是待会我们会在进程通信机制中看见信号量和 PV 操作的原因了&#x…

Ubuntu18.04使用Systemback制作系统镜像并还原

系列文章目录 文章目录 系列文章目录前言一、下载Systemback工具二、制作系统镜像到U盘三、安装制作系统 前言 在Ubuntu系统中开发项目时&#xff0c;有时会希望将项目移植到另外一台计算机&#xff08;如工控机等&#xff09;上进行部署&#xff0c;通常会在新计算机中安装Ub…

Qt +VTK+Cmake 编译和环境配置(第二篇,中级篇, 重新编译)

1.下载VTK和Cmake 这里不介绍了。我的VTK 8.2.0 cmake 3.27.4 就是不服这编译器了。重新来一次 打开Cmake&#xff0c;把VTK源文件路径和目标路径设置一下&#xff08;目标路径自己设置&#xff0c;随意&#xff09; 点击Configure&#xff1a;。 点击下一步 选择好 Qt的gcc…

国际版阿里云/腾讯云:弹性高性能计算E-HPC入门概述

入门概述 本文介绍E-HPC的运用流程&#xff0c;帮助您快速上手运用弹性高性能核算。 下文以创立集群&#xff0c;在集群中安装GROMACS软件并运转水分子算例进行高性能核算为例&#xff0c;介绍弹性高性能核算的运用流程&#xff0c;帮助您快速上手运用弹性高性能核算。运用流程…

selenium 自动化测试——环境搭建

安装python&#xff0c;并且使用pip命令安装 selenium pip3 install selenium 然后尝试第一次使用selenium 完成一个简单的测试自动化脚本 from selenium import webdriver from selenium.webdriver.common.by import By import timedriver webdriver.Chrome() driver.get(…

A 股个股资金流排行 API 数据接口

A 股个股资金流排行 API 数据接口 全量股票资金流排名&#xff0c;多时间区间&#xff0c;全量A股数据。 1. 产品功能 支持所有A股资金流数据查询&#xff1b;每日定时更新数据&#xff1b;支持多时间段查询&#xff1b;超高的查询效率&#xff0c;数据秒级返回&#xff1b;数…

【问大家】电商问答数据的采集与深度分析

1. 引言 电商运营多年&#xff0c;功能越来越完善&#xff0c;我们发现当您购买过该商品之后&#xff0c;在消息-互动这里会看到别的网友提问的有关该商品的问题&#xff0c;这个功能叫问大家。 问大家模块可以说填补了宝贝评价部分的短板&#xff0c;评价部分单向传播属性较…

CSS中你不得不知道的css优先级

在我们定义css样式时&#xff0c;经常出现两个或更多规则应用在同一元素上&#xff0c;这时就会出现优先级的问题。其实css为每一种基础选择器都分配了一个权重。 我们简化理解&#xff1a; CSS权重计算&#xff1a; 最顶层&#xff1a;!important 权重值&#xff1a;…

【Django】让SQLite数据库中表名支持重命名的方法

修改了数据库表名之后&#xff0c;更新数据库时跳错&#xff1a; django.db.utils.NotSupportedError: Renaming the japi_api_info table while in a transaction is not supported on SQLite < 3.26 because it would break referential integrity. Try adding atomic F…

无涯教程-JavaScript - PERCENTILE函数

PERCENTILE函数替代Excel 2010中的PERCENTILE.INC函数。 描述 该函数返回范围中值的第k个百分位数。您可以使用此功能建立接受阈值。 语法 PERCENTILE (array,k)争论 Argument描述Required/OptionalArrayThe array or range of data that defines relative standing.Requi…

230920-部署Gradio到已有FastAPI及服务器中

1. 官方例子 run.py from fastapi import FastAPI import gradio as grCUSTOM_PATH "/gradio"app FastAPI()app.get("/") def read_main():return {"message": "This is your main app"}io gr.Interface(lambda x: "Hello, …

37. 交换字符(第三期模拟笔试)

题目&#xff1a; 给定一个01串&#xff08;仅由字符0和字符1构成的字符串&#xff09;。每次操作可以交换两个相邻的字符。 例如&#xff1a;对于字符串"001110"来说&#xff0c; 可以交换第二个字符0和第三个字符1&#xff0c;交换之后的字符串变成了"0101…

十、MySQL(DQL)条件查询

1、基础语法&#xff1a; select 字段列表 from 表名 where 条件列表; 2、实际操作&#xff1a; &#xff08;1&#xff09;初始化表格 &#xff08;2&#xff09;查询number大于444的员工 -- 查询number大于444的员工 select * from things where number>444; &#xff…

解决 filezilla 连接服务器失败问题

问题描述&#xff1a; 开始一直用的 XFTP 后来&#xff0c;它变成收费软件了&#xff0c;所以使用filezilla 代替 XFTP 之前用的还好好的&#xff0c;今天突然就报错了&#xff1a;按要求输入相关字段&#xff0c;连接 连接失败&#xff01;&#xff01;&#xff01;o(╥﹏╥…

海外有哪些流行的支付方式?

大家好&#xff0c;我是老三&#xff0c;很久没写支付相关的文章了&#xff0c;这期给大家讲一讲&#xff0c;海外都在用哪些支付方式。 简介 我们先来看下两个主流电商产品的美国站的支付方式&#xff1a; 在国内&#xff0c;想必大家都习惯了支付宝和微信支付二分天下&…

联合体(共用体)的简单介绍

目录 概念&#xff1a; 联合的声明&#xff1a; 类比结构体&#xff1a; 联合体的大小&#xff1a; 联合的⼤⼩⾄少是最⼤成员的⼤⼩ 联合体的空间是共用的 联合体内部成员的赋值&#xff1a; 当最⼤成员⼤⼩不是最⼤对⻬数的整数倍的时候&#xff0c;就要对⻬到最⼤对⻬…

性能测试工具Jmeter你所不知道的东西····

谈到性能测试&#xff0c;大家一定会联想到Jmeter和LoadRunner,这两款工具目前在国内使用的相当广泛&#xff0c;主要原因是Jmeter是开源免费&#xff0c;LoadRunner 11在现网中存在破解版本。商用型性能测试工具对于中小型企业很难承担相关的费用。国内的性能测试工具有&#…

Java项目-苍穹外卖-Day08

文章目录 前言导入地址簿代码导入需求分析代码导入功能测试 用户下单需求分析接口设计数据库设计 代码开发功能测试 前言 本篇博客主要是用户端的功能完善 主要是三个功能 1.导入地址簿 2.点击去结算弹出结算页面 3.微信支付功能 导入地址簿代码导入 这个地址簿就是一个表的…

视频监控系统

一、音视频推流框架概述 1.什么是推流 转载&#xff1a;原文链接&#xff1a;【知识拓展】音视频中的推流与拉流_推流拉流_No8g攻城狮的博客-CSDN博客 推流&#xff1a;把采集阶段封包好的内容传输到服务器的过程。其实就是将现场的视频信号传到网络的过程。“推流”对网络要…

Java学习笔记之----I/O(输入/输出)一

在变量、数组和对象中存储的数据是暂时存在的&#xff0c;程序结束后它们就会丢失。想要永久地存储程序创建的数据&#xff0c;就需要将其保存在磁盘文件中(就是保存在电脑的C盘或D盘中&#xff09;&#xff0c;而只有数据存储起来才可以在其他程序中使用它们。Java的I/O技术可…