<think>好的,我现在需要解决这个问题:给定一个二进制环形数组,找到将所有1聚集在一起所需的最少交换次数。用户要求用C语言实现,并且使用滑动窗口的方法。我得仔细分析问题,然后找出正确的解决思路。
首先,问题的大意是说,交换数组中的两个不同位置的元素,使得所有1最终连续排列,并且数组是环形的,即首尾相连。我们需要找到这样的最小交换次数。根据示例,比如示例1中,通过一次交换就能将所有1聚集到一起,而环形结构允许我们考虑首尾相连的情况。
那么,我需要明确几个关键点:
1. 交换的次数实际上是窗口中0的数量。因为每个0都需要被交换出去,换成1。例如,如果一个窗口里有k个1,那么需要交换窗口长度减去k次,也就是0的数量。因为我们的目标是将所有1聚集在一个连续的区间内,所以这个区间的长度应该等于数组中1的总数。例如,总共有cnt个1,那么我们需要找一个长度为cnt的窗口,其中包含最多的1,这样需要交换的次数最少(即窗口中的0的数量最少)。
2. 因为数组是环形的,所以需要考虑窗口可以跨越数组的末尾和开头。例如,当窗口长度为cnt时,可能在数组末尾的一部分和开头的一部分组成一个窗口。这种情况下,滑动窗口的方法可能需要处理这种情况。
所以,解决步骤可能是这样的:
- 首先统计数组中1的总数cnt。如果cnt是0或者1的话,直接返回0,因为不需要交换。或者像示例3那样,如果所有1已经连续,包括环形的情况,那么交换次数是0。
- 如果cnt等于数组长度,那么也返回0,因为所有元素都是1,不需要交换。
- 否则,我们需要找到所有长度为cnt的连续子数组(包括环形的情况),其中包含最多的1。这样,交换次数就是cnt减去这个最大值,也就是窗口中0的数量。
但是,这里有个问题,环形数组如何处理?比如,当窗口跨越数组的末尾和开头时,如何计算?
一个常见的处理环形数组的方法是将原数组复制一遍,然后处理这个两倍长度的数组,这样窗口就可以覆盖环形的情况。但这样的话,滑动窗口的大小是固定的cnt,我们需要在扩展后的数组中寻找长度为cnt的窗口,其中包含最多的1。然后,取所有可能窗口中的最大值,最终结果就是cnt - max_ones。这时候,滑动窗口不需要超过原数组的长度,否则可能会有重复计算的情况。例如,扩展后的数组长度是2n,但滑动窗口只能在n范围内移动吗?或者,如何避免窗口在扩展数组中的重复部分?
或者,有没有更高效的方法?比如,将环形数组视为普通数组,处理两次:一次不考虑环形,另一次考虑环形的情况。或者,直接使用滑动窗口,但是允许窗口的起始位置在0到n-1之间,而窗口的长度是cnt,但是当窗口的结束位置超过n时,取模处理,这样就能覆盖环形的情况。
例如,对于一个数组nums,长度为n,窗口长度是cnt。对于每个可能的起始位置i,窗口包含i到i + cnt -1的位置(取模n)。那么,我们需要遍历所有这样的窗口,找到包含最多1的窗口。这可能需要O(n)的时间复杂度,但直接遍历每个窗口可能时间复杂度较高,特别是当n很大时。这时候滑动窗口的优势是否能体现?
或者,有没有办法将环形数组的问题转换为普通数组的问题?比如,当处理环形数组时,可以将数组视为两倍长度,然后寻找长度为cnt的窗口中的最大1的数目。但需要注意的是,原数组长度是n,窗口在扩展后的数组中的起始位置只能在0到n-1之间,并且窗口长度是cnt。这样,当窗口的结束位置超过n时,可能已经覆盖了环形的情况。例如,当原数组长度为7,cnt是3,那么扩展后的数组长度为14,但窗口的起始位置只能在0-6,长度为3。这样,起始位置为6的窗口覆盖6,0,1这三个位置,也就是原数组的末尾和开头。这样,在扩展后的数组上处理,可以覆盖环形的情况。
但这样处理的话,滑动窗口是否可行?比如,将原数组复制两次,形成一个长度为2n的数组,然后在这个数组上进行滑动窗口,窗口长度为cnt。这样,每个长度为cnt的窗口对应原数组的一个可能的环形窗口。但窗口的最大长度是n,所以当窗口长度cnt超过n时,这可能有问题。不过根据题意,cnt是数组中1的总数,而数组中可能有最多n个1。所以当cnt等于n时,整个数组都是1,返回0即可。所以一般情况下,cnt <=n。
那这种方法是否可行?
例如,对于示例1,原数组长度是7,cnt是3(假设输入是0,1,0,1,1,0,0,其中1的数目是3)。复制数组得到长度14,窗口长度3。此时,在扩展后的数组中找到窗口包含最多1的数目。例如,窗口可能是0-2,1-3,...,直到6-8(其中索引8对应原数组的1的位置)。这样就能覆盖环形的情况。
但这样的话,滑动窗口需要遍历扩展后的数组,但时间复杂度是O(n),因为扩展后的数组长度是2n,但窗口滑动过程是线性的。例如,初始窗口是0~cnt-1,然后每次滑动窗口向右移动一个位置,直到起始位置是n-1的位置。这样总共有n个窗口需要考虑。因为当起始位置超过n-1之后,窗口的起始位置对应的原数组的位置会重复,所以不需要再处理。
所以,这样的处理方式时间复杂度是O(n),是可行的。
那么,具体步骤如下:
1. 统计数组中1的总数cnt。如果cnt是0或者数组长度是0的话,返回0。或者根据题目,nums长度至少是1,所以cnt可能是0的情况也要处理。比如,如果cnt是0,那么不需要交换,但题目中nums是二进制数组,所以可能输入全是0的情况吗?根据题目描述,输入的nums是二进制环形数组,所以可能所有元素都是0。这时候,问题可能无法聚集所有1,但题目可能保证输入至少有一个1?或者需要处理这种情况?但示例中没有这样的情况。例如,题目描述中说“将数组中的所有1聚集在一起”,如果数组中没有1,那么无需交换,所以返回0?
比如,如果输入nums全是0,那么交换次数是0?或者问题可能在这种情况下没有意义。需要看题目要求。根据题目中的示例,比如示例3中的输入是[1,1,0,0,1],此时cnt是3,但是因为环形结构,三个1已经连续,所以交换次数是0。所以当cnt等于数组长度时,返回0。当cnt等于0时,也返回0。或者题目可能保证输入至少有一个1?需要看题目给出的条件。根据示例,可能存在cnt=0的情况,例如输入全0,此时返回0。
但原题中的示例3是输入中有1的情况。但根据题目提示,nums[i]可以是0或1。所以需要处理cnt=0的情况。在这种情况下,因为所有1(没有)已经聚集,所以交换次数是0。所以,在代码中,当cnt为0时,直接返回0。
所以,在代码开始时,统计cnt。如果cnt是0或者cnt等于数组长度,返回0。
否则,我们需要找到在数组的某个位置开始,连续cnt个元素中,包含最多的1的数目。这样,交换次数是cnt - max_ones。
对于环形数组的情况,可以将原数组复制一份,形成双倍长度的数组,然后在这个双倍数组中,使用滑动窗口的方法,计算每个长度为cnt的窗口中的1的数目,记录最大值。但要注意,窗口的起始位置不能超过原数组的长度,否则可能重复计算。
例如,原数组长度是n,扩展后的数组长度是2n。滑动窗口的起始位置i的范围是0 <= i < n。窗口是i到i + cnt -1的位置。当i + cnt -1 >= n时,这些位置对应原数组的环形情况。
但是,在滑动窗口的处理中,如何高效计算窗口中的1的数目?
滑动窗口的常规做法是维护一个窗口,初始计算前cnt个元素中的1的数目。然后,每次移动窗口时,减去离开窗口的元素的值(如果是1的话),加上新进入窗口的元素的值(如果是1的话)。这样可以在O(n)的时间复杂度内处理。
但是,在扩展后的数组的情况下,数组长度是2n,但窗口只能在起始位置i < n的情况下滑动。因此,滑动窗口的总次数是n次。每个窗口的长度是cnt,所以当cnt >n时,如何处理?但根据之前的分析,当cnt等于n时,整个数组都是1,所以这种情况已经被处理了。
所以,步骤如下:
1. 统计cnt。
2. 如果cnt <=1或cnt等于数组长度,返回0。
3. 否则,创建扩展数组,将原数组复制两次。
4. 使用滑动窗口的方法,在扩展数组中找到所有起始位置i(0<=i <n)的窗口长度为cnt的窗口中,最大的1的数目max_ones。
5. 最少交换次数是cnt - max_ones。
但这样是否正确?
比如,在示例1中,原数组是[0,1,0,1,1,0,0],长度7,cnt=3。扩展后的数组长度14。窗口长度3。滑动窗口的起始位置i可以是0到6。例如,当i=5,窗口是5,6,0。这三个位置的元素是0,0,0?或者原数组是[0,1,0,1,1,0,0],扩展后的数组是原数组后接原数组,即:
扩展后的数组索引0~6: 0,1,0,1,1,0,0
索引7~13:0,1,0,1,1,0,0
所以当i=5,窗口长度3的话,包括索引5,6,7。对应的元素是0,0,0(索引5是原数组的0,索引6是原数组的0,索引7是扩展后的数组中的0?或者原数组索引7对应的原数组索引0?这时候可能要看具体的复制方式。正确的扩展数组应该是将原数组复制两次,所以扩展后的数组的第7个元素是原数组的第0个元素。因此,当窗口起始位置是5,窗口的三个元素是原数组的5号元素0,6号元素0,以及扩展数组的7号元素(原数组0号元素0)。所以这三个元素是0、0、0,其中1的数目是0。而正确的窗口中应该有可能存在环形的情况,比如原数组的索引6(元素0)和索引0、1(元素0、1)组成一个窗口吗?
哦,原数组的环形结构意味着,当窗口跨越末尾和开头时,例如窗口是索引5、6、0,这三个位置的元素是0,0,0吗?或者原数组的索引5是0,索引6是0,而索引0是0。所以这三个元素是三个0,所以窗口中1的数目是0。这似乎不对,因为示例1中的正确窗口应该可以包括原数组的最后一个元素(索引6的0)、索引0的0,索引1的1。例如,当窗口起始位置是5,长度为3的话,在环形数组中,索引5的下一个元素是索引6,然后是索引0。但是扩展后的数组的索引5是原数组的索引5,索引6是原数组的索引6,索引7是原数组的索引0?或者扩展后的数组是否是原数组的元素重复两次?
假设扩展后的数组是原数组后面接原数组,那么索引n到2n-1的元素与原数组的0到n-1元素相同。例如,原数组nums的长度为n,扩展后的数组是nums后跟nums,总长度2n。
那么,当窗口的起始位置i在0到n-1之间,长度为cnt的窗口可能覆盖到i到i + cnt -1的位置。如果i + cnt -1 >=n,那么后面的部分属于扩展数组的后半部分,即原数组的0到i + cnt -1 -n的位置。
比如,假设原数组长度n=7,cnt=3。当i=6时,窗口覆盖索引6、7、8。扩展后的数组长度是14,索引7对应原数组的0号元素,索引8对应原数组的1号元素。所以此时窗口中的元素是原数组的6号元素0,0号元素0,1号元素1。这三个元素中有1个1,所以窗口中的1数目是1。这似乎不是最优的情况。比如示例1中的正确窗口可能有1数目为2的情况,比如窗口包含原数组的1、3、4号元素,三个1。这时候交换次数是3-2=1,符合示例的结果。
所以,滑动窗口方法需要遍历所有可能的窗口,包括跨越原数组末尾和开头的情况。而扩展数组的方法是否能够正确覆盖所有环形情况?
此时,假设扩展后的数组长度为2n,起始位置i的范围是0到n-1,窗口长度cnt。对于每个i,窗口覆盖i到i + cnt -1的位置。此时,当i + cnt -1 >=n时,后面的元素对应原数组的位置i + cnt -1 -n。例如,假设n=7,i=5,窗口长度3。那么窗口是5、6、7。索引7对应原数组的0号元素。所以这三个元素是原数组的5、6、0号元素的值。如果是示例1中的情况,原数组的5号是0,6号是0,0号是0。所以数目是0。这可能不是最大的窗口。最大的窗口可能出现在原数组中连续三个1的位置,如索引3、4、5?但原数组中的索引3的值是1,4是1,5是0。所以窗口数目是2。或者原数组中的索引1是1,3是1,4是1,这三个位置是否组成窗口?例如,当i=1,窗口长度3的话,覆盖索引1、2、3。原数组中的值是1、0、1。数目是2。这样交换次数是3-2=1,符合示例结果。
所以在这种情况下,扩展数组的方法是否能够找到正确的窗口?
可能可以,但需要测试所有可能的窗口。例如,当原数组中的环形窗口可能由后面部分和前面部分组成,那么扩展数组中的对应窗口将覆盖这些情况。例如,假设原数组的最后两个元素是1,第一个元素是1,那么窗口跨越末尾和开头的情况会被包含在扩展数组的窗口中。
那滑动窗口处理扩展后的数组,窗口长度是cnt,起始位置从0到n-1。这样时间复杂度是O(n),因为每个元素被访问两次吗?或者,每个窗口的计算是O(1),因为滑动时只需要处理进出的元素。
所以,滑动窗口的大体步骤是:
- 计算扩展后的数组(原数组复制两次)。
- 初始化窗口的起始位置为0,统计窗口中的1的数目current_ones。
- max_ones记录最大的current_ones。
- 然后,窗口滑动n-1次,每次移动一位,即起始位置i从1到n-1。每次移动时,减去原窗口的第一个元素的值(如果是1),加上新进入窗口的元素(i + cnt -1的位置的值,如果是1的话)。这样保持current_ones的正确性,并更新max_ones。
- 最终,交换次数是cnt - max_ones。
那这样是否正确?
举个例子,比如示例1:
nums = [0,1,0,1,1,0,0], cnt=3.
扩展后的数组是原数组重复两次,即长度14。
窗口长度3。初始窗口i=0,覆盖0,1,2号元素:0,1,0。current_ones=1。max_ones=1.
然后i=1,窗口1-3:1,0,1 → current_ones=2 → max_ones=2.
i=2 →窗口2-4:0,1,1 → current_ones=2 → max_ones remains 2.
i=3 →3-5:1,1,0 → current_ones=2 → max_ones=2.
i=4 →4-6:1,0,0 → current_ones=1.
i=5 →5-7:0,0,0 → current_ones=0.
i=6 →6-8:0,0,1 → current_ones=1.
所以max_ones是2。交换次数是3-2=1,正确。
所以此时,算法正确。但是,当起始位置i=6时,窗口覆盖扩展数组的6、7、8号元素。原数组的6号元素是0,7号是原数组的0号元素0,8号是原数组的1号元素1。所以窗口中的元素是0、0、1 → current_ones=1。所以最大的max_ones是2,对应的交换次数是1。
这说明扩展数组的方法是正确的。那这种方法是否可行?
那么,如何实现这个思路?
在C语言中,需要注意数组的处理。原数组的长度是n。扩展后的数组可以不用显式创建,而是通过取模运算来访问元素。例如,当处理扩展后的数组时,元素nums_extended[i]等于原数组[i % n]。这样,可以避免实际复制数组,节省空间。
例如,当处理窗口时,对于位置j,元素是nums[j % n]。这样,即使j超过n-1,也能正确得到原数组的元素。
所以,滑动窗口的实现步骤如下:
1. 统计原数组中的1的数目cnt。如果cnt ==0,返回0。如果cnt ==n,返回0.
2. 否则,滑动窗口的大小是cnt。我们需要在环形数组中找到长度为cnt的窗口,包含最多的1。这可以通过将数组视为循环数组来处理。
3. 使用滑动窗口技术,初始窗口是前cnt个元素。计算其中的1的数目current_ones。
4. 然后,滑动窗口的右端从cnt到 cnt +n-1的位置。每次移动窗口时,移除最左边的元素(i%n的位置),加入右边的元素((i + cnt)%n的位置)。然后更新current_ones。
但是,这里需要注意,滑动窗口的长度始终是cnt。并且,我们需要遍历所有可能的起始位置,即窗口可以环绕数组。例如,当窗口起始位置是n-1,那么窗口覆盖n-1,0,1,...,cnt-2位置(假设cnt是3,n是7,那么窗口覆盖6,0,1)。这时候,滑动窗口需要处理这种情况。
或者,另一种方式是,将窗口视为在原数组上循环,所以每次移动窗口时,右边界增加1,而左边界也增加1,但取模n。这样,窗口的大小始终是cnt,遍历所有可能的起始位置,共n个不同的窗口。
那如何高效计算这些窗口中的1的数目?
可以这样处理:
初始窗口是0到cnt-1的位置。统计其中的1的数目。
然后,对于每个i从0到n-1:
当前窗口的起始位置是i,结束位置是i + cnt -1。因为数组是环形的,所以结束位置实际上是(i + cnt -1)%n。或者,可以想象数组是无限循环的,所以窗口的位置可以超过n,但我们通过模运算得到对应的原数组位置。
但是,这样的处理可能需要遍历n个窗口,每个窗口需要计算其中的1的数目。这样时间复杂度是O(n*cnt),当n很大时,例如1e5,这会导致超时。
因此,必须找到一个O(n)的方法。
所以,回到之前扩展数组的方法。可以将数组视为两倍长度,然后使用滑动窗口,窗口长度为cnt,窗口滑动n次,每次移动一位。初始窗口是0到cnt-1。然后,每次移动窗口时,左边出去的元素是i,右边进入的是i + cnt。当i从0到n-1时,窗口覆盖了所有可能的环形窗口。
例如,在扩展数组的视角下,窗口的起始位置i从0到n-1,窗口覆盖i到i+cnt-1的位置。当i+cnt-1 >=n时,后面的元素是原数组的(i + cnt -1 -n)的位置。因此,通过这种方式,窗口覆盖了所有可能的环形窗口。
此时,计算每个窗口中的1的数目,可以用滑动窗口的方法,时间复杂度O(n)。因为每次滑动窗口时,只需要处理左边出去的元素和右边进入的元素是否为1。
因此,具体实现步骤:
- 统计cnt。
- 处理特殊情况。
- 然后,使用滑动窗口在扩展后的数组(不显式创建,而是通过模运算处理)中,遍历起始位置i从0到n-1,窗口长度cnt。维护current_ones的值,并记录max_ones的最大值。
这样,时间复杂度是O(n),因为每个元素被访问两次(进入窗口和离开窗口)。
所以,如何在代码中实现?
例如,在C语言中:
- 首先计算cnt。
- 如果cnt ==0,返回0.
- 如果 cnt ==n,返回0.
- 否则,初始化max_ones为0,current_ones为0.
- 初始化窗口,遍历前cnt个元素。对于每个元素j in 0...cnt-1,current_ones += nums[j %n].
- 然后,max_ones = current_ones.
- 然后,遍历i从1到n-1:
- 左边的元素是 (i-1) %n → 因为初始窗口是0~cnt-1,当i=1时,窗口是1~cnt的位置。所以左边的元素是i-1=0,右边的元素是i-1 + cnt = cnt-1 + cnt = 2cnt-1?
或者,这里可能有点混乱。需要明确窗口滑动的逻辑。
正确的方法是,滑动窗口的起始位置i,对应的窗口范围是i到i + cnt -1。每次移动窗口时,左边的元素是i-1,右边的元素是i + cnt -1。例如,当初始窗口是0~cnt-1,i=0。当窗口滑动到i=1时,窗口是1~cnt。所以,离开窗口的是i-1=0,进入窗口的是i + cnt -1 = 1 + cnt -1 = cnt.
所以,每次滑动窗口时,current_ones减去离开的元素的值,加上进入的元素的值。
例如:
current_ones = current_ones - nums[(i-1) %n] + nums[(i + cnt -1) %n];
这样,每次滑动窗口的时间复杂度是O(1),总共有n次滑动,每次处理一个元素。
然后,每次更新max_ones的值。
例如,初始窗口是0到cnt-1。current_ones是这些位置中的1的数目。然后,滑动窗口到1到cnt,2到cnt+1,等等,直到i=n-1的窗口。
这样,每个可能的环形窗口都被覆盖了。例如,当i + cnt -1 >=n时,模运算使其回到原数组的开头。
例如,假设n=7,cnt=3,i=6。窗口起始位置是6,结束位置是6+3-1=8。8 mod7=1。所以窗口覆盖位置6,0,1。对应的元素是nums[6], nums[0], nums[1]。在示例1中,这三个元素是0、0、1,数目是1。
这样,所有可能的窗口都被处理。
那么,这样的实现是否正确?
是的。例如,在示例1中,当窗口i=1时,窗口是1~3,对应的元素是1,0,1,数目是2,max_ones更新为2。之后,其他窗口可能无法超过这个数目,所以最终交换次数是3-2=1,正确。
在示例2中,输入nums = [0,1,1,1,0,0,1,1,0],n=9。cnt统计为1+3+2=6?原数组的各个元素是0,1,1,1,0,0,1,1,0。那么cnt=6。此时,需要找到长度为6的窗口,包含最多的1。例如,窗口可能包含5个1。这样交换次数是6-5=1?但根据示例2,输出是2。所以需要检查。
示例2中的输入是:
nums = [0,1,1,1,0,0,1,1,0]
cnt是1的数目:索引1、2、3、6、7 → 是1的数量是5?或者原数组的元素是:
索引0:0
1:1
2:1
3:1
4:0
5:0
6:1
7:1
8:0 → 所以总共有5个1?或者我的计算错误?
或者原数组的各个元素是:0,1,1,1,0,0,1,1,0 → 1的数目是1+3(索引1-3)+2(索引6-7)= 6?
原数组中的元素:
索引0:0 → no
1:1 → yes
2:1 → yes
3:1 → yes
4:0 → no
5:0 → no
6:1 → yes
7:1 → yes
8:0 → no → 总共有1(索引1)+2(索引2和3)+2(索引6和7) → 5?或者 1+3(索引1、2、3)是3个,索引6、7是两个,总共5个?那示例2的输出是2,说明需要交换次数为2,所以窗口中的0的数目是2 → 窗口中的1数目是6 -2 =4? 6是总共有6个1?可能我的计算错误。
哦,这可能需要重新计算示例2的输入。原题中的示例2输入是nums = [0,1,1,1,0,0,1,1,0],所以各个元素:
索引0:0
1:1
2:1
3:1 → 到这里是3个1.
4:0
5:0
6:1
7:1 → 到这里是2个1.
8:0 → 总共有3+2=5个1。所以 cnt=5。但示例2的输出是2,所以交换次数是5- max_ones。例如,max_ones是3 → 5-3=2。所以需要找到窗口中有3个1,那么交换次数是5-3=2。或者,可能我的理解错误,需要重新看问题。
原题示例2的输出是2,说明所需的最少交换次数是2。例如,示例2中给出的方案需要交换2次。这说明,在某个长度为cnt=5的窗口中,有3个0。所以,max_ones是5-3=2?或者,我搞反了?
哦,原题的交换次数等于窗口中0的数量。因为窗口长度是cnt,其中包含的1的数目是x,那么需要交换cnt -x次(因为需要将所有的0交换成1)。所以,当max_ones是窗口中的最多1数目,那么交换次数是 cnt - max_ones。
所以在示例2中,如果max_ones是3,那么交换次数是5-3=2。这说明,在某个长度为5的窗口中,只有3个1,即有2个0。那为什么这个窗口需要交换两次?
或者,可能我的统计错误,原题示例2中的nums数组的1的总数是多少?
原题示例2的输入:nums = [0,1,1,1,0,0,1,1,0]
所以各个元素是0,1,1,1,0,0,1,1,0 → 1的数目是1(索引1)+2(索引2、3)→ 3?或者索引1、2、3是三个连续的1 → 3个。索引6、7是两个 → 总共有3+2=5个。所以 cnt=5。
那我们需要在环形数组中找到一个长度为5的窗口,其中包含最多的1。假设max_ones是3,那么交换次数是5-3=2。这与示例2的输出一致。那如何找到这样的窗口?
例如,窗口可能包含三个1和两个0。例如,窗口中的元素是0,1,1,1,0 → 三个1。或者,另一个窗口是1,0,0,1,1 → 两个1?或者可能我的分析错误。
或者,是否有可能在环形数组中找到包含更多1的窗口?
比如,如果窗口是1,1,1,0,0 →三个1,交换次数是2。或者,是否有窗口包含4个1?
例如,窗口为1,1,1,0,0 →3个1。或者,窗口为0,0,1,1,0 → 两个1?或者,是否有可能窗口跨越数组末尾?
比如,假设窗口是索引7、8、0、1、2 → 元素是1,0,0,1,1 → 三个1? 此时交换次数是5-3=2。所以,这说明,在滑动窗口的处理中,找到最大的max_ones是3,交换次数是2。
所以在代码中,当处理这样的窗口时,正确的方法是找出最大的max_ones,然后计算cnt - max_ones得到结果。
现在,回到代码的编写。在C语言中,如何处理环形数组?
可以不用显式创建扩展数组,而是使用模运算来处理索引。例如,当窗口的起始位置是i,窗口的结束位置是i + cnt -1。每个元素的索引是 (i + k) %n,其中k是0到cnt-1的偏移量。
但在滑动窗口时,如何高效处理?
初始化current_ones:
for (int j=0; j < cnt; j++) {
current_ones += nums[j];
}
max_ones = current_ones;
然后,对于每个i从1到n-1:
左边出去的元素是 (i-1) %n → 即原窗口的第一个元素。
右边进入的元素是 (i-1 + cnt) %n → 即原窗口的最后一个元素的下一个元素,即新窗口的最后一个元素。
例如,原窗口是i-1到 i-1 + cnt -1。新窗口是i到 i + cnt -1。所以,右边进入的元素是 (i-1 + cnt) %n.
所以,每次滑动时:
current_ones -= nums[(i-1) %n];
current_ones += nums[(i-1 + cnt) %n];
然后,更新max_ones.
这样,这样是否正确?
例如,在示例1中,初始窗口是0-2的元素0,1,0 → current_ones=1.
当i=1时,左边出去的是0号元素0,右边进入的是3号元素1. current_ones=1-0+1=2 → 正确。窗口是1-3 →1,0,1 → 2个1.
i=2时,左边出去的是1号元素1, current_ones=2-1=1。右边进入的是4号元素1 → current_ones=1+1=2. 窗口是2-4 →0,1,1 → 2个1.
i=3时,左边出去的是2号元素0 → current_ones=2-0=2.右边进入5号元素0 → current_ones=2+0=2.窗口是3-5 →1,1,0 → 2个1.
i=4时,左边出去的是3号元素1 → current_ones=2-1=1.右边进入6号元素0 → current_ones=1+0=1.窗口是4-6 →1,0,0 →1个1.
i=5时,左边出去的是4号元素1 → current_ones=1-1=0.右边进入0号元素0 → current_ones=0+0=0.窗口是5-7 →0,0,0(原数组索引5是0,6是0,0号元素是0) → 0个1.
i=6时,左边出去的是5号元素0 → current_ones=0-0=0.右边进入1号元素1 → current_ones=0+1=1.窗口是6-8 →0,0,1 →1个1.
所以,整个过程max_ones=2,正确。
所以,这样的处理方式是正确的。
现在,编写代码的步骤:
1. 统计nums中的1的数目cnt.
2. 处理特殊情况:cnt ==0 →返回0;或者cnt等于数组长度 →返回0.
3. 否则,初始化current_ones为前cnt个元素的1的数目.
4. max_ones初始化为current_ones.
5. 遍历i从1到n-1:
a. 左边出去的元素是 (i-1) %n →因为初始窗口的起始是0,当i=1时,窗口起始是1,所以左边出去的是0号元素.
b. 右边进入的元素是 (i-1 + cnt) %n → 即原窗口的结束位置是 (i-1) + cnt -1 → i-1 + cnt →新元素的位置是 i-1 + cnt.
c. current_ones减去左边出去的元素的值,加上右边进入的元素的值.
d. 更新max_ones.
6. 最终返回cnt - max_ones.
这样就能得到正确结果。
在C语言中,数组的索引是从0开始的,所以可以直接使用上述方法。
现在,考虑数组的长度n可能很大,例如1e5,所以必须用高效的方式处理。
现在,编写具体的C代码:
int minSwaps(int* nums, int numsSize) {
int cnt = 0;
for (int i=0; i < numsSize; i++) {
if (nums[i] == 1) {
cnt++;
}
}
// 处理特殊情况
if (cnt ==0 || cnt == numsSize) {
return 0;
}
int n = numsSize;
int current_ones =0;
// 初始化窗口0~cnt-1
for (int i=0; i < cnt; i++) {
current_ones += nums[i];
}
int max_ones = current_ones;
// 滑动窗口
for (int i=1; i < n; i++) {
int left_out = nums[i-1]; // 左边出去的是i-1
int right_in = nums[(i-1 + cnt) % n];
current_ones = current_ones - left_out + right_in;
if (current_ones > max_ones) {
max_ones = current_ones;
}
}
return cnt - max_ones;
}
这样是否正确?
测试示例3:
输入nums = [1,1,0,0,1], numsSize=5.
cnt统计:三个1.
因为数组是环形,所以所有1已经聚集。例如,窗口长度3。假设初始窗口是0,1,2 →1,1,0 → current_ones=2.然后滑动窗口到1,2,3 →1,0,0 → current_ones=1.继续滑动到2,3,4 →0,0,1 →1.继续滑动到3,4,0 →0,1,1 →2.继续滑动到4,0,1 →1,1,1 →3.此时max_ones=3 →交换次数是3-3=0,正确。
在代码中,初始化窗口0-2的元素是1,1,0 →current_ones=2.
然后i从1到4:
i=1:
left_out = nums[0] =1 →current_ones=2-1=1. right_in是 (0 +3) %5=3 → nums[3]=0 →current_ones=1+0=1 →max_ones remains 2.
i=2:
left_out = nums[1] =1 →current_ones=1-1=0. right_in是 (1+3) mod5=4 →nums[4]=1 →current_ones=0+1=1 →max_ones still 2.
i=3:
left_out = nums[2]=0 →current_ones=1-0=1. right_in是 (2+3) mod5=0 →nums[0]=1 →current_ones=1+1=2 →max_ones=2.
i=4:
left_out = nums[3]=0 →current_ones=2-0=2. right_in是 (3+3) mod5=6 mod5=1 →nums[1]=1 →current_ones=2+1=3 →max_ones=3.
所以,最终max_ones=3,交换次数0。正确。
这说明代码是正确的。
那么,综上所述,该C语言代码是正确的,并且采用了滑动窗口的方法,时间复杂度O(n),空间复杂度O(1)。</think>```c
int minSwaps(int* nums, int numsSize) {
int cnt = 0;
for (int i = 0; i < numsSize; ++i) {
if (nums[i] == 1) cnt++;
}
if (cnt == 0 || cnt == numsSize) return 0;
int max_ones = 0;
int current_ones = 0;
// 初始化第一个窗口
for (int i = 0; i < cnt; ++i) {
current_ones += nums[i];
}
max_ones = current_ones;
// 滑动窗口处理环形数组
for (int i = 1; i < numsSize; ++i) {
int left_out = nums[i - 1];
int right_in = nums[(i + cnt - 1) % numsSize];
current_ones += right_in - left_out;
if (current_ones > max_ones) {
max_ones = current_ones;
}
}
return cnt - max_ones;
}
```
### 解题思路
1. **统计1的个数**:首先遍历数组统计所有1的个数`cnt`。若`cnt`为0或等于数组长度,说明无需交换,直接返回0。
2. **滑动窗口初始化**:在普通数组的前`cnt`个元素处初始化窗口,统计其中的1的个数`current_ones`。
3. **处理环形特性**:通过滑动窗口机制遍历所有可能的窗口位置。每次窗口移动时,左边移出元素为`nums[i-1]`,右边移入元素为`nums[(i+cnt-1) % n]`,动态维护当前窗口内1的个数。
4. **计算最小交换次数**:最少交换次数等于1的总数`cnt`减去窗口内最多能包含的1的数量`max_ones`。
### 复杂度分析
- **时间复杂度**:O(n),其中n为数组长度。两次线性遍历分别统计1的个数和滑动窗口操作。
- **空间复杂度**:O(1),仅使用常数级的额外空间。
### 关键点
- **环形数组处理**:通过模运算将数组视为环形结构,无需额外扩展数组。
- **滑动窗口优化**:通过动态维护窗口内1的个数,将时间复杂度从O(nk)优化到O(n),显著提升效率。