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

  • 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<Integer,Node> head;
    static HashMap<Integer,Node> 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<Integer> 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));
    }
}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355

推荐阅读更多精彩内容