动态规划之 0-1 字符数量问题

动态规划之 0-1 字符数量问题

  1. 前言

本问题来源于LeetCode问题,它隶属于经典的0-1背包选择问题,但本问题的特点是,它有两个两个背包,两个背包的约束条件并不相同,因而无法直接套用0-1背包的求解模板。新问题带来一定的挑战,进而要求我们从新的思维角度出发解决问题,引发DP高维数组尝试。

  1. 问题描述

题目的操作对象为,字符串数组,字符串内容规定只能是‘0’或‘1’,其它字符不允许包含在字符串内。引用LeetCode 问题描述。

给你一个二进制字符串数组 strs 和两个整数 mn 。请你找出并返回 strs 的最大子集的长度,该子集中 最多m0n1 。如果 x 的所有元素也是 y 的元素,集合 x 是集合 y子集

示例 1:

输入:strs = ["10", "0001", "111001", "1", "0"], m = 5, n = 3
输出:4
解释:最多有 5 个 0 和 3 个 1 的最大子集是 {"10","0001","1","0"} ,因此答案是 4 。
其他满足题意但较小的子集包括 {"0001","1"} 和 {"10","1","0"} 。{"111001"} 不满足题意,因为它含 4 个 1 ,大于 n 的值 3 。

示例 2:

输入:strs = ["10", "0", "1"], m = 1, n = 1
输出:2
解释:最大的子集是 {"0", "1"} ,所以答案是 2 。
  1. 递归的问题解决

动态规划问题一般都具有求最大值,最小值或计算满足某条件总数等特点,本题可以归结为求解约束条件下的最大子集数量问题。一般在进行设计动态规划DP数组之前,需要理清过程中变量的数量,如果过程中有两个可变量,那么可能设计DP二维数组就满足题目要求;如果过程中有N个可变量,那么就可能构建N维度数组比较符合题目要求。结合本题目特点,识别过程中有三个可变量需要记录其状态,第一个可变量是子集元素的数量,假定用符号i表示;第二个可变量是‘0’的元素数量,可以选择符号j来表示;第三个可变量为‘1’的元素数量,用符号’k’来表示。通过动态规划的练习发现,每次函数递归或递归退出,对所有变量,其实只能记录某一个状态。这个状态如果用递归记录,那么就需要借用栈的资源;这个状态如果用迭代输出,那么就需要利用for或者while循环进行相应的状态输出。值得一提的是,特定时刻的状态具有唯一性和确定性,这也是理解状态机概念的基本出发点。

基于动态规划的 CRCC解题框架,依照这个流程对问题进行逐步解析和剖析,加深动态规划流程的印象。

a.) 表针最优问题的子结构(Characterize the structure of the optimal solution)

问题需要求解最大的子集,从问题描述上来看,具备最大值的特点,从最朴素的理解出发,如果要求解最大子集,自然而然会定义问题:
f ( s e t , m , n ) = m a x { f ( s e t i , j , k ) } + 1 ; i   r e p r e s e n t s   t h e   n u m b e r   o f   e l e m e n t   i n   t h e   s t r i n g . 0 < = j < = m 0 < = k < = n f(set,m,n)=max\{f(set_i,j,k)\}+1;\\ i\ represents\ the\ number\ of\ element\ in\ the\ string. \\ 0<=j<=m \\ 0<=k<=n f(set,m,n)=max{f(seti,j,k)}+1;i represents the number of element in the string.0<=j<=m0<=k<=n
这个问题就涉及到最优子结构的问题,对于每次操作,其选择的代价都是1,如果不考虑区分‘0’和’1’,我们就可以构建遍历的二叉树,我们要求解的问题属于此遍历二叉树的子集。

在这里插入图片描述

b). 递归定义最优问题的值(Recursively define the value of an optimal solution)

递归定义的核心问题之一是确定递归退出的基本条件,此问题递归退出的以字符串数目为基准,当字符串总数满足上限的条件,此时就需要对0和1的数量进行判断,决定是满足0和1的数量要求,以及是否满足最大字符串数量的基本要求。

求解最优值的定义已经在上一小节中完成,无须赘述。

c). 计算最优解的值(Compute the value of the optimal solution)

利用递归算法,我们对最优问题进行递归计算,具体代码实现,采用C语言实现。实现过程中体现了部分回溯算法思想,定义全局变量times, 当对所有的字符串完成选择后,对其中的0和1的数量进行判断,如果满足要求且times的值比较大,那么就更新max_value的值。

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

#ifndef FIND_MAX_FORM_C
#define FIND_MAX_FORM_C
#include "find_max_form.h"

void find_max_form(int i, int zero, int one, char **strs, int str_size, int m, int n)
{
    if(i==str_size)
    {
        if(zero<=m && one<=n)
        {
            if(max_value<times)
            {
                max_value=times;
            }
        }
    }
    else
    {
        times++;
        find_max_form(i + 1, zero + num_of_zero(strs[i]), one + (strlen(strs[i]) - num_of_zero(strs[i])),strs,str_size,m,n);
        times--;
        find_max_form(i + 1, zero - num_of_zero(strs[i]), one -(strlen(strs[i]) - num_of_zero(strs[i])), strs,str_size, m, n);
    }
}

int num_of_zero(char *str)
{
    int num=0;

    while(*str!='\0')
    {
        if(*str=='0')
        {
            num++;
        }

        str++;
    }

    return num;
}
#endif

d.) 从计算过程中,构建最优解的集合(Construct an optimal solution from the computed information)

  1. 迭代方法问题解决(Bottom-up 方法)

采用bottom-up的迭代方法解决问题,最直观的理解就是白板上搭积木,很多问题都从白班或者∅着手,然后对不同的选择进行比较分析,DP动态数组的维度取决于递归中可变变量的数量。本问题中存在三个可变变量,那么就需要设计三维DP数组进行状态的跟踪dp[i][j][k]。

在这里插入图片描述

迭代方法的代码实现,

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

#ifndef FIND_MAX_FORM_C
#define FIND_MAX_FORM_C
#include "find_max_form.h"

int find_max_form(char **strs, int str_size, int m, int n)
{
    int i;
    int j;
    int k;
    int num_one;
    int num_zero;

    int dp[str_size+1][m+1][n+1];

    memset(dp,0,sizeof(dp));

    for(i=1;i<=str_size;i++)
    {
        num_zero=num_of_zero(*(strs+i-1));
        num_one=strlen(*(strs+i-1))-num_zero;

        for(j=0;j<=m;j++)
        {
            for(k=0;k<=n;k++)
            {
                if(j>=num_zero && k>=num_one)
                {
                    dp[i][j][k] = max(dp[i - 1][j][k],dp[i - 1][j - num_zero][k - num_one]+1);
                }
                else
                {
                    dp[i][j][k]=dp[i-1][j][k];
                }
            }
        }
    }

    return dp[str_size][m][n];

}

int num_of_zero(char *str)
{
    int num=0;

    while(*str!='\0')
    {
        if(*str=='0')
        {
            num++;
        }

        str++;
    }

    return num;
}

int max(int m, int n)
{
    return(m>n?m:n);
}
#endif

  1. 总结

本问题可以理解为两个背包的容量问题,针对同一物品展开,它具有两个属性,同一物品分开放置到两个背包中,而且两个背包的最大容量不一致。

参考资料:

474. 一和零 - 力扣(Leetcode)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值