leetcode 995. K 连续位的最小翻转次数(Minimum Number of K Consecutive Bit Flips) 贪心

版权声明:点个关注(^-^)V https://blog.csdn.net/weixin_41793113/article/details/88621542

在仅包含 0 和 1 的数组 A 中,一次 K 位翻转包括选择一个长度为 K 的(连续)子数组,同时将子数组中的每个 0 更改为 1,而每个 1 更改为 0

返回所需的 K 位翻转的次数,以便数组没有值为 0 的元素。如果不可能,返回 -1

 

示例 1:

输入:A = [0,1,0], K = 1
输出:2
解释:先翻转 A[0],然后翻转 A[2]。

示例 2:

输入:A = [1,1,0], K = 2
输出:-1
解释:无论我们怎样翻转大小为 2 的子数组,我们都不能使数组变为 [1,1,1]。

示例 3:

输入:A = [0,0,0,1,0,1,1,0], K = 3
输出:3
解释:
翻转 A[0],A[1],A[2]: A变成 [1,1,1,1,0,1,1,0]
翻转 A[4],A[5],A[6]: A变成 [1,1,1,1,1,0,0,0]
翻转 A[5],A[6],A[7]: A变成 [1,1,1,1,1,1,1,1]

 

提示:

  1. 1 <= A.length <= 30000
  2. 1 <= K <= A.length

贪心,从头开始扫描,遇到0就翻一次,如果i+k大于n说明已经不能再翻了,说明无论如果都不可能翻成功,return -1

class Solution {
   public static int minKBitFlips(int[] A, int K) {
        int ans=0;
        int n=A.length;
        for(int i=0;i<n;i++)
        	if(A[i]==0) {
        		ans++;
        		for(int j=i;j<i+K;j++) {
        			if(i+K>n)
        				return -1;
        			A[j]=1-A[j];
        		}
        	}
//        for(int i=0;i<n;i++)//因为是按顺序翻的,所以已经保证了前面没有0,不用再扫描一遍
//        	if(A[i]==0)
//        		return -1;
    	return ans;
    }
}

[蓝桥杯][历届试题]翻硬币很类似

题目描述

小明正在玩一个“翻硬币”的游戏。 

桌上放着排成一排的若干硬币。我们用  *  表示正面,用  o  表示反面(是小写字母,不是零)。 

比如,可能情形是:**oo***oooo 

如果同时翻转左边的两个硬币,则变为:oooo***oooo 

现在小明的问题是:如果已知了初始状态和要达到的目标状态,每次只能同时翻转相邻的两个硬币,那么对特定的局面,最少要翻动多少次呢? 

我们约定:把翻动相邻的两个硬币叫做一步操作。

 

输入

两行等长的字符串,分别表示初始状态和要达到的目标状态。每行的长度< 1000 

输出

一个整数,表示最小操作步数。 

样例输入

*o**o***o*** 
*o***o**o*** 

样例输出

1
import java.util.Scanner;

public class 蓝桥杯_翻硬币 {

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		char[] ch1 = in.next().toCharArray();
		char[] ch2 = in.next().toCharArray();
		int n = ch1.length;
		int ans=0;
		for(int i=0;i<n-1;i++) {
			if(ch1[i]!=ch2[i]) {
				ans++;
				if(ch1[i]=='*')
					ch1[i]='o';
				else
					ch1[i]='*';
				if(ch1[i+1]=='*')
					ch1[i+1]='o';
				else
					ch1[i+1]='*';
			}
		}
		
		System.out.println(ans);
	}

}

 

 

下面继续讨论这道leetcode原题,其实这暴力的翻,复杂度是O(n*k),还可以再继续一个优化,记录开始标记和结束标记

 

思路

如果最左边的元素是 0,那么我们一定要翻转从位置 0 开始的子数组。类似的,如果最左边的元素是 1,我们不应该翻转从位置 0 开始的子数组。这证明了我们可以贪心地执行这一过程:在明确是否要反转第一个子数组之后(位置 0 至 K-1),我们可以考虑将数组中第一个元素(值为 1)移除,然后重复这个过程。

我们还可以做得更好。每次翻转一个子数组 A[i], A[i+1], ..., A[i+K-1],我们可以考虑这样的两种事件:第一种是 “开始事件”,标记位置 i 为我们翻转子数组的开始,另一种是 “结束事件” ,标记位置 i+K 是我们翻转子数组的结束。使用这些事件,我们就可以知道某一个位置被多少个重叠的翻转子数组覆盖了:它的数值等于 “开始事件” 的数量减去 “结束事件” 的数量。

算法

当我们翻转一个子数组的时候,让我们称翻转子数组的下标集合为一个区间。我们将维护一个变量 flip,也就是覆盖当前位置的重叠区间数量。我们只关心 flip 对 2 取模之后的值。

当我们翻转从 i 开始的一个区间,我们在位置 i+K 创建一个 “结束事件” 的提示,表明在那里要把翻转状态置反。

class Solution {
    public int minKBitFlips(int[] A, int K) {
        int N = A.length;
        int[] hint = new int[N];
        int ans = 0, flip = 0;

        // 当我们翻转子数组形如 A[i], A[i+1], ..., A[i+K-1]
        // 我们可以在此位置置反翻转状态,并且在位置 i+K 设置一个提醒,告诉我们在那里也要置反翻转状态
        for (int i = 0; i < N; ++i) {
            flip ^= hint[i];
            if (A[i] == flip) {  // 我们是否必须翻转从此开始的子数组
                ans++;  // 翻转子数组 A[i] 至 A[i+K-1]
                if (i + K > N) return -1;  // 如果没办法翻转整个子数组了,那么就不可行
                flip ^= 1;
                if (i + K < N) hint[i + K] ^= 1;
            }
        }

        return ans;
    }
}

 

猜你喜欢

转载自blog.csdn.net/weixin_41793113/article/details/88621542
0条评论
添加一条新回复