读书笔记-python数据结构与算法分析(一)-异序词检测

本文探讨了异序词检测问题的三种解决方案:清点法(O(n^2))、排序法(O(n^2)或O(nlog(n)))和计数法(O(n))。通过实例展示了如何分别利用这些方法检查字符串是否为异序词,并分析了各自的优缺点及时间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

要展示不同数量级的算法,一个好例子就是经典的异序词检测问题。如果一个字符串只是重排了另一个字符串的字符,那么这个字符串就是另一个的异序词,比如heart与earth,以及python与typhon。为了简化问题,假设要检查的两个字符串长度相同,并且都是由26个英文字母的小谢形式组成的。我们的目标是编写一个布尔函数,它接受两个字符串,并能判断它们是否为异序词。

1 清点法

清点第一个字符串的每个字符,看看它们是否都出现在第2个字符串中。如果是,那么两个字符串必然是异序词。清点是通过python中的特殊值None取代字符来实现的。但是,因为python中的字符串是不可修改的,所以先要江第2个字符串转换成列表。在字符列表中检查第1个字符串中的每个字符,如果找到了,就替换掉。

  • python
def inventory(s1, s2):
    """
    清点法 O(n^2)
    :param s1:
    :param s2:
    :return:
    """
    s2_list = list(s2)
    pos1 = 0
    still_ok = True
    # 遍历s1
    while pos1 < len(s1) and still_ok:
        pos2 = 0
        found = False
        # 遍历s2
        while pos2 < len(s2_list) and not found:
            if s1[pos1] == s2_list[pos2]:
                found = True
            else:
                pos2 += 1
        # 如果找到了相同的字符,则把list中的值赋值为None
        if found:
            s2_list[pos2] = None
        else:
            still_ok = False
        pos1 += 1
    return still_ok
  • go
//
//  inventory
//  @Description: 清点法 O(n^2)
//  @param s1
//  @param s2
//  @return bool
//
func inventory(s1, s2 string) bool {
	s1Array := strings.Split(s1, "")
	s2Array := strings.Split(s2, "")

	pos1 := 0
	stillOk := true
	for pos1 < lenS1 && stillOk {
		pos2 := 0
		found := false
		for pos2 < lenS2 && !found {
			if s1Array[pos1] == s2Array[pos2] {
				found = true
			} else {
				pos2++
			}
		}
		if found {
			s2Array[pos2] = ""
		} else {
			stillOk = false
		}
		pos1++
	}
	return stillOk
}

注意,对于s1中的n个字符,检查每一个时都要遍历s2中的n个字符。要匹配s1中的一个字符,列表中的n个位置都要被访问一次。因此,访问次数就成了从1到n的整数之和。公式如下,所以时间复杂度为O(n^2)
∑i=1n=n(n+1)2=12n2+12n \sum_{i=1}^n=\frac{n(n+1)}{2}=\frac{1}{2}n^2+\frac{1}{2}n i=1n=2n(n+1)=21n2+21n

2 排序法

尽管s1与s2是不同的字符串,但只要由相同的字符构成,它们就是异序词。基于这一点,可以采用另一个方案。如果按照字母表顺序给字符串排序,异序词得到的结果将是同一个字符串。可以先将字符串转换为列表,然后使用内建的sort方法对列表排序。

  • python
def sort_(s1, s2):
    """
    排序法 O(n^2)或O(nlog(n))
    :param s1:
    :param s2:
    :return:
    """
    s1_list = list(s1)
    s2_list = list(s2)

    s1_list.sort()
    s2_list.sort()

    for index, value in enumerate(s1_list):
        if s2_list[index] != value:
            return False
    return True
  • go
//
//  sort_
//  @Description: 排序法 O(n^2)或O(nlog(n))
//  @param s1
//  @param s2
//  @return bool
//
func sort_(s1, s2 string) bool {
	s1Array := strings.Split(s1, "")
	s2Array := strings.Split(s2, "")
	sort.Strings(s1Array)
	sort.Strings(s2Array)

	for index, value := range s1Array {
		if value != s2Array[index] {
			return false
		}
	}
	return true
}

乍看之下,你可能会认为这个算法的时间复杂度是O(n),因为在排序之后需要遍历一次就可以比较n个字符。但是,调用两次sort方法不是没有代价。排序的时间复杂度基本上是O(n^2)或O(nlogn),所以排序操作起主导作用。也就是说,该算法和排序过滤的数量级相同。

3 计数法

最后一个方案基于这样一个事实:两个异序词有相同数目的a、相同数目的b、相同数目的c,等等。要判断两个字符串是否为异序词,先数一下每一个出现的次数。因为字符可能有26种,所以使用26个计数器,对应每个字符。每遇到一个字符,就将对应的计数器加1。最后,如果两个计数器列表相同,那么两个字符串肯定是异序词。

  • python
def count(s1, s2):
    """
    计数法 O(n)
        ord: 它以一个字符(长度为1的字符串)作为参数,返回对应的 ASCII 数值,或者 Unicode 数值,如果所给的 Unicode 字符超出了你的 Python 定义范围,则会引发一个 TypeError 的异常
    :param s1:
    :param s2:
    :return:
    """
    # 26个字母
    c1 = [0] * 26
    c2 = [0] * 26

    for i in s1:
        index = ord(i) - ord('a')
        c1[index] += 1

    for i in s2:
        index = ord(i) - ord('a')
        c2[index] += 1

    return c1 == c2
  • go
//
//  count
//  @Description: 计数法 O(n) 单引号的字符为rune类型,用int可转ASCII
//  @param s1
//  @param s2
//  @return bool
//
func count(s1, s2 string) bool {
	var c1 [26]int
	var c2 [26]int
	startA := int('a')

	for _, ch := range s1 {
		c1[int(ch) - startA] ++
	}
	for _, ch := range s2 {
		c2[int(ch) - startA] ++
	}
	return c1 == c2
}

这个方案也有循环。但不同于方案1,这个方案的循环没有嵌套。两个计数循环都是n阶的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值