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

  • 给定一个无序数组arr,如果只能再一个子数组上排序,返回如果让arr整体有序,需要排序的最短子数组长度。
/**
 * 给定一个无序数组arr,如果只能再一个子数组上排序,
 * 返回如果让arr整体有序,需要排序的最短子数组长度。
 */
public class MimSubArrayLen {

    public static int minSubArrayLen(int[] arr){
        if(arr == null || arr.length == 0){
            return 0;
        }

        // 从左向右遍历右边需要排序的边界
        int right = arr.length-1;
        int max = Integer.MIN_VALUE;
        for (int i = 0; i < arr.length; i++) {
            if(arr[i] > max){
                max = arr[i];
            }else {
                right = i;
            }
        }

        // 从右向左遍历左边需要排序的边界
        int left = 0;
        int min = Integer.MAX_VALUE;
        for (int i = arr.length-1; i >= 0; i--) {
            if(arr[i] < min){
                min = arr[i];
            }else{
                left = i;
            }
        }

        return right - left +1;
    }

    public static void main(String[] args) {
        int[] arr = { 1, 2, 4, 7, 10, 11, 7, 12, 6, 7, 16, 18, 19 };
        System.out.println(minSubArrayLen(arr));
    }
}

  • 给定一个正数数组 arr,其中所有的值都为整数,以下是最小不可组成和的概念:
    把 arr 每个子集内的所有元素加起来会出现很多值,其中最小的记为 min,最大的记为max 在区间[min,max]上,如果有数不可以被arr某一个子集相加得到,那么其中最小的那个数是arr 的最小不可组成和 在区间[min,max]上,如果所有的数都可以被arr的某一个子集相加得到,那么max+1是arr的最 小不可组成和
    请写函数返回正数数组 arr 的最小不可组成和。
    【举例】
    arr=[3,2,5]。子集{2}相加产生 2 为 min,子集{3,2,5}相加产生 10 为 max。在区间[2,10] 上,4、 6 和 9 不能被任何子集相加得到,其中 4 是 arr 的最小不可组成和。 arr=[1,2,4]。子集{1}相加产生 1 为 min,子集{1,2,4}相加产生 7 为 max。在区间[1,7]上, 任何 数都可以被子集相加得到,所以 8 是 arr 的最小不可组成和。
    【进阶】
    如果已知正数数组 arr 中肯定有 1 这个数,是否能更快地得到最小不可组成和?
/**
 * 给定一个正数数组 arr,其中所有的值都为整数,以下是最小不可组成和的概念:
 * 把 arr 每个子集内的所有元素加起来会出现很多值,其中最小的记为 min,最大的记为max 在区间[min,max]上,
 * 如果有数不可以被arr某一个子集相加得到,那么其中最小的那个数是arr 的最小不可组成和 在区间[min,max]上,
 * 如果所有的数都可以被arr的某一个子集相加得到,那么max+1是arr的最 小不可组成和
 * 请写函数返回正数数组 arr 的最小不可组成和。
 * 【举例】
 * arr=[3,2,5]。子集{2}相加产生 2 为 min,子集{3,2,5}相加产生 10 为 max。在区间[2,10] 上,4、 6 和 9 不能被任何子集相加得到,其中 4 是 arr 的最小不可组成和。
 * arr=[1,2,4]。子集{1}相加产生 1 为 min,子集{1,2,4}相加产生 7 为 max。在区间[1,7]上, 任何 数都可以被子集相加得到,所以 8 是 arr 的最小不可组成和。
 * 【进阶】
 * 如果已知正数数组 arr 中肯定有 1 这个数,是否能更快地得到最小不可组成和?
 */
public class MinNotSum {
    // 暴力递归
    public static int minNotSum(int[] arr) {
        if (arr == null || arr.length == 0) {
            // 不存在
            return -1;
        }
        // 收集所有min ~ max 的能得到的累加和
        Set set = new HashSet<>();
        process(arr, 0, 0, set);

        // 找到最小值
        int min = Integer.MAX_VALUE;
        int sum = 0;
        for (int i = 0; i < arr.length; i++) {
            min = Math.min(min, arr[i]);
            sum += arr[i];
        }

        for (int i = min + 1; i <= sum; i++) {
            if (!set.contains(i)) {
                return i;
            }
        }

        return sum+1;
    }
    // 枚举了所有子数组
    // 递归含义:0 ~ index - 1位置的所有累加和已经在set中,求index开始向后的子数组累加和
    // 且 0到index的一个累加和时sum
    private static void process(int[] arr,int index,int sum,Set set){
        // 如果此时已经没有元素可选
        if(index == arr.length){
            if(!set.contains(sum)){
                set.add(sum);
            }
            return;
        }

        // 要当前元素,求一个从index ~ arr.length-1的子数组累加和
        process(arr,index+1,sum+arr[index],set);

        // 不要当前元素
        process(arr,index+1,sum,set);
    }

    // 动态规划
    public static int dp(int[] arr){
        if (arr == null || arr.length == 0) {
            // 不存在
            return -1;
        }
        int sum = 0;
        int min = Integer.MAX_VALUE;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
            min = Math.min(min,arr[i]);
        }
        Set set = new HashSet<>();
        int cur = 0;
        boolean[][] dp = new boolean[arr.length][sum+1];
        for (int i = 0; i < arr.length; i++) {// arr[0..i] 0
            dp[i][0] = true;
        }
        dp[0][arr[0]] = true;
        for (int i = 1; i < arr.length; i++) {
            for (int j = 1; j <= sum; j++) {
                dp[i][j] = dp[i - 1][j] || ((j - arr[i] >= 0) ? dp[i - 1][j - arr[i]] : false);
            }
        }
        for (int j = min; j <= sum; j++) {
            if (!dp[arr.length - 1][j]) {
                return j;
            }
        }
        return sum + 1;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{1,2,4};
        System.out.println(minNotSum(arr));
        System.out.println(dp(arr));
    }
}

// 已知arr中肯定有1这个数
    public static int unformedSum3(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        Arrays.sort(arr); // O (N * logN)
        int range = 1;
        // arr[0] == 1
        for (int i = 1; i != arr.length; i++) {
            if (arr[i] > range + 1) {
                return range + 1;
            } else {
                range += arr[i];
            }
        }
        return range + 1;
    }
  • 给定一个有序的正数数组arr和一个正数range,如果可以自由选择arr中的数字,想累加得 到 1~range 范围上所有的数,返回arr最少还缺几个数。
    【举例】
    arr = {1,2,3,7},range = 15
    想累加得到 1~15 范围上所有的数,arr 还缺 14 这个数,所以返回1 arr = {1,5,7},range = 15
    想累加得到 1~15 范围上所有的数,arr 还缺 2 和 4,所以返回2

  • 一个数组中,如果两个数的公共因子有大于1,则认为这两个数之间有通路,返回数组中,有多少个独立的域

  • 技巧:加速求一个数因子:只需要求1~根号下num。

/**
 * 一个数组中,如果两个数的公共因子有大于1,则认为这两个数之间有通路
 * 返回数组中,有多少个独立的域
 */
public class Problem04 {

    // O(n*k) k 数组的大小
    public static int problem(int[] arr){
        if(arr == null || arr.length == 0){
            return 0;
        }
        UnionFind union = new UnionFind(arr);
        for (int i = 0; i < arr.length; i++) {
            for (int j = i+1; j < arr.length; j++) {
                if(gcd(arr[i],arr[j]) > 1){
                    union.union(arr[i],arr[j]);
                }

            }

        }

        return union.size();
    }

    // O(n*根号下k) k
    public static int problem2(int[] arr){
        if(arr == null || arr.length == 0){
            return 0;
        }
        UnionFind union = new UnionFind(arr);
        // k : 含有的因子
        // v : 含有此因子的数
        HashMap map = new HashMap<>();

        for (int i = 0; i < arr.length; i++) {
            // 如果一个数num,求一个数的最大公约数,可以一对一对的求其中一个公约数最大可以到
            // 根号下num(向下取整)
            int limit = (int)Math.sqrt(arr[i]);
            for (int j = 1; j <= limit; j++) {
                if(arr[i] % j == 0){
                    // 到这里说明j是arr[i]的一个因子
                    // 但是我们要排除1,不符合题意
                    if(j != 1){
                        if(!map.containsKey(j)){
                            // arr[i] 含有j这个因子,但是集合中还没有发现其他含有这个因子的数,
                            // 不需要合并,记录下
                            map.put(j,arr[i]);
                        }else{
                            // 记录中已有元素含有这个因子,合并这个数和arr[i]
                            union.union(map.get(j),arr[i]);
                        }
                    }
                    // 和j配对的另一个arr[i]的因子
                    int ot = arr[i] / j;
                    if(ot != 1){
                        if(!map.containsKey(ot)){
                            // arr[i] 含有j这个因子,但是集合中还没有发现其他含有这个因子的数,
                            // 不需要合并,记录下
                            map.put(ot,arr[i]);
                        }else{
                            // 记录中已有元素含有这个因子,合并这个数和arr[i]
                            union.union(map.get(ot),arr[i]);
                        }
                    }
                }
            }

        }

        return union.size();
    }
    // 求最大公约数
    private static int gcd(int a,int b){
        return b == 0 ? a : gcd(b,a%b);
    }

    // 并查集
    static class UnionFind{
        private HashMap map;
        private int size;
        // 给我一批数据形成并查集
        public UnionFind(int[] arr){
            if(arr == null || arr.length == 0){
                return;
            }
            this.size = arr.length;
            map = new HashMap<>(arr.length);
            // 每个元素单独成一个集合
            for(int item : arr){
                Node node = new Node(item);
                node.size = 1;
                node.parent = node;
                map.put(item,node);
            }

        }
        // 判断两个元素是否属于同一个集合
        public boolean isSameSet(int  v1,int v2){
            if(map.get(v1) == null || map.get(v2) == null){
                return false;
            }
            Node father1 = findFather(map.get(v1));
            Node father2 = findFather(map.get(v2));
            // 判断两个元素是否有相同的代表元素,如果有则是同一个集合
            return father1 == father2;
        }

        // 把两个元素所在的集合合并成一个集合
        public void union(int k1,int k2){
            if(map.get(k1) == null || map.get(k2) == null){
                return;
            }
            if(isSameSet(k1,k2)){
                return;
            }

            Node father1 = findFather(map.get(k1));
            Node father2 = findFather(map.get(k2));
            Node big = father1.size >= father2.size ? father1 : father2;
            Node small = father1.size < father2.size ? father1 : father2;
            small.parent = big;
            // 从新计算集合的元素元素葛素
            big.size = big.size+small.size;
            // map.put(big.value,big);
            // 成功union后 独立集合个体数减一
            size -- ;
        }

        // 返回有几个不相连的集合
        public int size(){
            return size;
        }

        // 给定一个节点,寻找他的代表元素
        // 扁平化?
        private Node findFather(Node node){
            Node cur = node;
            Node temp = node;
            Stack stack = new Stack<>();
            if(cur != null){
                while (cur.parent != cur){
                    stack.push(cur);
                    cur = cur.parent;
                }
            }
            // 扁平化
            while (!stack.isEmpty()){
                Node pop = stack.pop();
                pop.parent = cur;
            }
            return cur;
        }
    }

    static class Node{
        int value;
        Node next;
        Node parent;
        int size;

        public Node(int value){
            this.value = value;
        }
    }

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

//        UnionFind union = new UnionFind(arr);
//        System.out.println(union.isSameSet(3,2));
//        System.out.println(union.isSameSet(6,3));
//        System.out.println(union.isSameSet(7,4));
//        union.union(2,3);
//        System.out.println("size:"+union.size());
//        System.out.println(union.isSameSet(2,3));
//        System.out.println(union.isSameSet(7,3));
//        union.union(7,3);
//        System.out.println("size:"+union.size());
//        System.out.println(union.isSameSet(7,3));
//        System.out.println(union.isSameSet(6,3));
//        union.union(6,7);
//        System.out.println("size:"+union.size());
//        System.out.println(union.isSameSet(6,3));

    }
}
  • 给定一个全是小写字母的字符串str,删除多余字符,使得每种字符只保留一个,并让 最终结果字符串的字典序最小
    【举例】
    str = "acbc",删掉第一个'c',得到"abc",是所有结果字符串中字典序最小的。
    str = "dbcacbca",删掉第一个'b'、第一个'c'、第二个'c'、第二个'a',得到"dabc", 是所有结 果字符串中字典序最小的。
/**
 * 给定一个全是小写字母的字符串str,删除多余字符,使得每种字符只保留一个,并让 最终结果字符串的字典序最小
 * 【举例】
 * str = "acbc",删掉第一个'c',得到"abc",是所有结果字符串中字典序最小的。
 * str = "dbcacbca",删掉第一个'b'、第一个'c'、第二个'c'、第二个'a',得到"dabc", 是所有结 果字符串中字典序最小的。
 */
public class MinStr {
    public static String minStr(String str){
        if(str == null || str.equals("")){
            return "";
        }
        // 1.流程统计每个字符的个数
        // 2.遍历字符串,每遍历一个字符就在统计的词频表中把该字符的数量减一
        // 当其中某一个字符的数量被减为0时停止遍历,从其实位置开始到结束位置之间选一个
        // 最前的字典序最小的字符作为保留字符,在他之前的字符全部删掉,之后的和他相同的字符
        // 也删掉。
        // 从新进行12步骤直到每个字符都剩一个就找到了字典序最小且无重复字符的字符串


       return process(str);
    }

    private static String process(String str){
        if(str == null || str.equals("")){
            return "";
        }
        // 数组下标表示字符
        int[] c = new int[255];
        char[] chars = str.toCharArray();
        for (int i = 0; i < chars.length; i++) {
            c[chars[i]]++;
        }
        // 统计遍历过的最小的字符
        int min = 0;
        String res="";
        int index = 0;
        for (int i = 0; i < chars.length; i++) {
            min =chars[min] <= chars[i] ? min : i;
            if((--c[chars[i]]) == 0){
                res = String.valueOf(chars[min]);
                index = min;
                break;
            }
        }


        return res + process(str.substring(index+1).replace(String.valueOf(chars[index]),""));
    }

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

  • 在一个字符串中找到没有重复字符子串中最长的长度。
    例如:
    abcabcbb没有重复字符的最长子串是abc,长度为3
    bbbbb,答案是b,长度为1
    pwwkew,答案是wke,长度是3
    要求:答案必须是子串,"pwke" 是一个子字符序列但不是一个子字符串。
/**
 * 在一个字符串中找到没有重复字符子串中最长的长度。
 * 例如:
 * abcabcbb没有重复字符的最长子串是abc,长度为3
 * bbbbb,答案是b,长度为1
 * pwwkew,答案是wke,长度是3
 * 要求:答案必须是子串,"pwke" 是一个子字符序列但不是一个子字符串。
 */
public class MaxLongstSubString {

    public static int maxLongstSubString(String str){

        int[] map = new int[256];
        for (int i = 0; i < map.length; i++) {
            map[i] = -1;
        }
        char[] c = str.toCharArray();
        int pre = -1;
        int len = 0;
        int cur = 0;
        for (int i = 0;i < c.length;i++){
            pre = Math.max(pre,map[c[i]]);
            cur = i - pre;
            len = Math.max(len,cur);
            map[c[i]] = i;
        }

        return len;
    }

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

  • 给定两个数组arrx和arry,长度都为N。代表二维平面上有N个点,第i个点的x 坐标和y坐标分别为arrx[i]和arry[i],返回求一条直线最多能穿过多少个点?
/**
 * 给定两个数组arrx和arry,长度都为N。代表二维平面上有N个点,第i个点的x 坐
 * 标和y坐标分别为arrx[i]和arry[i],返回求一条直线最多能穿过多少个点?
 */
public class MaxPoint {
    
    public static int maxPoint(int[] arrx,int[] arry){
        if(arrx == null || arrx.length == 0 || arry == null || arry.length == 0){
            return 0;
        }
            
     
        // 有斜率
        // k:x_y,v int
        HashMap map = new HashMap<>();
        // 结果
        int res = 0;
        for (int i = 0; i < arrx.length; i++) {
            String key = "";
          
            // 共点
            int point = 0;
            // 共x
            int xNum = 0;
            // 共y
            int yNum = 0;
            
            for (int j = i+1; j < arrx.length; j++) {
                int x = arrx[i];
                int y = arry[i];
                if(x == arrx[j] && y == arry[j]){
                    // 共点
                    point ++;
                }else if(y == arry[j]){
                    // 共x轴,y坐标都相同
                    xNum++;
                }else if(x == arrx[j]){
                    yNum++;
                }{
                    // 存在斜率
                    int tempX = arrx[j] - x;
                    int tempY = arry[j] - y;
                    int gcd = gcd(tempX,tempY);
                    int kX = tempX / gcd;
                    int kY = tempY / gcd;
                    key = kX+"_"+kY;
                    if(!map.containsKey(key)){
                        map.put(key,1);
                    }else{
                        map.put(key,map.get(key)+1);
                    }
                }
            }
            
            res = Math.max(res,Math.max(xNum,Math.max(yNum,map.get(key)))+point);
        }
        return res;
    }
    
    private static int gcd(int a,int b){
        return b == 0 ? a :gcd(b,a%b);
    }
}

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