剑指OFFER

文章目录

    • 1 数组
      • 03. 数组中重复的数字
      • 04. 二维数组中的查找
      • 11. 旋转数组的最小数字
      • 39. 数组中出现次数超过一半的数字
      • 40. 最小的k个数
      • 51. 数组中的逆序对
      • 53 - I. 在排序数组中查找数字 I
      • 53 - II. 0~n-1中缺失的数字
      • 56 - I. 数组中数字出现的次数
      • 56 - II. 数组中数字出现的次数 II
      • 57 - I. 和为s的两个数字
      • 57 - II. 和为s的连续正数序列
      • 59 - I. 滑动窗口的最大值
      • 59 - II. 队列的最大值
    • 2 字符串
      • 05. 替换空格
      • 50. 第一个只出现一次的字符
      • 58 - I. 翻转单词顺序
      • 58 - II. 左旋转字符串
      • 67. 把字符串转换成整数
    • 3 链表
      • 06. 从尾到头打印链表
      • 52. 两个链表的第一个公共节点
    • 4 二叉树
      • 54. 二叉搜索树的第k大节点
      • 55 - I. 二叉树的深度
      • 55 - II. 是否为平衡二叉树
      • 68 - I. 二叉搜索树的最近公共祖先
      • 68 - II. 二叉树的最近公共祖先
    • 5 栈和队列
      • 09. 用两个栈实现队列
    • 6 场景
      • 10- I. 斐波那契数列
      • 10- II. 青蛙跳台阶问题
      • 49. 丑数
      • 61. 扑克牌中的顺子
      • 62. 圆圈中最后剩下的数字
      • 63. 股票的最大利润
      • 64. 求1+2+…+n
      • 65. 不用加减乘除做加法
      • 66. 构建乘积数组

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;
    }
  • 面试题03. 数组中重复的数字 (哈希表 / 原地交换,清晰图解)

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];
    }
  • 面试题11. 旋转数组的最小数字(二分法,清晰图解)

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;
    }
  • 面试题39. 数组中出现次数超过一半的数字(摩尔投票法,清晰图解)

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 - I. 在排序数组中查找数字 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;
    }
  • 面试题53 - II. 0~n-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
     */
  • 面试题56 - II. 数组中数字出现的次数 II(位运算 + 有限状态自动机,清晰图解)

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. 和为 s 的两个数字(双指针 + 证明,清晰图解)

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()][]);
    }
  • 什么是滑动窗口,以及如何用滑动窗口解这道题(C++/Java/Python)

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 - I. 滑动窗口的最大值(单调队列,清晰图解)

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();
    }
}
  • 剑指 Offer 59 - II. 队列的最大值(单调双向队列,清晰图解)

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 - I. 翻转单词顺序(双指针 / 库函数,清晰图解)

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)原地左移(数组倒置)

  • 面试题58 - II. 左旋转字符串(切片 / 列表 / 字符串,清晰图解)

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;
    }
  • 面试题67. 把字符串转换成整数(数字越界处理,清晰图解)

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;
    }
  • 集合,双指针等3种解决方式,后两种击败了100%的用户

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);
    }
  • 面试题54. 二叉搜索树的第 k 大节点(中序遍历 + 提前返回,清晰图解)

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 - I. 二叉树的深度(后序遍历、层序遍历,清晰图解)

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;
    }
  • 面试题55 - II. 平衡二叉树(从底至顶、从顶至底,清晰图解)

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 - I. 二叉搜索树的最近公共祖先(迭代 / 递归,清晰图解)

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;
    }
  • 面试题68 - II. 二叉树的最近公共祖先(后序遍历 DFS ,清晰图解)

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];
    }
  • 面试题49. 丑数(动态规划,清晰图解)

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;
    }
  • 面试题61. 扑克牌中的顺子(集合 Set / 排序,清晰图解)

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];
    }
  • 面试题63. 股票的最大利润(动态规划,清晰图解)

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;
    }
  • 面试题64. 求 1 + 2 + … + 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;
    } 
  • 面试题65. 不用加减乘除做加法(位运算,清晰图解)

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;
    }
  • 面试题66. 构建乘积数组(表格分区,清晰图解)

你可能感兴趣的:(笔记)