02 mai 2024

50 DSA-Questions: Trie #36, #37

Implement Trie (Prefix Tree)

A trie (pronounced as "try") or prefix tree is a tree data structure used to efficiently store and retrieve keys in a dataset of strings. There are various applications of this data structure, such as autocomplete and spellchecker.

class Node {
    Character value;
    Node[] children;
    boolean end;

    public Node(Character value) {
        this.value = value;
        this.children = new Node[26];
        this.end = false;
    }

    public Node getChild(char ch) {
        return children[ch - 'a'];
    }

    public Node addChild(Node child) {
        this.children[child.value - 'a'] = child;
        return child;
    }
}

class Trie {
    Node root;

    public Trie() {
        root = new Node(null);
    }
   
    public void insert(String word) {
        boolean end;
        if (word.length() > 0) {
            Node node = root;
            int i = 0;
            do {
                Character ch = Character.valueOf(word.charAt(i));
                end = i == word.length()-1;
                Node child = node.getChild(ch);
                if (child == null) {
                    child = node.addChild(new Node(ch));
                }
                child.end = end || child.end;
                node = child;
                i++;
            } while (!end);
        }
    }
   
    public boolean search(String word) {
        return search(word, false);
    }

    public boolean startsWith(String prefix) {
        return search(prefix, true);
    }

    private boolean search(String word, boolean prefix) {
        int i = 0;
        Node node = root;
        while (true) {
            Character ch = Character.valueOf(word.charAt(i));
            Node child = node.getChild(ch);
            if (child == null) {
                return false;
            }
            if (i == word.length() - 1) {
                return prefix ? true : child.end;
            }
            i++;
            node = child;
        }
    }
}

#36. Design Add and Search Words Data Structure

Design a data structure that supports adding new words and finding if a string matches any previously added string. The searched word may contain dots '.' where dots can be matched with any letter.

class Node {
    Character value;
    Node[] children;
    boolean end;

    public Node(Character value) {
        this.value = value;
        this.children = new Node[26];
        this.end = false;
    }

    public Node getChild(char ch) {
        return children[ch - 'a'];
    }

    public Node addChild(Node child) {
        this.children[child.value - 'a'] = child;
        return child;
    }
}

class WordDictionary {
    Node root;

    public WordDictionary() {
        root = new Node(null);
    }
   
    public void addWord(String word) { // la fel ca la Trie
        boolean end;
        if (word.length() > 0) {
            Node node = root;
            int i = 0;
            do {
                Character ch = Character.valueOf(word.charAt(i));
                end = i == word.length()-1;
                Node child = node.getChild(ch);
                if (child == null) {
                    child = node.addChild(new Node(ch));
                }
                child.end = end || child.end;
                node = child;
                i++;
            } while (!end);
        }
    }

    public boolean search(String word) {
        return search(word, root, 0);
    }

    private boolean search(String word, Node node, int i) {
        while (i < word.length()) {
            Character ch = Character.valueOf(word.charAt(i));
            boolean isLetter = ch != '.';
            if (isLetter) {
                Node child = node.getChild(ch);
                if (child == null) {
                    return false;
                }
                if (i == word.length() - 1) {
                    return child.end;
                }
                node = child;
                i++;
            } else { // in plus fata de Trie, match pe caracterul '.'
                for (int j=0; j<node.children.length; j++) {
                    Node child = node.children[j];
                    if (child != null) { // match condition
                        if (i == word.length() - 1 && child.end) {
                            return true;
                        }
                        boolean res = search(word, child, i+1);
                        if (res) {
                            return true;
                        }
                    }
                }
                return false;
            }
        }
        return false;
    }
}

#37. Word Search II

Given an m x n board of characters and a list of strings words, return all words on the board. Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

class Node {
    Character value;
    Node[] children;
    boolean end;
    int kidsCount; // camp aditional
    Node parent; // camp aditional

    public Node(Character value) {
        this.value = value;
        this.children = new Node[26];
        this.end = false;
        this.kidsCount = 0;
        this.parent = null;
    }

    public Node getChild(char ch) {
        return children[ch - 'a'];
    }

    public Node addChild(Node child) {
        child.parent = this;
        this.children[child.value - 'a'] = child;
        this.kidsCount++;
        return child;
    }

    public void removeChild(char ch) {
        children[ch - 'a'] = null;
        this.kidsCount--;
    }

    public boolean hasChild() {
        return this.kidsCount > 0;
    }
}

enum SearchStatus {
    NOT_FOUND,
    PREFIX,
    FOUND
};

class Trie {
    Node root;

    public Trie() {
        root = new Node(null);
    }
   
    public void insert(String word) { // identic Trie
        boolean end;
        if (word.length() > 0) {
            Node node = root;
            int i = 0;
            do {
                Character ch = Character.valueOf(word.charAt(i));
                end = i == word.length()-1;
                Node child = node.getChild(ch);
                if (child == null) {
                    child = node.addChild(new Node(ch));
                }
                child.end = end || child.end;
                node = child;
                i++;
            } while (!end);
        }
    }

    public void remove(String word) { // metoda noua
        int i = 0;
        Node node = root;
        while (i < word.length()) {
            Character ch = Character.valueOf(word.charAt(i));
            Node child = node.getChild(ch);
            i++;
            node = child;
        }

        while (node != root) {
            if (node.hasChild()) {
                node.end = false;
                break;
            }
            node.parent.removeChild(node.value);
            node = node.parent;
        }
    }

    public SearchStatus search(String word) {
        int i = 0;
        Node node = root;
        while (true) {
            Character ch = Character.valueOf(word.charAt(i));
            Node child = node.getChild(ch);
            if (child == null) {
                return SearchStatus.NOT_FOUND;
            }
            if (i == word.length() - 1) {
                return child.end ? SearchStatus.FOUND : SearchStatus.PREFIX;
            }
            i++;
            node = child;
        }
    }
}

class Solution {
    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        for (String word : words) {
            trie.insert(word);
        }

        List<String> result = new ArrayList<>();
        for (int i=0; i<board.length; i++) {
            for (int j=0; j<board[i].length; j++) {
                check(i, j, board, "", trie, "", result);
            }
        }

        return result;
    }

    private void hunt(int i, int j, char[][] board, String word, Trie trie, String path, List<String> result) {
        // left
        check(i, j-1, board, word, trie, path, result);

        // right
        check(i, j+1, board, word, trie, path, result);

        // up
        check(i-1, j, board, word, trie, path, result);

        // down
        check(i+1, j, board, word, trie, path, result);
    }

    private void check(int i, int j, char[][] board, String word, Trie trie, String path, List<String> result) {
        if (i < 0 || j < 0 || i >= board.length || j >= board[0].length) {
            return;
        }
        if (!isNewPos(i, j, path)) {
            return;
        }
       
        word = word + board[i][j];
        SearchStatus status = trie.search(word);
        switch (status) {
            case SearchStatus.FOUND:
                result.add(word);
                trie.remove(word); // optimizare: stergere cuvant dupa ce a fost adaugat la rezultat
            case SearchStatus.PREFIX:
                path = path + getPosKey(i,j);
                hunt(i, j, board, word, trie, path, result);
        }
    }

    private String getPosKey(int i, int j) {
        return i + "-" + j + "#";
    }

    private boolean isNewPos(int i, int j, String path) {
        int index = path.indexOf(getPosKey(i, j));
        if (index < 0) {
            return true;
        }
        if (index == 0) {
            return false;
        }
        char ch = path.charAt(index - 1);
        return ch != '#';
    }
}

25 aprilie 2024

50 DSA-Questions: Trees #34, #35

#34. Binary Tree Level Order Traversal

Given the root of a binary tree, return the level order traversal of its nodes' values. (i.e., from left to right, level by level) as a list of lists.

 public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    int level;
    TreeNode() {}
    TreeNode(int val) { this.val = val; }
    TreeNode(int val, TreeNode left, TreeNode right) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

class Solution {
    public List<List<Integer>> levelOrder(TreeNode root) {
        if (root == null) {
            return new ArrayList<>();
        }
       
        root.level = 0;
        int currentLevel = 0;

        List<List<Integer>> result = new ArrayList<>();
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);

        while (!queue.isEmpty()) {
            List<Integer> partialRes = new ArrayList<>();
            while (!queue.isEmpty()) {
                if (queue.peek().level == currentLevel) {
                    TreeNode node = queue.poll();
                    partialRes.add(node.val);
                    if (node.left != null) {
                        node.left.level = currentLevel + 1;
                        queue.add(node.left);
                    }
                    if (node.right != null) {
                        node.right.level = currentLevel + 1;
                        queue.add(node.right);
                    }
                } else {
                    currentLevel++;
                    break;
                }
            }
            if (!partialRes.isEmpty()) {
                result.add(partialRes);
            }
        }
       
        return result;
    }
}

#35. Serialize & Deserialize Binary Tree

Serialization is the process of converting a data structure or object into a sequence of bits so that it can be stored in a file or memory buffer, or transmitted across a network connection link to be reconstructed later in the same or another computer environment. Design an algorithm to serialize and deserialize a binary tree. There is no restriction on how your serialization/deserialization algorithm should work. You just need to ensure that a binary tree can be serialized to a string and this string can be deserialized to the original tree structure.

! exceeds memory limit for larger sets of data

public class Codec {
    // Encodes a tree to a single string --->> scris ca arbore complet "1,2,3,null,4"
    public String serialize(TreeNode root) {
        StringBuilder serial = new StringBuilder();

        Map<Integer, Integer> posVal = new HashMap<>();
        buildPosVal(root, 0, posVal);
        Optional<Integer> maxPos = posVal.keySet().stream().max(Integer::compare);
        if (!maxPos.isPresent()) {
            return "";
        }

        int highestOneBit = Integer.highestOneBit(maxPos.get() + 1);
        int len;
        switch (highestOneBit) {
            case 0:
                len = 1;
                break;
            case 1:
                len = 3;
                break;
            default:
                len = highestOneBit * 2 - 1;
        }

        for (int i=0; i<len; i++) {
            serial.append(posVal.containsKey(i) ? posVal.get(i)+"" : "N").append(",");
        }
       
        return serial.toString();
    }

    private void buildPosVal(TreeNode node, Integer level, Map<Integer, Integer> posVal) {
        if (node == null) {
            return;
        }
        posVal.put(level, node.val);
        buildPosVal(node.left, 2 * level + 1, posVal);
        buildPosVal(node.right, 2 * level + 2, posVal);
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data.length() <= 1) {
            return null;
        }
        String str[] = data.split(",");
        return buildFrom(new TreeNode(str[0]), 0, str);
    }

    private TreeNode buildFrom(TreeNode treeElem, int k, String[] elements) {
        if (treeElem == null) {
            return null;
        }
       
        if (elements.length > 2 * k + 1 && !"N".equals(elements[2 * k + 1])) {
            TreeNode left = new TreeNode(elements[2 * k + 1]);
            treeElem.left = buildFrom(left, 2 * k + 1, elements);
        }

        if (elements.length > 2 * k + 2 && !"N".equals(elements[2 * k + 2])) {
            TreeNode right = new TreeNode(elements[2 * k + 2]);            
            treeElem.right = buildFrom(right, 2 * k + 2, elements);
        }

        return treeElem;
    }
}

! slightly better BUT not good enough :(

 class TreeNode {
     int val;
     TreeNode left;
     TreeNode right;
     int index;
     TreeNode(int x) { val = x; }
}

public class Codec {
    // Encodes a tree to a single string --->> scris ca arbore complet "0=1,1=2,2=3,3=null,4=4"
    public String serialize(TreeNode root) {
        if (root == null) {
            return "";
        }

        root.index = 0;
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);

        StringBuilder serial = new StringBuilder();
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            addDataWithFilling(serial, node);
            if (node.left != null) {
                node.left.index = 2 * node.index + 1;
                queue.add(node.left);
            }
            if (node.right != null) {
                node.right.index = 2 * node.index + 2;
                queue.add(node.right);
            }
        }

        return serial.toString();
    }

    private void addDataWithFilling(StringBuilder serial, TreeNode node) {
        int lastIndex = serial.toString().lastIndexOf(",");
        if (lastIndex > -1) {
            int beforeLastIndex = serial.toString().substring(0, lastIndex).lastIndexOf(",");
            String lastData = beforeLastIndex > -1 ?
                    serial.toString().substring(beforeLastIndex + 1, lastIndex) :
                    serial.toString().substring(0, lastIndex);
            int lastPos = extractIndexFromData(lastData);
            for (int i=lastPos+1; i<node.index; i++) {
                serial.append(node.index + "=N,"); // null
            }
        }
        serial.append(node.index + "=" + node.val + ",");
    }

    private int extractIndexFromData(String data) {
        return Integer.parseInt(data.substring(0, data.indexOf("=")));
    }

    private Integer extractValueFromData(String data) {
        try {
            return Integer.parseInt(data.substring(data.indexOf("=") + 1));
        } catch(Exception e) {
            return null;
        }
    }

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        // idem
    }
}

! accepted with good memory usage

public class Codec {
    // Encodes a tree to a single string --->> scris ca arbore complet "1,2,N,N,3,4,N,N,5,N,N,"
    public String serialize(TreeNode root) {
        if (root == null) {
            return "";
        }

        StringBuilder sb = new StringBuilder();
        sb.append(root.val).append(",");
        String result = dfs(root, sb).toString();

        return result;
    }

    private StringBuilder dfs(TreeNode node, StringBuilder serial) {
        if (node.left != null) {
            serial.append(node.left.val).append(",");
            serial = dfs(node.left, serial);
        } else {
            serial.append("N,");
        }
        if (node.right != null) {
            serial.append(node.right.val).append(",");
            serial = dfs(node.right, serial);
        } else {
            serial.append("N,");
        }
        return serial;
    }

    private Integer extractValueFromData(String data) { // data = "val,....." => val
        int indexStop = data.indexOf(",");
        if (indexStop > -1) {
            try {
                return Integer.parseInt(data.substring(0, indexStop));
            } catch(Exception e) {
                return null;
            }
        }
        return null;
    }

    private void updateSerial() { // remove first record
        int commaIndex = serial.indexOf(",");
        if (commaIndex > -1) {
            serial = serial.substring(commaIndex+1);
        }
    }

    String serial;

    // Decodes your encoded data to tree.
    public TreeNode deserialize(String data) {
        if (data.length() <= 1) {
            return null;
        }
       
        Integer value = extractValueFromData(data);
        this.serial = data;
        updateSerial();
        if (value != null) {
            TreeNode root = new TreeNode(value);
            return buildFrom(root);
        }
        return null;
    }

    private TreeNode buildFrom(TreeNode treeElem) {
        if (treeElem == null) {
            return null;
        }

        Integer value = extractValueFromData(serial);
        updateSerial();
        if (value != null) {
            TreeNode left = new TreeNode(value);
            treeElem.left = buildFrom(left);
        }

        value = extractValueFromData(serial);
        updateSerial();
        if (value != null) {
            TreeNode right = new TreeNode(value);
            treeElem.right = buildFrom(right);
        }

        return treeElem;
    }
}