使字符串平衡的最少删除次数[动态规划+状态压缩+中心划分思想]

前言

动态规划,就是把问题分解成相同性质规模越小的递推问题,而有时候不仅仅是知道当前的什么状态就可以了,有时候需要对当前进行细分类,从而变成二维dp,或者高维dp。除此之外,中心划分思想也是一个经典的算法思想。

一、使字符串平衡的最少删除次数

二、一题多解

一题多解,打开视野,打开思考角度,训练各种思维方式,完成对问题的深入理解,才能有所提高。

1、动态规划与压缩

定义:f[i][0]为当前以字符a结尾的平衡字符串所删除字符的最少次数;f[i][1]为当前以字符b结尾的平衡字符串所删除字符的最少次数。
当 curCh == 'a’时,f[i][0] = f[i - 1][0];f[i][1] = f[i - 1][1] + 1;
反之,f[i][0] = f[i - 1][0] + 1;f[i][1] = min(f[i - 1][0],f[i - 1][1]);

func minimumDeletions(s string) int {
    f := make([][2]int,len(s) + 1)
    for i := 1;i <= len(s);i++ {
        if s[i - 1] == 'a' {
            f[i][0] = f[i - 1][0]
            f[i][1] = f[i - 1][1] + 1
        }else {
            f[i][0] = f[i - 1][0] + 1
            f[i][1] = min(f[i - 1][0],f[i - 1][1])
        }
    }
    n := len(s)
    if f[n][0] > f[n][1] {
        return f[n][1]
    }
    return f[n][0]
}
func min(x,y int) int {
    if x < y {
        return x
    }
    return y
}

状态压缩,

func minimumDeletions(s string) int {
    f1,f2 := 0,0
    n := len(s)
    for i := 0;i < n;i++ {
        if s[i] == 'a' {
            f2 = f2 + 1
        }
        if s[i] == 'b' {
            f1,f2 =f1 + 1,min(f1,f2)
        }
    }
    return min(f1,f2)
}
func min(x,y int) int {
    if x < y {
        return x
    }
    return y
}

总结: 题多自然是优势,更重要的是你在题中学到了多少东西?比如认真分析问题、专心不放任何一处细节、提出好问题和解决方案、学习别人的优雅编码,以及更抽象的思路。

2、中心划分思想

别人的代码,

func minimumDeletions(s string) int {
    // b必须在右侧,a必须在左侧,乃平衡字符串。
    // 最糟糕的情况 bbaa
    // 把不平衡的因子抽象出来,即lb,ra,通过遍历整个字符串,来了解可变因子的情况,在其中记录最少抽象因子个数。
    // 最多的情况不过是把a删完/b删完。
    // 存看代码看一半天知道是对的一条路径,但是每触摸的到本质,看来源质,才发现是遍历字符串是,在当前字符和右侧相邻字符间画一条线,左边的应该全是a,后边的应该全是b,通过ab因子记录左右情况就知道了。
    lb,ra := 0,strings.Count(s,"a")
    ans := ra
    for _,c := range s {
        if c == 'a' {
            ra--
        }
        if t := lb + ra;ans > t {
            ans = t
        }
        if c == 'b' {
            lb++
        }
    }
    return ans
}

本质就是,以当前字符与右邻居为中线,看左边有多少个多余的b数lb,右边还剩多少多余的a数ra!

当知道本质是中心划分思想时,我们可以根据源质,另辟蹊径。
给定如下定义,以当前字符与右邻居中间为中线,将字符串分为左子串和右子串,定义左子串中的a为平衡字符(因为能组合成平衡字符串),定义右子串中的b为平衡字符,在当前状态下,其他的都视为非平衡字符。
所以记录左边有多少平衡字符a,右边还剩多少平衡字符b(毕竟是正序遍历,只能看剩余),总字符串为n,那么要删掉的字符就是非平衡字符,一共n - la - rb;

// 知道了源质,就可以开辟出另一条路径
// 遍历字符串,当前字符左侧包括自己的‘a’字符定义为平衡字符,而右边的'b'定义为平衡字符
// 其他左侧b右侧a,在当前划分下,被认定为非平衡字符。
// 中心点划分思想
func minimumDeletions(s string) int {
    la,rb := 0,strings.Count(s,"b")
    ans,n := len(s) - la - rb,len(s)
    for _,c := range s {
        if c == 'a' {
            la++
        }
        if c == 'b' {
            rb--
        }
        // la + rb是有效的平衡字符
        // n - la - rb,就是当前状态下无法确认的平衡字符--作为无效字符,将其删掉,直接让有效平衡字符组成的字符串平衡。
        if t := n - la - rb;ans > t {
            ans = t
        }
    }
    return ans
}
// 利用更多潜在规律,比如b和a的ascall码只相差1
func minimumDeletions(s string) int {
    num := strings.Count(s,"b") // 平衡字符
    n := len(s)
    ans := n - num // 总字符数n - 平衡字符num = 不平衡字符数
    for _,c := range s {
        num += int(('a' - c) * 2 + 1)
        if t := n - num;ans > t {
            ans = t
        }
    }
    return ans
}

总结

1)动态规划,把问题分解成相同性质规模越小的递推问题,状态压缩,降低空间复杂度,减少没必要的空间浪费。
2)中心划分思想,以当前位置为中心,存储两边的信息(可以使用变量或者数据结构),来判定当前字符串的情况。
3)根据一条解答路径,发现问题的源质(本质)时,就可以从源质出发,另辟蹊径,这也是创新的一种过程,先学表现再溯源最后创新!
4)一题多解,打开视野,打开思考角度,训练各种思维方式,完成对问题的深入理解,才能有所提高。
5)题多自然是优势,更重要的是你在题中学到了多少东西?比如认真分析问题、专心不放任何一处细节、提出好问题和解决方案、学习别人的优雅编码,以及更抽象的思路。
6)利用问题中更多的潜在规律和联系,完成更优雅的编码,如这里b字符和a字符的ascall码只相差1,而每次删除也只删一个,可以写出更加优雅的代码。

参考文献

[1] LeetCode 使字符串平衡的最少删除次数

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值