题目:
给你一个由字符 '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
}