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

  • 一个数组的异或和是指数组中所有的数异或在一起的结果,给定一个数组arr,求最大子数组异或和。
    1.思路一:利用预处理数组求出以每个位置结尾时,从0位置到结尾位置的异或和,由于eor(i) = eor(0~j) ^ eor(j+1~i) -> eor(j+1~i) = eor(0~i) ^ eor(0j)加速求异或和。我们可以以i位置结尾,0i,1i,2i,...,i~i,分别求出异或和取最大值,每个i位置都求一遍找到最大子数组异或和。
    2.思路二利用前缀树(把树转化为二进制)。前缀树种有所有从0位置开始到i-1位置的前缀树记录,当要求以i位置结尾时的最大子数组前缀和时,我们不知道前面从哪个位置开始和i位置的数异或起来是最大的,我们可以求出现在从0位置到i位置的异或和,然后在前缀树中利用贪心帮我们找一个最大的前缀和。下面是利用前缀树求的代码。如果最高位是0,那么他希望遇到和他相反的数1,后面的位都希望遇到相反的数,那么该位还是1,如果最高位是1那么希望遇到那么希望遇到1让他最高位变为0成为一个整数,后续希望遇到和他相反的数成为1.时间复杂度O(N)
/**
 *  一个数组的异或和是指数组中所有的数异或在一起的结果,给定一个数组arr,求最大子数组异或和
 */
public class MaxSubArrayEorSum {
    static class Node{
        // 只有两个分支0,或者1
        Node[] nexts;
        public Node(){
            // 0位置就代表位0,一位置就代表位1
            this.nexts = new Node[2];
        }
    }
    // 前缀树中保存了0~i-1,的所有异或和
    // 前缀树,两个功能向前缀树中加入一个树,形成前缀树
    // 给定一个数,求出和前缀树中的最大异或和
    static class PrefixTree{
        // 定义前缀树根节点
        Node root;
        public PrefixTree(){
            // node 中的 nexts 0和1位置分别代表是否存在0和1这条路
            root = new Node();
        }

        // 向前缀树中添加元素
        public  void add(int num){
            Node cur = root;
            // 从最高位开始添加,int类型的值为32位
            for (int index = 31; index >= 0; index--) {
                // 当前的index位是几(0或者1)
                int bit = (num >> index) & 1;
                // 有就复用,没有就新建
                cur.nexts[bit] = cur.nexts[bit] == null ? new Node() : cur.nexts[bit];
                cur = cur.nexts[bit];
            }

        }

        // 给定一个数,求出和前缀树中的最大异或和
        public  int getMaxEorSum(int num){
            Node cur = root;
            // 选择哪条路
            int path;
            int res = 0;
            // 从最高位开始选
            for (int index = 31; index >= 0 ; index--) {
                // 当前的index位是几(0或者1)
                int bit = (num >> index) & 1;
                // 如果bit是最高位
                // 1.bit == 1,那么他会选择1这跳路径,使其变为0(整数)
                // 2.bit == 0,那么他会选择0,这条路,使其保持整数。
                if(index == 31){
                    path = bit == 1 ? 1 : 0 ;
                }else{
                    // 普遍位置
                    path = bit == 1 ? 0 : 1;
                }
                // 判断这条路径有没有路,如果没有路那么只能选另一条路(不可能每条路都是空,初始时会把0加入前缀树,代表前缀和为0)
                int temp = cur.nexts[path] == null ? path ^ 1 : path;
                // temp就是最高位的值了
                res |= (temp ^ bit) << index;
                cur = cur.nexts[temp];
            }
            return res;
        }

    }
    public static int maxSubArrayEorSum(int[] arr){
        if(arr == null || arr.length == 0){
            return 0;
        }
        // 构建前缀树
        PrefixTree prefixTree = new PrefixTree();
        // 开始前缀树为空,里面的前缀和为0;
        prefixTree.add(0);
        int max = Integer.MIN_VALUE;
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            // 0到i位置的异或和
            eor ^= arr[i];
            int maxEorSum = prefixTree.getMaxEorSum(eor);
            max = Math.max(max,maxEorSum);
            prefixTree.add(eor);
        }
        return max;
    }  
}

  • 给定一个只由 0(假)、1(真)、&(逻辑与)、|(逻辑或)和^(异或)五种字符组成 的字符串express,再给定一个布尔值 desired。返回express能有多少种组合 方式,可以达到desired的结果。
    【举例】
    express="1^0|0|1",desired=false
    只有 1^((0|0)|1)和 1^(0|(0|1))的组合可以得到 false,返回 2。 express="1",desired=false
    无组合则可以得到false,返回0
/**
 * 给定一个只由 0(假)、1(真)、&(逻辑与)、|(逻辑或)和^(异或)五种字符组成 的字符串express,
 * 再给定一个布尔值 desired。返回express能有多少种组合 方式,可以达到desired的结果。
 * 【举例】
 * express="1^0|0|1",desired=false
 * 只有 1^((0|0)|1)和 1^(0|(0|1))的组合可以得到 false,返回 2。 express="1",desired=false
 * 无组合则可以得到false,返回0
 */
public class BooleanNum {
    // 暴力递归
    public static int booleanNum(String express,boolean desired){
        // 如果字符串为空,或者是空字符串不能组成
        if(express == null || express.equals("")){
            return 0;
        }
        // 前置校验字符串是否符合要求
        // 字符串必须为偶数,奇数位置必须为逻辑运算符,偶数位置必须为 0,1
        if(!isValid(express)){
            return 0;
        }

        // 范围上尝试的模型
        char[] chars = express.toCharArray();

        return process(chars,0,chars.length - 1,desired);

    }

    // 在L -> R 上返回满足desired 的组合数
    private static int process(char[] c,int L,int R,boolean desired){
        if(L == R){
            if(desired){
                return c[L] == '1' ? 1 : 0;
            }else{
                return c[L] == '0' ? 1 : 0;
            }
        }

        // 假设以i字符作为最后的运算符
        int sum = 0;
        for(int i = L+1; i < R;i += 2){
           // 左边符合的个数,右边符合的个数
            if(desired){
                // 如果是true
                switch (c[i]){
                    case '|': sum += process(c,L,i-1,true) * process(c,i+1,R,true) +
                            process(c,L,i-1,true) * process(c,i+1,R,false) +
                            process(c,L,i-1,false) * process(c,i+1,R,true);
                        break;
                    case '&': sum += process(c,L,i-1,true) * process(c,i+1,R,true);
                        break;
                    case '^': sum += process(c,L,i-1,false) * process(c,i+1,R,true) +
                            process(c,L,i-1,true) * process(c,i+1,R,false);
                        break;
                }

            }else {
                switch (c[i]){
                    case '|': sum += process(c,L,i-1,false) * process(c,i+1,R,false);
                        break;
                    case '&': sum += process(c,L,i-1,true) * process(c,i+1,R,false)+
                            process(c,L,i-1,false) * process(c,i+1,R,true)+
                            process(c,L,i-1,false) * process(c,i+1,R,false);
                        break;
                    case '^': sum += process(c,L,i-1,false) * process(c,i+1,R,false) +
                            process(c,L,i-1,true) * process(c,i+1,R,true);
                        break;
                }


            }
        }

        return sum;
    }

    // 动态规划
    // 有三个可变参数,第三个参数只有两种情况,建立两张二维表就够了
    public static int dp(String express,boolean desired){
        // 如果字符串为空,或者是空字符串不能组成
        if(express == null || express.equals("")){
            return 0;
        }
        // 前置校验字符串是否符合要求
        // 字符串必须为偶数,奇数位置必须为逻辑运算符,偶数位置必须为 0,1
        if(!isValid(express)){
            return 0;
        }
        char[] c = express.toCharArray();
        int n = c.length;
        int[][] trueDp = new int[n][n];
        int[][] falseDp = new int[n][n];
        // 由于是范围上尝试模型表的左下半区域全都无效
        // trueDp表 奇数列奇数行不用填
        for (int i =0;i < n; i+=2){
            trueDp[i][i] = c[i] == '1' ? 1 : 0;
            falseDp[i][i] = c[i] == '0' ? 1 : 0;
        }

        for (int row = n - 3; row >= 0; row = row - 2) {
            for (int col = row + 2; col < n; col = col + 2) {
                for(int i = row+1; i < col;i += 2){
                    // 左边符合的个数,右边符合的个数

                        // 如果是true
                        switch (c[i]){
                            case '|': trueDp[row][col] += trueDp[row][i-1] * falseDp[i+1][col] +
                                                         falseDp[row][i-1] * trueDp[i+1][col] +
                                                         trueDp[row][i-1] * trueDp[i+1][col];
                                break;
                            case '&':trueDp[row][col] += trueDp[row][i-1] * trueDp[i+1][col];
                                break;
                            case '^': trueDp[row][col]+= trueDp[row][i-1] * falseDp[i+1][col]+
                                                        falseDp[row][i-1]* trueDp[i+1][col];
                                break;
                        }


                        switch (c[i]){
                            case '|': falseDp[row][col]+= falseDp[row][i-1]* falseDp[i+1][col];
                                break;
                            case '&': falseDp[row][col]+= trueDp[row][i-1] * falseDp[i+1][col]+
                                                         falseDp[row][i-1] * trueDp[i+1][col]+
                                                         falseDp[row][i-1] * falseDp[i+1][col];
                                break;
                            case '^': falseDp[row][col]+= trueDp[row][i-1] * trueDp[i+1][col] +
                                                         falseDp[row][i-1] * falseDp[i+1][col];
                                break;
                        }



                }
            }
        }
        return desired ? trueDp[0][n-1]:falseDp[0][n-1];

    }

    private static boolean isValid(String express){
        char[] c = express.toCharArray();
        // 是否为奇数长度
        if(express.length() % 2 == 0){
            return false;
        }
        for (int i = 0; i < c.length; i++) {
            if(i % 2 == 0 && (c[i] != '1' && c[i] != '0')){
                // 偶数位置必须为01
                return false;
            }else if(i % 2 != 0 && (c[i] != '|' && c[i] != '&' && c[i] != '^')){
                return false;
            }
        }
        return true;
    }
}

  • 给出一组正整数arr,你从第0个数向最后一个数,每个数的值表示你从这个位置可以向右跳跃的最大长度,计算如何以最少的跳跃次数跳到最后一个数。
/**
 * 给出一组正整数arr,你从第0个数向最后一个数,
 * 每个数的值表示你从这个位置可以向右跳跃的最大长度
 * 计算如何以最少的跳跃次数跳到最后一个数。
 */
public class MinStepNum {
    public static int minStepNum(int[] arr){
        if(arr == null || arr.length == 0){
            return 0;
        }

        // 当前跳了几步
        int step = 0;
        // 当前步数的最右距离
        int right = 0;
        // 如果在多跳一步能跳到哪里
        int next = arr[0];
        int n = arr.length;
        for (int i = 0; i < n; i++) {
            if(i > right){
                // 需要增加步数
                step++;
                right = next;
            }
            if(i+arr[i] > next){
                next = i + arr[i];
            }
        }
        return step;
    }
    public static void main(String[] args) {
        int[] arr = { 3, 2, 3, 1, 1, 4 };
        System.out.println(minStepNum(arr));
    }

}

  • 给定两个有序数组arr1和arr2,再给定一个正数K,求两个数累加和最大的前K个,两个数必须分别来自arr1和arr2
/**
 * 给定两个有序数组arr1和arr2,再给定一个正数K
 * 求两个数累加和最大的前K个,两个数必须分别来自arr1和arr2
 */
public class MaxSum {
    static class Node{
        // arr1中的下标
        int index1;
        // arr2中的下标
        int index2;
        // 累加和
        int sum;
        public Node(int index1,int index2,int sum){
            this.index1 = index1;
            this.index2 = index2;
            this.sum = sum;
        }
    }
    // 大根堆比较器
    static class MaxHeapComparator implements Comparator<Node>{

        @Override
        public int compare(Node o1, Node o2) {
            return o2.sum - o1.sum ;
        }
    }
    public static int[] maxSum(int[] arr1,int[] arr2,int k){
        if(arr1 == null || arr2 == null || arr1.length == 0 || arr2.length == 0){
            return null;
        }
        int n1 = arr1.length;
        int n2 = arr2.length;
        k = Math.min(k,arr1.length*arr2.length);
        // 大根堆
        PriorityQueue<Node> heap = new PriorityQueue(new MaxHeapComparator());
        // 去重表
        boolean[][] set = new boolean[arr1.length][arr2.length];
        int[] res = new int[k];
        int index = 0;
        heap.offer(new Node(n1 -1,n2-1,arr1[n1-1]+arr2[n2-1]));
        set[n1-1][n2-1] = true;
        while (index < k && !heap.isEmpty()){
            Node poll = heap.poll();
            res[index++] = poll.sum;
            // 当前位置的左边和上边加入到大根堆中,这两个位置的和是除了当前位置最大的值
            if(poll.index1-1 >=0 && !set[poll.index1-1][poll.index2]){
                heap.offer(new Node(poll.index1-1,poll.index2,arr1[poll.index1-1]+arr2[poll.index2]));
                set[poll.index1-1][poll.index2] = true;
            }
            if(poll.index2-1 >=0 && !set[poll.index1][poll.index2-1]){
                heap.offer(new Node(poll.index1,poll.index2-1,arr1[poll.index1]+arr2[poll.index2-1]));
                set[poll.index1][poll.index2-1] = true;
            }

        }
        return res;

    }

}

给定一个正数数组arr,返回该数组能不能分成4个部分,并且每个部分的累加和相等,切分位置的数不要。
例如:
arr=[3, 2, 4, 1, 4, 9, 5, 10, 1, 2, 2] 返回true
三个切割点下标为2, 5, 7. 切出的四个子数组为[3,2], [1,4], [5], [1,2,2],
累加和都是5

package newyangfei.trainingcamq3.day06;

import java.util.HashMap;

/**
 * 给定一个正数数组arr,返回该数组能不能分成4个部分,并且每个部分的累加和相等,切分位置的数不要。
 * 例如:
 * arr=[3, 2, 4, 1, 4, 9, 5, 10, 1, 2, 2] 返回true
 * 三个切割点下标为2, 5, 7. 切出的四个子数组为[3,2], [1,4], [5], [1,2,2],
 * 累加和都是5
 */
public class CutArray {
    public static boolean cutArray(int[] arr){
        if(arr == null || arr.length < 7){
            // 切成四部分,至少也要七个数
            return false;
        }
        // 用一个map记录某个位置的前缀和
        HashMap<Integer,Integer> map = new HashMap<>();
        // 生成前缀和位置表
        int sum = arr[0];
        for (int i = 1; i < arr.length; i++) {
            map.put(sum,i);
            sum += arr[i];

        }
        int n = arr.length;
        // 由题意可知,第一刀的位置必须从第二个元素开始(下标1),不能超过n-6
        int a = arr[0];
        for (int i = 1; i < n-5; i++) {
            // 枚举所有第一刀的可能性
            // 假如在i位置切了第一刀,那么我们可以得到0~i-1的累加和假设为a,
            // 则第二刀的位置的前缀和一定等于a + a + arr[i]
            // 第三刀的位置前缀和一定满足 a+ a+a + arr[i] + arr[i2](第二刀的位置)
            // 如果前面都存在,验证最后一段是否等于a
            if(map.containsKey(a) && map.get(a) == i){
                // 第一刀存在才继续往下,否则枚举下一个第一刀的位置
                // 如果有第二刀,第二刀应该满足的前缀和
                int cut2 = 2*a+arr[i];
                if(map.containsKey(cut2)){
                    // 有第二刀才继续往下进行切分
                    // 第三道应该满足
                    int cut3 = 3*a+arr[i]+arr[map.get(cut2)];
                    if(map.containsKey(cut3)){
                        // 有第三单,判断是否第四部分也是a
                        int index = map.get(cut3);
                        int lastSum = cut3 + arr[index] + a;
                        if(lastSum == sum){
                            return true;
                        }

                    }
                }
            }
            a += arr[i];
        }

        return false;
    }

}

  • 给定三个字符串str1、str2和aim,如果aim包含且仅包含来自str1和str2的所有字符, 而且在aim中属于str1的字符之间保持原来在str1中的顺序,属于str2的字符之间保持 原来在str2中的顺序,那么称aim是str1和str2的交错组成。实现一个函数,判断aim是 否是str1和str2交错组成
    【举例】 str1="AB",str2="12"。那么"AB12"、"A1B2"、"A12B"、"1A2B"和"1AB2"等都是 str1 和 str2 的 交错组成

/**

  • 给定三个字符串str1、str2和aim,如果aim包含且仅包含来自str1和str2的所有字符,
  • 而且在aim中属于str1的字符之间保持原来在str1中的顺序,属于str2的字符之间保持 原来在str2中的顺序,
  • 那么称aim是str1和str2的交错组成。实现一个函数,判断aim是 否是str1和str2交错组成
  • 【举例】 str1="AB",str2="12"。那么"AB12"、"A1B2"、"A12B"、"1A2B"和"1AB2"等都是 str1 和 str2 的 交错组成
    */
public class Staggered {
    public static boolean isStaggered(String str1,String str2,String aim){
        if(str1 != null && str2 != null && str1.length()+str2.length() != aim.length()){
            return false;
        }

        char[] c1 = str1.toCharArray();
        char[] c2 = str2.toCharArray();
        char[] a = aim.toCharArray();
        int len1 = c1.length;
        int len2 = c2.length;
        int lenA = a.length;

        // 定义二维dp表,dp[i][j] 表示,在c1中取0~i-1,个字符,在c2中取0~j-1字符
        // 在a中取0~i+j-1个字符,能否形成交错组成。
        boolean[][] dp = new boolean[len1+1][len2+1];

        // 两个字符串都取0个形成一个0个的aim
        dp[0][0] = true;
        // 第一行
        for (int i = 1; i < dp[0].length; i++) {
            dp[0][i] = c2[i-1] == a[i-1] ? dp[0][i-1] : false;
        }

        // 第一列
        for (int i = 1; i < dp.length; i++) {
            dp[i][0] = c1[i-1] == a[i-1] ? dp[i-1][0] : false;
        }

        // 普遍位置
        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[0].length; j++) {
                // c1中取0 - i-1,个字符,c2中取0 - j-1个字符
                // 和a中 0~i+j-1比较
                if(c1[i-1] == a[j+i-1] && dp[i-1][j]){
                    dp[i][j] = dp[i-1][j];
                }
                if(c2[j-1] == a[j+i-1] && dp[i][j-1]){
                    dp[i][j] = dp[i][j-1];
                }
            }
        }

        return dp[len1][len2];
    }
    
}

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

推荐阅读更多精彩内容