玩玩算法题——Episode 2

Leetcode每日一题:最大字符串匹配数目

题干如下:

给你一个下标从 0 开始的数组 words ,数组中包含互不相同的字符串。

如果字符串 words[i] 与字符串 words[j] 满足以下条件,我们称它们可以匹配:

字符串 words[i] 等于 words[j] 的反转字符串。
0 <= i < j < words.length
请你返回数组 words 中的 最大 匹配数目。

注意,每个字符串最多匹配一次。

示例 1:

输入:words = ["cd","ac","dc","ca","zz"]
输出:2
解释:在此示例中,我们可以通过以下方式匹配 2 对字符串:

  • 我们将第 0 个字符串与第 2 个字符串匹配,因为 word[0] 的反转字符串是 "dc" 并且等于 words[2]。
  • 我们将第 1 个字符串与第 3 个字符串匹配,因为 word[1] 的反转字符串是 "ca" 并且等于 words[3]。
    可以证明最多匹配数目是 2 。
    示例 2:

输入:words = ["ab","ba","cc"]
输出:1
解释:在此示例中,我们可以通过以下方式匹配 1 对字符串:

  • 我们将第 0 个字符串与第 1 个字符串匹配,因为 words[1] 的反转字符串 "ab" 与 words[0] 相等。
    可以证明最多匹配数目是 1 。
    示例 3:

输入:words = ["aa","ab"]
输出:0
解释:这个例子中,无法匹配任何字符串。

提示:

1 <= words.length <= 50
words[i].length == 2
words 包含的字符串互不相同。
words[i] 只包含小写英文字母。


Star Rating: 2.68(个人观点)

SR转换:
Leetcode简单 -> 约等于1~2星难度
Leetcode普通 -> 约等于3~4星难度
Leetcode困难 -> 约等于5~6星难度


首先,仍然有一种很暴力的解法,复杂度是O(n^2),,也就是固定一个字符串,尝试去找另一个字符串。

但是,这种算法的效率是很低的,因此我们需要想一个更好的方法

我们会发现:
(1)由于words不能包含相同的字符串,因此两个字母相同的字符串不能和任何其他字符串匹配。

Leetcode的题比较偏向数据结构的应用,所以我们要尽量往这个方向去思考...不过对于这个题来说仍然想不到任何思路。
但是我们可以先写一下BF的方法:

class Solution(object):
    def maximumNumberOfStringPairs(self, words):
        """
        :type words: List[str]
        :rtype: int
        """
        count = 0
        for i in range (len(words)):
            if words[i][0] == words[i][1]:
                continue
            for j in range (len(words)):
                if words[i][0] == words[j][1] and words[i][1] == words[j][0]:
                    count += 1
        return count // 2 #每一对匹配字符串计算了两次,所以返回的值要除以2

不过...有没有更好的方法呢?
能够正确提交,32ms(35.71% defeated) and 13.21MB(7.14% defeated)

Current: 23pp(New!)
Overall: 124pp(+19pp)

现在我们思考一下,有没有更好的方法呢?

ChatGPT给出了一个能够拿到34pp的方法:
这个问题可以通过使用字典(哈希表)来提高效率。我们可以将每个字符串的反转形式作为键,然后统计每个反转形式的出现次数。在遍历过程中,如果当前字符串的反转形式已经在字典中出现过,则说明可以进行匹配,匹配的次数就是该反转形式在字典中的出现次数。最后取所有匹配次数的和即为最大匹配数目。

以下是相应的代码:(ChatGPT一开始产生的代码是错的,所以我们又稍微改了一点)

from collections import defaultdict

class Solution(object):
    def maximumNumberOfStringPairs(self, words):
        """
        :type words: List[str]
        :rtype: int
        """
        reverse_counts = defaultdict(int)

        for word in words:
            if word[0] != word[1]:
                reverse_form = word[1] + word[0]
                if reverse_counts[reverse_form] == 1:
                    reverse_counts[reverse_form] += 1
                else:
                    reverse_counts[word[0] + word[1]] += 1
        # 这样精心设计以后,一对匹配的字符串,会在reverse_counts中产生一个值为2的键

        max_matches = sum(count for count in reverse_counts.values() if count % 2 == 0) // 2 #找到值为2的键的总个数
        return max_matches

这种方法的时间复杂度为 O(n),其中 n 是字符串数组的长度。我们通过一次遍历统计了每个反转形式的出现次数,然后计算匹配次数的和。因为字典的查找操作是 O(1) 的平均复杂度,所以总体上是线性时间的。

defaultdict 是 Python 中的一个特殊字典,它可以在创建时指定默认值的类型,当访问一个不存在的键时,会返回默认值。在这个问题中,我们使用 defaultdict(int) 创建了一个默认值为 0 的字典,即如果某个键不存在,则返回 0。

在上述代码中,reverse_counts 是一个字典,它的键是字符串的反转形式,值是该反转形式出现的次数。由于我们在创建 defaultdict(int) 时指定了默认值为 0,所以当访问一个不存在的键时,会返回 0。

这样做的好处是,在遍历 words 数组时,我们无需判断每个键是否存在,直接通过 reverse_counts[reverse_form] 就能获取到相应的值,而且不存在的键会自动被初始化为 0。

使用 defaultdict 可以简化代码,使其更加清晰和简洁。

Yes~ 性能有了显著提升 11ms(100.00% Defeated)和11.54MB(100.00% Defeated)

Current: 34pp(+11pp)
Overall: 135pp(+11pp)

Leetcode 1237. 找出给定方程的正整数解

Star Rating: 3.62
题目描述如下:


给你一个函数 f(x, y) 和一个目标结果 z,函数公式未知,请你计算方程 f(x,y) == z 所有可能的正整数数对 x 和 y。满足条件的结果数对可以按任意顺序返回。

尽管函数的具体式子未知,但它是单调递增函数,也就是说:

f(x, y) < f(x + 1, y)
f(x, y) < f(x, y + 1)
函数接口定义如下:

interface CustomFunction {
public:
// Returns some positive integer f(x, y) for two positive integers x and y based on a formula.
int f(int x, int y);
};
你的解决方案将按如下规则进行评判:

判题程序有一个CustomFunction 的 9 种实现组成的列表,以及一种为特定的 z 生成所有有效数对的答案的方法
判题程序接受两个输入function_id(决定使用哪种实现测试你的代码)以及目标结果 z
判题程序将会调用你实现的 findSolution 并将你的结果与答案进行比较。
如果你的结果与答案相符,那么解决方案将被视作正确答案,即 Accepted 。

示例 1:

输入:function_id = 1, z = 5
输出:[[1,4],[2,3],[3,2],[4,1]]
解释:function_id = 1 暗含的函数式子为 f(x, y) = x + y
以下 x 和 y 满足 f(x, y) 等于 5:
x=1, y=4 -> f(1, 4) = 1 + 4 = 5
x=2, y=3 -> f(2, 3) = 2 + 3 = 5
x=3, y=2 -> f(3, 2) = 3 + 2 = 5
x=4, y=1 -> f(4, 1) = 4 + 1 = 5
示例 2:

输入:function_id = 2, z = 5
输出:[[1,5],[5,1]]
解释:function_id = 2 暗含的函数式子为 f(x, y) = x * y
以下 x 和 y 满足 f(x, y) 等于 5:
x=1, y=5 -> f(1, 5) = 1 * 5 = 5
x=5, y=1 -> f(5, 1) = 5 * 1 = 5

提示:

1 <= function_id <= 9
1 <= z <= 100
题目保证 f(x, y) == z 的解处于 1 <= x, y <= 1000 的范围内。
1 <= x, y <= 1000 的前提下,题目保证 f(x, y) 是一个 32 位有符号整数。


"""
   This is the custom function interface.
   You should not implement it, or speculate about its implementation
   class CustomFunction:
       # Returns f(x, y) for any given positive integers x and y.
       # Note that f(x, y) is increasing with respect to both x and y.
       # i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
       def f(self, x, y):
"""

class Solution(object):
    def findSolution(self, customfunction, z):
        """
        :type num: int
        :type z: int
        :rtype: List[List[int]]
        """
        

不过为什么看到这个题的时候,我完全没有思路呢???...

比如说我们的输入z = 6, customfunction是某个未知的运算...
假如说我们先运行customfunction.f(1,1).它在大部分情况等于1,但也有不等于的情况

如果f(1,1)大于z,那么直接返回一个空列表(因为运算函数是递增函数,所以这个时候不可能有solution)
如果f(1,1)小于z???

那么我们分别尝试下f(1,2)和f(2,1)
假如结果是f(1,2)大于z,f(2,1)小于z?
根据结果,我们可以知道f(2,2)大于max(f(1,2),f(2,1)),因此f(2,2)可以不需要尝试了,我们只需要再次尝试f(3,1)就可以了....

如果f(1,2)和f(2,1)都小于z呢?
那么此时,我们显然需要(1,3),(2,2)和(3,1)都试一试!

我们不妨画图表示一下这两种情况:

如果是第一种情况:

4   x   x   x   x
3   x   x   x   x
2   >   x   x   x
1   <   <   !   ?
    1   2   3   4

如果是第二种情况

4   ?   ?   ?   ?
3   !   ?   ?   ?
2   <   !   ?   ?
1   <   <   !   ?
    1   2   3   4

其中"!"代表接下来要计算的f(x,y),而?表示不知道需不需要计算,大于、小于和等于用">","<"和"="表示,"x"表示不需要计算的值。
显然,一个">"或者一个"="会让处于它右上角的部分全部变成"x",而"<"出现的时候,它右边和它上边的值都需要在下一次再次计算(如果没有受到其他">"或者"="的影响。

那么我们的第一个版本的算法也就出现了:——可能比较像动态规划

"""
   This is the custom function interface.
   You should not implement it, or speculate about its implementation
   class CustomFunction:
       # Returns f(x, y) for any given positive integers x and y.
       # Note that f(x, y) is increasing with respect to both x and y.
       # i.e. f(x, y) < f(x + 1, y), f(x, y) < f(x, y + 1)
       def f(self, x, y):
"""

class Solution(object):
    def findSolution(self, customfunction, z):
        """
        :type num: int
        :type z: int
        :rtype: List[List[int]]
        """
        result = []
        #初始化一个1000*1000的矩阵(列表),其值全部为"?"
        #计算f(1,1),如果f(1,1)>z,那么直接返回一个空列表
        #如果f(1,1)=z,直接返回[[1,1]]
        #如果f(1,1)<z,将f(1,2)和f(2,1)置为"!"(如果它们不是"x"的话)
        #计算所有的矩阵中"!"出现的f(x,y)
        #把结果和z进行比较,并将"!"赋值为">","<"或者"="
        #如果结果是">",那么将它右边、上边和右上的矩阵值全部置为"x"
        #如果结果是"=",把这组解放在结果上,并将它右边、上边和右上的矩阵值全部置为"x"
        #如果结果是"<",那么将它右边相邻的元素和上边相邻非"x"的元素置为"!"
        #重复上述过程,直到矩阵中没有"?"为止,返回result.

懒得写具体的实现了,所以让ChatGPT生成了一下:
以下是根据你的描述完成的第一个版本的算法。这个算法使用了一个二维矩阵,代表所有可能的 f(x, y) 的计算状态。在计算过程中,通过不断地更新矩阵的值,得到符合条件的解。

class Solution(object):
    def findSolution(self, customfunction, z):
        """
        :type customfunction: CustomFunction
        :type z: int
        :rtype: List[List[int]]
        """
        result = []
        matrix = [['?' for _ in range(1001)] for _ in range(1001)]

        # 计算 f(1, 1)
        f_1_1 = customfunction.f(1, 1)
        if f_1_1 > z:
            return result
        elif f_1_1 == z:
            result.append([1, 1])
            return result
        else:
            matrix[1][1] = '!'
            matrix[1][2] = matrix[2][1] = '!'

        # 计算所有可能的 f(x, y)
        for x in range(1, 1001):
            for y in range(1, 1001):
                if matrix[x][y] == '!':
                    f_x_y = customfunction.f(x, y)
                    if f_x_y == z:
                        result.append([x, y])
                        matrix[x][y] = '='
                        matrix[x+1:][y+1:] = 'x'
                    elif f_x_y > z:
                        matrix[x][y] = '>'
                        matrix[x+1:][y+1:] = 'x'
                    else:
                        matrix[x][y] = '<'
                        matrix[x+1][y] = matrix[x][y+1] = '!'

        return result

这个算法对 f(x, y) 进行了一些预处理,然后通过矩阵的更新,找到所有可能的解。尽管这个算法可能不是最优解,但它是一种直观的解法,可以帮助你更好地理解问题。

这个版本的算法显然是可以通过测试的,不过耗时和空间都很大。
2490ms(12.50%)和20.28MB(12.50%)

Current: 48pp(New!)
Overall: 176pp(+41pp)

那么,还有没有其他的方法可以解决这个问题呢
...我之前说过这个题似乎比较像动态规划...但是我们要如何利用这个特性呢?
...

对于这个问题,我们可以考虑使用二分搜索来加速查找。首先,我们观察到 f(x, y) 是递增函数,因此对于给定的 xyf(x, y) 的值是唯一确定的。

我们可以按照以下步骤进行优化:

  1. 固定一个变量,比如 x,然后在范围 [1, 1000] 内使用二分搜索查找 y,使得 f(x, y) == z
  2. 如果找到了满足条件的 y,将 [x, y] 添加到结果中。
  3. 增加 x 并重复步骤 1。

这样,我们可以通过二分搜索在 y 的范围内快速找到符合条件的解,从而减小时间复杂度。以下是实现这一优化的代码:

class Solution(object):
    def findSolution(self, customfunction, z):
        """
        :type customfunction: CustomFunction
        :type z: int
        :rtype: List[List[int]]
        """
        result = []

        for x in range(1, 1001):
            left, right = 1, 1000

            while left <= right:
                mid = (left + right) // 2
                value = customfunction.f(x, mid)

                if value == z:
                    result.append([x, mid])
                    break
                elif value < z:
                    left = mid + 1
                else:
                    right = mid - 1

        return result

这是Python所写的代码,时间复杂度从O(n^2)减到了O(nlogn),这里ChatGPT利用了对于特定的x来说,使f(x,y)=z的y是唯一的,利用这个特点简化了时间复杂度。

那么,我们能否把这两个特性结合在一起呢?

class Solution(object):
    def findSolution(self, customfunction, z):
        """
        :type customfunction: CustomFunction
        :type z: int
        :rtype: List[List[int]]
        """
        result = []

        x,y,limit_x,limit_y = 1,1,1001,1001 #数据初始化

        while x < limit_x or y < limit_y:
            left, right = 1, limit_y
            if limit_y == 1:
                break
            while left <= right:
                mid = (left + right) // 2
                value = customfunction.f(x, mid)

                if value == z:
                    result.append([x, mid])
                    y = limit_y
                    break
                elif value < z:
                    left = mid + 1
                else:
                    y = limit_y
                    right = mid - 1

            x += 1

        return result

那么这段算法的性能是124ms(25.00%)和13.13MB(12.50%),和之前相比有了显著的提升!
Current: 56pp(+13pp)
Overall: 184pp(+8pp)

不过我为什么感觉还可以优化...我为什么会想到y = limit_y呢?...
后来发现这并不是把它们结合在一起的方法...而是...

class Solution(object):
    def findSolution(self, customfunction, z):
        """
        :type customfunction: CustomFunction
        :type z: int
        :rtype: List[List[int]]
        """
        result = []

        x,y,limit_x,limit_y = 1,1,1001,1001 #数据初始化

        while x < limit_x or y < limit_y:
            left, right = 1, limit_y
            if limit_y == 1:
                break
            while left <= right:
                mid = (left + right) // 2
                value = customfunction.f(x, mid)

                if value == z:
                    result.append([x, mid])
                    y = limit_y #用于快速跳出内层循环
                    limit_y = mid #这样子才对嘛!~ 动态更新limit_y~(利用x遍历递增的性质,之后的寻找只需要在1,limit_y)范围内执行,并采用二分查找提升效率~
                    break
                elif value < z:
                    left = mid + 1
                else:
                    y = limit_y
                    limit_y = mid
                    right = mid - 1

            x += 1

        return result

这段代码取得了显著的时间和空间开销的下降!~
16ms(100.00%)和11.59MB(100.00%)

Current: 79pp(+23pp)
Overall: 207pp(+23pp)

Luogu P1013. 进制位

Star Rating: 3.73


题目描述

著名科学家卢斯为了检查学生对进位制的理解,他给出了如下的一张加法表,表中的字母代表数字。 例如:

\[\def\arraystretch{2} \begin{array}{c||c|c|c|c} \rm + & \kern{.5cm} \rm \mathclap{L} \kern{.5cm} & \kern{.5cm} \rm \mathclap{K} \kern{.5cm} & \kern{.5cm} \rm \mathclap{V} \kern{.5cm} & \kern{.5cm} \rm \mathclap{E} \kern{.5cm} \\ \hline\hline \rm L & \rm L & \rm K & \rm V & \rm E \\ \hline \rm K & \rm K & \rm V & \rm E & \rm \mathclap{KL} \\ \hline \rm V & \rm V & \rm E & \rm \mathclap{KL} & \rm \mathclap{KK} \\ \hline \rm E & \rm E & \rm \mathclap{KL} & \rm \mathclap{KK} & \rm \mathclap{KV} \\ \end{array}\]

其含义为:

\(L+L=L\)\(L+K=K\)\(L+V=V\)\(L+E=E\)

\(K+L=K\)\(K+K=V\)\(K+V=E\)\(K+E=KL\)

\(\cdots\)

\(E+E=KV\)

根据这些规则可推导出:\(L=0\)\(K=1\)\(V=2\)\(E=3\)

同时可以确定该表表示的是 \(4\) 进制加法。

输入格式

第一行一个整数 \(n\)\(3\le n\le9\))表示行数。

以下 \(n\) 行,每行包括 \(n\) 个字符串,每个字符串间用空格隔开。)

若记 \(s_{i,j}\) 表示第 \(i\) 行第 \(j\) 个字符串,数据保证 \(s_{1,1}=\texttt +\)\(s_{i,1}=s_{1,i}\)\(|s_{i,1}|=1\)\(s_{i,1}\ne s_{j,1}\)\(i\ne j\))。

保证至多有一组解。

输出格式

第一行输出各个字母表示什么数,格式如:L=0 K=1 \(\cdots\) 按给出的字母顺序排序。不同字母必须代表不同数字。

第二行输出加法运算是几进制的。

若不可能组成加法表,则应输出 ERROR!

样例 #1

样例输入 #1

5
+ L K V E
L L K V E
K K V E KL
V V E KL KK
E E KL KK KV

样例输出 #1

L=0 K=1 V=2 E=3
4

首先,我们在拿到这个题以后,一定要考虑一下先把数据输入进去

n = int(input())
table = []
for i in range(n):
    line = list(map(str, input().split()))
    teble.append(line)

然后...看不懂了...

假如说表格是这样的:

+   0   1
0   0   1
1   1   10

这就是一个简单的二进制加法表
与此同时我们也可以改变0 和 1的顺序:

+   0   1
1   1   10
0   0   1

这也是一个二进制表格
根据题意可知,\(s_{i,1} \neq s_{j,1}\),所以结果进制数一定大于等于表格大小。(不一定是等于)

如果是三进制表格,那么他会是什么样的呢?

+   2   1   0
0   2   1   0
1   10  2   1
2   11  10  2

或者说,如果0,1,2不是递增或者递减顺序?

+   2   0   1
1   10  1   2
0   2   0   1
2   11  2  10

显然,我们已经找到破局的方法了!
我们需要先找到"0"是多少!---因为0一定在和第一行数字相等的一行,以及和第一列数字相等的一列当中,这一行和这一列相交的地方对应的字母就是"0"!
如果找到了"0",接下来要如何去做呢?
无论是几进制,两个一位数相加的结果不可能超过这个进制数字的"10+10".因此,我们可以去找结尾为"0"对应字母的值,然后断定其前边的数字是"1"!(这也就说明,这个表格右下角的子表中的值如果是两位数,不可能超过10+10!)
如果能确定出"1"是多少,那么如何确定其他的值呢?(如果有的话)
找到"1"以后,我们去找"1"对应的行和"1"对应的列,找到"2"对应的字母值,然后就简单了!固定"1"对应的行,找到"2"对应的列的字母,就能找到"3",....直到"n"对应的列为"10"为止结束循环。
(实际上,我们也可以看出来,如果输入的表格长度是n,那它应该是一个n-1进制运算表!)
思路渐渐清晰,我们也来写一下对应的代码!

s = int(input())
table = []
for i in range(s):
    line = list(map(str, input().split()))
    table.append(line)

dict = {} #存储最后的映射结果

#在table中寻找table[1:][k]与table[1:][0]相等的k和table[l][1:]与table[0][1:]相等的[l],那么table[k][l]对应的字母的映射值为0.

#遍历寻找结尾字符为table[k][l]的长度为2的第一个字符串table[m][n],确定table[m][n][0]对应字母的映射值为1.

#如果进制数为2,即n=3,则返回结果

#否则,找到table[m][n][0]在table[0][1:]对应的位置p和table[m][n][0]在table[1:][0]对应的位置q,找到table[p][q]对应的位置字母的映射值为2

#如果进制数为3,即n=4,则返回结果

#否则,遍历table[1:][p],然后在table[1:][0]中找到table[p][q]对应的位置,确定这个映射值为3,并记录这个值作为下一次在table[1:][0]中寻找的索引,如果还没有确定所有字母,重复上述操作,直到字典长度达到s - 1.

#按照格式输出结果

让ChatGPT生成了一下代码:

n = int(input())
table = []

for i in range(n):
    line = list(map(str, input().split()))
    table.append(line)

def find_mapping(table, char, n, known_mapping):
    for i in range(1, n):
        if table[i][0] == char:
            return i, 0
        if table[0][i] == char:
            return 0, i
    for i in range(1, n):
        for j in range(1, n):
            if table[i][j] == char:
                if table[i][0] in known_mapping:
                    return i, 0
                else:
                    return 0, j

def solve(table):
    n = len(table)
    known_mapping = {}  # 存储映射结果

    # 寻找0的映射
    for i in range(1, n):
        for j in range(1, n):
            if len(table[i][j]) == 2:
                char = table[i][j][1]
                known_mapping[char] = 0
                break

    # 寻找1到n-1的映射
    for value in range(1, n):
        for i in range(1, n):
            for j in range(1, n):
                if len(table[i][j]) == 2 and table[i][j][0] == str(value):
                    char = table[i][j][1]
                    if char not in known_mapping:
                        row, col = find_mapping(table, char, n, known_mapping)
                        known_mapping[char] = value

    return known_mapping

def print_result(known_mapping):
    result_str = ' '.join([f'{char}={value}' for char, value in sorted(known_mapping.items())])
    print(result_str)

def main():
    known_mapping = solve(table)
    if len(known_mapping) != n - 1:
        print('ERROR!')
    else:
        print_result(known_mapping)

if __name__ == "__main__":
    main()

看看能否通过测试吧!
第一次测试发现有三个WE...可能是需要按照表格第一行的顺序输出映射值,字典的顺序可能和表格第一行/第一列顺序不一样...
(因为本题有一个前置条件,行和列出现的顺序是相同的)
..诶?
也许我们可以简化一下代码!

s = int(input())
table = []
for i in range(s):
    line = list(map(str, input().split()))
    table.append(line)

dict = {} #存储最后的映射结果

#在table中寻找table[1:][k]与table[1:][0]相等的k,那么table[k][k]对应的字母的映射值为0.

#遍历寻找结尾字符为table[k][k]的长度为2的第一个字符串table[m][n],确定table[m][n][0]对应字母的映射值为1.

#如果进制数为2,即n=3,则返回结果

#否则,找到table[m][n][0]在table[0][1:]对应的位置p,找到table[p][p]对应的位置字母的映射值为2

#如果进制数为3,即n=4,则返回结果

#否则,遍历table[1:][p],然后在table[1:][0]中找到table[p][q]对应的位置,确定这个映射值为3,并记录这个值作为下一次在table[1:][0]中寻找的索引,如果还没有确定所有字母,重复上述操作,直到字典长度达到s - 1.

#按照表格中第一行的字母顺序输出结果,并输出进制数

对应的代码如下:

s = int(input())
table = []

for i in range(s):
    line = list(map(str, input().split()))
    table.append(line)

#print(table)

def solve(table):
    n = len(table)
    known_mapping = {}  # 存储映射结果

    # 寻找0的映射
    for i in range(1, n):
        #print(f"i={i}")
        if table[i][1:] == table[0][1:]:
            char = table[i][i][0]
            known_mapping[char] = 0
            break

    # 寻找1的映射
    for i in range(1,n):
        for j in range(1,n):
            if len(table[i][j]) == 2:
                known_mapping[table[i][j][0]] = 1
                break
        if len(known_mapping) == 2:
            break
    
    if n == 3:
        return known_mapping

    # 寻找2的映射,初始化一个next_str
    next_str = table[i][j][0]

    for i in range(1,n):
        if table[0][i] == next_str:
            known_mapping[table[i][i]] = 2
            idx_1 = i
            break
    
    if n == 4:
        return known_mapping

    next_str = table[idx_1][idx_1]

    #寻找3到n-2的映射,range的范围是(3,n-1)
    for value in range(3, n-1):
        for i in range(1, n):
            # 在第1行找到"2",..."n-1"对应的索引
            if table[0][i] == next_str:
                known_mapping[table[idx_1][i]] = value
                next_str = table[idx_1][i]
                break

    return known_mapping

def print_result(known_mapping):
    for i in range(1,s):
        print(f"{table[0][i]}={known_mapping[table[0][i]]}",end=" ")
    print()
    print(s - 1)

def main():
    known_mapping = solve(table)
    #print(known_mapping)
    if len(known_mapping) != s - 1:
        print('ERROR!')
    else:
        print_result(known_mapping)

if __name__ == "__main__":
    main()

这段代码拿到了80分的分数,不过为什么会有一个过不去的测试用例呢...?

Current: 48pp(New!)
Overall: 246pp(+39pp)

读取的是M,期望E

可能是对某个异常情况没有正确处理吧...
为了进一步验证算法正确性,我们需要对Table全部检查一遍,如果输出有错误,那么也会返回error!

s = int(input())
table = []

for i in range(s):
    line = list(map(str, input().split()))
    table.append(line)

#print(table)

def solve(table):
    n = len(table)
    known_mapping = {}  # 存储映射结果

    # 寻找0的映射
    for i in range(1, n):
        #print(f"i={i}")
        if table[i][1:] == table[0][1:]:
            char = table[i][i][0]
            known_mapping[char] = 0
            break

    # 寻找1的映射
    for i in range(1,n):
        for j in range(1,n):
            if len(table[i][j]) == 2:
                known_mapping[table[i][j][0]] = 1
                break
        if len(known_mapping) == 2:
            break
    
    if n == 3:
        return known_mapping

    # 寻找2的映射,初始化一个next_str
    next_str = table[i][j][0]

    for i in range(1,n):
        if table[0][i] == next_str:
            known_mapping[table[i][i]] = 2
            idx_1 = i
            break
    
    if n == 4:
        return known_mapping

    next_str = table[idx_1][idx_1]

    #寻找3到n-2的映射,range的范围是(3,n-1)
    for value in range(3, n-1):
        for i in range(1, n):
            # 在第1行找到"2",..."n-1"对应的索引
            if table[0][i] == next_str:
                known_mapping[table[idx_1][i]] = value
                next_str = table[idx_1][i]
                break

    return known_mapping

def print_result(known_mapping):
    for i in range(1,s):
        print(f"{table[0][i]}={known_mapping[table[0][i]]}",end=" ")
    print()
    print(s - 1)

def cv_int(string,known_mapping):
    if len(string) == 1:
        return known_mapping[string]
    else:
        return known_mapping[string[0]] * (s-1) + known_mapping[string[1]]


def main():
    known_mapping = solve(table)
    #print(known_mapping)
    if len(known_mapping) != s - 1:
        print('ERROR!')
    else:
        #检查原来的表格,验证每个值是否成立
        int_table = []
        for i in range(0,s):
            int_table_line = []
            for j in range(0,s):
                if i == 0 and j == 0:
                    int_table_line.append(0)
                else:
                    int_table_line.append(cv_int(table[i][j],known_mapping))
            int_table.append(int_table_line)
        #print(int_table)
        valid = True
        for i in range(1,s):
            for j in range(1,s):
                valid = valid and int_table[i][0] + int_table[0][j] == int_table[i][j]
        if valid:
            print_result(known_mapping)
        else:
            print('ERROR!')

if __name__ == "__main__":
    main()

那么我们也终于通过了这个题目~!

Current:70pp(+22pp)
Overall:267pp(+21pp)

洛谷P1008 三连击

Star Rating:2.64


题目背景

本题为提交答案题,您可以写程序或手算在本机上算出答案后,直接提交答案文本,也可提交答案生成程序。

题目描述

\(1, 2, \ldots , 9\)\(9\) 个数分成 \(3\) 组,分别组成 \(3\) 个三位数,且使这 \(3\) 个三位数构成 \(1 : 2 : 3\) 的比例,试求出所有满足条件的 \(3\) 个三位数。

输入格式

输出格式

若干行,每行 \(3\) 个数字。按照每行第 \(1\) 个数字升序排列。

样例 #1

样例输入 #1

样例输出 #1

192 384 576
* * *
...

* * *
(剩余部分不予展示)

总感觉这道题...应该只是考查我们的理解能力吧..
我大概知道是什么意思了...

感觉有一种很快的方法就是把答案直接列出来,然后print > <
不过...我们要如何才能知道所有的答案呢?

三个数是1:2:3,因此第一个数必须是一个小于333的数字;
要同时出现1~9这9个数字,因此第一个数中必须出现三个不同的数字,而且不能出现0
这样就确定了第一个数一定在123~329之间

基于此方法,我们设计了一个初版的基于穷举的程序把它们都找出来

first_int = 123

while first_int <= 329:
    dict = {}
    for char in str(first_int):
        dict[char] = 1
    for char in str(first_int * 2):
        dict[char] = 1
    for char in str(first_int * 3):
        dict[char] = 1
    if len(dict) == 9:
        print(first_int, first_int * 2, first_int * 3)
    first_int += 1

看起来程序输出了正确结果~

192 384 576
219 438 657
267 534 801
273 546 819
327 654 981

一共有5组解!
提交一下试试~不过...发现一个不对的解...因为中间这组把0也映射进去了!
微调了一下!

first_int = 123

while first_int <= 329:
    dict = {}
    for char in str(first_int):
        dict[char] = 1
    for char in str(first_int * 2):
        dict[char] = 1
    for char in str(first_int * 3):
        dict[char] = 1
    if len(dict) == 9 and '0' not in dict: #如果0不在字典中
        print(first_int, first_int * 2, first_int * 3)
    first_int += 1

这样的话就只有4组解了!

192 384 576
219 438 657
273 546 819
327 654 981

Yes, Accepted啦~

Current: 24pp(New!)
Overall: 284pp(+14pp)

posted @ 2024-01-18 12:03  Yuzu_OvO(喵露露版)  阅读(32)  评论(0)    收藏  举报