破译密码
描述
牛牛收到了一个任务,任务要求牛牛破译一个密码。牛牛将被给予两个字符串s1和s2,均由四个小写字母构成。需要破译的密码为从s1变换到s2最少需要的变换次数。变换的方式是这样:每次变换可以选择当前字符串中的一个位置,然后剩下的三个位置的字符从左到右分别加上2,3,5,若是超出'z',则重新从'a'开始,例如:对于字符串"abcd",我们选择'c'的位置进行变换,则变换之后的字符串为"ceci";对于字符串"qyzr",我们选择'r'位置进行变换,则变换之后的字符串为"sber"。
示例
输入:"aaaa","ccgk"
返回值:2
说明:第一次变换选择第一个'a',变成"acdf",第二次变换选择第二个'c',变成"ccgk",故答案为2
方法一
图解+思路分析
根据图形可以看到本题是一个四叉树,每个节点为字符串中的一个位置,然后剩下的三个位置的字符从左到右分别加上2,3,5变换后的字符串,因此可以利用广度优先搜索,设置队列存放字符串,设置标志数组标记字符串是否访问过,那么s1变换到s2最少需要的变换次数就是四叉树的层数
核心代码
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最终的答案 * @param s1 string 表示初始的字符串 * @param s2 string 表示目标的字符串 * @return int */ Set<String> index = new HashSet();//不会有重复元素 Queue<String> q = new LinkedList();//增加删除的效率高 public int solve(String s1, String s2) { if(s1 == s2) return 0; q.offer(s1); int count = 0; while(!q.isEmpty()) { int size = q.size(); for(int j=size;j>0;j--) { String cur = q.poll(); if(cur.equals(s2)) return count; if(index.contains(cur)) continue;//当前字符串已经访问过进行下次循环 else index.add(cur);//如果没有访问过则进行标记 for(int i = 0; i < 4; i++) { String c=convert(cur,i); if(!index.contains(c))//变换后的字符串未在index中 q.offer(c);//变换后的字符串 加入到队列中 } } count++; } return count; } public String convert(String s, int i) { char[] str = s.toCharArray(); int[] add = new int[] {2,3,5}; int index = 0; for(int j = 0; j < 4; j++) { if(j == i) continue; str[j] = (char)('a' + (str[j] - 'a' + add[index]) % 26); index++; } return String.valueOf(str); } }
复杂度分析
- 时间复杂度:需要访问队列中的字符串,总的字符串为$O(26^4)$,时间复杂度为$O(26^4)$,因此时间复杂度为$O(1)$
- 空间复杂度:需要使用队列和标记数组,空间复杂度为$O(26^4)$,因此总的空间复杂度为$O(1)$
方法二
图解+思路分析
根据方法一中的二叉树,我们将其转换为数字表示,可以看到固定一个位置,其他位置可以进行加法操作,因此有四种情况,分别为:
- 固定第一个位置,设置固定次数为$i$,后面三个位置的字符加2、3、5,那么表示为$s1[1]+=2*i、s1[2]+=3*i、s1[3]+=5*i$
- 固定第二个位置,设置固定次数为$j$,其余三个位置从左至右的字符加2、3、5,那么表示为$s1[0]+=2*j、s1[2]+=3*j、s1[3]+=5*j$
- 固定第三个位置,设置固定次数为$k$,其余三个位置从左至右的字符加2、3、5,那么表示为$s1[0]+=2*k、s1[1]+=3*k、s1[3]+=5*k$
- 固定第四个位置,设置固定次数为$l$,其余三个位置从左至右的字符加2、3、5,那么表示为$s1[0]+=2*l、s1[1]+=3*l、s1[2]+=5*l$
因此四层循环得到变换后的字符串为:
- $s1[0]+=2*j+2*k+2*l$
- $s1[1]+=2*i+3*k+3*l$
- $s1[2]+=3*i+3*j+5*l$
- $s1[3]+=5*i+5*j+5*k$
因此每次比较变换后的字符串与字符串s2,如果一致则更新变换的次数,变换的次数即为$(i+k+j+l)$,最后输出最小的变换次数
核心代码
class Solution { public: /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最终的答案 * @param s1 string 表示初始的字符串 * @param s2 string 表示目标的字符串 * @return int */ int solve(string s1, string s2) { // write code here if(s1==s2) return 0; vector<int> distance(4,0); for(int i=0;i<4;i++) { distance[i]=(s2[i] - s1[i] + 26) % 26; } int count = 26 * 4; //四层嵌套循环 for(int i = 0; i < 26; i++) for(int j = 0; j < 26; j++) for(int k = 0; k < 26; k++) for(int l = 0; l < 26; l++)//变换后的字符串数 if(distance[0] == (2*j + 2*k + 2*l) % 26 && distance[1] == (2*i + 3* k + 3*l) % 26 && distance[2] == (3*i + 3*j + 5*l) % 26 && distance[3] == 5*(i + j + k) % 26) count = min(count, i + j + k + l); return count; } };
复杂度分析
- 时间复杂度:每个位置的字符选择有26种,每个字符串一共有4个字符,四层循环遍历,每一层循环遍历的次数为26,总的时间复杂度为$O(26^4)$,因此时间复杂度为$O(1)$
- 空间复杂度:空间复杂度为$O(1)$