#P2279. 第1题-无线基站名字相似度
          
                        
                                    
                      
        
              - 
          
          
                      2000ms
            
          
                      Tried: 478
            Accepted: 100
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              华为
                                
            
                        
              时间 :2024年10月9日-留学生
                              
                      
          
 
- 
                        算法标签>动态规划          
 
第1题-无线基站名字相似度
题目大意
给定两个字符串 a 和 b,可以通过删除、插入和替换三种操作将字符串 a 转换为字符串 b。要求计算将字符串 a 转换为字符串 b 的最小代价
思路
该问题类似于编辑距离问题,因此可以考虑使用动态规划来解决。我们定义 dp[i][j] 表示将字符串 a 的前 i 个字符转换为字符串 b 的前 j 个字符的最小代价。
边界情况 当 i 或 j 为 0 时,表示其中一个字符串为空,此时只能通过插入或删除操作将一个字符串转换为另一个。每次插入或删除的代价是 3,因此 dp[i][j] 的值为 (i + j) * 3。
状态转移 状态转移可以分为三种情况:
删除操作:将 a[i] 删除,那么转换成字符串 b 的代价为 dp[i-1][j] + 3。
插入操作:向 a 中插入一个字符 b[j],那么转换代价为 dp[i][j-1] + 3。
替换操作:将 a[i] 替换为 b[j],代价为 dp[i-1][j-1] + cal(a[i], b[j])。这里 cal 函数用于计算字符 a[i] 和 b[j] 的替换代价。
计算替换代价 cal cal(a, b) 是用于计算两个字符替换代价的函数,规则如下: 1.如果两个字符相同,替换代价为 0; 2.如果两个字符都在字符串 s1("wirel@com")中,或者都在字符串 s2("#hfv#gbts")中,则替换代价为 1; 3.如果一个字符在 s1 中,另一个字符在 s2 中,替换代价为 2; 4.如果两个字符都不在上述字符集中,替换代价为 3;
题目分析
本题的目标是计算两个基站名字的相似度,可以通过动态规划方法来解决,类似于编辑距离问题。在编辑距离问题中,允许对字符串进行三种操作:增、删、改,每种操作都有不同的代价。题目要求通过最少的操作将一个基站名字转换为另一个基站名字,并输出最小得分。
具体来说:
- 插入一个字符,代价是 3 分;
 - 删除一个字符,代价也是 3 分;
 - 替换一个字符,替换的代价根据给定的分组规则不同,可能是 1 分、2 分或 3 分。
 
解决思路
动态规划是解决此类问题的有效方法。我们可以定义 dp[i][j] 表示将字符串 a 的前 i 个字符转换为字符串 b 的前 j 个字符的最小代价。
边界条件
- 当 
i为 0 时,表示a是空字符串,此时只能通过插入操作将b的前j个字符依次插入进来,代价为j * 3。 - 当 
j为 0 时,表示b是空字符串,此时只能通过删除操作将a的前i个字符依次删除,代价为i * 3。 
状态转移方程
对于任意的 dp[i][j],可以通过三种操作转移得到:
- 删除操作:将 
a[i]删除,代价为dp[i-1][j] + 3; - 插入操作:向 
a中插入一个字符b[j],代价为dp[i][j-1] + 3; - 替换操作:将 
a[i]替换为b[j],代价为dp[i-1][j-1] + cal(a[i], b[j]),其中cal是计算替换代价的函数。 
替换代价的计算(函数 cal)
- 如果两个字符相同,替换代价为 
0; - 如果两个字符都在组 
s1("wirel@com")中,或者都在组s2("#hfv#gbts")中,替换代价为1; - 如果一个字符在组 
s1,另一个字符在组s2,替换代价为2; - 如果两个字符都不在任何组内,替换代价为 
3。 
动态规划表的初始化
首先,我们将 dp 数组初始化为 (i+j)*3,表示在最坏的情况下,通过删除和插入操作将两个字符串互相转换的代价。
动态规划的填表过程
从 dp[1][1] 开始,通过上面三种操作的最小代价来更新 dp[i][j]。对于每个位置 (i, j),我们根据前面已经计算好的状态,选择代价最小的一种操作来更新。
复杂度分析
- 时间复杂度:由于我们使用了双重循环填表,每次操作的时间复杂度是常数级别,因此总的时间复杂度为 O(n * m),其中 
n是字符串a的长度,m是字符串b的长度。 - 空间复杂度:使用了一个大小为 
2005 x 2005的二维数组dp,因此空间复杂度为 O(n * m)。 
代码如下
cpp
#include <bits/stdc++.h>
using namespace std;
string a, b;
int n, m;
// dp 表,用于存储从 a 到 b 的最小转换代价
int dp[2005][2005];
// 字符集,用于判断字符之间的替换代价
string s1 = "wirel@com", s2 = "#hfv#gbts";
// 计算字符 a 和 b 之间的替换代价
int cal(char a, char b) {
	// 如果两个字符相等,代价为 0
	if (a == b) return 0;
	// 如果两个字符都在 s1 中或都在 s2 中,代价为 1
	if (s1.find(a) != string::npos && s1.find(b) != string::npos) {
		return 1;
	}
	else if (s2.find(a) != string::npos && s2.find(b) != string::npos) {
		return 1;
	}
	// 如果一个字符在 s1 中,另一个字符在 s2 中,代价为 2
	else if (s2.find(a) != string::npos && s1.find(b) != string::npos) {
		return 2;
	}
	else if (s1.find(a) != string::npos && s2.find(b) != string::npos) {
		return 2;
	}
	// 其他情况下,代价为 3
	else {
		return 3;
	}
}
int main() {
	cin >> a >> b;
	n = a.length();
	m = b.length();
	a = ' ' + a;
	b = ' ' + b;
	
	// 初始化 dp 数组
	for (int i = 0; i <= n; i++) {
		for (int j = 0; j <= m; j++) {
			// 如果 i 和 j 都为 0,即两个空字符串之间的代价为 0
			if (!i && !j)
				dp[i][j] = 0;
			// 如果 i 为 0,表示将空字符串变为 b 的前 j 个字符,代价为 j * 3(即插入 j 个字符)
			else if (!i)
				dp[i][j] = j * 3;
			// 如果 j 为 0,表示将 a 的前 i 个字符变为空字符串,代价为 i * 3(即删除 i 个字符)
			else if (!j)
				dp[i][j] = i * 3;
			// 否则初始化为 (i+j) * 3
			else
				dp[i][j] = (i + j) * 3;
		}
	}
	
	// 动态规划过程
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			// 计算 dp[i][j] 时,首先考虑插入和删除操作,选择三种操作中的最小值并加上代价 3
			dp[i][j] = min({dp[i - 1][j], dp[i][j - 1], dp[i][j]}) + 3;
			// 然后考虑替换操作,使用 cal 函数计算替换代价
			dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + cal(a[i], b[j]));
		}
	}
	
	// 输出将字符串 a 转换为字符串 b 的最小代价
	cout << dp[n][m];
}
python
# 字符集,用于判断字符之间的替换代价
s1 = "wirel@com"
s2 = "#hfv#gbts"
# 计算字符 a 和 b 之间的替换代价
def cal(a, b):
    # 如果两个字符相等,代价为 0
    if a == b:
        return 0
    # 如果两个字符都在 s1 中或都在 s2 中,代价为 1
    if (a in s1 and b in s1) or (a in s2 and b in s2):
        return 1
    # 如果一个字符在 s1 中,另一个字符在 s2 中,代价为 2
    elif (a in s1 and b in s2) or (a in s2 and b in s1):
        return 2
    # 其他情况下,代价为 3
    else:
        return 3
def main():
    # 输入两个字符串
    a = input().strip()
    b = input().strip()
    # 获取字符串的长度
    n = len(a)
    m = len(b)
    # 初始化 dp 数组,大小为 (n+1) x (m+1)
    dp = [[0] * (m + 1) for _ in range(n + 1)]
    # 初始化 dp 数组
    for i in range(n + 1):
        for j in range(m + 1):
            # 如果 i 和 j 都为 0,即两个空字符串之间的代价为 0
            if i == 0 and j == 0:
                dp[i][j] = 0
            # 如果 i 为 0,表示将空字符串变为 b 的前 j 个字符,代价为 j * 3(即插入 j 个字符)
            elif i == 0:
                dp[i][j] = j * 3
            # 如果 j 为 0,表示将 a 的前 i 个字符变为空字符串,代价为 i * 3(即删除 i 个字符)
            elif j == 0:
                dp[i][j] = i * 3
            # 否则初始化为 (i+j) * 3
            else:
                dp[i][j] = (i + j) * 3
    # 动态规划过程
    for i in range(1, n + 1):
        for j in range(1, m + 1):
            # 计算 dp[i][j] 时,首先考虑插入和删除操作,选择三种操作中的最小值并加上代价 3
            dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i][j]) + 3
            # 然后考虑替换操作,使用 cal 函数计算替换代价
            dp[i][j] = min(dp[i][j], dp[i - 1][j - 1] + cal(a[i - 1], b[j - 1]))
    # 输出将字符串 a 转换为字符串 b 的最小代价
    print(dp[n][m])
if __name__ == "__main__":
    main()
java
import java.util.Scanner;
public class Main {
    static String s1 = "wirel@com";  // 用于判断字符替换代价的字符集 s1
    static String s2 = "#hfv#gbts";  // 用于判断字符替换代价的字符集 s2
    static int[][] dp = new int[2005][2005];  // dp 数组,存储最小转换代价
    // 计算字符 a 和 b 之间的替换代价
    public static int cal(char a, char b) {
        // 如果两个字符相等,代价为 0
        if (a == b) return 0;
        // 如果两个字符都在 s1 中或都在 s2 中,代价为 1
        if (s1.indexOf(a) != -1 && s1.indexOf(b) != -1) {
            return 1;
        } else if (s2.indexOf(a) != -1 && s2.indexOf(b) != -1) {
            return 1;
        }
        // 如果一个字符在 s1 中,另一个字符在 s2 中,代价为 2
        else if (s2.indexOf(a) != -1 && s1.indexOf(b) != -1) {
            return 2;
        } else if (s1.indexOf(a) != -1 && s2.indexOf(b) != -1) {
            return 2;
        }
        // 其他情况下,代价为 3
        else {
            return 3;
        }
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 输入两个字符串
        String a = sc.nextLine();
        String b = sc.nextLine();
        int n = a.length();
        int m = b.length();
        // 初始化 dp 数组
        for (int i = 0; i <= n; i++) {
            for (int j = 0; j <= m; j++) {
                // 如果 i 和 j 都为 0,即两个空字符串之间的代价为 0
                if (i == 0 && j == 0) {
                    dp[i][j] = 0;
                }
                // 如果 i 为 0,表示将空字符串变为 b 的前 j 个字符,代价为 j * 3(即插入 j 个字符)
                else if (i == 0) {
                    dp[i][j] = j * 3;
                }
                // 如果 j 为 0,表示将 a 的前 i 个字符变为空字符串,代价为 i * 3(即删除 i 个字符)
                else if (j == 0) {
                    dp[i][j] = i * 3;
                }
                // 否则初始化为 (i+j) * 3
                else {
                    dp[i][j] = (i + j) * 3;
                }
            }
        }
        // 动态规划过程
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= m; j++) {
                // 计算 dp[i][j] 时,首先考虑插入和删除操作,选择三种操作中的最小值并加上代价 3
                dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i][j]) + 3;
                // 然后考虑替换操作,使用 cal 函数计算替换代价
                dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + cal(a.charAt(i - 1), b.charAt(j - 1)));
            }
        }
        // 输出将字符串 a 转换为字符串 b 的最小代价
        System.out.println(dp[n][m]);
    }
}
        题目内容
在无线通信中,每一个基站都会有一个名字,一般同一个区域的基站名字会比较相近,可以通过判断两个基站名字相似程度来识别它是否在同一区域。通过基站间的名字字符串之间转换,来判断两个基站名字的相似度。字符之间的转换只有3种操作(增,删,改):
1、增:插入一个字符;
2、删:删除一个字符;
3、改: 替换一个字符;
并且以上3种操作分别对应不同的打分项,得分越低,说明相似度越高。
1)增 3分; 2) 删 3分; 3)替换,字符在以下两分组内同一组的得1分,分别在两个组的得2分,其他得3分
组1{′w′,′i,′r′,′e′,′l,′@′,′c′,′o′,′m′}
组2{′h,′f′,′v′,'#',′g′,′b′,′t′,′s′}
给定两个无线基站名字,请识别出相似度(即字符转换操作的最低得分)。
输入描述
输入两个名字字符串
注:字符串长度范围[1,2000]。
输出描述
输出两者之间的相似度
样例1
输入
chu
xu		
输出
6
说明
基站名字1为“chu",基站名字2“xu”,进行这两个基站名字间的字符转换步骤:
第1步c替换为x:chu−>xhu,c在组1内,x不在,所以得分3
第2步 h删除: xhu−>xu,得分3
总得分为6,所以相似度为6,输出6
样例2
输入
jinhailu
jinzhanglu
输出
8
说明
基站名字1为“jinhailu",基站名字2“jinzanglu",进行这两个基站名字间的字符转换步骤:
路径1:
第1步 h替换为z:jinhailu−>jinzailu,h在组2,z不在,所以得分为3
第2步 i替换为n:jinzailu−>jinzanlu,i在组1,n不在,所以得分为3
第3步 插入g:jinzanlu−>jinzanglu,得分为3
总得分为7
路径2:
第1步 h替换为z:jinhailu−>jinzailu,h在组2,z不在,所以得分为3
第2步 插入n:jinzailu−>jinzanilu,插入操作得分为3
第3步 i替换为g:jinzanilu−>jinzanglu,i在组1,g在组2,所以得分为2
总得分为8
所以路径2,得分少,选择路径2,输出8