【精選】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;
    }

 

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