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

  • int[] d,d[i]:i号怪兽的能力
    int[] p,p[i]:i号怪兽要求的钱
    开始时你的能力是0,你的目标是从0号怪兽开始,通过所有的怪兽。
    如果你当前的能力,小于i号怪兽的能力,你必须付出p[i]的钱,贿赂这个怪兽,然后怪兽就会加入你,他的能力直接累加到你的能力上;如果你当前的能力,大于等于i号怪兽的能力,你可以选择直接通过,你的能力并不会下降,你也可以选择贿赂这个怪兽,然后怪兽就会加入你,他的能力直接累加到你的能力上。
    返回通过所有的怪兽,需要花的最小钱数。
/**
 * int[] d,d[i]:i号怪兽的能力
 * int[] p,p[i]:i号怪兽要求的钱
 * 开始时你的能力是0,你的目标是从0号怪兽开始,通过所有的怪兽。
 * 如果你当前的能力,小于i号怪兽的能力,你必须付出p[i]的钱,贿赂这个怪兽,
 * 然后怪兽就会加入你,他的能力直接累加到你的能力上;如果你当前的能力,大于等于i号怪兽的能力,
 * 你可以选择直接通过,你的能力并不会下降,你也可以选择贿赂这个怪兽,然后怪兽就会加入你,他的能力直接累加到你的能力上。
 * 返回通过所有的怪兽,需要花的最小钱数。
 */
public class MinMoney {
    // 暴力递归
    public static int minMoney(int[] d,int[] p){
        if(d == null || p == null){
            return 0;
        }
        return process(d,p,0,0);
    }

    // 含义:应经通过了0 ~ index - 1的怪兽,此时的能力是able,index及其往后的的怪兽最少需要多少钱才可以全部通过
    private static int process(int[] d,int[] p,int index,int able){
        if(index == d.length){
            // 如果已经没有怪兽了,那么花费为0
            return 0;
        }
        if(able < d[index]){
            // 此时的能力小于需要通过怪兽的能力,
            // 则只能花钱通过,且获得怪兽的能力
            return process(d,p,index+1,able+d[index])+p[index];
        }else{
            // 能力够,则可花钱,可不花钱
            int p1 = process(d,p,index+1,able);
            int p2 = process(d,p,index+1,able+d[index]) + p[index];
            return Math.min(p1,p2);
        }
    }

    // 动态规划
    public static long dp(int[] d,int[] p){
        int len1 = d.length;
        int len2 = p.length;
        int a = 0;
        for (int i = 0; i < d.length; i++) {
            a+=d[i];
        }
        long[][] dp = new long[len1+1][a+1];
        // dp[i][j],此时来到i号怪兽,能力为j,需要的钱,
        // dp[len1+1][i] = 0;
        for (int i = 0; i <= a; i++) {
            dp[0][i] = 0;
        }
        for(int i = dp.length -2;i>=0;i--){
            for (int j = 0; j <= dp[0].length; j++) {
                if (j + d[i] > a) {
                    continue;
                }
                if(j < d[i]){
                    // 此时的能力小于需要通过怪兽的能力,
                    // 则只能花钱通过,且获得怪兽的能力
                    dp[i][j] =  dp[i+1][j+d[i]]+p[i];
                }else{
                    // 能力够,则可花钱,可不花钱
                    long p1 = dp[i+1][j];
                    long p2 = dp[i+1][j+d[i]]+ p[i];
                    dp[i][j] =  Math.min(p1,p2);
                }
            }
        }

        return dp[0][0];
    }
    
}

1.给定一个字符串,如果只能在后面添加字符,最少添加几个能让字符串整体都是回文串。
2.给定一个字符串,如果可随意添加字符,最少添加几个能让字符串整体都是回文串。

/**
 * 给定一个字符串,如果可随意添加字符,最少添加几个能让字符串整体都是回文串。
 */

// 范围上尝试模型
public class MinAddCharNum {

    public static int minAddCharNum(String str){
        if(str == null){
            return 0;
        }
        // 定义dp数组,dp[i][j] 表示从i位置开始,j位置结束的子串要变成回文串需要添
        // 几个字符
        // i的取值范围 0~str.length(),j的取值范围0~str.length()
        char[] c = str.toCharArray();
        int[][] dp = new int[c.length][c.length];
        //范围上尝试的模型i<=j,除了对角线以外,左下部分都是无效区域
        // 对角线表示i = j时,也就是子串只有一个字符的时候需要添的字符数为0
        // 对角线的上一条斜线表示两个字符时需要添加的字符数,两个字符相等则是0,不等则是1
        for (int i = 0;i < c.length-1;i++){
            dp[i][i+1] = c[i] == c[i+1] ? 0 : 1;
        }

        // 普遍位置i,j
        // 1,c[i] = c[j] ,也就是i,j位置字符相等,那么就看i+1到j-1子串需要添加几个字符变为回文串
        // 2,c[i] != c[j],此时又可分为两种
        //   1),i位置先不管,看i+1~j的子串需要添加几个字符,最后在加上和i位置配对的1个
        //   2),j位置先不管,看i~j-1的子串需要添加几个字符,最后在加上和j位置配对的1个
        //   1),2)中选最小的

        for(int j = 2;j < c.length;j++){
            for (int i = j-2;  i >= 0; i--) {
                if(c[i] == c[j]){
                    dp[i][j] = dp[i+1][j-1];
                }else{
                    int p1 = dp[i+1][j] + 1;
                    int p2 = dp[i][j-1] + 1;
                    dp[i][j] = Math.min(p1,p2);
                }
            }
        }

        return dp[0][c.length-1];
    }

    public static void main(String[] args) {
        String str = "AB1CD2EFG3H43IJK2L1MN";
        System.out.println(minAddCharNum(str));
    }
}

public static String getPalindrome1(String str) {
        if (str == null || str.length() < 2) {
            return str;
        }
        char[] chas = str.toCharArray();
        int[][] dp = getDP(chas);
        char[] res = new char[chas.length + dp[0][chas.length - 1]];
        int i = 0;
        int j = chas.length - 1;
        int resl = 0;
        int resr = res.length - 1;
        while (i <= j) {
            if (chas[i] == chas[j]) {
                res[resl++] = chas[i++];
                res[resr--] = chas[j--];
            } else if (dp[i][j - 1] < dp[i + 1][j]) {
                res[resl++] = chas[j];
                res[resr--] = chas[j--];
            } else {
                res[resl++] = chas[i];
                res[resr--] = chas[i++];
            }
        }
        return String.valueOf(res);
    }
  • 一种消息接收并打印的结构设计,已知一个消息流会不断地吐出整数 1~N,但不一定按照顺序吐出。如果上次打印的数为 i, 那么当 i+1 出现时,请打印 i+1 及其之后接收过的并且连续的所有数,直到 1~N 全部接收 并打印完,请设计这种接收并打印的结构。初始时默认i==0
/**
 * 一种消息接收并打印的结构设计
 * 已知一个消息流会不断地吐出整数 1~N,但不一定按照顺序吐出。如果上次打印的数为 i,
 * 那么当 i+1 出现时,请打印 i+1 及其之后接收过的并且连续的所有数,直到 1~N 全部接收
 * 并打印完,请设计这种接收并打印的结构。初始时默认i==0
 */
public class PrintNum {
    static class Node{
        int no;
        String info;
        Node next;
        public Node(int no,String info){
            this.no = no;
            this.info = info;
        }
    }
    static HashMap head;
    static HashMap tail;
    static int curPrintIndex;
    public PrintNum(){
        this.head = new HashMap<>();
        this.tail = new HashMap<>();
        this.curPrintIndex = 1;
    }

    public static void printNum(int no,String info){

         Node node = new Node(no,info);
        // 当有消息进来获取他是几号消息,加入头表,和尾表
        head.put(node.no,node);
        tail.put(node.no,node);
        // 当前信息的前一个序号是否在尾表中
        if(tail.containsKey(node.no-1)){
            // 如果在围标中,把他从围标中删除
            Node pre = tail.get(node.no - 1);
            tail.remove(node.no-1);
            head.remove(node.no);
            pre.next = node;
        }
        // 在头表中是否有当前节点后一个节点
        if(head.containsKey(node.no+1)){
            // 如果有跟新头表中的头为当前节点
            Node next = head.get(node.no + 1);
            node.next = next;
            tail.remove(next.no);
            head.remove(next.no);
        }
        // 如果当前来到的节点刚好是要打印的序列则打印
        if(node.no == curPrintIndex){
            print(node);
            System.out.println(" ");
        }
    }

    private static void print(Node node){
        // 打印节点并在头尾表中删除
        Node cur = node;
        while (cur != null){
            System.out.print(cur.info + " ");
            cur = cur.next;
            curPrintIndex ++;

        }
        head.remove(node.no);
        tail.remove(curPrintIndex -1);
    }
}

  • 现有n1+n2种面值的硬币,其中前n1种为普通币,可以取任意枚,后n2种为纪念币,
    每种最多只能取一枚,每种硬币有一个面值,问能用多少种方法拼出m的面值?
/**
 * 现有n1+n2种面值的硬币,其中前n1种为普通币,可以取任意枚,后n2种为纪念币,
 * 每种最多只能取一枚,每种硬币有一个面值,问能用多少种方法拼出m的面值?
 */
public class MoneyNum {

    public static int moneyNum(int[] arr1,int[] arr2,int m){
        if(arr1 == null && arr2 == null){
            return 0;
        }

        // 对普通币求动态规划
        int len1 = arr1.length;
        int[][] dp1 = new int[len1][m+1];
        // dp[i][j] 表示0~号位置的货币随意选,刚好筹够j元的方法数
        // 第一列,货币随便选,凑够0元的方法数
        for (int i = 0; i < len1; i++) {
            // 凑出0元都只有一种方法,那就是什么都选
            dp1[i][0] = 1;
        }
        // 第一行
        for (int i = 0; i <= m; i++) {
            dp1[0][i] = i % arr1[0] == 0 ? 1 : 0;
        }

        for (int i = 0; i < len1; i++) {
            for (int j = 0; j <= m; j++) {
                dp1[i][j] = dp1[i-1][j] + (j - arr1[i] >= 0 ? dp1[i][j-arr1[i]] : 0);
            }
        }

        int len2 = arr2.length;
        // 纪念币
        int[][] dp2 = new int[len1][m+1];
        for (int i = 0; i < len2; i++) {
            // 凑出0元都只有一种方法,那就是什么都选
            dp2[i][0] = 1;
        }
        for (int i = 0; i <= m; i++) {
            dp2[0][i] = arr1[0]== m  ? 1 : 0;
        }


        for (int i = 0; i < len2; i++) {
            for (int j = 0; j <= m; j++) {
                dp2[i][j] = dp1[i-1][j] + (j-arr2[i] >= 0 ?dp2[i-1][j-arr2[i]] : 0);
            }
        }

        int res = 0;
        for (int i = 0; i <= m; i++) {
            res += dp1[len1-1][i] * dp2[len2-1][m - i];
        }
        return res;
    }

    public static void main(String[] args) {
        
    }
}

  • 给定一个正数N,表示你在纸上写下1~N所有的数字,返回在书写的过程中,一共写下了多少个1
  • 先给出可整合数组的定义:如果一个数组在排序之后,每相邻两个数差的绝对值 都为 1, 则该数组为可整合数组。例如,[5,3,4,6,2]排序之后为[2,3,4,5,6], 符合每相邻两个数差的绝对值 都为 1,所以这个数组为可整合数组。 给定一个整型数组 arr,请返回其中最大可整合子数组的长度。例如, [5,5,3,2,6,4,3]的最大 可整合子数组为[5,3,2,6,4],所以返回 5。
/**
 * 先给出可整合数组的定义:如果一个数组在排序之后,每相邻两个数差的绝对值 都为 1,
 * 则该数组为可整合数组。例如,[5,3,4,6,2]排序之后为[2,3,4,5,6],
 * 符合每相邻两个数差的绝对值 都为 1,所以这个数组为可整合数组。 给定一个整型数组 arr,
 * 请返回其中最大可整合子数组的长度。例如, [5,5,3,2,6,4,3]的最大 可整合子数组为[5,3,2,6,4],所以返回 5。
 */
public class KeZhengHeShu {
    public static int getMaxLen(int[] arr){
        if(arr == null || arr.length == 0){
            return 0;
        }

        // 题中可整合数的定义: 1.排好序后,依次递增,且增量为1.
        // 暴力方法枚举所有数组(O(n^2)),排序子数组(O(nlogn)) -> O(n^3logn)
        // 重新定可整合数: 1.子数组中没有重复值,2.子数组中最大值减最小值等于元素个数减1

        // 最大的可整合数
        int res = 0;
        Set set = new HashSet<>();
        // 枚举所有的子数组
        for (int i = 0; i < arr.length; i++) {
            set.clear();
            set.add(arr[i]);
            int max = arr[0];
            int min = arr[0];
            for (int j = i+1; j < arr.length; j++) {
                if(set.contains(arr[j])){
                    // 换头
                    break;
                }
                set.add(arr[j]);
                min = Math.min(arr[j],min);
                max = Math.max(arr[j],max);
                if(max - min == set.size()-1){
                    res = Math.max(res,set.size());
                }
            }
        }
        return res;
    }

    public static void main(String[] args) {
        int[] arr = { 5, 5, 3, 2, 6, 4, 3 };
        System.out.println(getMaxLen(arr));
    }
}

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