算法与数据结构系列--京东笔试题-01比梳理

question:

题目描述:

小明有一个只包含0和1的字符串,现在小明希望将整个字符串尽可能的切割成多个字符串,要求是每个字符串里面0出现的次数和1出现的次数的比例是一致的。解释:假设一个字符串出现0的次数是a次,出现1的次数是b,另一个字符串出现0的次数是c次,出现1的次数是d次,那么这两个字符串01出现次数比例相同表示a:b=c:d,即ad=bc。注意这里a,b,c,d都是可以为0的。现在对于一个字符串的所有前缀字符串,输出最多可以切割成多少个符合要求的字符串。

输入描述:

第一行一个整数n,表示01字符串的长度,1<=n<=500000
第二行一个长度为n的01字符串。

输出描述:

一行n个整数,第i个数表示原字符串中下标由0到i组成的前缀字符串可以切割出多少符合要求的字符串。

样例输入

样例1:

输入:
	3
	001
输出:
	1 2 1

提示:

  • 第一个前缀字符串是0,没法切,答案是1
  • 第二个前缀字符串是00,可以切得到0,0,答案是2
  • 第三个前缀字符串是001,可以切,但是切后没法保证比例要求,因此答案是1

样例2:

输入:
	9
	010100001
输出:
	1 1 1 2 1 2 1 1 3

题目分析与建模:

- 考虑输入:
输入为一个01字符串,记为s
以样例1为例
将s编码为集合或者元组(计算机只能识别离散的编码,因此编码成集合或者元组进行运算)
因为本题考虑分割子串,子串是排列,而非组合,有位置上的区别,因此编码为元组
s=(0,0,1)

- 考虑输出:
输出s字符串各个子字符串即(0),(0,0),(0,0,1)可以分割的符合比例相等的子串的最大个数。
即将s字符串分解为s=((s1),(s2),…,(sk)),使得s1中01比=s2中01比=…=sk中01比。且k要最大。


输入,输出实际上是子字符串集合到可分解最大符合01比条件子串个数的映射,如下所示:

{s.substr([0,i]) | 0<=i<=s.size()-1} -> {(k) | k属于N+}

其中substr表示子串,[0,i]表示从0到i区间的子串,N+表示从1开始的正整数。


-考虑算法与数据结构:

因为让输出的是最大的k值,所以考虑是最优化问题,可以考虑是贪心或者是动态规划。

本题并没有明显的贪心策略,因此考虑是否是动态规划,或者通过暴力枚举手段进行解题。

动态规划两个条件,一个是最优化问题,一个是具有子结构且无后效性。

尝试考虑如下问题:
字符串s=((s1),(s2),…(sk)),设s‘=((s1),(s2),…(sk-1));
则s划分子串时一定有情况s=((s’),(sk));
也就是说,要将s划分成k个01比例相等的子串的子问题就是,将s的某个满足某个01比例的子串划分成k-1个符合该01比例的子串!
s=((s1),(s2),…(sk)) 且这k个子串01比例均为a:b;
其子问题:
s‘=((s1),(s2),…(sk-1)) 且者k-1个子串01比例均为a:b;
用区间表示子串则为:
s.substr([0,i])=s.substr([0,j])+s.substr([j+1,i])
其中s.substr([0,i]) 是k个01比例均为a:b的子串拼成的字符串;
s.substr([0,j])是k-1个01比例均为a:b的子串拼成的字符串;
s.substr([j+1,i])是第k个符合01比例为a:b的子串。
其中0<=i<s.size(),0<=j<I;

回顾之前的集合之间的映射关系,

{s.substr([0,i]) | 0<=i<=s.size()-1} -> {(k) | k属于N+}

根据上面子问题的分析,我们可以得知,划分子串的k的个数与01比例a:b有关,因此我们的映射关系重新表示为:

{s.substr([0,i]) | 0<=i<=s.size()-1} -> {(k,a,b) | a,b,k属于N+}

1.设置状态:
我们把前面这个集合用dp数组表示,dp[i]表示s.substr([0,i])
把后面这个k,a,b用结构体HashNode表示,表示a:b的01比例下,可以划分的子串为k个。

typedef struct HashNode{

	int a;
	int b;

	int k;

	HashNode(const int a,const int b,const int count):a(a),b(b),count(k){};
}HashNode;

则映射关系为:dp[i]=[(k1,a1,b1),(k2,a2,b2),…]
上面的映射关系表示为,s的[0,i]区间子串可以划分成k1个以a1:b1为01比例的子串,可以划分成k2个以a2:b2为01比例的子串,…
2.状态转移方程
结合以上的分析,我们可以得到状态转移方程
(设c=s.substr([j+1,i])中0的个数,d=s.substr[j+1,i]中1的个数)

//若遍历dp[j],有比例相等,那么继承dp[j][l]比例,并计算出来的k值加1
if dp[j][l in dp[j].size()].a*d==dp[j][l in dp[j].size()].b*c
	dp[i]添加HashNode(dp[j][l].a,dp[j][l].b,dp[j][l].k+1);
//比例不相等情况,不可以进行分割子串
dp[i]添加HashNode(s.substr([0,i])中0个数,s.substr([0,i])中1个数,1)
	

代码:

#include <iostream>
#include <vector>
#include <string>
#include <queue>

using namespace std;

/*
a:b,0和1的比例
k:能划分的最长子串个数
*/
typedef struct HashNode{

	int a;
	int b;

	int k;

	HashNode(const int a,const int b,const int k):a(a),b(b),k(k){};
}HashNode;

//获取字符串s中的0和1的个数,c:0的个数,d:1的个数
void get01Count(string s,int &c,int &d){
	c=0;
	d=0;
	for(int i=0;i<s.size();i++){
		if(s[i]=='0') c++;
		else d++;
	}
}

//重载运算符
ostream& operator<<(ostream &cout,HashNode p){
	cout<<"0count:"<<p.a<<",1count:"<<p.b<<",count:"<<p.k<<endl;
	return cout;
}

struct cmp{

	bool operator()(const HashNode &a,const HashNode &b){
		return a.k>b.k;
	}

};

vector<vector<HashNode> > getMaxCount(string s){

	//初始化dp数组
	//dp[i]表示[0,i]区间内的一个结构体,记录了最大划分子串个数和其比例
	int size=s.size();
	vector<vector<HashNode> > dp(size,vector<HashNode>());
	int c=0,d=0;
	
	// vector<priority_queue <HashNode,vector<HashNode>,cmp> > dp(size,priority_queue <HashNode,vector<HashNode>,cmp>());

	HashNode temp=HashNode(0,0,1);

	temp.a=(s[0]=='0')?1:0;
	temp.b=(s[0]=='1')?1:0;

	dp[0].push_back(temp);

	//[0,i]=[0,j]+substr(j+1,i-j); j属于[0,i-1]
	for(int i=1;i<size;i++){
		for(int j=0;j<i;j++){
			//获取[j+1,i]子串的0,1个数c,d
			get01Count(s.substr(j+1,i-j),c,d);

			//遍历[0,j]的分割子串的所有情况,如果符合,那么就继承其比例,k++
			for(auto l:dp[j]){
				if(l.a*d==l.b*c){
					cout<<"["<<0<<","<<i<<"]"<<"区间="<<"["<<0<<","<<j<<"]区间+"<<"["<<j+1<<","<<i<<"]区间子串"<<endl;
					cout<<"["<<0<<","<<j<<"]区间比例,a:"<<l.a<<",b:"<<l.b<<endl;
					cout<<"["<<j+1<<","<<i<<"]区间子串比例,c:"<<c<<",d:"<<d<<endl;
					cout<<endl;
					HashNode temp=HashNode(l.a,l.b,l.k+1);
					dp[i].push_back(temp);
				}
			}

		}
		
		//添加最大可分割就是本身
		cout<<s.substr(0,i+1)<<","<<c<<","<<d<<endl;
		get01Count(s.substr(0,i+1),c,d);
		cout<<"hhhh["<<0<<","<<i<<"]区间比例,c:"<<c<<",d:"<<d<<endl;
		cout<<endl;
		HashNode temp=HashNode(c,d,1);
		dp[i].push_back(temp);
		
	}

	return dp;
}

int main(){

	string s;
	cout<<"请输入01字符串"<<endl;
	cin>>s;
	vector<vector<HashNode> > dp=getMaxCount(s);

	for(auto i:dp){
		sort(i.begin(),i.end(),cmp());
		cout<<i.front().k<<" ";
	}
	cout<<endl;

	return 0;
}



运行结果:

运行结果

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值