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

博客分析了如何通过翻转01串使其变为全零的最少步骤问题,探讨了贪心策略和不同搜索算法(BFS、ID搜索)的应用,并提供了相关代码实现。对于特定的01串形式,提出了有效的解决方案和翻转步数的计算方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

[开关问题]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
开关问题是指给定一个01字符,我们可以对字符任意位置进行操作,操作后,此位置与其相邻的两个位置的字符改变('1'变'0','0'变'1')。现在的问题是,给定多组01字符,我们需要计算每个字符进行操作后,使得字符中的1的个数最大的翻转次数。 根据贪心算法,我们可以从左向右处理字符,遇到0就直接往后跳。如果有连续的1,我们一次性消去三个。所以,步数最多的情况可能为1000...00的形式。这种形式在翻转的过程中,相当于1向后移动,而且字符中1的个数依次变化。例如,在长度为6的字符中,我们需要进行5步操作才能使字符变为全0,而在长度为8的字符中,我们需要进行6步操作才能使字符变为全0。 因此,对于每个给定的01字符,我们可以通过计算字符中连续1的个数来确定需要进行的最大翻转次数。然后将这个次数与字符中1的个数相比较,取较小的值作为最终结果。这样我们就可以得到每个字符进行操作后,使得字符中的1的个数最大的翻转次数。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [[开关问题]01翻转全变 阿里笔试2020 Apare_xzc](https://blog.youkuaiyun.com/qq_40531479/article/details/105280758)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值