【精选】JAVA算法题(二十五)

好长时间没有写博客了,之前因为期末考试耽误了一段时间,回家又玩了几天,然后又赶来上海入职,所以就把博客这事给忘了,哈哈,懒惰啊。

一、最长回文字符串

题目:

/**
 * 给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
 * 在构造过程中,请注意区分大小写。比如 "Aa" 不能当做一个回文字符串。
 * <p>
 * 注意:
 * 假设字符串的长度不会超过 1010。
 * <p>
 * 示例 1:
 * 输入:
 * "abccccdd"
 * 输出:
 * 7
 * 解释:
 * 我们可以构造的最长的回文串是"dccaccd", 它的长度是 7。
 */

这道题的意思很简单,给你一些字母,问你最长可以组成多长的回文字符串。那就想一下回文字符串的判定条件呗,就是中心对称,那就是说中间可以有一个不一样的字符,两边的字符出现的次数是偶数的,那就这样计算吧。

大体的思想就是先对每个字符出现的次数进行计数然后对偶数加和,只加一个奇数,其它奇数减一变偶数加和

    public static int method1(String s) {
        int[] a = new int[256];
        for (char c : s.toCharArray()) {
            a[c]++;
        }
        int res = 0;
        boolean flag = true;
        for (int i = 0; i < 256; i++) {
            if (a[i] % 2 == 0) {
                res += a[i];
            } else {
                if (flag) {
                    res += a[i];
                    flag = false;
                } else {
                    res += a[i] - 1;
                }
            }
        }
        return res;
    }

二、第三大的数

题目:

/**
 * 给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是O(n)。
 * 
 * 示例 1:
 * 输入: [3, 2, 1]
 * 输出: 1
 * 解释: 第三大的数是 1.
 * 
 * 示例 2:
 * 输入: [1, 2]
 * 输出: 2
 * 解释: 第三大的数不存在, 所以返回最大的数 2 .
 * 
 * 示例 3:
 * 输入: [2, 2, 3, 1]
 * 输出: 1
 * 
 * 解释: 注意,要求返回第三大的数,是指第三大且唯一出现的数。
 * 存在两个值为2的数,它们都排第二。
 */

先给出一种不符合要求的解法,先排序在去重前三大的数,如果去重后的数少于三个就返回最大的数,否则返回第三大的数

    public static int method1(int[] nums) {
        if (nums.length == 1) {
            return 0;
        }
        Arrays.sort(nums);
        int[] results = new int[3];
        int num = 0;
        for (int i = nums.length - 1; i >= 0; i--) {
            if (num < 3 && nums[i] != results[num - 1 < 0 ? 0 : num - 1]) {
                results[num++] = nums[i];
            }
        }
        if (num < 3) {
            return results[0];
        } else {
            return results[2];
        }
    }

很明显它的复杂度是超过了O(n)的,sort()的复杂度是O(nlgn)

其实这道题也不难想,就是记录前三大的数都是多少,然后遇到新的数就不断传递就好了,再开始要判断一下数组长度,还有就是在传递数字的时候记录一下改变了多少次,以此来判断有没有第三大的数。

    public static int method2(int[] nums) {
        if (nums.length == 1) {
            return nums[0];
        }
        if (nums.length == 2) {
            return Math.max(nums[0], nums[1]);
        }
        int max1 = Integer.MIN_VALUE;
        int max2 = Integer.MIN_VALUE;
        int max3 = Integer.MIN_VALUE;
        boolean f = true;
        int flag = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == Integer.MIN_VALUE && f) {
                flag++;
                f = false;
            }
            if (nums[i] > max1) {
                flag++;
                //原先第二大传递给第三大
                max3 = max2;
                //原先最大值传递给第二大
                max2 = max1;
                //更新最大值
                max1 = nums[i];
            } else if (nums[i] > max2 && nums[i] < max1) {
                flag++;
                max3 = max2;
                max2 = nums[i];
            } else if (nums[i] > max3 && nums[i] < max2) {
                flag++;
                max3 = nums[i];
            }
        }
        return flag >= 3 ? max3 : max1;
    }

还是相同的思想,我们来简化一下代码,不使用flag来计数判断是否有第三大的数了,而是在最后判断表示第三大的变量是否改变过,这样判断第三大的数是否出现过。

    public static int method3(int[] nums) {
        long first = Long.MIN_VALUE, second = Long.MIN_VALUE, third = Long.MIN_VALUE;
        for (long num : nums) {
            if (num > first) {
                third = second;
                second = first;
                first = num;
            } else if (num > second && num < first) {
                third = second;
                second = num;
            } else if (num > third && num < second) {
                third = num;
            }
        }
        return third == Long.MIN_VALUE ? (int) first : (int) third;
    }

三、路径数量

题目:

/**
 * 给定一个二叉树,它的每个结点都存放着一个整数值。
 * 找出路径和等于给定数值的路径总数。
 * 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
 * 二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。
 *
 * 示例:
 * root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 8
 *
 *       10
 *      /  \
 *     5   -3
 *    / \    \
 *   3   2   11
 *  / \   \
 * 3  -2   1
 *
 * 返回 3。和等于 8 的路径有:
 *
 * 1.  5 -> 3
 * 2.  5 -> 2 -> 1
 * 3.  -3 -> 11
 */
public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;
    TreeNode(int x) { val = x; }
}

一看这道题就感觉要用递归解,其实很多树的题用递归来解都非常合适,有一个比较简单的思路来解这道题,就是以每一个节点作为根节点向下寻找是否有符合条件的路径,有的话就把路径数加一,可以用目标数值减去每一个节点,如果可以得到零的话就说明这是一条符合条件的路径,因为可能出现负值,所以不必对下于零的情况做处理。

    private static int pathnumber;
    public int method1(TreeNode root, int sum) {
        if(root == null) return 0;
        Sum(root,sum);
        method1(root.left,sum);
        method1(root.right,sum);
        return pathnumber;
    }

    public static void Sum(TreeNode root, int sum){
        if(root == null) return;
        sum-=root.val;
        if(sum == 0){
            pathnumber++;
        }
        Sum(root.left,sum);
        Sum(root.right,sum);
    }

简化一下代码就是这个样子,把计数也合到递归里面

    public int method2(TreeNode root, int sum) {
        if (root == null) return 0;
        return method2(root.left, sum) + method2(root.right, sum) + dfs(root, sum);
    }

    public static int dfs(TreeNode node, int sum) {
        if (node == null) return 0;
        int count = 0;
        if (node.val == sum) count = 1;
        return count + dfs(node.left, sum - node.val) + dfs(node.right, sum - node.val);
    }

换汤不换药,可以使用Map来记录每个数值出现的次数,如果加和等于目标数值了便计数,比较重要的有两句话,开始的,<0,1>键值对和最后的回溯。

    public static int method3(TreeNode root, int sum) {
        HashMap<Integer,Integer> map = new HashMap();
        //<和,次数>
        map.put(0, 1);
        return FindSubTree(root,0,sum,map);
    }

    public static int FindSubTree(TreeNode root,int sum,int target,Map<Integer,Integer> map) {
        if(root==null) {
            return 0;
        }
        sum +=root.val;

        int res = map.getOrDefault(sum-target, 0);
        map.put(sum, map.getOrDefault(sum, 0)+1);

        res +=FindSubTree(root.left,sum,target,map);
        res +=FindSubTree(root.right,sum,target,map);

        //回溯
        map.put(sum, map.get(sum)-1);
        return res;
    }

四、N叉树的层次遍历

题目:

/**
 * 给定一个 N 叉树,返回其节点值的层序遍历。 (即从左到右,逐层遍历)。
 *
 * 例如,给定一个 3叉树 :
 * LevelOrder.java
 *
 * 返回其层序遍历:
 *
 * [
 *      [1],
 *      [3,2,4],
 *      [5,6]
 * ]
 *
 * 说明:
 *     树的深度不会超过 1000。
 *     树的节点总数不会超过 5000。
 *
 */
class Node {
    public int val;
    public List<Node> children;

    public Node() {}

    public Node(int _val,List<Node> _children) {
        val = _val;
        children = _children;
    }
};

其实很简单,和树的层次遍历一样,可以使用两个队列,先把根节点放到一个队列里,然后取出来的时候加入一个list,再把子节点放到另一个队列里,取出来的时候加入一个list里面,这样不断在两个队列里面捣腾,直到两个队列都为空。

    public List<List<Integer>> method1(Node root) {
        Queue<Node> nodeQueueA=new LinkedBlockingQueue<>();
        Queue<Node> nodeQueueB=new LinkedBlockingQueue<>();
        List<List<Integer>> lists=new ArrayList<>();
        if (root!=null){
            nodeQueueA.offer(root);
        }
        while (!nodeQueueA.isEmpty()||!nodeQueueB.isEmpty()){
            List<Integer> integers=new ArrayList<>();
            while (!nodeQueueA.isEmpty()){
                Node node=nodeQueueA.poll();
                integers.add(node.val);
                for (Node node1:node.children){
                    nodeQueueB.offer(node1);
                }
            }
            if (!integers.isEmpty()){
                lists.add(integers);
                integers=new ArrayList<>();
            }
            while (!nodeQueueB.isEmpty()){
                Node node=nodeQueueB.poll();
                integers.add(node.val);
                for (Node node1:node.children){
                    nodeQueueA.offer(node1);
                }
            }
            if (!integers.isEmpty()) {
                lists.add(integers);
            }
        }
        return lists;
    }

也可以使用一个队列,这样你就需要控制每一次取出来多少个了,用以实现分层,每次要把当前队列中节点的所有子节点放入一个list,并把所有子节点放入队列。

    public List<List<Integer>> method2(Node root) {
        Queue<Node> nodeQueue=new LinkedBlockingQueue<>();
        List<List<Integer>> lists=new ArrayList<>();
        if (root!=null){
            nodeQueue.offer(root);
            List<Integer> integers=new ArrayList<>();
            integers.add(root.val);
            lists.add(integers);
        }
        while (!nodeQueue.isEmpty()){
            List<Integer> integers=new ArrayList<>();
            int size=nodeQueue.size();
            for (int i=0;i<size;i++){
                Node node=nodeQueue.poll();
                for (Node node1:node.children){
                    nodeQueue.offer(node1);
                    integers.add(node1.val);
                }
            }
            if (!integers.isEmpty()){
                lists.add(integers);
            }
        }
        return lists;
    }

简化版本

    public List<List<Integer>> method3(Node root) {
        List<List<Integer>> result = new LinkedList<>();
        if (root == null) {
            return result;
        }
        Node head = root;
        Queue<Node> queue = new LinkedList<>();
        queue.add(head);
        while (!queue.isEmpty()) {
            int size = queue.size();
            List<Node> nextLayer = new ArrayList<>();
            List<Integer> layer = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                Node node = queue.poll();
                nextLayer.addAll(node.children);
                layer.add(node.val);
            }
            result.add(layer);
            queue.addAll(nextLayer);
        }
        return result;
    }

还有一种办法就是用dfs,递归的时候带上层数,这样就知道加到哪个list里面了

    private static List<List<Integer>> list = new ArrayList<>();
    public static List<List<Integer>> method4(Node root) {
        dfs(root, 0);
        return list;
    }

    private static void dfs(Node root, int level){
        if(root == null){
            return;
        }
        if(list.size()<level+1){
            List<Integer> l = new ArrayList<>();
            list.add(l);
        }
        list.get(level).add(root.val);
        for (Node node : root.children){
            dfs(node, level+1);
        }
    }

五、字母异位词

题目:

/**
 * 给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
 * 字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
 * 说明:
 *     字母异位词指字母相同,但排列不同的字符串。
 *     不考虑答案输出的顺序。
 *
 * 示例 1:
 * 输入:
 * s: "cbaebabacd" p: "abc"
 * 输出:
 * [0, 6]
 * 解释:
 * 起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
 * 起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。
 *
 *  示例 2:
 * 输入:
 * s: "abab" p: "ab"
 * 输出:
 * [0, 1, 2]
 * 解释:
 * 起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
 * 起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
 * 起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。
 */

字母异位词很好判断,只要里面所包含的字母出现的次数一致就叫字母异位词,一个很明显的思路就是存储s子串的字母构成,如果和p一样就记录其实索引,往后不断抛弃s子串尾的字母,加入新的字母进行比对。

    public static List<Integer> method2(String s, String p) {
        List<Integer> result = new ArrayList<>();
        int[] p_letter = new int[26];
        for (int i = 0; i < p.length(); i++) {
            //记录p里面的数字分别有几个
            p_letter[p.charAt(i) - 'a']++;
        }
        int start = 0; int end = 0;
        int[] between_letter = new int[26];
        //记录两个指针之间的数字都有几个
        while (end < s.length()) {
            int c = s.charAt(end++) - 'a';
            //每一次拿到end指针对应的字母
            between_letter[c]++;
            //让这个字母的数量+1 如果这个字母的数量比p里面多了,说明这个start座标需要排除
            while (between_letter[c] > p_letter[c]) {
                between_letter[s.charAt(start++) - 'a']--;
            }
            if (end - start == p.length()) {
                result.add(start);
            }
        }
        return result;
        }
    public static List<Integer> method3(String s, String p) {
        List<Integer> list = new ArrayList<>();
        int[] record = new int[128];
        for(char c: p.toCharArray()){
            record[c]++;
        }
        char[] arr = s.toCharArray();
        int l=0, r=0;
        while(r<s.length()){
            if(record[arr[r]]>0){
                record[arr[r]]--;
                r++;
                if((r-l) == p.length()){
                    list.add(l);
                }
            }
            else{
                record[arr[l]]++;
                l++;
            }
        }
        return list;
    }

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章