数据结构与算法学习笔记(训练营三)-经典面试五

  • 给定两个字符串str1和str2,再给定三个整数ic、dc和rc,分别代表插入、删 除和替换一个字符的代价,返回将str1编辑成str2的最小代价。
    【举例】
    str1="abc",str2="adc",ic=5,dc=3,rc=2 从"abc"编辑成"adc",把'b'替换成'd'是代价最小的,所以返回2
    str1="abc",str2="adc",ic=5,dc=3,rc=100 从"abc"编辑成"adc",先删除'b',然后插入'd'是代价最小的,所以返回8
    str1="abc",str2="abc",ic=5,dc=3,rc=2 不用编辑了,本来就是一样的字符串,所以返回0
/**
 * 给定两个字符串str1和str2,再给定三个整数ic、dc和rc,分别代表插入、删 除和替换一个字符的代价,返回将str1编辑成str2的最小代价。
 * 【举例】
 * str1="abc",str2="adc",ic=5,dc=3,rc=2 从"abc"编辑成"adc",把'b'替换成'd'是代价最小的,所以返回2
 * str1="abc",str2="adc",ic=5,dc=3,rc=100 从"abc"编辑成"adc",先删除'b',然后插入'd'是代价最小的,所以返回8
 * str1="abc",str2="abc",ic=5,dc=3,rc=2 不用编辑了,本来就是一样的字符串,所以返回0
 */
public class MinEditorDistance {

    public static int minEditorDistance(String str1,String str2,int ic,int dc,int rc){
        if(str1 == null && str2 == null){
            return 0;
        }

        // 申请一个dp表dp[i][j]表示,str1有i个字符,str2有j个字符,从str1编辑为str2的最小代价
        // 多申请一个位置,i= 0表示str1是空字符串,j=0表示str2是空字符串
        char[] c1 = str1.toCharArray();
        char[] c2 = str2.toCharArray();
        int len1 = c1.length;
        int len2 = c2.length;
        int[][] dp = new int[len1+1][len2+1];
        // 从空字符串变成空字符串代价为0
        dp[0][0] = 0;

        // 第一行表示str1为空字符串变成str2的字符串需要的代价,必定是一个j倍的插入代价(有几个字符就有几个插入代价)
        for (int i = 1; i <= len2; i++) {
            dp[0][i] = i * ic;
        }

        // 第一列表示str1是有i个字符的,要变成str2空字符串需要的代价是i个删除待机
        for (int i = 1; i <= len1; i++) {
            dp[i][0] = i*dc;
        }

        // 普通位置的可能性
        // 1.str1,str2字符最后一个字符相等,那就看str1的0~i-1,位置变成str2的0~ j-1需要多少代价
        // 2.str1,str2字符最后一个字符不相等,需要忍受一个替换代价
        // 1.2只会同时满足一个
        // 3.用str1 0 ~ i-1,之前的变成str2,最后在删除str1i位置的元素需要一个删除代价
        // 4.用str1 0~i位置编辑成str2 0~j-1的字符串,最后在str1i+1位置添加一个,需要一个插入代价

        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                if(c1[i-1] == c2[j-1]){
                    dp[i][j] = dp[i-1][j-1];
                }else {
                    dp[i][j] = dp[i-1][j-1] + rc;
                }
                int p3 = dp[i-1][j] + dc;
                int p2 = dp[i][j-1] + ic;
                dp[i][j] = Math.min(dp[i][j],Math.min(p2,p3));
            }
        }
        return dp[len1][len2];
    }
    
}

  • 给定两个字符串s1和s2,问s2最少删除多少字符可以成为s1的子串?比如 s1 = "abcde",s2 = "axbc"返回1。s2删掉'x'就是s1的子串了。
/**
 * 给定两个字符串s1和s2,问s2最少删除多少字符可以成为s1的子串?
 * 比如 s1 = "abcde",s2 = "axbc"
 * 返回1。s2删掉'x'就是s1的子串了。
 */
public class DeleteMIinChar {

    public static int deleteMinChar(String str1,String str2){
        if(str1 == null || str2.length() == 0){
            return 0;
        }

        // 枚举str1所有的子串,看看str2通过最小编辑距离能否成为str1的子串,
        // 此最小编辑距离只有删除操作,可以吧删除的代价看做是一

        int res = Integer.MAX_VALUE;
        for (int i = 0; i < str1.length(); i++) {
            // i是子串的开头
            for (int j = i+1; j <= str1.length(); j++) {
                // 由于用到了substring左闭右开所以子串的结尾是j+1,
                // 每个子串取做最小编辑距离
                res = Math.min(res,minEditorDistance(str2,str1.substring(i,j),1));
            }
        }
        return res;
    }

    // 阉割版,只有删除操作
    private static int minEditorDistance(String str1,String str2,int dc){
        if(str1 == null && str2 == null){
            return 0;
        }

        // 申请一个dp表dp[i][j]表示,str1有i个字符,str2有j个字符,从str1编辑为str2的最小代价
        // 多申请一个位置,i= 0表示str1是空字符串,j=0表示str2是空字符串
        char[] c1 = str1.toCharArray();
        char[] c2 = str2.toCharArray();
        int len1 = c1.length;
        int len2 = c2.length;
        int[][] dp = new int[len1+1][len2+1];
        // 从空字符串变成空字符串代价为0
        dp[0][0] = 0;

        // 第一行表示str1为空字符串变成str2的字符串需要的代价,只有删除操作无法完成
        for (int i = 1; i <= len2; i++) {
            dp[0][i] = Integer.MAX_VALUE;
        }

        // 第一列表示str1是有i个字符的,要变成str2空字符串需要的代价是i个删除待机
        for (int i = 1; i <= len1; i++) {
            dp[i][0] = i*dc;
        }

        // 普通位置的可能性
        // 1.str1,str2字符最后一个字符相等,那就看str1的0~i-1,位置变成str2的0~ j-1需要多少代价
        // 2.str1,str2字符最后一个字符不相等,需要忍受一个替换代价
        // 1.2只会同时满足一个
        // 3.用str1 0 ~ i-1,之前的变成str2,最后在删除str1i位置的元素需要一个删除代价
        // 4.用str1 0~i位置编辑成str2 0~j-1的字符串,最后在str1i+1位置添加一个,需要一个插入代价

        for (int i = 1; i <= len1; i++) {
            for (int j = 1; j <= len2; j++) {
                // 假设无法编辑
                dp[i][j] = Integer.MAX_VALUE;
                if(dp[i-1][j] != Integer.MAX_VALUE){
                    dp[i][j] = dp[i-1][j] + dc;
                }
                if(c1[i-1] == c2[j-1] && dp[i-1][j-1] != Integer.MAX_VALUE){
                    dp[i][j] = Math.min(dp[i][j],dp[i-1][j-1]);
                }
            }
        }
        return dp[len1][len2];
    }


    public static void main(String[] args) {
        String str1 = "abc";
        String str2 = "avmlakds";
        System.out.println(deleteMinChar(str1,str2));
    }
}

  • 求完全二叉树节点的个数,要求时间复杂度低于O(N)
/**
 * 求完全二叉树节点的个数
 * 要求时间复杂度低于O(N)
 */
public class NodeNum {

    public static int nodeNum(Node head){
        if(head == null){
            return 0;
        }

        // 流程,先求树的高度h,然后再求右树的高度r
        // 1.如果右树的高度小于深度,那么右树就是一棵满二叉树
        // 2.如果右树高度等于深度,则左树是一棵满二叉树,然后递归调用不是满的一遍
        return process(head,1,treeH(head,1));
    }

    // 给你一个节点,和现在的层数,返回node为头的节点个数
    private static int process(Node node,int level,int h){
        if(level == h){
            return 1;
        }
        int i = treeH(node.right, level + 1);
        int sum = 0;
        if(i == h){
            sum = (1 << (h - level)) + process(node.right,level+1,h);
        }else{
            sum = (1 << (h - level-1)) + process(node.left,level+1,h);
        }
        return sum;
    }

    // 计算node为头的二叉树的高度
    private static int treeH(Node node,int level){
        if(node == null){
            return 0;
        }
        int l = level;
        Node cur = node.left;
        while (cur != null){
            l++;
            cur = cur.left;
        }
        return l;
    }

    public static void main(String[] args) {
        Node head = new Node(1);
        head.left = new Node(2);
        head.right = new Node(3);
        head.left.left = new Node(4);
        head.left.right = new Node(5);
        head.right.left = new Node(6);
        System.out.println(nodeNum(head));
    }
}

  • LRU内存替换算法的实现
public class Code04_LeastRecentlyUsedCache {

    public static class Node {
        public K key;
        public V value;
        public Node last;
        public Node next;

        public Node(K key, V value) {
            this.key = key;
            this.value = value;
        }
    }

    // 双向链表
    // 从head到tail所有节点都是串好的
    public static class NodeDoubleLinkedList {
        private Node head;
        private Node tail;

        public NodeDoubleLinkedList() {
            head = null;
            tail = null;
        }

        // 如果一个新的节点加入,放到尾巴上去
        public void addNode(Node newNode) {
            if (newNode == null) {
                return;
            }
            // newNode != null
            if (head == null) { // 双向链表中一个节点也没有
                head = newNode;
                tail = newNode;
            } else { // 双向链表中之前有节点,tail(非null)
                tail.next = newNode;
                newNode.last = tail;
                tail = newNode;
            }
        }

        // 潜台词 : 双向链表上一定有这个node
        // node分离出,但是node前后环境重新连接
        // node放到尾巴上去
        public void moveNodeToTail(Node node) {
            if (this.tail == node) {
                return;
            }
            if (this.head == node) { // 当前node是老头部
                this.head = node.next;
                this.head.last = null;
            } else { // 当前node是中间的一个节点
                node.last.next = node.next;
                node.next.last = node.last;
            }
            node.last = this.tail;
            node.next = null;
            this.tail.next = node;
            this.tail = node;
        }

        // 把头节点删掉并返回
        public Node removeHead() {
            if (this.head == null) {
                return null;
            }
            Node res = this.head;
            if (this.head == this.tail) { // 链表中只有一个节点的时候
                this.head = null;
                this.tail = null;
            } else {
                this.head = res.next;
                res.next = null;
                this.head.last = null;
            }
            return res;
        }

    }

    public static class MyCache {
        private HashMap> keyNodeMap;
        private NodeDoubleLinkedList nodeList;
        private final int capacity;

        public MyCache(int cap) {
            if (cap < 1) {
                throw new RuntimeException("should be more than 0.");
            }
            keyNodeMap = new HashMap>();
            nodeList = new NodeDoubleLinkedList();
            capacity = cap;
        }

        public V get(K key) {
            if (keyNodeMap.containsKey(key)) {
                Node res = keyNodeMap.get(key);
                nodeList.moveNodeToTail(res);
                return res.value;
            }
            return null;
        }

        public void set(K key, V value) {
            if (keyNodeMap.containsKey(key)) {
                Node node = keyNodeMap.get(key);
                node.value = value;
                nodeList.moveNodeToTail(node);
            } else { // 这是一个新加的记录,有可能出现替换
                if (keyNodeMap.size() == capacity) {
                    removeMostUnusedCache();
                }
                Node newNode = new Node(key, value);
                keyNodeMap.put(key, newNode);
                nodeList.addNode(newNode);
            }
        }

        private void removeMostUnusedCache() {
            Node removeNode = nodeList.removeHead();
            keyNodeMap.remove(removeNode.key);
        }

    }

    public static void main(String[] args) {
        MyCache testCache = new MyCache(3);
        testCache.set("A", 1);
        testCache.set("B", 2);
        testCache.set("C", 3);
        System.out.println(testCache.get("B"));
        System.out.println(testCache.get("A"));
        testCache.set("D", 4);
        System.out.println(testCache.get("D"));
        System.out.println(testCache.get("C"));

    }

}
  • 给定两个字符串,记为start和to,再给定一个字符串列表list,list中一定包含to list中没有重复字符串,所有的字符串都是小写的。
    规定: start每次只能改变一个字符,最终的目标是彻底变成to,但是每次变成的新字符串必须在list 中存在。
    请返回所有最短的变换路径。
    【举例】
    start="abc",end="cab",list={"cab","acc","cbc","ccc","cac","cbb","aab","abb"}
    转换路径的方法有很多种,但所有最短的转换路径如下:
    abc -> abb -> aab -> cab
    abc -> abb -> cbb -> cab
    abc -> cbc -> cac -> cab
    abc -> cbc -> cbb -> cab
    流程: 生成每个字符串的邻居字符串列表,邻居字符串的定义是:一个字符串通过改变一个字符就可以变成另一个字符串,那么另一个字符串就是这个字符的邻居。
/**
 * 给定两个字符串,记为start和to,再给定一个字符串列表list,list中一定包含to list中没有重复字符串,所有的字符串都是小写的。
 * 规定: start每次只能改变一个字符,最终的目标是彻底变成to,但是每次变成的新字符串必须在list 中存在。
 * 请返回所有最短的变换路径。
 * 【举例】
 * start="abc",end="cab",list={"cab","acc","cbc","ccc","cac","cbb","aab","abb"}
 * 转换路径的方法有很多种,但所有最短的转换路径如下:
 * abc -> abb -> aab -> cab
 * abc -> abb -> cbb -> cab
 * abc -> cbc -> cac -> cab
 * abc -> cbc -> cbb -> cab
 */
public class MinPath {
    public static List> minPath(String start,String to,List list){
        if(list == null || !list.contains(to)){
            return null;
        }

        HashMap> stringListHashMap = neighborList(list);
        HashMap distance = distance(start, stringListHashMap);
        LinkedList pathList = new LinkedList<>();
        List> res = new ArrayList<>();
        getShortestPaths(start,to,stringListHashMap,distance,pathList,res);
        return res;

    }
    // 邻居表
    private static HashMap> neighborList(List list){
        // 生成字符串的邻居表
        HashMap> neighborList = new HashMap<>();
        for (String item : list){
            // 每个字符每个位置都在a~z中变换,看list中是否存在,存在则这个字符串就是他的邻居
            char[] c = item.toCharArray();
            List temp = new ArrayList<>();
            for (int i = 0; i < c.length; i++) {
                for (char j = 'a'; j <= 'z' ; j++) {
                    if(c[i] != j){
                        char o = c[i];
                        c[i] = j;
                        String s = String.valueOf(c);
                        if(list.contains(s)){
                            temp.add(s);
                        }
                        c[i] = o;
                    }
                }

            }
            neighborList.put(item,temp);
        }
        return neighborList;
    }

    // 生成一个距离表,表示每个字符串到start字符春的距离
    private static HashMap distance(String start,HashMap> map){
        Set set = new HashSet<>();
        Queue queue = new LinkedList();
        queue.offer(start);
        HashMap distance = new HashMap<>();
        distance.put(start,0);
        set.add(start);
        while (!queue.isEmpty()){
            String poll = queue.poll();
            List list = map.get(poll);
            for (String item : list){
                if(!set.contains(item)){
                    distance.put(item,distance.get(poll) + 1);
                    queue.offer(item);
                    set.add(item);
                }
            }
        }

        return distance;
    }

    private static void getShortestPaths(
            String cur, String to,
            HashMap> nexts,
            HashMap distances,
            LinkedList path,
            List> res) {
        path.add(cur);
        if (to.equals(cur)) {
            res.add(new LinkedList(path));
        } else {
            for (String next : nexts.get(cur)) {
                if (distances.get(next) == distances.get(cur) + 1) {
                    getShortestPaths(next, to, nexts, distances, path, res);
                }
            }
        }
        path.pollLast();
    }
    
}

你可能感兴趣的:(数据结构与算法学习笔记(训练营三)-经典面试五)