python实现蓝桥杯深度搜索(DFS)之Bit Compressor

python实现蓝桥杯深度搜索(DFS)之Bit Compressor

问题描述

问题描述

数据压缩的目的是为了减少存储和交换数据时出现的冗余。这增加了有效数据的比重并提高了传输速率。有一种压缩二进制串的方法是这样的:
  将连续的n个1替换为n的二进制表示(注:替换发生当且仅当这种替换减少了二进制串的总长度)
  (译者注:连续的n个1的左右必须是0或者是串的开头、结尾)
  比如:11111111001001111111111111110011会被压缩成10000010011110011。原串长为32,被压缩后串长为17.
  这种方法的弊端在于,有时候解压缩算法会得到不止一个可能的原串,使得我们无法确定原串究竟是什么。请你写一个程序来判定我们能否利用压缩后的信息来确定原串。给出原串长L,原串中1的个数N,以及压缩后的串。
  L<=16 Kbytes,压缩后的串长度<=40 bits。

输入格式

第一行两个整数L,N,含义同问题描述
  第二行一个二进制串,表示压缩后的串

输出格式

输出"YES"或"NO"或"NOT UNIQUE"(不包含引号)
  分别表示:
  YES:原串唯一
  NO:原串不存在
  NOT UNIQUE:原串存在但不唯一

样例输入

样例1:
32 26
10000010011110011
样例2:
9 7
1010101
样例3:
14 14
111111

样例输出

样例1:YES
样例2:NOT UNIQUE
样例3:NO

题目解析

这道题就是要将压缩后的二进制进行还原,然后去查看还原后的二进制串是否唯一,如果唯一,就输出YES,如果不唯一就输出NOT UNIQUE,如果不存在,就输出NO。

思路

参数说明

nowIndex:正在处理的字符下标

getZero:当前获得的0的数量

getOne:已经检索到的1的数量

l:原串的长度

l_one:原串1的个数

lzero:原串0的个数

answer:标记答案是否唯一

temptZero:临时变量

temptOne:临时变量

initNum:存放第一组的参数

initString:存放压缩后的传,以列表的形式。

首先,我们拿案例输入的第一个案例来分析,这个题的第一个难点,我觉得应该是如果有一个1后面跟了很多的0的话,我们没有办法去判断到底怎么切割。以及,二进制怎么转换成十进制。第二个难点,就是分情况去讨论。现在我们先来说一下这个题目要分什么条件讨论。

第一个情况

首先,由于我们是用二进制去表示原来的1的长度,所以这里就会遇到一个问题,首先,如果原来是0110的话,其实我们可以不用压缩,因为压缩过后是0010这个长度和原来的长度并没有发生任何的改变,所以这个变形是没有意义的;然而,也会有这种情况,就是说,万一我们原来的二进制切片是01110,被压缩成了0110,这个也是有可能的。所以,第一个 情况就是将这两个现象区分开来,分别讨论

第二个情况

就是说,如果碰到010的话,我们是不用去压缩的,甚至不用动它,因为确实没有压缩的必要。

剪枝

当然,为了减少时间复杂度,我们还需要剪枝。

首先,如果说我们访问的当前字符下标如果越界了,就可以直接返回了。

然后就是说,如果answer的值大于1了,我们也直接返回,没有再讨论下去的必要了。

接着,如果我们接收到的0已经超过了原来的0的话,那么也没有必要讨论下去了。

最后就是,如果我们接收到的1也超过了原来的1的话,我们也没有必要再继续下去了。

基线条件(也就是递归的结束条件)

当我们访问的下标等于压缩后的长度的时候,注意,虽然我们下标试从0开始的,本来因该是到压缩后的长度减1,但是由于我们最后一次递归还是会给下标加一,所以这样就刚好等于原来的长度了。由于在python中初始化列表比较的麻烦,而且由于内部的花销平坦的内存管理机制,这里没有必要先初始化列表,然后一个个个的给它赋值,直接在列表的最后面加一个任意整数就行,这是为了防止 列表的下标越界而报错。

判断条件

当我们寻找到的1以及0的个数和原串是相等的话,我们就认为这个条件是符合的。由于我们是按顺序枚举,所以我们所讨论出来的情况也一定是原串的排列,或者是另一种展开方式。不会出现个数正确但是排列完全不对的情况。

如何二进制转十进制

由于我们是从左往右遍历,所以在二进制转十进制的时候,需要一些的变化。

temptOne = 2*temptOne
temptOne += int(initString[i])

这两行代码就是二进制转十进制。由于我们是从左往右移动,所以每移动一次,十进制的值就会乘以两倍。这就是第一行的意义。同时,由于0001,0010,这种是没有意义的,所以我们每次移动的时候一定是0011从左往右移动,或者0111或者1111,这种时候,我们就应该给0010+1然后向右移动,这就是第二行的大致意思。所以第二行要么加后一位的0,要么加后一位的1。这个处理方法还是很精妙的。

代码


def dfs(nowIndex,getZero,getOne):
    """
        :param nowIndex: 当前正在处理的下标
        :param getZero: 已经检索到的0的个数
        :param getOne: 已经检索到的1的个数
        :return: 直接返回,将函数结束
        """
    global l,lzero,l_one,answer # 声明全局变量
    
    
    '''下面是剪枝的条件'''
    if answer > 1: return
    if nowIndex > l: return
    if getZero > lzero: return 
    if getOne > l_one : return
    

	'''基线条件'''
    if nowIndex == lcom:
        if getZero == lzero and getOne == l_one:
            answer += 1
            return

	'''开始正常的检索'''
    temptZero = getZero
    while initString[nowIndex] == '0':
        temptZero += 1
        nowIndex += 1

    temptOne = 0
    for i in range(nowIndex,lcom):
        temptOne = 2*temptOne
        temptOne += int(initString[i])
        # 处理0110 展开
        if temptOne == 2:
            continue
        if initString[i+1] == '1':
            continue
        next = i + 1
        while initString[next]=='0':
            next += 1
        dfs(next,temptZero+next-i-1,getOne+temptOne)
        if temptOne == 3:
            dfs(next,temptZero+next-i-1,getOne+2)


    

if __name__ == '__main__':
    # 第一次输入会有两个数,一个是原串的长度,还有一个就是原串1的个数
    initNum = input().split()
    l = int(initNum[0])
    l_one = int(initNum[1])
    lzero = l-l_one

    # 第二次输入的是压缩后的串
    initString = list(input())
    lcom = len(initString)
    initString.append(5) # 这个是让列表的长度变成17,这样防止列表的下标越界。

    # 然后就是标识串是否唯一的变量: answer
    answer = 0

    dfs(0,0,0)
    # 判断是否唯一
    if answer == 1:
        print("YES")

    elif answer >=2:
        print("NOT UNIQUE")

    else:
        print("NO")

参考博客

  1. https://blog.csdn.net/weixin_43856851/article/details/104933960
  2. https://blog.csdn.net/Raymond_YP/article/details/104450936
  3. https://blog.csdn.net/yi_qing_z/article/details/88084875

你可能感兴趣的:(算法,蓝桥杯,python,算法,剪枝)