目标和 [从暴力dfs -> 状态记录 -> 动态递推 -> 状态压缩]

285 阅读2分钟

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第17篇文章,目标和 [从暴力dfs -> 状态记录 -> 动态递推 -> 状态压缩] - 掘金 (juejin.cn)

前言

从数组中找多个数字,其之和等于target,一般的思想就是dfs进行组合。 但耗时太多,则可以将dfs中重复计算的地方进行记录,空间换时间,即记忆搜索法。 但这种记忆搜索法只记录了一些重复计算的地方,还没将空间换时间发挥到极致。 此时可动态递推来完成求解,即动态规划。动态规划中的状态往往是层层递推的,所以在空间上可以做到进一步优化,即动态压缩。

一、目标和

image.png

二、dfs - > 状压

1、暴力dfs

// 暴力dfs
    public int findTargetSumWays(int[] nums, int target) {
        return dfs(nums,0,target);

    }
    private int dfs(int[] nums,int i,int target){
        if(i == nums.length)
            return target == 0 ? 1 : 0;
        
        return dfs(nums,i + 1,target - nums[i]) + dfs(nums,i + 1,target + nums[i]);
    }

2、状态记录

// 记忆数组
    final static int N = 1000;
    public int findTargetSumWays(int[] nums, int target) {
        int[][] f = new int[nums.length][4 * N + 1];
        
        return dfs(nums,0,target,f);

    }
    private int dfs(int[] nums,int i,int target,int[][] f){
        if(i == nums.length)
            return target == 0 ? 1 : 0;

        if(f[i][target + 2 * N] != 0) return f[i][target + 2 * N];

        f[i][target + 2 * N] = dfs(nums,i + 1,target - nums[i],f) + dfs(nums,i + 1,target + nums[i],f);

        return f[i][target + 2 * N];
    }

3、动态递推

// 动态规划。
    // f[i][j]:表示前i个数组成和为target的个数。
    final static int N = 1000;
    public int findTargetSumWays(int[] nums, int target) {
        int[][] f = new int[nums.length + 1][4 * N + 1];
        f[0][2 * N] = 1;
        
        for(int i = 1;i <= nums.length;i++){
            for(int j = 0;j <= 4 * N;j++){
                if(j >= nums[i - 1]) f[i][j] = f[i - 1][j - nums[i - 1]];
                if(j + nums[i - 1] <= 4 * N) f[i][j] += f[i - 1][j + nums[i - 1]]; 
            }
        }
        return f[nums.length][2 * N + target];

    }

4、状态压缩

// 状态压缩,由于每次状态都和上一层有关,且和前面的未知位置有关,所以采用一维数组,
    // 由于和前后状态都有关,所有需两个一维数组。
    final static int N = 1000;
    public int findTargetSumWays(int[] nums, int target) {
        int[][] f = new int[2][4 * N + 1];
        int idx = 0;
        f[idx][2 * N] = 1;
        for(int i = 1;i <= nums.length;i++){
            for(int j = 0;j <= 4 * N;j++){
                f[(idx + 1) & 1][j] = 0;
                if(j >= nums[i - 1]) f[(idx + 1) & 1][j] = f[idx][j - nums[i - 1]];
                if(j + nums[i - 1] <= 4 * N) f[(idx + 1) & 1][j] += f[idx][j + nums[i - 1]]; 
            }
            idx = (++idx) & 1;
        }
        return f[idx][2 * N + target];

    }

总结

1)从最简单的dfs暴力逻辑;到利用空间记录重复计算,即记忆搜索;再到极致的空间换时间,即动态规划;最后到空间优化,即状态压缩。形成一个完整的逻辑链,一个完整的优化闭环。

2)做好完整逻辑闭环,知其中每个细节点的因果逻辑,才能充分理解它,做到融合贯通,举一反三。

参考文献

[1] LeetCode 目标和