29 mai 2024

50 DSA-Questions: DP #45, #46

#45. Climbing Stairs - vezi si aici

You are climbing a staircase. It takes n steps to reach the top. Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top?

class Solution {
    public int climbStairs(int n) {
        if (n <= 0) {
            return 0;
        }
        Integer[] cache = new Integer[n+1];
        return check(n, 0, cache);
    }

    private int check(int steps, int ways, Integer[] cache) {
        if (steps < 0) {
            return 0;
        }
        if (steps == 0) {
            return 1;
        }

        //return check(steps - 1, ways + 1, cache) + check(steps - 2, ways + 1, cache);
       
        cache[steps] = 0;
        if (steps >= 1) {
            if (cache[steps-1] == null) {
                cache[steps-1] = check(steps - 1, ways + 1, cache);
            }
            cache[steps] += cache[steps-1];
        }
        if (steps >= 2) {
            if (cache[steps-2] == null) {
                cache[steps-2] = check(steps - 2, ways + 1, cache);
            }
            cache[steps] += cache[steps-2];
        }

        return cache[steps];
    }
}

#46. Coin Change

You are given an integer array of coins representing coins of different denominations and an integer amount representing a total amount of money. Return the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

// solutie greedy & backtracking care depaseste timpul alocat

class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount == 0) {
            return 0;
        }

        List<Integer> sortedCoins = Arrays.stream(coins).boxed().collect(Collectors.toList());
        Collections.sort(sortedCoins, Collections.reverseOrder());

        return change(amount, sortedCoins, 0);
    }

    private int change(int amount, List<Integer> coins, int coinsToHere) {
        if (coins.isEmpty()) {
            return -1; // impossible to satisfy
        }
        if (coins.get(0) > amount) {
            return change(amount, coins.subList(1, coins.size()), coinsToHere); // search with lower value coins
        }
        int maxFullCoins = amount / coins.get(0);
        int rest = amount % coins.get(0);
        if (rest == 0) {
            return maxFullCoins + coinsToHere; // exact match
        }
        int minSoFar = Integer.MAX_VALUE;
        boolean found = false;
        for (int i=maxFullCoins; i>=0; i--) {
            int coinsNeeded = change(rest, coins.subList(1, coins.size()), i + coinsToHere);
            if (coinsNeeded != -1 && minSoFar > coinsNeeded) { // if successful and more optimal change
                minSoFar = coinsNeeded;
                found = true;
            }
            rest += coins.get(0);
        }
        return found ? minSoFar : -1;
    }
}

// solutie mai buna (programare dinamica bottom-up) dar tot depaseste timpul pt date de intrare mari

class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount == 0) {
            return 0;
        }

        List<Integer> sortedCoins = Arrays.stream(coins).boxed().collect(Collectors.toList());
        Collections.sort(sortedCoins, Collections.reverseOrder());

// idea: make an array of all amounts <= amount
// such that array[k] = optimal number of coins to compose amount k
// then derive array[amount] by looking back and taking the min sum
// array[i] + array[j] such that i + j = k
       
        Integer changes[] =  getInitialChanges(sortedCoins, amount);

        changes = refine(changes);

        return changes[amount] != null ? changes[amount] : -1;
    }

    private Integer[] getInitialChanges(List<Integer> sortedCoins, int amount) {
        Integer changes[] =  new Integer[amount + 1];
        changes[0] = 0;
        for (int coin : sortedCoins) {
            int maxCoins = amount / coin; // how many coins can fill <= amount
            for (int i=1; i<=maxCoins; i++) {
                if (changes[coin * i] == null) { // if not null, amount was filled with larger & less coins
                    changes[coin * i] = i;
                }
            }
        }
        return changes;
    }

    private Integer[] refine(Integer[] changes) {
        for (int i=1; i<changes.length; i++) {
            for (int j=i-1; j>=i/2; j--) {
                if (changes[j] == null || changes[i-j] == null) {
                    continue;
                }
                int prevSum = changes[j] + changes[i-j]; // j + i - j = i (current amount)
                changes[i] = changes[i] == null ? prevSum : Math.min(changes[i], prevSum);
            }
        }
        return changes;
    }
}

// solutie acceptabila

class Solution {
    public int coinChange(int[] coins, int amount) {
        if (amount == 0) {
            return 0;
        }

        Integer changes[] =  new Integer[amount + 1];
        changes[0] = 0;
        for (int i=1; i<changes.length; i++) {
            changes[i] = amount + 1; // something greater than amount pieces of 1's
        }
        for (int i=1; i<changes.length; i++) { // building the table bottom-up for amounts from 1 to amount
            for (int j=0; j<coins.length; j++) { // consider taking this coin to compose amount i
                if (coins[j] <= i) { // if current coin can fit current amount
                    int restAmount = i - coins[j];
                    // keep as is or update taking current coin + what else needed to fill restAmount
                    changes[i] = Math.min(changes[i], changes[restAmount] + 1); // 1 for taking coins[j]
                    // will update only if changes[restAmount] is known & optimal enough
                }
            }
        }
        return changes[amount] == amount + 1 ? -1 : changes[amount];
    }
}

28 mai 2024

50 DSA-Questions: Graph #43, #44

#43. Number of Islands

Given an m x n 2D binary grid which represents a map of '1's (land) and '0's (water), return the number of islands. An island (1's) is surrounded by water (0's) and is formed by connecting adjacent lands horizontally or vertically.

class Solution {
    public int numIslands(char[][] grid) {
        if (grid.length == 0) {
            return 0;
        }

        int[][] markedGrid = new int[grid.length][grid[0].length];
        for (int i=0; i<markedGrid.length; i++) {
            for (int j=0; j<markedGrid[i].length; j++) {
                markedGrid[i][j] = 0;
            }
        }

        int tag = 1;
        for (int i=0; i<grid.length; i++) {
            for (int j=0; j<grid[i].length; j++) {
                if (markedGrid[i][j] == 0) {
                    AtomicBoolean modified = new AtomicBoolean(false);
                    markedGrid = markGrid(i, j, grid, markedGrid, tag, modified);
                    if (modified.get()) {
                        tag++;
                    }
                }
            }
        }
       
        return tag - 1;
    }

    private int[][] markGrid(int i, int j, char[][] grid, int[][] markedGrid, int tag, AtomicBoolean modified) {
        if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0' || markedGrid[i][j] > 0) {
            return markedGrid;
        }

        markedGrid[i][j] = tag;
        markedGrid = markGrid(i-1, j, grid, markedGrid, tag, modified);
        markedGrid = markGrid(i+1, j, grid, markedGrid, tag, modified);
        markedGrid = markGrid(i, j-1, grid, markedGrid, tag, modified);
        markedGrid = markGrid(i, j+1, grid, markedGrid, tag, modified);
        modified.set(true);

        return markedGrid;
    }
}

#44. Pacific Atlantic Water Flow

There is an m x n rectangular island that borders both the Pacific Ocean and the Atlantic Ocean. The Pacific Ocean touches the island's left and top edges, and the Atlantic Ocean touches the island's right and bottom edges. 

The island is partitioned into a grid of square cells. You are given an m x n integer matrix height where heights[r][c] represent the height above sea level of the cell at coordinate (r, c).

The island receives a lot of rain, and the rainwater can flow to neighboring cells directly north, south, east, and west if the neighboring cell's height is less than or equal to the current cell's height. Water can flow from any cell adjacent to an ocean into the ocean.

Return a 2D list of grid coordinates results where result[i] = [ri, ci] denotes that rainwater can flow from cell (ri, ci) to both the Pacific and Atlantic oceans.

// solutie cu timp ridicat de executie

class Solution {
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        Map<Integer,Set<Integer>> edges = new HashMap<>();
        for (int i=0; i<heights.length; i++) {
            for (int j=0; j<heights[0].length; j++) {
                int label = getNodeLabel(i, j, heights[0].length);
                edges.put(label, getNeighbors(i,j,heights)); // edge from label to its neighbors (water flows to)
            }
        }

        findAllEdges(edges, heights[0].length, heights.length);

        List<List<Integer>> results = new ArrayList<>();
        for (int node : edges.keySet()) {
            boolean pacific = nearPacific(node, heights[0].length);
            boolean atlantic = nearAtlantic(node, heights[0].length, heights.length);
            if (!pacific || !atlantic) {
                for (int conn : edges.get(node)) {
                    pacific = pacific || nearPacific(conn, heights[0].length);
                    atlantic = atlantic || nearAtlantic(conn, heights[0].length, heights.length);
                    if (pacific && atlantic) {
                        break;
                    }
                }
            }
            if (pacific && atlantic) {
                addResult(node, heights[0].length, results);
            }
        }

        return results;
    }

    private void addResult(int node, int M, List<List<Integer>> results) {
        int i = getI(node, M), j = getJ(node, M);
        List<Integer> res = new ArrayList<>();
        res.add(i);
        res.add(j);
        results.add(res);
    }

    private void findAllEdges(Map<Integer, Set<Integer>> edges, int M, int N) {
        Map<Integer, Set<Integer>> allEdges = new HashMap(edges);
        boolean doContinue = true;
        while (doContinue) {
            doContinue = false;
            Set<Integer> toRemove = new HashSet<>();
           
            for (int a : edges.keySet()) {
                Set<Integer> toAdd = new HashSet<>();
                for (int b : edges.get(a)) {
                    if (edges.get(b) != null) {
                        toAdd.addAll(edges.get(b));
                        toAdd.remove(a);
                    }
                }
               
                int size = edges.get(a).size();
                edges.get(a).addAll(toAdd);
                boolean modified = size < edges.get(a).size();
                if (!modified) {
                    toRemove.add(a); // if no new connections added to a, it will never add more
                }
               
                allEdges.get(a).addAll(toAdd);
                doContinue = doContinue || modified;
            }

            for (int a : toRemove) {
                edges.remove(a);
            }
        }
        edges = allEdges;
    }

    private boolean nearPacific(int nodeLabel, int M) {
        int i = getI(nodeLabel, M), j = getJ(nodeLabel, M);
        return i == 0 || j == 0;
    }

    private boolean nearAtlantic(int nodeLabel, int M, int N) {
        int i = getI(nodeLabel, M), j = getJ(nodeLabel, M);
        return i == N - 1 || j == M - 1;
    }

    private Set<Integer> getNeighbors(int i, int j, int[][] heights) {
        Set<Integer> neighbors = new HashSet<>(); // to whom water flows from (i,j)
        if (i>0 && heights[i][j] >= heights[i-1][j]) {
            neighbors.add(getNodeLabel(i-1, j, heights[0].length));
        }
        if (j>0 && heights[i][j] >= heights[i][j-1]) {
            neighbors.add(getNodeLabel(i, j-1, heights[0].length));
        }
        if (i<heights.length-1 && heights[i][j] >= heights[i+1][j]) {
            neighbors.add(getNodeLabel(i+1, j, heights[0].length));
        }
        if (j<heights[0].length-1 && heights[i][j] >= heights[i][j+1]) {
            neighbors.add(getNodeLabel(i, j+1, heights[0].length));
        }
        return neighbors;
    }

    private int getNodeLabel(int i, int j, int M) {
        return i * M + j;
    }

    private int getI(int nodeLabel, int M) {
        return nodeLabel / M;
    }

    private int getJ(int nodeLabel, int M) {
        return nodeLabel % M;
    }
}

// solutie acceptabila ca timp

class Solution {
    private enum Tag {UNK, PAC, ATL, BOTH};

    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        // init tags
        Tag[][] tags = new Tag[heights.length][heights[0].length];
        for (int i=0; i<heights.length; i++) {
            for (int j=0; j<heights[0].length; j++) {
                tags[i][j] = Tag.UNK;
            }
        }
        for (int j=0; j<heights[0].length; j++) {
            tags[0][j] = Tag.PAC;
            tags[heights.length-1][j] = updateTag(tags[heights.length-1][j], Tag.ATL);
        }
        for (int i=0; i<heights.length; i++) {
            tags[i][0] = updateTag(tags[i][0], Tag.PAC);
            tags[i][heights[0].length-1] = updateTag(tags[i][heights[0].length-1], Tag.ATL);
        }

        // tag all
        for (int i=0; i<heights.length; i++) {
            for (int j=heights[0].length-1; j>=0; j--) {
                tags = tagNode(i, j, heights, tags, new ArrayList<>()); // top-right to bottom-left
            }
        }
        for (int i=heights.length-1; i>=0; i--) {
            for (int j=0; j<heights[0].length; j++) {
                tags = tagNode(i, j, heights, tags, new ArrayList<>()); // bottom-left to top-right
            }
        }
        for (int i=0; i<heights.length; i++) {
            for (int j=0; j<heights[0].length; j++) {
                tags = tagNode(i, j, heights, tags, new ArrayList<>()); // top-left to bottom-right
            }
        }
        for (int i=heights.length-1; i>=0; i--) {
            for (int j=heights[0].length-1; j>=0; j--) {
                tags = tagNode(i, j, heights, tags, new ArrayList<>()); // bottom-right to top-left
            }
        }

        List<List<Integer>> results = new ArrayList<>();
        for (int i=0; i<heights.length; i++) {
            for (int j=0; j<heights[0].length; j++) {
                if (tags[i][j] == Tag.BOTH) {
                    List<Integer> res = new ArrayList<>();
                    res.add(i);
                    res.add(j);
                    results.add(res);
                }
            }
        }

        return results;
    }

    private Tag[][] tagNode(int i, int j, int[][] heights, Tag[][] tags, List<Integer> trace) {
        int currentNode = getNodeLabel(i, j, heights[0].length);
        trace.add(currentNode);

        if (i>0 && heights[i][j] >= heights[i-1][j]) {
            int next = getNodeLabel(i-1, j, heights[0].length);
            if (tags[i-1][j] == Tag.UNK && !trace.contains(next)) {
                tagNode(i-1, j, heights, tags, trace);
            }
            tags[i][j] = updateTag(tags[i][j], tags[i-1][j]);
        }
        if (j>0 && heights[i][j] >= heights[i][j-1]) {
            int next = getNodeLabel(i, j-1, heights[0].length);
            if (tags[i][j-1] == Tag.UNK && !trace.contains(next)) {
                tagNode(i, j-1, heights, tags, trace);
            }
            tags[i][j] = updateTag(tags[i][j], tags[i][j-1]);
        }
        if (i<heights.length-1 && heights[i][j] >= heights[i+1][j]) {
            int next = getNodeLabel(i+1, j, heights[0].length);
            if (tags[i+1][j] == Tag.UNK && !trace.contains(next)) {
                tagNode(i+1, j, heights, tags, trace);
            }
            tags[i][j] = updateTag(tags[i][j], tags[i+1][j]);
        }
        if (j<heights[0].length-1 && heights[i][j] >= heights[i][j+1]) {
            int next = getNodeLabel(i, j+1, heights[0].length);
            if (tags[i][j+1] == Tag.UNK && !trace.contains(next)) {
                tagNode(i, j+1, heights, tags, trace);
            }
            tags[i][j] = updateTag(tags[i][j], tags[i][j+1]);
        }

        return tags;
    }

    private Tag updateTag(Tag initialTag, Tag newTag) {
        if (initialTag == Tag.UNK || initialTag == null) {
            return newTag;
        }
        if (newTag == Tag.UNK) {
            return initialTag;
        }
        if (initialTag != newTag) {
            return Tag.BOTH;
        }
        return initialTag; // PAC sau ATL sau BOTH
    }

    private int getNodeLabel(int i, int j, int M) {
        return i * M + j;
    }
}

26 mai 2024

50 DSA-Questions: Graph #41, #42

#41. Clone graph

Given a reference of a node in a connected undirected graph. Return a deep copy (clone) of the graph. Each node in the graph contains a value (int) and a list (List[Node]) of its neighbors. Moreover:

  • acyclic graph
  • root value is 1
  • node values are unique starting from 1

/* class Node { public int val; public List neighbors; } */

class Solution {
    Map<Integer, Node> visited;

    public Node cloneGraph(Node node) {
        if (node == null) {
            return null;
        }

        visited = new HashMap<>();

        Node copy = cloneGraph(node, null);

        visited.clear();

        return copy;
    }

    private Node cloneGraph(Node node, Node from) {        
        Node copy = new Node(node.val);
        visited.put(copy.val, copy);
       
        List<Node> neighbors = new ArrayList<>();
        if (from != null) {
            neighbors.add(visited.get(from.val));
        }
        visitNeighbors(node, from, neighbors);
        copy.neighbors = neighbors;

        return copy;
    }

    private void visitNeighbors(Node original, Node from, List<Node> neighbors) {
        for (Node neighbor : original.neighbors) {
            if (from != null && neighbor.val == from.val) {
                continue;
            }
            if (visited.containsKey(neighbor.val)) {
                neighbors.add(visited.get(neighbor.val));
            } else {
                Node newNeigh = cloneGraph(neighbor, original);
                neighbors.add(newNeigh);
            }
        }
    }
}


#42. Course schedule (Aflarea dacă un graf orientat conține cicluri)

There are a total of numCourses courses you have to take, labeled from 0 to numCourses - 1. You are given an array of prerequisites where prerequisites[i] = [ai, bi] indicates that you must take course bi first if you want to take course ai. For example, the pair [0, 1], indicates that to take course 0 you have to first take course 1.

Return true if you can finish all courses. Otherwise, return false. 

// solutie cu timp ridicat de executie

class Solution {
    public boolean canFinish(int numCourses, int[][] prereq) {
        if (prereq.length == 0) {
            return true;
        }

        Map<Integer, List<Integer>> edges = new HashMap<>();
        for (int i=0; i<numCourses; i++) {
            edges.put(i, new ArrayList<>());
        }
        for (int i=0; i < prereq.length; i++) {
            int from = prereq[i][0], to = prereq[i][1];
            if (from == to) {
                return false;
            }
            edges.get(from).add(to);
        }

        Set<Integer> visited = new HashSet<>();
        for (int i=0; i<numCourses; i++) {
            if (!visited.contains(i)) {            
                boolean res = dfs(i, edges, new HashSet<>(), visited);
                if (!res) {
                    return false;
                }
            }
        }
        return true;
    }

    private boolean dfs(int node, Map<Integer, List<Integer>> edges, Set<Integer> ancestors, Set<Integer> visited) {
        visited.add(node);
       
        boolean res = true;
        for (int neighbor : edges.get(node)) {
            if (ancestors.contains(neighbor)) {
                return false;
            }
            Set<Integer> copy = new HashSet<>(ancestors);
            copy.add(node);          
            res = res && dfs (neighbor, edges, copy, visited);
        }        

        return res;
    }
}

// solutie acceptabila

class Solution {
    public boolean canFinish(int numCourses, int[][] prereq) {
        if (prereq.length == 0) {
            return true;
        }

        // init
        Map<Integer, Set<Integer>> edges = new HashMap<>();
        for (int i=0; i<numCourses; i++) {
            edges.put(i, new HashSet<>());
        }

        // build edges & do basic checks
        for (int i=0; i < prereq.length; i++) {
            int from = prereq[i][0], to = prereq[i][1];
            if (from == to) {
                return false;
            }
            if (edges.containsKey(to) && edges.get(to).contains(from)) {
                return false;
            }
            edges.get(from).add(to);
        }

        // simplify, add/remove edges using transitions
        // if 1->2 and 2->3 then 1->3
        // if 3->1 then false
        boolean doContinue = true;
        while (doContinue) {
            doContinue = false;
            Iterator<Integer> aSet = edges.keySet().iterator();
            while (aSet.hasNext()) {
                int a = aSet.next();
                Set<Integer> toAdd = new HashSet<>();
                Set<Integer> toRemove = new HashSet<>();
                Iterator<Integer> bSet = edges.get(a).iterator();
                while (bSet.hasNext()) {
                    int b = bSet.next();
                    if (edges.containsKey(b)) {
                        Iterator<Integer> cSet = edges.get(b).iterator();
                        while (cSet.hasNext()) {
                            int c = cSet.next();
                            if (edges.containsKey(c) && edges.get(c).contains(a)) {
                                return false;
                            }
                            if (!edges.get(a).contains(c)) {
                               // edges.get(a).add(c); // ConcurrentModificationException
                               toAdd.add(c);
                               doContinue = true;
                            }
                        }
                    }
                    // edges.get(a).remove(b); // ConcurrentModificationException
                    toRemove.add(b);
                }

                edges.get(a).removeAll(toRemove);
                edges.get(a).addAll(toAdd);
            }
        }

        return true;
    }
}