题意整理
- 给定字符串s1和s2,均由4个字母组成。
- 求s1变换到s2,至少要变换多少次。
- 变换的规则是,固定其中一位,其他三位从左到右依次加上2、3和5,如果超过'z',重新从'a'开始。
方法一(BFS)
1.解题思路
- 初始化一个队列和set,队列用于存储每次变化的字符串和当前变换的次数,set用于去重,如果已经在队列,则不会再入队。
- 每次判断当前字符串是否等于目标字符串,如果是,则直接返回变换次数。如果不是,则寻找当前字符串可能变换出的所有字符串,加入到队列。
- 寻找可能的字符串按照题目规则进行模拟。
动图展示:
2.代码实现
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最终的答案 * @param s1 string 表示初始的字符串 * @param s2 string 表示目标的字符串 * @return int */ public int solve (String s1, String s2) { //初始化队列,队列里的元素由当前String和步数step组成 LinkedList<Pair> queue=new LinkedList<>(); queue.offer(new Pair(s1,0)); //初始化set,用于去重 Set<String> set=new HashSet<>(); set.add(s1); while(!queue.isEmpty()){ //取出当前元素 Pair pair=queue.poll(); String curString=pair.s; int curStep=pair.step; //如果等于s2,说明破译了密码,直接返回step if(curString.equals(s2)){ return curStep; } //找到经过变换后可能存在的所有字符串 String[] next=getNext(curString); for(String ss:next){ //如果不在set,则放入队列 if(!set.contains(ss)){ queue.offer(new Pair(ss,curStep+1)); set.add(ss); } } } return -1; } //找到经过变换后可能存在的所有字符串 private String[] getNext(String s){ String alpha="abcdefghijklmnopqrstuvwxyz"; String[] res=new String[4]; for(int i=0;i<4;i++){ StringBuilder sb=new StringBuilder(); //固定0位置时,1、2、3位置分别加2、3、5 if(i==0){ int index1=(s.charAt(1)-'a'+2)%26; int index2=(s.charAt(2)-'a'+3)%26; int index3=(s.charAt(3)-'a'+5)%26; sb.append(s.charAt(0)) .append(alpha.charAt(index1)) .append(alpha.charAt(index2)) .append(alpha.charAt(index3)); res[i]=sb.toString(); } //固定1位置时,0、2、3位置分别加2、3、5 if(i==1){ int index0=(s.charAt(0)-'a'+2)%26; int index2=(s.charAt(2)-'a'+3)%26; int index3=(s.charAt(3)-'a'+5)%26; sb.append(alpha.charAt(index0)) .append(s.charAt(1)) .append(alpha.charAt(index2)) .append(alpha.charAt(index3)); res[i]=sb.toString(); } //固定2位置时,0、1、3位置分别加2、3、5 if(i==2){ int index0=(s.charAt(0)-'a'+2)%26; int index1=(s.charAt(1)-'a'+3)%26; int index3=(s.charAt(3)-'a'+5)%26; sb.append(alpha.charAt(index0)) .append(alpha.charAt(index1)) .append(s.charAt(2)) .append(alpha.charAt(index3)); res[i]=sb.toString(); } //固定3位置时,0、1、2位置分别加2、3、5 if(i==3){ int index0=(s.charAt(0)-'a'+2)%26; int index1=(s.charAt(1)-'a'+3)%26; int index2=(s.charAt(2)-'a'+5)%26; sb.append(alpha.charAt(index0)) .append(alpha.charAt(index1)) .append(alpha.charAt(index2)) .append(s.charAt(3)); res[i]=sb.toString(); } } return res; } } //当前String和步数组成的类 class Pair{ String s; int step; Pair(String s,int step){ this.s=s; this.step=step; } }
3.复杂度分析
- 时间复杂度:总共可能有个不同的字符串,所以时间复杂度是。
- 空间复杂度:最坏情况下,需要额外大小的队列,所以空间复杂度是。
方法二(枚举)
1.解题思路
- 首先统计每个位置的变换次数。
- 然后四层循环,枚举所有位置的固定次数,如果变换次数等于对应位置需要的变换次数,则对应的固定次数就是正确的破解方式,而固定次数之和即是所求的变换次数,取最小值即可。
举例说明:
比如固定j位置时,i位置一定是加2,固定k位置,i也必定是加2,固定l位置时,i还是加2,所以需要满足这个等式,才能到达目标字符串。再比如固定i时,j位置需要加2,固定k时,j位置需要加3,固定l时,j位置还是需要加3,所以需要满足这个等式。同理还需要满足和。由于超过'z'需要重新从'a'开始,所以每次要对26取余。
2.代码实现
import java.util.*; public class Solution { /** * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可 * * 返回最终的答案 * @param s1 string 表示初始的字符串 * @param s2 string 表示目标的字符串 * @return int */ public int solve (String s1, String s2) { //初始化变换数组 int[] delta=new int[4]; for(int i=0;i<4;i++){ //统计每一位总共需要变换多少次 delta[i]=(s2.charAt(i)-s1.charAt(i)+26)%26; } //用于记录最小变换次数 int res=100; //枚举每个位置的固定次数 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((2*(j+k+l))%26==delta[0] &&(2*i+3*(k+l))%26==delta[1] &&(3*(i+j)+5*l)%26==delta[2] &&(5*(i+j+k))%26==delta[3]){ //4个位置固定次数之和,即是总变换次数 res=Math.min(res,i+j+k+l); } } } } } return res; } }
3.复杂度分析
- 时间复杂度:总共可能有个不同的字符串,所以时间复杂度是。
- 空间复杂度:最坏情况下,需要额外大小的队列,所以空间复杂度是。