劍指OFFER

oldman_zhou發表於2021-01-03

1 陣列

03. 陣列中重複的數字

https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/

在一個長度為 n 的陣列 nums 裡的所有數字都在 0~n-1 的範圍內。陣列中某些數字是重複的,但不知道有幾個數字重複了,也不知道每個數字重複了幾次。請找出陣列中任意一個重複的數字。

1)Hash or Set

2)原地交換

    /**
     * 原地交換 : o(n)
     */
    public int findRepeatNumber(int[] nums) {
        int i = 0, n = nums.length;
        while (i < n) {
            if (nums[i] == i) {
                i++;
                continue;
            }
            if (nums[nums[i]] == nums[i]) return nums[i];
            // swap
            int tmp = nums[nums[i]];
            nums[nums[i]] = nums[i];
            nums[i] = tmp;
        }
        return -1;
    }

04. 二維陣列中的查詢

https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/

在一個 n * m 的二維陣列中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個高效的函式,輸入這樣的一個二維陣列和一個整數,判斷陣列中是否含有該整數。

1)BST

    /**
     * BST: 右上角開始 O(M+N)
     */
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length < 1) {
            return false;
        }
        int m = 0, n = matrix[0].length - 1;
        while (n >= 0 && m < matrix.length) {
            if (matrix[m][n] == target) {
                return true;
            } else if (matrix[m][n] > target) {
                n--;
            } else {
                m++;
            }
        }
        return false;
    }

11. 旋轉陣列的最小數字

https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/

輸入一個遞增排序的陣列的一個旋轉,輸出旋轉陣列的最小元素。例如,陣列 [3,4,5,1,2] 為 [1,2,3,4,5] 的一個旋轉,該陣列的最小值為1。

1)線性查詢

2)二分法

    /**
     * 二分 : o(logn)
     */
    public int minArray(int[] numbers) {
        int i = 0, j = numbers.length - 1;
        while (i < j) {
            int m = (i + j) / 2;
            if (numbers[m] > numbers[j]) i = m + 1;
            else if (numbers[m] < numbers[j]) j = m;
            else j--;
        }
        return numbers[i];
    }

39. 陣列中出現次數超過一半的數字

https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/

陣列中有一個數字出現的次數超過陣列長度的一半,請找出這個數字。

1)Hash

2)排序:陣列中點一定是眾數

3)摩爾投票法

    /**
     * 摩爾投票 : o(n)
     */
    public int majorityElement(int[] nums) {
        int votes = 0, x = 0;
        for (int n : nums) {
            // 投票數為0,則假設當前n為眾數
            if (votes == 0) x = n;
            votes += n == x ? 1 : -1;
        }
        // 驗證 x 是否為眾數
        return x;
    }

40. 最小的k個數

https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/

輸入整數陣列 arr ,找出其中最小的 k 個數。例如,輸入4、5、1、6、2、7、3、8這8個數字,則最小的4個數字是1、2、3、4。

1)排序 or 快排

    /**
     * 快排 : o(n)
     */
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0) return new int[0];
        partition(arr, 0, arr.length - 1, k);
        return Arrays.stream(arr).limit(k).toArray();
    }

    public void partition(int[] array, int start, int end, int k) {
        int p = array[start], i = start, j = end;
        while (i < j) {
            while (i < j && array[j] >= p) {
                j--;
            }
            array[i] = array[j];
            while (i < j && array[i] <= p) {
                i++;
            }
            array[j] = array[i];
        }
        array[j] = p;
        if (j == k - 1) {
            return;
        } else if (j > k - 1) {
            partition(array, start, j - 1, k);
        } else {
            partition(array, j + 1, end, k);
        }
    }

2)大頂堆(PriorityQueue)

    /**
     * 大頂堆 : o(nlogk)
     */
    public int[] getLeastNumbers(int[] arr, int k) {
        if (k == 0) return new int[0];
        Queue<Integer> heap = new PriorityQueue<>((o1, o2) -> o2 - o1);
        for (int n : arr) {
            if (heap.size() < k) {
                heap.offer(n);
            } else if (heap.peek() > n) {
                heap.poll();
                heap.offer(n);
            }
        }
        return heap.stream().mapToInt(x -> x).toArray();
    }

51. 陣列中的逆序對

https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof/

53 - I. 在排序陣列中查詢數字 I

https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/

統計一個數字在排序陣列中出現的次數。

    /**
     * 二分法 : o(logn)
     */
    public int search(int[] nums, int target) {
        // methodA : 分兩次找出左右邊界
        // methodB : 分別找出 target 和 target-1 的右邊界
        return binarySearch(nums, target) - binarySearch(nums, target - 1);
    }

    public int binarySearch(int[] nums, int tar) {
        int i = 0, j = nums.length - 1;
        while (i <= j) {
            int m = (i + j) / 2;
            if (nums[m] <= tar) i = m + 1;
            else j = m - 1;
        }
        return i;
    }

53 - II. 0~n-1中缺失的數字

https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof/

一個長度為n-1的遞增排序陣列中的所有數字都是唯一的,並且每個數字都在範圍0~n-1之內。在範圍0~n-1內的n個數字中有且只有一個數字不在該陣列中,請找出這個數字。

    /**
     * 二分法 : o(logn)
     */
    public int missingNumber(int[] nums) {
        int l = 0, r = nums.length - 1;
        while (l <= r) {
            int m = l + (r - l) / 2;
            if (nums[m] == m) {
                l = m + 1;
            } else {
                r = m - 1;
            }
        }
        return l - 1;
    }

56 - I. 陣列中數字出現的次數

https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/
Medium : marked

一個整型陣列 nums 裡除兩個數字之外,其他數字都出現了兩次。請寫程式找出這兩個只出現一次的數字。要求時間複雜度是O(n),空間複雜度是O(1)。

    /**
     * 異或和分組 : o(n)
     */
    public int[] singleNumbers(int[] nums) {
        int ret = 0;
        for (int n : nums) {
            ret ^= n;
        }
        // 從右往左找出異或結果不為1的位
        int d = 1;
        while ((d & ret) == 0) {
            d <<= 1;
        }

        int[] res = new int[2];
        // 分組並異或
        for (int n : nums) {
            if ((d & n) == 0) {
                res[0] ^= n;
            } else {
                res[1] ^= n;
            }
        }
        return res;
    }

56 - II. 陣列中數字出現的次數 II

https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/
Medium : marked

在一個陣列 nums 中除一個數字只出現一次之外,其他數字都出現了三次。請找出那個只出現一次的數字。

    /**
     * 有限狀態自動機
     */
    public int singleNumber(int[] nums) {
        int ones = 0, twos = 0;
        for(int num : nums){
            ones = ones ^ num & ~twos;
            twos = twos ^ num & ~ones;
        }
        return ones;
    }

    /**
     * hashMap
     */

57 - I. 和為s的兩個數字

https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof/

輸入一個遞增排序的陣列和一個數字s,在陣列中查詢兩個數,使得它們的和正好是s。如果有多對數字的和等於s,則輸出任意一對即可。

    /**
     * 碰撞雙指標 : o(n)
     */
    public int[] twoSum(int[] nums, int target) {
        int l = 0, r = nums.length - 1;
        while (l < r) {
            int s = nums[l] + nums[r];
            if (s < target) {
                l++;
            } else if (s == target) {
                return new int[]{nums[l], nums[r]};
            } else {
                r--;
            }
        }
        return new int[0];
    }

57 - II. 和為s的連續正數序列

https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/

輸入一個正整數 target ,輸出所有和為 target 的連續正整數序列(至少含有兩個數)。

序列內的數字由小到大排列,不同序列按照首個數字從小到大排列。

   /**
     * 滑動視窗 : o(n)
     */
    public int[][] findContinuousSequence(int target) {
        // 左閉右開
        int l = 1, r = 1, sum = 0;
        List<int[]> res = new ArrayList<>();
        while (l <= target / 2) {
            if (sum < target) {
                sum += r;
                // 有邊界右滑
                r++;
            } else if (sum == target) {
                int[] arr = new int[r - l];
                for (int i = l; i < r; i++) {
                    arr[i - l] = i;
                }
                res.add(arr);
                sum -= l;
                l++;
            } else {
                sum -= l;
                l++;
            }
        }
        return res.toArray(new int[res.size()][]);
    }

59 - I. 滑動視窗的最大值

https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof/

給定一個陣列 nums 和滑動視窗的大小 k,請找出所有滑動視窗裡的最大值。

1) 模擬

    /**
     * 模擬 : o(nk)
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || k <= 0 || nums.length < k) {
            return new int[0];
        }
        int length = nums.length - k + 1;
        int[] result = new int[length];
        int curMax;
        for (int i = 0; i < length; i++) {
            curMax = Integer.MIN_VALUE;
            for (int j = 0; j < k; j++) {
                curMax = Math.max(nums[i+j], curMax);
            }
            result[i] = curMax;
        }
        return result;
    }

2)單調(雙端)佇列

    /**
     * 單調佇列 : o(n)
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || k <= 0 || nums.length < k) {
            return new int[0];
        }
        int length = nums.length - k + 1;
        int[] res = new int[length];
        // 記錄下標,防止重複元素
        Deque<Integer> queue = new LinkedList<>();
        for (int i = 0; i < nums.length; i++) {
            while (!queue.isEmpty() && nums[queue.peekLast()] < nums[i]) {
                queue.pollLast();
            }
            queue.offer(i);
            if (i >= k - 1) {
                // 形成第一個視窗
                if (!queue.isEmpty() && queue.peek() == i - k)
                    queue.poll();
                res[i - k + 1] = nums[queue.peek()];
            }
        }
        return res;
    }

59 - II. 佇列的最大值

https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/
Medium

請定義一個佇列並實現函式 max_value 得到佇列裡的最大值,要求函式max_value、push_back 和 pop_front 的均攤時間複雜度都是O(1)。

1)queue + deque(雙端佇列)

class MaxQueue {
    Queue<Integer> queue;
    Deque<Integer> deque;

    public MaxQueue() {
        queue = new LinkedList<>();
        deque = new LinkedList<>();
    }

    public int max_value() {
        return deque.isEmpty() ? -1 : deque.peekFirst();
    }

    public void push_back(int value) {
        queue.offer(value);
        while(!deque.isEmpty() && deque.peekLast() < value)
            deque.pollLast();
        deque.offerLast(value);
    }

    public int pop_front() {
        if(queue.isEmpty()) return -1;
        if(queue.peek().equals(deque.peekFirst()))
            deque.pollFirst();
        return queue.poll();
    }
}

2 字串

05. 替換空格

https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/

請實現一個函式,把字串 s 中的每個空格替換成"%20"。

1)遍歷新增

50. 第一個只出現一次的字元

https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/

在字串 s 中找出第一個只出現一次的字元。如果沒有,返回一個單空格。 s 只包含小寫字母。

1)Hash

    /**
     * Hash : o(n)
     */
    public char firstUniqChar(String s) {
        Map<Character, Boolean> map = new HashMap<>();
        char[] array = s.toCharArray();
        for (char c : array) {
            map.put(c, !map.containsKey(c));
        }
        for (char c : array) {
            if (map.get(c)) return c;
        }
        return ' ';
    }

2)點陣圖

    /**
     * 點陣圖 : o(n)
     */
    public char firstUniqChar(String s) {
        int[] map = new int[26];
        char[] chars = s.toCharArray();
        for (char c : chars) {
            map[c - 'a']++;
        }
        for (char c : chars) {
            if (map[c - 'a'] == 1) return c;
        }
        return ' ';
    }

3)有序雜湊表(LinkedHashMap)

58 - I. 翻轉單詞順序

https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof/

輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字元的順序不變。為簡單起見,標點符號和普通字母一樣處理。例如輸入字串"I am a student. “,則輸出"student. a am I”。

1)雙指標

2)字串分割

    /**
     * 雙指標 : o(n)
     */
    public String reverseWords(String s) {
        // 去除首尾空格
        s = s.trim();
        int l = s.length() - 1, r = l;
        StringBuilder sb = new StringBuilder();
        while (l >= 0) {
            while (l >= 0 && s.charAt(l) != ' ') {
                l--;
            }
            sb.append(s, l + 1, r + 1).append(" ");
            // 跳過空格
            while (l >= 0 && s.charAt(l) == ' ') {
                l--;
            }
            r = l;
        }
        return sb.toString().trim();
    }

58 - II. 左旋轉字串

https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof/

字串的左旋轉操作是把字串前面的若干個字元轉移到字串的尾部。請定義一個函式實現字串左旋轉操作的功能。比如,輸入字串"abcdefg"和數字2,該函式將返回左旋轉兩位得到的結果"cdefgab"。

1)字串切片

    public String reverseLeftWords(String s, int n) {
        return s.substring(n, s.length()) + s.substring(0, n);
    }

2)列表遍歷拼接

    public String reverseLeftWords(String s, int n) {
        StringBuilder sb = new StringBuilder();
        int size = s.length();
        for (int i = n; i < n + size; i++) {
            sb.append(s.charAt(i % size));
        }
        return sb.toString();
    }

3)原地左移(陣列倒置)

67. 把字串轉換成整數

https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof/
Medium

寫一個函式 StrToInt,實現把字串轉換成整數這個功能。不能使用 atoi 或者其他類似的庫函式。

    /**
     * 數學 : o(n)
     */
    public int strToInt(String str) {
        char[] chars = str.toCharArray();
        if (chars.length == 0) return 0;
        int i = 0;
        // 除去空格
        while (chars[i] == ' ') {
            i++;
            if (i == chars.length) return 0;
        }
        int res = 0, flag = str.charAt(i) == '-' ? -1 : 1;
        if (str.charAt(i) == '-' || str.charAt(i) == '+') i++;
        for (int j = i; j < chars.length; j++) {
            if (chars[j] < '0' || chars[j] > '9') break;
            if (res > Integer.MAX_VALUE / 10 || (res == Integer.MAX_VALUE / 10 && chars[j] > '7')) {
                // 越界
                return flag == 1 ? Integer.MAX_VALUE : Integer.MIN_VALUE;
            }
            res = res * 10 + (chars[j] - '0');
        }

        return flag * res;
    }

3 連結串列

06. 從尾到頭列印連結串列

https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/

輸入一個連結串列的頭節點,從尾到頭反過來返回每個節點的值(用陣列返回)。

1)遞迴

    /**
     * 遞迴 : o(n)
     */
    public int[] reversePrint(ListNode head) {
        if (head == null) return new int[0];
        List<Integer> list = new ArrayList<>();
        recur(head, list);
        return list.stream().mapToInt(x -> x).toArray();
    }

    public void recur(ListNode root, List<Integer> list) {
        if (root.next != null) {
            recur(root.next, list);
        }
        list.add(root.val);
    }

2)棧

    /**
     * 棧 : o(n)
     */
    public int[] reversePrint(ListNode head) {
        Stack<ListNode> stack = new Stack<>();
        while (head != null) {
            stack.push(head);
            head = head.next;
        }
        int[] res = new int[stack.size()];
        while (!stack.isEmpty()) {
            res[res.length - stack.size()] = stack.pop().val;
        }
        return res;
    }

52. 兩個連結串列的第一個公共節點

https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/

輸入兩個連結串列,找出它們的第一個公共節點。

    /**
     * 雙指標 : o(m+n)
     */
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode currA = headA, currB = headB;
        // 交點在 l1 + l2 + c 處
        while (currA != currB) {
            currA = currA == null ? headB : currA.next;
            currB = currB == null ? headA : currB.next;
        }
        return currA;
    }

4 二叉樹

樹的遍歷方式總體分為兩類:深度優先搜尋(DFS)、廣度優先搜尋(BFS);

常見的 DFS : 先序遍歷(根-左-右)、中序遍歷(左-根-右)、後序遍歷(左-右-根);
常見的 BFS : 層序遍歷(按層遍歷)。

54. 二叉搜尋樹的第k大節點

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof/

給定一棵二叉搜尋樹,請找出其中第k大的節點。

二叉搜尋樹的中序遍歷為遞增序列。

    /**
     * 中序遍歷的倒序 o(n)
     */
    public int kthLargest(TreeNode root, int k) {
        this.k = k;
        inorder(root);
        return res;
    }

    private int k;
    private int res;

    public void inorder(TreeNode root) {
        if (root == null || k == 0) {
            return;
        }
        // 先找最大
        inorder(root.right);
        if (--k == 0) {
            res = root.val;
            return;
        }
        inorder(root.left);
    }

55 - I. 二叉樹的深度

https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/

輸入一棵二叉樹的根節點,求該樹的深度。從根節點到葉節點依次經過的節點(含根、葉節點)形成樹的一條路徑,最長路徑的長度為樹的深度。

    /**
     * DFS : 遞迴 or 棧 o(n)
     * BFS : 佇列
     */
    public int maxDepth(TreeNode root) {
        if (root == null) {
            return 0;
        }
        return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
    }

55 - II. 是否為平衡二叉樹

https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/

輸入一棵二叉樹的根節點,判斷該樹是不是平衡二叉樹。如果某二叉樹中任意節點的左右子樹的深度相差不超過1,那麼它就是一棵平衡二叉

    /**
     * DFS : 自底向上後序 o(n)
     */
    public boolean isBalanced(TreeNode root) {
        return balanced(root) != -1;
    }

    public int balanced(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int left = balanced(root.left);
        if (left == -1) {
            return -1;
        }
        int right = balanced(root.right);
        if (right == -1) {
            return -1;
        }
        return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1;
    }

68 - I. 二叉搜尋樹的最近公共祖先

https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/

給定一個二叉搜尋樹, 找到該樹中兩個指定節點的最近公共祖先。

    /**
     * 迭代 : o(n)
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        while (root != null) {
            if (root.val < p.val && root.val < q.val) {
                // p,q 都在 root 的右子樹中
                root = root.right;
            } else if (root.val > p.val && root.val > q.val) {
                // p,q 都在 root 的左子樹中
                root = root.left;
            } else {
                break;
            }
        }
        return root;
    }

68 - II. 二叉樹的最近公共祖先

https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/

給定一個二叉樹, 找到該樹中兩個指定節點的最近公共祖先。

    /**
     * 後續遍歷 : o(n)
     */
    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if (root == null || root == p || root == q) {
            return root;
        }
        // 遞迴遍歷左子樹,只要在左子樹中找到了p或q,則先找到誰就返回誰
        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        // 如果在左子樹中 p和 q都找不到,則 p和 q一定都在右子樹中
        if (left == null) return right;
        if (right == null) return left;

        return root;
    }

5 棧和佇列

09. 用兩個棧實現佇列

https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/

用兩個棧實現一個佇列。

class CQueue {

    public void appendTail(int value) {
        stack1.push(value);
    }

    public int deleteHead() {
        if (stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        return stack2.isEmpty() ? -1 : stack2.pop();
    }

6 場景

10- I. 斐波那契數列

https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/

    /**
     * 動規 : o(n)
     */
    public int fib(int n) {
        int f0 = 0, f1 = 1, tmp = 0;
        for (int i = 0; i < n; i++) {
            tmp = (f1 + f0) % 1000000007;
            f0 = f1;
            f1 = tmp;
        }
        return f0;
    }

10- II. 青蛙跳臺階問題

https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/

一隻青蛙一次可以跳上1級臺階,也可以跳上2級臺階。求該青蛙跳上一個 n 級的臺階總共有多少種跳法。

1)解法同上,f0=1

49. 醜數

https://leetcode-cn.com/problems/chou-shu-lcof/
Medium : Marked

我們把只包含質因子 2、3 和 5 的數稱作醜數(Ugly Number)。求按從小到大的順序的第 n 個醜數。

1)動規+指標

    public int nthUglyNumber(int n) {
        int a = 0, b = 0, c = 0;
        int[] dp = new int[n];
        dp[0] = 1;
        for(int i = 1; i < n; i++) {
            int n2 = dp[a] * 2, n3 = dp[b] * 3, n5 = dp[c] * 5;
            dp[i] = Math.min(Math.min(n2, n3), n5);
            if(dp[i] == n2) a++;
            if(dp[i] == n3) b++;
            if(dp[i] == n5) c++;
        }
        return dp[n - 1];
    }

61. 撲克牌中的順子

https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof/

從撲克牌中隨機抽5張牌,判斷是不是一個順子,即這5張牌是不是連續的。2~10為數字本身,A為1,J為11,Q為12,K為13,而大、小王為 0 ,可以看成任意數字。A 不能視為 14。

1)去重+最大間距

    /**
     * 去重+最大間距 : o(n)
     */
    public boolean isStraight(int[] nums) {
        // 1. 不能重複 2. max-min < 5
        Set<Integer> set = new HashSet<>();
        int joker = 0, max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
        for (int n : nums) {
            if (n == 0) {
                joker++;
                continue;
            }
            max = Math.max(max, n);
            min = Math.min(min, n);
            if (!set.add(n)) {
                return false;
            }
        }
        return max - min < 5;
    }

2)排序(點陣圖)+模擬

    /**
     * 模擬 : o(n)
     */
    public boolean isStraight(int[] nums) {
        int[] map = new int[14];
        // 排序
        for (int n : nums) {
            if (n == 0) {
                map[n]++;
                continue;
            }
            if (map[n] > 0) return false;
            map[n] = 1;
        }
        // 找到最小
        int i = 0, count = 0;
        while (map[++i] == 0) ;
        for (int j = i; j < map.length; j++) {
            if (map[j] == 0 && map[0] > 0) {
                map[0]--;
                count++;
            } else if (map[j] != 0) {
                count++;
            } else {
                break;
            }
        }
        // 加上剩餘大小王
        count += map[0];
        return count == 5;
    }

62. 圓圈中最後剩下的數字

https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/

0,1,n-1這n個數字排成一個圓圈,從數字0開始,每次從這個圓圈裡刪除第m個數字。求出這個圓圈裡剩下的最後一個數字。

1)模擬 o(mn)

2)數學方法(約瑟夫環)

    /**
     * 數學迭代 : o(n)
     * f(n, m) = (f(n-1, m) + m) % n
     */
    public int lastRemaining(int n, int m) {
        // 最後一定是下標為0的人存活,反推最初位置
        int pos = 0;
        for (int i = 2; i <= n; i++) {
            // 每次迴圈右移
            pos = (pos + m) % i;
        }
        return pos;
    }

63. 股票的最大利潤

https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof/
Medium

假設把某股票的價格按照時間先後順序儲存在陣列中,請問買賣該股票一次可能獲得的最大利潤是多少?

    /**
     * 動規 : o(n)
     */
    public int maxProfit(int[] prices) {
        // dp[i] = max( dp[i-1], price[i]-min(price[0:i]) )
        if (prices == null || prices.length < 2) {
            return 0;
        }
        int n = prices.length;
        int[] dp = new int[n];
        dp[0] = 0;
        int min = prices[0];
        for (int i = 1; i < n; i++) {
            dp[i] = Math.max(dp[i - 1], prices[i] - min);
            min = Math.min(min, prices[i]);
        }

        return dp[n - 1];
    }

64. 求1+2+…+n

https://leetcode-cn.com/problems/qiu-12n-lcof/
Medium

求 1+2+…+n ,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。

1)遞迴+短路(遞迴和迴圈相互轉換)

    /**
     * 遞迴+短路 o(n) o(n)
     */
    public int sumNums(int n) {
        boolean x = n > 1 && (n += sumNums(n - 1)) > 0;
        return n;
    }

65. 不用加減乘除做加法

https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/

寫一個函式,求兩個整數之和,要求在函式體內不得使用 “+”、“-”、“*”、“/” 四則運算子號。

1)位運算

    public int add(int a, int b) {
        while(b != 0) { // 當進位為 0 時跳出
            int c = (a & b) << 1;  // c = 進位
            a ^= b; // a = 非進位和
            b = c; // b = 進位
        }
        return a;
    } 

66. 構建乘積陣列

https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof/
Medium

給定一個陣列 A[0,1,…,n-1],請構建一個陣列 B[0,1,…,n-1],其中 B 中的元素 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

1)動規+對稱遍歷

     /**
     * 動規 + 對稱遍歷 : o(n)
     */
    public int[] constructArr(int[] a) {
        if (a.length == 0) return new int[0];
        int[] b = new int[a.length];
        b[0] = 1;
        // 計算下三角
        for (int i = 1; i < a.length; i++) {
            b[i] = b[i - 1] * a[i - 1];
        }
        int tmp = 1;
        // 計算上三角
        for (int i = a.length - 2; i >= 0; i--) {
            tmp *= a[i + 1];
            b[i] *= tmp;
        }
        return b;
    }

相關文章