Open In App

Minimum clicks to convert string X to Y

Given a starting string X of 3 characters, finishing string Y of 3 characters and an array of forbidden strings. The task is to find the minimum number of clicks to reach Y from X. 

Rules:

Note: If the starting string X is also a possible combination of forbidden characters, then also the result should be -1.

Input: X = “znw”, Y = “lof”, N = 4 (no of forbidden strings)
forbidden =
{ ” qlb “, ” jcm “, ” mhoq ” },
{ ” azn “, ” piy “, ” vj ” },
{ ” by “, ” oy “, ” ubo ” },
{ ” jqm “, ” f “, ” ej ” }
Output: Minimum no of clicks required is 22
Explanation: Since no combination out of the given forbidden strings forms the final string Y, so the string Y becomes valid.

Thus minimum number of clicks required is computed as:
z – l in forward direction by 12 clicks
z – l in backward direction by 14 clicks
n – o in forward direction by 1 click
n – o in backward direction by 25 clicks
w – f in forward direction by 9 clicks
w – f in backward direction by 17 clicks

Total minimum clicks = 12 + 1 + 9 = 22.

Input: X = “bdb”, Y = “xxx”, N = 1 (no of forbidden strings)
forbidden = { ” ax “, ” acx “, ” bxy ” }
Output: Minimum no of clicks required is -1
Explanation: As “xxx” is a possible combination of forbidden characters, therefore it is not possible to reach Y from X.

Approach:

Use BFS(Breadth First Search) with certain modifications in order to get the min number of clicks, bypassing the forbidden strings.     

  1. Since each of the 3 positions can contain alphabets, hence create a 3D visited array of dimensions 26 * 26 * 26 in order to traverse the word states.
  2. To visualize each of the restricted words, create another 3D array of dimensions 26 * 26 * 26 to keep track of the words which must never be visited in the traversal.
  3. Since each of the 3 characters changes in a circular manner i.e, letters will change in a circular fashion on each click, there is a need to take care to modulo 26 every time the next letter is reached. 
  4. Let current state of word be [X Y Z]. Then on a single click movement, the 6 states are possible.
  5. Hence create 3 utility arrays dx, dy, dz to keep the traversal a streamlined process. Store each word state in a struct having 4 fields namely a, b, c (each of the 3 characters) and distance from the starting words.
  6. The six stare are following: 

 [ X+1 Y Z ], [ X-1 Y Z ], [ X Y+1 Z ], [ X Y-1 Z ], [ X Y Z+1 ], [ X Y Z-1 ].

Below is the implementation of above approach:




// C++ code for above program.
#include <bits/stdc++.h>
using namespace std;
#define int long long int
 
// each node represents a word state
struct node {
    int a, b, c;
    // dist from starting word X
    int dist;
};
 
// 3D visited array
bool visited[26][26][26];
 
// 3D restricted array
bool restricted[26][26][26];
 
// utility arrays for single step
// traversal in left and right
int dx[6] = { 1, -1, 0, 0, 0, 0 };
int dy[6] = { 0, 0, 1, -1, 0, 0 };
int dz[6] = { 0, 0, 0, 0, 1, -1 };
 
// function to find the
// minimum clicks.
void solve(string start,
        string end, int qx,
        const vector<vector<string> >& forbidden)
{
 
    memset(visited, 0,
        sizeof(visited));
    memset(restricted, 0,
        sizeof(restricted));
 
    for (auto vec : forbidden) {
 
        string a = vec[0];
        string b = vec[1];
        string c = vec[2];
 
        for (auto x : a)
            for (auto y : b)
                for (auto z : c) {
 
                    // each invalid word is
                    // decoded and marked as
                    // restricted = true.
                    restricted[x - 'a']
                            [y - 'a']
                            [z - 'a']
                        = true;
                }
    }
 
    // starting and ending letter a
    int sa = start[0] - 'a';
    int ea = end[0] - 'a';
 
    // starting and ending letter b
    int sb = start[1] - 'a';
    int eb = end[1] - 'a';
 
    // starting and ending letter c
    int sc = start[2] - 'a';
    int ec = end[2] - 'a';
 
    if (restricted[sa][sb][sc]
        or restricted[ea][eb][ec]) {
 
        // check if starting word
        // or finishing word is
        // restricted or not
        cout << -1 << endl;
 
        return;
    }
 
    // queue of nodes for BFS
    queue<node> q;
 
    // initial starting word pushed in
    // queue. dist = 0 for starting word
    q.push({ sa, sb, sc, 0 });
 
    // mark as visited
    visited[sa][sb][sc] = true;
 
    while (!q.empty()) {
        node x = q.front();
        q.pop();
 
        // final destination reached condition
        if (x.a == (end[0] - 'a')
            and x.b == (end[1] - 'a')
            and x.c == (end[2] - 'a')) {
 
            cout << x.dist
                << endl;
            return;
        }
 
        int DIST = x.dist;
        for (int i = 0; i < 6; i++) {
 
            // mod 26 for circular letter sequence
 
            // next letter for a
            int A = (x.a + dx[i] + 26) % 26;
 
            // next letter for b
            int B = (x.b + dy[i] + 26) % 26;
 
            // next letter for c
            int C = (x.c + dz[i] + 26) % 26;
 
            if (!restricted[A][B][C]
                and !visited[A][B][C]) {
 
                // if a valid word state,
                // push into queue
                q.push({ A, B, C, DIST + 1 });
                visited[A][B][C] = true;
            }
        }
    }
 
    // reach here if not possible
    // to reach final word Y
    cout << -1 << endl;
}
 
// Driver Code
signed main()
{
    // starting string
    string X = "znw";
 
    // final string
    string Y = "lof";
 
    // no of restricting word vectors
    int N = 4;
 
    vector<vector<string> > forbidden
        = { { "qlb", "jcm", "mhoq" },
            { "azn", "piy", "vj" },
            { "by", "oy", "ubo" },
            { "jqm", "f", "ej" } };
 
    solve(X, Y, N, forbidden);
    return 0;
}




// Java code addition
 
import java.io.*;
import java.util.*;
 
public class GFG {
 
    // each node represents a word state
    static class Node {
        int a, b, c;
        // dist from starting word X
        int dist;
 
        public Node(int a, int b, int c, int dist) {
            this.a = a;
            this.b = b;
            this.c = c;
            this.dist = dist;
        }
    }
 
    // 3D visited array
    static boolean[][][] visited = new boolean[26][26][26];
 
    // 3D restricted array
    static boolean[][][] restricted = new boolean[26][26][26];
 
    // utility arrays for single step
    // traversal in left and right
    static int[] dx = { 1, -1, 0, 0, 0, 0 };
    static int[] dy = { 0, 0, 1, -1, 0, 0 };
    static int[] dz = { 0, 0, 0, 0, 1, -1 };
 
    // function to find the minimum clicks.
    public static void solve(String start, String end, int qx,
            final ArrayList<ArrayList<String>> forbidden) {
 
        Arrays.fill(visited[0][0], false);
        Arrays.fill(restricted[0][0], false);
 
        for (ArrayList<String> vec : forbidden) {
 
            String a = vec.get(0);
            String b = vec.get(1);
            String c = vec.get(2);
 
            for (char x : a.toCharArray())
                for (char y : b.toCharArray())
                    for (char z : c.toCharArray()) {
 
                        // each invalid word is
                        // decoded and marked as
                        // restricted = true.
                        restricted[x - 'a']
                                [y - 'a']
                                [z - 'a']
                            = true;
                    }
        }
 
        // starting and ending letter a
        int sa = start.charAt(0) - 'a';
        int ea = end.charAt(0) - 'a';
 
        // starting and ending letter b
        int sb = start.charAt(1) - 'a';
        int eb = end.charAt(1) - 'a';
 
        // starting and ending letter c
        int sc = start.charAt(2) - 'a';
        int ec = end.charAt(2) - 'a';
 
        if (restricted[sa][sb][sc]
            || restricted[ea][eb][ec]) {
 
            // check if starting word
            // or finishing word is
            // restricted or not
            System.out.println(-1);
 
            return;
        }
 
        // queue of nodes for BFS
        Queue<Node> q = new LinkedList<Node>();
 
        // initial starting word pushed in
        // queue. dist = 0 for starting word
        q.add(new Node(sa, sb, sc, 0));
 
        // mark as visited
        visited[sa][sb][sc] = true;
 
        while (!q.isEmpty()) {
            Node x = q.poll();
 
            // final destination reached condition
            if (x.a == (end.charAt(0) - 'a')
                && x.b == (end.charAt(1) - 'a')
                && x.c == (end.charAt(2) - 'a')) {
 
                System.out.println(x.dist);
                return;
            }
 
            int DIST = x.dist;
            for (int i = 0; i < 6; i++) {
 
                // mod 26 for circular letter sequence
 
                // next letter for a
                int A = (x.a + dx[i] + 26) % 26;
 
                // next letter for b
                int B = (x.b + dy[i] + 26) % 26;
 
                // next letter for c
                int C = (x.c + dz[i] + 26) % 26;
 
                if (!restricted[A][B][C]
                    && !visited[A][B][C]) {
 
                    // if a valid word state,
                    // push into queue
                    q.add(new Node(A, B, C, DIST + 1 ));
                    visited[A][B][C] = true;
                }
            }
        }
 
        // reach here if not possible
        // to reach final word Y
        System.out.println(-1);
    }
 
    // Driver Code
    public static void main(String[] args)
    {
        // starting string
        String X = "znw";
 
        // final string
        String Y = "lof";
 
        // no of restricting word vectors
        int N = 4;
 
        ArrayList<ArrayList<String>> forbidden = new ArrayList<ArrayList<String>>(3);
 
        // Adding elements to the 2D ArrayList
        forbidden.add(new ArrayList<String>(Arrays.asList("qlb", "jcm", "mhoq" )));
        forbidden.add(new ArrayList<String>(Arrays.asList("azn", "piy", "vj")));
        forbidden.add(new ArrayList<String>(Arrays.asList("by", "oy", "ubo")));
        forbidden.add(new ArrayList<String>(Arrays.asList( "jqm", "f", "ej")));
        solve(X, Y, N, forbidden);
    }
     
}
 
// The code is contributed by Arushi Goel.




# Python code for above program.
from collections import deque
 
dx = [1, -1, 0, 0, 0, 0]
dy = [0, 0, 1, -1, 0, 0]
dz = [0, 0, 0, 0, 1, -1]
 
# each node represents a word state
class Node:
    def __init__(self, a, b, c, dist):
        self.a = a
        self.b = b
        self.c = c
        # dist from starting word X
        self.dist = dist
 
# function to find the
# minimum clicks.
def solve(start, end, qx, forbidden):
    visited = [[[False for _ in range(26)]
                for _ in range(26)]
               for _ in range(26)]
    restricted = [[[False for _ in range(26)]
                   for _ in range(26)]
                  for _ in range(26)]
 
    for a, b, c in forbidden:
        for x in a:
            for y in b:
                for z in c:
                    # each invalid word is
                    # decoded and marked as
                    # restricted = true.
                    restricted[ord(x) - ord('a')][ord(y) -
                                                  ord('a')][ord(z) - ord('a')] = True
 
    # starting and ending letter a
    sa, ea = ord(start[0]) - ord('a'), ord(end[0]) - ord('a')
     
    # starting and ending letter b
    sb, eb = ord(start[1]) - ord('a'), ord(end[1]) - ord('a')
     
    # starting and ending letter c
    sc, ec = ord(start[2]) - ord('a'), ord(end[2]) - ord('a')
 
    if restricted[sa][sb][sc] or restricted[ea][eb][ec]:
        # check if starting word
        # or finishing word is
        # restricted or not
        print(-1)
        return
 
    q = deque()
     
    # initial starting word pushed in
    # queue. dist = 0 for starting word
    q.append(Node(sa, sb, sc, 0))
     
    # mark as visited
    visited[sa][sb][sc] = True
 
    while q:
        x = q.popleft()
        # final destination reached condition
        if x.a == ea and x.b == eb and x.c == ec:
            print(x.dist)
            return
 
        DIST = x.dist
        for i in range(6):
            # mod 26 for circular letter sequence
            # next letter for a
            A = (x.a + dx[i] + 26) % 26
            # next letter for b
            B = (x.b + dy[i] + 26) % 26
            # next letter for c
            C = (x.c + dz[i] + 26) % 26
 
            if not restricted[A][B][C] and not visited[A][B][C]:
                # if a valid word state,
                # push into queue
                q.append(Node(A, B, C, DIST + 1))
                visited[A][B][C] = True
 
    # reach here if not possible
    # to reach final word Y
    print(-1)
 
# Driver Code
 
# starting string
X = "znw"
 
# final string
Y = "lof"
 
# no of restricting word vectors
N = 4
forbidden = [["qlb", "jcm", "mhoq"], ["azn", "piy", "vj"], ["by", "oy", "ubo"], ["jqm", "f", "ej"]]
 
solve(X, Y, N, forbidden)




// C#// each node represents a word state code for above program.
using System;
using System.Collections.Generic;
// each node represents a word state
public struct Node
{
    public int a, b, c;
     // dist from starting word X
    public int dist;
}
 
public class Program
{
     
 
    public static void Solve(string start, string end, int N, List<List<string>> forbidden)
    {
        // 3D visited array
        bool[,,] visited = new bool[26,26,26];
        // 3D restricted array
        bool[,,] restricted = new bool[26,26,26];
 
        foreach (List<string> vec in forbidden)
        {
            string a = vec[0];
            string b = vec[1];
            string c = vec[2];
 
            foreach (char x in a)
            {
                foreach (char y in b)
                {
                    foreach (char z in c)
                    {
                          // each invalid word is
                    // decoded and marked as
                    // restricted = true.    // starting and ending letter a
                        restricted[x - 'a', y - 'a', z - 'a'] = true;
                    }
                }
            }
        }
    // starting and ending letter a
        int sa = start[0] - 'a';
        int ea = end[0] - 'a';
    // starting and ending letter b
        int sb = start[1] - 'a';
        int eb = end[1] - 'a';
    // starting and ending letter c
        int sc = start[2] - 'a';
        int ec = end[2] - 'a';
 
        if (restricted[sa, sb, sc] || restricted[ea, eb, ec])
        {
              // check if starting word
        // or finishing word is
        // restricted or not
            Console.WriteLine("-1");
            return;
        }
    // queue of nodes for BFS
        Queue<Node> q = new Queue<Node>();
           // initial starting word pushed in
    // queue. dist = 0 for starting word
        q.Enqueue(new Node { a = sa, b = sb, c = sc, dist = 0 });
          // mark as visited
        visited[sa, sb, sc] = true;
 
        while (q.Count > 0)
        {
            Node x = q.Dequeue();
  // final destination reached condition
            if (x.a == ea && x.b == eb && x.c == ec)
            {
                Console.WriteLine(x.dist);
                return;
            }
 
            int DIST = x.dist;
            // utility arrays for single step
// traversal in left and right
            int[] dx = { 1, -1, 0, 0, 0, 0 };
            int[] dy = { 0, 0, 1, -1, 0, 0 };
            int[] dz = { 0, 0, 0, 0, 1, -1 };
 
            for (int i = 0; i < 6; i++)
            {
                   // mod 26 for circular letter sequence
 
            // next letter for a
                int A = (x.a + dx[i] + 26) % 26;
                 // next letter for b
                int B = (x.b + dy[i] + 26) % 26;
                 // next letter for c
                int C = (x.c + dz[i] + 26) % 26;
 
                if (!restricted[A, B, C] && !visited[A, B, C])
                {
                    q.Enqueue(new Node { a = A, b = B, c = C, dist = DIST + 1 });
                    visited[A, B, C] = true;
                }
            }
        }
    // reach here if not possible
    // to reach final word Y
        Console.WriteLine("-1");
    }
     
// Driver Code
    public static void Main()
    {
        // starting string
        string X = "znw";
        // final string
        string Y = "lof";
        // no of restricting word vectors
        int N = 4;
        List<List<string>> forbidden = new List<List<string>>
        {
            new List<string>{"qlb", "jcm", "mhoq"},
            new List<string>{"azn", "piy", "vj"},
            new List<string>{"by", "oy", "ubo"},
            new List<string>{"jqm", "f", "ej"}
        };
        Solve(X, Y, N, forbidden);
    }
}




// JavaScript code for above program.
const dx = [1, -1, 0, 0, 0, 0];
const dy = [0, 0, 1, -1, 0, 0];
const dz = [0, 0, 0, 0, 1, -1];
 
// each node represents a word state
class Node {
  constructor(a, b, c, dist) {
    this.a = a;
    this.b = b;
    this.c = c;
    // dist from starting word X
    this.dist = dist;
  }
}
 
// function to find the minimum clicks.
function solve(start, end, forbidden) {
  const visited = Array.from({ length: 26 }, () =>
    Array.from({ length: 26 }, () => Array.from({ length: 26 }, () => false))
  );
  const restricted = Array.from({ length: 26 }, () =>
    Array.from({ length: 26 }, () => Array.from({ length: 26 }, () => false))
  );
 
  for (const [a, b, c] of forbidden) {
    for (const x of a) {
      for (const y of b) {
        for (const z of c) {
          // each invalid word is
          // decoded and marked as
          // restricted = true.
          restricted[x.charCodeAt() - 97][y.charCodeAt() - 97][
            z.charCodeAt() - 97
          ] = true;
        }
      }
    }
  }
 
  // starting and ending letter a
  const sa = start.charCodeAt(0) - 97;
  const ea = end.charCodeAt(0) - 97;
 
  // starting and ending letter b
  const sb = start.charCodeAt(1) - 97;
  const eb = end.charCodeAt(1) - 97;
 
  // starting and ending letter c
  const sc = start.charCodeAt(2) - 97;
  const ec = end.charCodeAt(2) - 97;
 
  if (restricted[sa][sb][sc] || restricted[ea][eb][ec]) {
    // check if starting word or finishing word is restricted or not
    console.log(-1);
    return;
  }
 
  const q = [];
  // initial starting word pushed in queue. dist = 0 for starting word
  q.push(new Node(sa, sb, sc, 0));
 
  // mark as visited
  visited[sa][sb][sc] = true;
 
  while (q.length > 0) {
    const x = q.shift();
    // final destination reached condition
    if (x.a === ea && x.b === eb && x.c === ec) {
      console.log(x.dist);
      return;
    }
 
    const DIST = x.dist;
    for (let i = 0; i < 6; i++) {
      // mod 26 for circular letter sequence
      // next letter for a
      const A = ((x.a + dx[i] + 26) % 26) | 0;
      // next letter for b
      const B = ((x.b + dy[i] + 26) % 26) | 0;
      // next letter for c
      const C = ((x.c + dz[i] + 26) % 26) | 0;
 
      if (!restricted[A][B][C] && !visited[A][B][C]) {
        // if a valid word state, push into queue
        q.push(new Node(A, B, C, DIST + 1));
        visited[A
        ][B][C] = true;
      }
    }
  }
 
  // reach here if not possible to reach final word Y
  console.log(-1);
}
 
// Driver Code
// starting string
const X = "znw";
 
// final string
const Y = "lof";
 
// no of restricting word vectors
const N = 4;
const forbidden = [
  ["qlb", "jcm", "mhoq"],
  ["azn", "piy", "vj"],
  ["by", "oy", "ubo"],
  ["jqm", "f", "ej"],
];
 
solve(X, Y, forbidden);

Output
22

Time Complexity: O(26 * 26 * 26), since at max, there can be 26*26*26 word states.
Space Complexity: O(26 * 26 * 26)


Article Tags :