6270. 每种字符至少取 K 个

111 阅读1分钟

题目:
给你一个由字符 'a''b''c' 组成的字符串 s 和一个非负整数 k 。每分钟,你可以选择取走 s 最左侧 还是 最右侧 的那个字符。

你必须取走每种字符 至少 k 个,返回需要的 最少 分钟数;如果无法取到,则返回 **-1 。
算法:

方法一:双指针
先从右往左遍历,直到三个元素都大于等于k,记位置为j,此时n-j已经包含了满足条件的字符个数。我们从i:=0开始往右遍历,每次i右移一个位置时,判断j能否右移(j右移肯定取的字符串最少),一直遍历到(直到不满足i<n&& j <n为止)

func takeCharacters(s string, k int) int {
    n := len(s)
    cnt := make([]int, 3)
    // 可能从右边一个数都不用取,为了保证初始的ans = n - j == 0, j初始值为n
    j := n
    for cnt[0] < k || cnt[1] < k || cnt[2] < k {
        if j == 0 {
            return -1
        }
        j --
        cnt[s[j] - 'a'] ++
        

    }
    // if j < 0 {
    //     j = 0
    // }
    fmt.Println(j)
    ans := n - j
    for i := 0; i < n && j < n; i ++ {
        cnt[s[i] - 'a'] ++
        for j < n && cnt[s[j] - 'a'] > k {
            cnt[s[j] - 'a'] --
            j ++
        }
        ans = min(ans, i + 1 + n - j)
    }
    return ans
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

方法二:滑动窗口
从两端取abc的数量>=k则,中间保留的abc数量<=total{a,b,c}-k。用i,j维护双指针的起点和终点。

func takeCharacters(s string, k int) int {
    n := len(s)
    count := make([]int, 3)
    // middleMax保存中间最多包含的abc的个数
    middleMax := make([]int, 3)
    for i := range s {
        count[s[i] - 'a'] ++
        
    }
    for i := range count {
        if count[i] < k {
            return  -1
        }
        middleMax[i] = count[i] - k
    }
    ans := math.MaxInt32
    middleConut := make([]int, 3)
    for i, j := 0, 0; j < n && i < n; j ++ {
        middleConut[s[j] - 'a'] ++
        // 这个循环保证跳出循环后,计算ans = min(ans, n - (j - i + 1))时,将[i,j]截掉后总是得到合法的字符串。
        for middleConut[s[j] - 'a'] > middleMax[s[j] - 'a'] {
            // s[j]不等于s[i]也没关系,随着i往右移动,总会减少s[j]的数量。
            middleConut[s[i] - 'a'] --
            i ++
        }
        ans = min(ans, n - (j - i + 1))
    }
    return ans
}

func min(a, b int) int {
    if a < b {
        return a
    }
    return b
}

方法三:二分查找 思路:假设可选择的最小长度为lim(初始化[0,n]),看能否满足"abc"均大于等于k,如果不行则再猜,逼近正确答案。猜测lim之后先从左侧选取lim个字符,然后左侧一次减少,右侧一次增加,如得到的字符串能满足"abc"均大于等于k,则可以往小猜lim

func takeCharacters(s string, k int) int {
    n := len(s)
    charCount := make([]int, 3)
    for i := range s {
        charCount[s[i] - 'a'] ++    
    }
    for i := range charCount {
        if charCount[i] < k {
            return  -1
        }
    }
    var valid func (lim int) bool
    valid = func(lim int) bool {
        count := make([]int, 3)
        
        for i := 0; i < lim; i ++ {
            count[s[i] - 'a'] ++
        }
        if count[0] >= k && count[1] >= k && count[2] >= k {
            return true
        }
        j := n - 1
        for i := 1; i <= lim; i ++ {
            count[s[lim - i] - 'a'] --
            count[s[j] - 'a'] ++
            j --
            if count[0] >= k && count[1] >= k && count[2] >= k {
                return true
            }
        }
        return false
    }
    left, right := 0, n
    for left < right {
        mid := (left + right) / 2
        if valid(mid) {
            right = mid
        } else {
            left = mid + 1
        }
        
    }
    return left
}