[开关问题]01串翻转全变零 阿里笔试2020 Apare_xzc

[开关问题]01串翻转全变为零 Apare_xzc


问题描述:

    有一个01串,长度为len(1<=len<=20),我们可以对这个字符串进行翻转操作,定义如下:

我们可以对字符串任意位置进行操作,操作后,此位置与其相邻的两个位置的字符改变(‘1’变’0’,‘0’变’1’)。

问最少翻转多少次这个字符串可以全部变为为’0’。输出最少步数,如果无法实现,输出NO

输入描述:

        多组输入,第一行一个正整数T(1<=T<=100), 下面有T行,每行一个01字符串(长度<=20,可能为全零串)。

输出要求:

        每个输入输出一行,输出最少步数,如果无法实现,输出”NO“(不带引号)

样例输入:

7
0
1
00
01
10
11
111

样例输出:

0
1
0
NO
NO
1
1

(阿里2020笔试题)

分析:

我们先手动计算出一些串的答案,粗略地探究一下其中的规律:

输入:0
答案:0
分析:不需要翻转
输入:1
答案:1
分析:翻转第一位即可。
        1 ---flip(1)---> 0
输入:00
答案:0
分析:不需要翻转
输入:01
答案:NO
分析:无论翻转哪一位,都只会是01和10两个状态,所以是NO。
输入:11
答案:1
分析:只需要翻转第一位或第二位即可。
11 ---flip(1) or flip(2) ---> 00
输入:000
答案:0
分析:不需要翻转
输入:001:
答案:2
分析:001 --- flip(1) ---> 111 --- flip(2) ---> 000
输入:010
答案:3
分析:可以先把1移到最左边,转化为100
010 ---flip(1)---> 100 --->flip(2)---> 011 ---> flip(3) ---> 000
输入:011:
答案:1
分析:直接翻转第三位即可
011 ---flip(3)---> 000
输入:100:
答案:2
分析:同001,可以先翻转第3位变为三个1,再翻转第2位。亦可以先翻转第二位变为011,然后再翻转第三位
100 ---flip(2)---> 011 ---flip(3)---> 000
100 ---flip(3)---> 111 ---flip(2)---> 000
输入:101
答案:2
分析:先翻转第一位变为011
101 ---flip(1)---> 011 ---flip(3) ---> 000
输入:110
答案:1
分析:同011,直接翻转第一位即可
110 ---flip(1)---> 000
输入:111
答案:1
分析:直接翻转第2位即可
111 ---flip(2)---> 000
输入:0000
答案:0
分析:不需要翻转
输入:0001
答案:3
分析:先翻转1,再翻转2,可以把1向后传递
0001 ---flip(1)---> 1101 ---flip(2)---> 0011 ---flip(4)---> 0000 
输入:1000
答案:3
分析:可以做0001的镜像,也可以如下
1000 ---flip(1)---> 0100 ---flip(3)---> 0011 ---flip(4)---> 0000 
1000 ---flip(4)---> 1011 ---flip(3)---> 1100 ---flip(1)---> 0000
输入:0010
答案:2
分析:先翻转第1位,然后变为3连串
0010 ---flip(1)---> 1110 ----flip(2)---> 0000
输入:10000
答案: NO
分析:穷举后发现无法全部变为零
输入:100000
答案:4
分析:先翻转第一位,让1向后移动。然后每次1都向后移动,最后变为00...0011的状态
100000 ---flip(2)---> 011000 ---flip(3)---> 000100 ---flip(5)
---> 000011 ---flip(6)---> 000000
输入:1000,000
答案:5
分析:
1000,000 ---flip(1)---> 0100,000 ---flip(3)---> 0011,000 ---flip(5)
--->0000,100 ---flip(6)---> 0000,011 ---flip(7)---> 0000,000
输入:10,000,000
答案:NO
分析:无论先翻转第一位还是先翻转第二位都不行
10,000,000 ---flip(2)---> 01,100,000 ---flip(3)---> 00,010,000 --->flip(5)
--->00,001,100 ---flip(7)---> 00,000,010 ---flip(8)---> 00,000,001

规律总结与分析:

  1. 左右镜像对称的字符串的答案相同
  2. 并不是所有的串都可以翻转为全零串
  3. 似乎所有的串若可以变为全零串,则存在升序的翻转序列
  4. 形如1000...00的串,如果0的个数对3取模后余数为1,则无法还原。若余数为0,先翻转第1位即可。若余数为2,先翻转第2位即可。
  5. 我们考虑是否可以贪心。为了保证无后效性,我们可以从左往右进行操作。如果s[i]为1,那么我们就翻转i+1,这样就不会影响到前面已经全为零的串。但是第一个位置很特殊,因为翻转第一个位置,不会影响前面的字符(因为它本来就是第一个), 所以若s[1]为1,我们可以翻转第1位,亦可以翻转第2位。我们可以看如下的例子:
110 ---flip(1)---> 000
111 ---flip(2)---> 000
101 ---flip(1)---> 011 ---flip(3)---> 000
001 ---flip(1)---> 111 ---flip(3) --->000
100 ---flip(2)---> 011 ---flip(3)--->000

我们可以看到,无论第1位是’0’还是’1’, 在有些情况下,我们需要最先翻转第1位,有些情况下,我们需要最先翻转第2位。我们不好做出判断。我们不如这两种策略都试一次,取最优的。
6. 我猜想,步数最多的状况就是形如:1000...000,因为我们要让这单个的1从前往后一个接一个传递,直到转化成形如0000..0011的情况,才可以翻转最后一位变零。我们可以分析一下。
7. 至此,我们的贪心策略出炉了:

  • 先flip(1),然后令i从第1位至倒数第二位遍历,若该位为1,则flip(i+1), 若最后剩下00...00001,则不行。
  • 然后不flip(1),零i从第1为至倒数第二位遍历,最后先从第2位开始翻转,若最后剩下00...0001,则不行。
  • 两次取最少的步数,若两次都没有还原成功,则无法实现,输出NO。

我们可以用这个贪心算法大概计算一下长度小于等于20的字符串最大的翻转次数。由第7点描述的算法可知,我们从左向右处理,遇到0就直接往后跳。如果有连续的1,我们一下子就可以消去三个。所以,步数最多的情况可能为1000...00的形式。这种形式在翻转的过程中,相当于1向后移动,而且字符串中1的个数依次变化。1个->2个->1个->2个... 示例如下:

6个零,5步
1000,000 --->
0100,000 --->
0011,000 --->
0000,100 --->
0000,011 --->
0000,000
8个零,6步
100,000,000 --->
011,000,000 --->
000,100,000 --->
000,011,000 --->
000,000,100 --->
000,000,011 --->
000,000,000

根据我们之前的规律,len-1为后面零的个数。

  • (len-1)%3==1则无解。
  • (len-1)%3==0,我们先翻转第1位,则字符串中1的规律为:1 -> 11 -> 1 -> 11,一共经历floor((len-1)/3)个周期,每个周期长度为2,步数为floor((len-1)/3) * 2, 最后还要加一步翻转最后一位,使得00…0011变为全零,此时的步数为:(len-1)/3*2 + ((len-1)%3==0)
  • (len-1)%3==2,我们先翻转第2位,则字符串中1的规律为:11 -> 1->11->1, 一共经历floor((len-1)/3)个周期,每个周期长度为2,步数为(len-1)/3 * 2
  • 所以有解的形如1000...00的串在此贪心策略下的最少步数为:(len-1)/3*2 + ((len-1)%3==0)
  • 1000…0(18个零)的最少步数为:18/3*2+(18%3==0) = 6*2+1 = 13
  • 1000…0(19个零)因为19%3==1,所以无解
  • 1000…0(17个零)的最少步数为:17/3*2 = 10
  • 所以我们猜测可能长度为20的字符串在有解的情况下最少步数的上限可能为13(或者稍微比13大一些)
  • 至此我们可以写出上述贪心策略的代码了:

贪心策略代码:

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
void Flip(string& str,int p) //翻转字符串
{
	for(int i=p-1;i<=p+1;++i)
		if(i>=0&&i<str.length())
			str[i] = (str[i]=='0')?'1':'0'; 
}
int cal(string s) {
	string tmp = s;
	int len = s.length();
	int cnt = 0,ans=-1;
	//翻第一个 
	Flip(s,0),cnt=1; 
	for(int i=0;i<len-1;++i)
	{
		if(s[i]=='0') continue;
		Flip(s,i+1);++cnt;
	}
	if(s[len-1]!='0') ans = INF;
	else ans = cnt;
	//不翻第一个 
	cnt = 0;
	s = tmp;
	for(int i=0;i<len-1;++i)
	{
		if(s[i]=='0') continue;
		Flip(s,i+1);++cnt;
	}
	if(s[len-1]=='1') cnt = INF;
	ans = min(ans,cnt);
	return ans;
} 
int main(void) {
//	freopen("in0.txt","r",stdin);
//	freopen("tanxin.txt","w",stdout);
	int T;
	cin>>T;
	while(T--)
	{
		string s;
		cin>>s;
		int res = cal(s);
		if(res>1000) puts("NO"); 
		else cout<<res<<endl;	
	} 
	return 0;
} 

我们可以暴力bfs搜索,然后和贪心对拍一下:

bfs代码如下:

#include <bits/stdc++.h>
using namespace std;
int len;
unordered_map<string,int> mp;
void rev(string& s,int p)
{
	for(int i=p-1;i<=p+1;++i)
	{
		if(i>=0&&i<len) 
		{
			if(s[i]=='1') s[i] = '0';
			else s[i] = '1';
		}
	}
}
int bfs(string st,string ed)
{
	mp.clear();
	mp[st] = 0;
	queue<string> Q;
	Q.push(st);
	string now,to;
	while(!Q.empty())
	{
		now = Q.front();Q.pop();
		to = now;
		if(now==ed) return mp[now]; 
		for(int i=0;i<len;++i) //判断每一位 
		{
			for(int p=i-1;p<=i+1;++p)
			{
				if(p<0||p>=len) continue;
				rev(to,p);
				if(to==ed)
				{
					return mp[now]+1;
				} 
				if(mp[to]) //搜过了 
				{
					rev(to,p);continue;
				}
				Q.push(to); mp[to] = mp[now]+1;
				rev(to,p);
			}
		}		
	}	
	return -1;
}
int main(void)
{
//	freopen("in0.txt","r",stdin);
//	freopen("bfsout.txt","w",stdout);
	int T;
	string st;
	cin>>T; 
	while(T--)
	{
		cin>>st;
		len = st.length();
		string ed;
		for(int i=0;i<len;++i) ed+='0'; 
		if(st==ed)
		{
			puts("0");continue;
		}
		int ans = bfs(st,ed);
		if(ans==-1){
			puts("NO");
			continue;
		} 
		cout<<ans<<endl;	
	}
	return 0;
}

我们中规中矩地BFS计算了几百个小数据之后,发现和贪心的输出是一致的,证明我们的贪心策略应该问题不大。我们来算算BFS的复杂度。
刚才我们得到的结论是答案的的最大值为13。BFS的最坏时间复杂度就是20^13,这不太能接受。一个大数据都跑不完的。


我们可以从还原态全零字符串开始BFS,求一下20位的串,答案的上限是不是13。我们这次用bitset维护01串。

000...00(20个零)BFS打表代码:

#include <bits/stdc++.h>
using namespace std;
char a[20];
int r[1<<20];
int len,step;
unordered_map<string,int> mp;
void bfs(int sz)
{
	bitset<20> st,now,to;
	queue<bitset<20> > Q; 
	Q.push(st); //入队之前标记 
	r[st.to_ulong()] = 0;
	while(!Q.empty())
	{
		now = Q.front();
		Q.pop();
		for(int i=0;i<sz;++i)
		{
			to = now;
			to.flip(i);
			if(i-1>=0) to.flip(i-1);
			if(i+1<sz) to.flip(i+1);
			int idx = to.to_ulong();
			if(r[idx]!=-1) continue;
			r[idx] = r[now.to_ulong()]+1;
			step = r[idx];	
			Q.push(to);
		} 
	}
}
int main(void) {
//	freopen("out.txt","w",stdout);
	memset(r,-1,sizeof(r));
	r[0] = 0;
	int M = 1<<20;
	bfs(20);
	cout<<"step = "<<step<<endl;
	for(int i=0;i<M;++i)
	{
		bitset<20> bt = i;
		cout<<bt<<" ";
		cout<<r[i]<<endl;
	}
	return 0;
}

在这里插入图片描述

我们打表之后,发现20位的字符串,答案上限的确是13。

双向BFS的复杂度为:2*20^(13/2),这个勉强可以跑出来:

双向BFS代码:

#include <bits/stdc++.h>
using namespace std;
const int INF = 0x3f3f3f3f;
int len;
void Flip(string& s,int p) {
	for(int i=p-1;i<=p+1;++i)
		if(i>=0&&i<len) 
			s[i] = (s[i]=='0')?'1':'0'; 
}
int two_dir_bfs(string st,string ed)
{
	if(st==ed) return 0;
	unordered_map<string,int> mp,mp2;
	queue<string> Q,Q2;
	mp[st] = 0;
	mp2[ed] = 0;
	string now,to;
	Q.push(st); Q2.push(ed);
	while(Q.size()>0||Q2.size()>0) {
		if(Q.size()<=Q2.size()&&!Q.empty()) {
			now = Q.front();Q.pop();
			for(int i=0;i<len;++i) {
				to = now; 
				Flip(to,i);
				if(mp2.find(to)!=mp2.end()) return mp2[to]+mp[now]+1;
				if(mp.find(to)!=mp.end()) continue;
				Q.push(to); mp[to] = mp[now]+1;
			}
		} else {
			now = Q2.front();Q2.pop();
			for(int i=len-1;i>=0;--i) {
				to = now;
				Flip(to,i);
				if(mp.find(to)!=mp.end()) return mp[to]+mp2[now]+1;
				if(mp2.find(to)!=mp2.end()) continue;
				Q2.push(to); mp2[to] = mp2[now]+1;
			}
		}
	}
	return INF;
}
unordered_map<string,int> mp;
int main(void) {
//	freopen("in0.txt","r",stdin);
//	freopen("two_dir_bfs_out.txt","w",stdout);
	int T;cin>>T;
	while(T--)
	{
		string st,ed;
		cin>>st;
		len = st.length();
		for(int i=0;i<len;++i) ed +='0';
		int ans = two_dir_bfs(st,ed);
		if(ans>100) puts("NO");
		else printf("%d\n",ans);	
	}
	return 0;
}

双向BFS显然快很多,但还是可能超时。
我们都暴力搜索了,不如再试试IDA算法。虽然这里不适合用IDA,因为有的字符串是无解的,但是我们刚才贪心分析也好,BFS打表也罢,得到了上限为13,大约为(len-1)/32+1。我们可以来一发IDA,我们限制最大搜索深度为(len-1)/32+1, 然后跑IDA算法即可。我们深度从0开始递增,步长为1,第一次搜到目标态的深度就是我们的答案。

IDA*搜索代码如下:

#include <bits/stdc++.h>
using namespace std;
char a[100];
int len,ok,restep;
void rev(int pos)
{
	for(int i=pos-1;i<=pos+1;++i)
		if(i>=0&&i<len) a[i] = (a[i]=='0')?'1':'0';
}
void dfs(int step,int pre) {
	if(ok) return;
	bool flag = true;
	for(int i=0;i<len;++i) {
		if(a[i]=='1') {
			flag = false;break;
		}
	}
	if(flag) {
		ok = true;return;
	}
	//
	if(step==restep) return;
	
	for(int i=0;i<len;++i)
	{
		if(i==pre) continue;
		if(i-1>=0&&i+1<len&&a[i]=='0'&&a[i+1]=='0'&&a[i-1]=='0') continue;
		rev(i);
		dfs(step+1,i);
		rev(i);
	}
}
int main(void) {
//	freopen("in0.txt","r",stdin);
//	freopen("idastar.out","w",stdout);
	int T;
	scanf("%d",&T);
	while(T--)
	{
		scanf("%s",a);
		len = strlen(a);
		ok = false;
		for(int i=0;i<=13&&i<=len+1;++i)
		{
			restep = i;
			dfs(0,-1);
			if(ok) break;
		}
		if(!ok) puts("NO"); 
		else printf("%d\n",restep);	
	}
	return 0;
}

虽然IDA* 同时有dfs和bfs的好处,但是奈何最大深度13太大。小数据还是跑的飞快的,字符串答案大了的话,比BFS也好不到哪里去。但终归是一种方法吧。
综上,还是贪心复杂度最低(废话),O(n)。

数据生成代码:

#include <bits/stdc++.h>
using namespace std;
int len;
char a[30];
void dfs(int x)
{
	if(x==len)
	{
		a[len] = 0;
		puts(a);
		return;
	}
	a[x] = '0';
	dfs(x+1);
	a[x] = '1';
	dfs(x+1);
}
int main(void) {
	freopen("in0.txt","w",stdout);
	for(int i=1;i<=20;++i)
	{
		len = i;
		dfs(0);
	}
	return 0;
} 

对拍比较程序代码:

#include <bits/stdc++.h>
using namespace std;
string f1 = "idastarout.txt";
string f2 = "tanxin.txt";
int main(void) {
	fstream in1,in2;
	in1.open(f1);
	in2.open(f2);
	string a,b;
	int cnt = 0;
	int cerr = 0;
	while(getline(in1,a))
	{
		++cnt;
		getline(in2,b);
		if(a!=b)
		{
			++cerr;
			cout<<"On line "<<cnt<<": "<<a<<" | "<<b<<endl;	
		}	
	}	
	cout<<"不同的地方有:"<<cerr<<"个"<<endl; 
	return 0;
} 

xzc
2020.4.3
1:08


猜你喜欢

转载自blog.csdn.net/qq_40531479/article/details/105280758