动态规划之-目标和(target sum)

文章介绍了如何使用动态规划和回溯算法解决LeetCode上的目标和问题。动态规划通过转换成0-1背包问题来求解,而回溯算法的核心在于递归过程中对元素赋值的处理。文章强调了两种方法的关键点和适用场景,并提供了代码实现。
摘要由CSDN通过智能技术生成

动态规划之-目标和(target sum)

  1. 前言

目标和的问题被划分在动态规划的体型范畴内,但是拿到题目的第一直觉是,这是一个典型的回溯题目,对于数组中的每个元素,我们可以选择添加+或添加-,值得一提的是,遍历过程中对于每个元素只有两个选择,要么赋值+号,要么赋值-号,不允许跳过任何元素进行操作。

如果要借助动态规划实现,则需要转换思维,转换为0-1背包问题,假定我们把正整数数组分为两个集合,一组集合里的元素前面冠以-号,一组集合前面冠以符号;假定冠以符号的所有元素的和为neg, 问题可以转化为,给定目标和定义为target
t a r g e t = ( s u m − n e g ) − n e g ; target=(sum-neg)-neg; target=(sumneg)neg;
再进一步简化上面算式,最终可以得到求解neg的表达式,
n e g = ( s u m − t a r g e t ) / 2 neg=(sum-target)/2 neg=(sumtarget)/2
接下来问题就可以抽象为0-1背包问题,从n个元素中选择某些元素,使其和等于neg。

具体问题描述,请参考Leetcod,494. 目标和 - 力扣(Leetcode)

  1. 解决方案

a.)回溯算法

对于回溯算法,其核心部分在于递归调用前后,对保留的赋值进行处理(power set问题 或 N皇后问题),如果为二叉树,直接上两个递归函数即可解决问题。

之前的回溯算法的处理,都是在递归函数之前和递归函数之后,针对操作元素进行增删处理,本次递归和之前方案稍微不同,它直接是递归变量的值的改变,而递归变量本身的值被递归函数很好的保留下来,本身过程中并不发生任何改变。

这样的回溯的特点是,传递的递归变量值发生改变,而在递归函数里面的变量本身的值并不发生系统的改变,这是本次回溯算法的精华部分和值得学习地方。

b.) 动态规划算法

本问题可通过转换,形成0-1背包问题形式的动态规划算法,动态规划算法的状态转移方程可以参照0-1背包问题,很容易撰写出来。
d p [ i ] [ j ] = d p [ i − 1 ] [ j ] ,   i f    j < n u m s [ i ] dp[i][j]=dp[i-1][j],\ if\ \ j<nums[i] dp[i][j]=dp[i1][j], if  j<nums[i]

d p [ i ] [ j ] = d p [ i − 1 ] [ j ] + d p [ i − 1 ] [ j − n u m s [ i − 1 ] ] ,   i f    j < n u m s [ i ] dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]],\ if\ \ j<nums[i] dp[i][j]=dp[i1][j]+dp[i1][jnums[i1]], if  j<nums[i]

动态规划的边界条件也非常清晰,如果不作任何选择,而且最终的总重为0的话,此方案就是最终结论之一,所以动态规划的基础dp[0][0]=1, 其实此条件其实等效于倒退到最后的值,而且值为0;那么之前的过程就是一个合理的值。

  1. 代码实现

代码实现的过程很常规,没有特别的地方。在回溯方法当中,如果对所有元素遍历/选择完成,此时如果总和等于目标值,那么就把数量加1.

/**
 * @file target_sum.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-03-14
 * 
 * @copyright Copyright (c) 2023
 * 
 */

#ifndef TARGET_SUM_C
#define TARGET_SUM_C
#include "target_sum.h"
int times=0;

void target_sum_backtrack(int *nums, int i, int nums_size, int target, int sum)
{
    int j;
    if(i==nums_size)
    {
        if(sum==target)
        {
            times = times + 1;
        }
    }
    else
    {
        //i start from 0 index, it is better to understand this situation
        target_sum_backtrack(nums,i+1,nums_size,target,sum+nums[i]);        
        target_sum_backtrack(nums, i + 1, nums_size, target, sum-nums[i]);
    }    
}

#endif

对于动态规划,相对而言稍微复杂,主要在于neg的计算方法

/**
 * @file target_sum.c
 * @author your name (you@domain.com)
 * @brief 
 * @version 0.1
 * @date 2023-03-14
 * 
 * @copyright Copyright (c) 2023
 * 
 */

#ifndef TARGET_SUM_C
#define TARGET_SUM_C
#include "target_sum.h"

int target_sum(int *nums, int nums_size, int target)
{
    
    int sum = 0;
    for (int i = 0; i < nums_size; i++)
    {
        sum += nums[i];
    }
    int diff = sum - target;
    if (diff < 0 || diff % 2 != 0)
    {
        return 0;
    }
    int n = nums_size, neg = diff / 2;
    int dp[n + 1][neg + 1];
    memset(dp, 0, sizeof(dp));
    dp[0][0] = 1;
    for (int i = 1; i <= n; i++)
    {
        int num = nums[i - 1];
        for (int j = 0; j <= neg; j++)
        {
            dp[i][j] = dp[i - 1][j];
            if (j >= num)
            {
                dp[i][j] += dp[i - 1][j - num];
            }
        }
    }
    return dp[n][neg];
}
#endif

  1. 总结

通过本示例学习,对回溯的方法和技巧有了更多的了解,同时对于动态规划的问题转换也有了直观的理解。对于回溯和动态规划,还需要继续练习,不断坚强巩固。

参考资料

  1. 494. 目标和 - 力扣(Leetcode)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值