├── README.md └── code ├── ListNode.java ├── TreeNode.java ├── offer03.java ├── offer032.java ├── offer04.java ├── offer05.java ├── offer06.java ├── offer07.java ├── offer08.java ├── offer09.java ├── offer10.java ├── offer102.java ├── offer103.java ├── offer104.java ├── offer11.java ├── offer12.java ├── offer13.java ├── offer14.java ├── offer142.java ├── offer15.java ├── offer16.java ├── offer17.java ├── offer18.java ├── offer182.java ├── offer19.java ├── offer20.java ├── offer21.java ├── offer22.java ├── offer23.java ├── offer24.java ├── offer25.java ├── offer26.java ├── offer27.java ├── offer28.java ├── offer29.java ├── offer30.java ├── offer31.java ├── offer32.java ├── offer322.java ├── offer323.java ├── offer33.java ├── offer34.java ├── offer35.java ├── offer36.java ├── offer37.java ├── offer38.java ├── offer39.java ├── offer40.java ├── offer41.java ├── offer42.java ├── offer43.java ├── offer44.java ├── offer45.java ├── offer46.java ├── offer47.java ├── offer48.java ├── offer49.java ├── offer50.java ├── offer502.java ├── offer51.java ├── offer52.java ├── offer53.java ├── offer532.java ├── offer54.java ├── offer55.java ├── offer552.java ├── offer56.java ├── offer562.java ├── offer57.java ├── offer572.java ├── offer58.java ├── offer582.java ├── offer59.java ├── offer592.java ├── offer60.java ├── offer61.java ├── offer62.java ├── offer63.java ├── offer64.java ├── offer65.java ├── offer66.java ├── offer67.java ├── offer68.java └── offer682.java /README.md: -------------------------------------------------------------------------------- 1 | # 剑指Offer 81/75 2 | 3 | `oj` 现使用[力扣 oj ](https://leetcode-cn.com/problemset/lcof/)(点击题目, 即可跳转到该题的 oj 页面. 4 | 5 | `*` 表示力扣 oj 中没有的题目, 仍使用牛客 oj. 6 | 7 | `题号` 表示在剑指 offer 第二版中的题号. 8 | 9 | ## 索引 10 | 11 | 题号|题目|解答| 12 | ----- | ----- | ----- 13 | |3-I|[数组中重复的数字](https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/)|[java](code/offer03.java)| 14 | |3-II|[数组中重复的数字 II](https://leetcode-cn.com/problems/find-the-duplicate-number/)|[java](code/offer032.java)| 15 | |4|[二维数组中的查找](https://leetcode-cn.com/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/)|[java](code/offer04.java)| 16 | |5|[替换空格](https://leetcode-cn.com/problems/ti-huan-kong-ge-lcof/)|[java](code/offer05.java)| 17 | |6|[从尾到头打印链表](https://leetcode-cn.com/problems/cong-wei-dao-tou-da-yin-lian-biao-lcof/)|[java](code/offer06.java)| 18 | |7|[重建二叉树](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/)|[java](code/offer07.java)| 19 | |8*|[二叉树的下一个节点](https://www.nowcoder.com/practice/9023a0c988684a53960365b889ceaf5e?tpId=13&tqId=11210&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)|[java](code/offer08.java)| 20 | |9|[用两个栈实现队列](https://leetcode-cn.com/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/)|[java](code/offer09.java)| 21 | |10-I|[斐波那契数列](https://leetcode-cn.com/problems/fei-bo-na-qi-shu-lie-lcof/)|[java](code/offer10.java)| 22 | |10-II|[青蛙跳台阶问题](https://leetcode-cn.com/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/)|[java](code/offer102.java)| 23 | |10-III*|[变态跳台阶](https://www.nowcoder.com/practice/22243d016f6b47f2a6928b4313c85387?tpId=13&tqId=11162&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)|[java](code/offer103.java)| 24 | |10-IV*|[矩形覆盖](https://www.nowcoder.com/practice/72a5a919508a4251859fb2cfb987a0e6?tpId=13&tqId=11163&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)|[java](code/offer104.java)| 25 | |11|[旋转数组的最小数字](https://leetcode-cn.com/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/)|[java](code/offer11.java)| 26 | |12|[矩阵中的路径](https://leetcode-cn.com/problems/ju-zhen-zhong-de-lu-jing-lcof/)|[java](code/offer12.java)| 27 | |13|[机器人的运动范围](https://leetcode-cn.com/problems/ji-qi-ren-de-yun-dong-fan-wei-lcof/)|[java](code/offer13.java)| 28 | |14-I|[剪绳子](https://leetcode-cn.com/problems/jian-sheng-zi-lcof/)|[java](code/offer14.java)| 29 | |14-II|[剪绳子 II](https://leetcode-cn.com/problems/jian-sheng-zi-ii-lcof/)|[java](code/offer142.java)| 30 | |15|[二进制中 1 的个数](https://leetcode-cn.com/problems/er-jin-zhi-zhong-1de-ge-shu-lcof/)|[java](code/offer15.java)| 31 | |16|[数值的整数次方](https://leetcode-cn.com/problems/shu-zhi-de-zheng-shu-ci-fang-lcof/)|[java](code/offer16.java)| 32 | |17|[打印从 1 到最大的 n 位数](https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/)|[java](code/offer17.java)| 33 | |18-I|[删除链表的节点](https://leetcode-cn.com/problems/shan-chu-lian-biao-de-jie-dian-lcof/)|[java](code/offer18.java)| 34 | |18-II*|[删除链表中重复的节点](https://www.nowcoder.com/practice/fc533c45b73a41b0b44ccba763f866ef?tpId=13&tqId=11209&tPage=1&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)|[java](code/offer182.java)| 35 | |19|[正则表达式匹配](https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/)|[java](code/offer19.java)| 36 | |20|[表示数值的字符串](https://leetcode-cn.com/problems/biao-shi-shu-zhi-de-zi-fu-chuan-lcof/)|[java](code/offer20.java)| 37 | |21|[调整数组顺序使奇数位于偶数前面](https://leetcode-cn.com/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/)|[java](code/offer21.java)| 38 | |22|[链表中倒数第 k 个节点](https://leetcode-cn.com/problems/lian-biao-zhong-dao-shu-di-kge-jie-dian-lcof/)|[java](code/offer22.java)| 39 | |23*|[链表中环的入口结点](https://www.nowcoder.com/practice/253d2c59ec3e4bc68da16833f79a38e4?tpId=13&tqId=11208&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)|[java](code/offer23.java)| 40 | |24|[反转链表](https://leetcode-cn.com/problems/fan-zhuan-lian-biao-lcof/)|[java](code/offer24.java)| 41 | |25|[合并两个排序的链表](https://leetcode-cn.com/problems/he-bing-liang-ge-pai-xu-de-lian-biao-lcof/)|[java](code/offer25.java)| 42 | |26|[树的子结构](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)|[java](code/offer26.java)| 43 | |27|[二叉树的镜像](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/)|[java](code/offer27.java)| 44 | |28|[对称的二叉树](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)|[java](code/offer28.java)| 45 | |29|[顺时针打印矩阵](https://leetcode-cn.com/problems/shun-shi-zhen-da-yin-ju-zhen-lcof/)|[java](code/offer29.java)| 46 | |30|[包含 min 函数的栈](https://leetcode-cn.com/problems/bao-han-minhan-shu-de-zhan-lcof/)|[java](code/offer30.java)| 47 | |31|[栈的压入, 弹出序列](https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/)|[java](code/offer31.java)| 48 | |32-I|[从上到下打印二叉树](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/)|[java](code/offer32.java)| 49 | |32-II|[从上到下打印二叉树 II](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/)|[java](code/offer322.java)| 50 | |32-III|[从上到下打印二叉树 III](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)|[java](code/offer323.java)| 51 | |33|[二叉搜索树的后序遍历序列](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-hou-xu-bian-li-xu-lie-lcof/)|[java](code/offer33.java)| 52 | |34|[二叉树中和为某一值的路径](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)|[java](code/offer34.java)| 53 | |35|[复杂链表的复制](https://leetcode-cn.com/problems/fu-za-lian-biao-de-fu-zhi-lcof/)|[java](code/offer35.java)| 54 | |36|[二叉搜索树与双向链表](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/)|[java](code/offer36.java)| 55 | |37|[序列化二叉树](https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof)|[java](code/offer37.java)| 56 | |38|[字符串的排列](https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof)|[java](code/offer38.java)| 57 | |39|[数组中出现次数超过一半的数字](https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof)|[java](code/offer39.java)| 58 | |40|[最小的 k 个数](https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof)|[java](code/offer40.java)| 59 | |41|[数据流中的中位数](https://leetcode-cn.com/problems/shu-ju-liu-zhong-de-zhong-wei-shu-lcof)|[java](code/offer41.java)| 60 | |42|[连续子数组的最大和](https://leetcode-cn.com/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof)|[java](code/offer42.java)| 61 | |43|[1 ~ n 整数中 1 出现的次数](https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof)|[java](code/offer43.java)| 62 | |44|[数字序列中某一位的数字](https://leetcode-cn.com/problems/shu-zi-xu-lie-zhong-mou-yi-wei-de-shu-zi-lcof)|[java](code/offer44.java)| 63 | |45|[把数组排成最小的数](https://leetcode-cn.com/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof)|[java](code/offer45.java)| 64 | |46|[把数字翻译成字符串](https://leetcode-cn.com/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof)|[java](code/offer46.java)| 65 | |47|[礼物的最大价值](https://leetcode-cn.com/problems/li-wu-de-zui-da-jie-zhi-lcof)|[java](code/offer47.java)| 66 | |48|[最长不重复字符的子字符串](https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof)|[java](code/offer48.java)| 67 | |49|[丑数](https://leetcode-cn.com/problems/chou-shu-lcof)|[java](code/offer49.java)| 68 | |50-I|[第一个只出现一次的字符](https://leetcode-cn.com/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof)|[java](code/offer50.java)| 69 | |50-II*|[字符流中第一个不重复的字符](https://www.nowcoder.com/practice/00de97733b8e4f97a3fb5c680ee10720?tpId=13&tqId=11207&tPage=3&rp=3&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking)|[java](code/offer502.java)| 70 | |51|[数组中的逆序对](https://leetcode-cn.com/problems/shu-zu-zhong-de-ni-xu-dui-lcof)|[java](code/offer51.java)| 71 | |52|[两个链表的第一个公共节点](https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof)|[java](code/offer52.java)| 72 | |53-I|[在排序数组中查找数字 I](https://leetcode-cn.com/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof)|[java](code/offer53.java)| 73 | |53-II|[0 ~ n - 1 中缺失的数字](https://leetcode-cn.com/problems/que-shi-de-shu-zi-lcof)|[java](code/offer532.java)| 74 | |54|[二叉搜索树的第 k 大节点](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-di-kda-jie-dian-lcof)|[java](code/offer54.java)| 75 | |55-I|[二叉树的深度](https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof)|[java](code/offer55.java)| 76 | |55-II|[平衡二叉树](https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof)|[java](code/offer552.java)| 77 | |56-I|[数组中数字出现的次数](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof)|[java](code/offer56.java)| 78 | |56-II|[数组中数字出现的次数 II](https://leetcode-cn.com/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof)|[java](code/offer562.java)| 79 | |57-I|[和为 s 的两个数字](https://leetcode-cn.com/problems/he-wei-sde-liang-ge-shu-zi-lcof)|[java](code/offer57.java)| 80 | |57-II|[和为 s 的连续正数序列](https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof)|[java](code/offer572.java)| 81 | |58-I|[反转单词顺序](https://leetcode-cn.com/problems/fan-zhuan-dan-ci-shun-xu-lcof)|[java](code/offer58.java)| 82 | |58-II|[坐旋转字符串](https://leetcode-cn.com/problems/zuo-xuan-zhuan-zi-fu-chuan-lcof)|[java](code/offer582.java)| 83 | |59-I|[滑动串口的最大值](https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof)|[java](code/offer59.java)| 84 | |59-II|[队列的最大值](https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof)|[java](code/offer592.java)| 85 | |60|[n 个骰子的点数](https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof)|[java](code/offer60.java)| 86 | |61|[扑克牌中的顺子](https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof)|[java](code/offer61.java)| 87 | |62|[圆圈中最后剩下的数字](https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof)|[java](code/offer62.java)| 88 | |63|[股票的最大利润](https://leetcode-cn.com/problems/gu-piao-de-zui-da-li-run-lcof)|[java](code/offer63.java)| 89 | |64|[求 1 + 2 + ... + n](https://leetcode-cn.com/problems/qiu-12n-lcof)|[java](code/offer64.java)| 90 | |65|[不用加减乘除做加法](https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof)|[java](code/offer65.java)| 91 | |66|[构建乘积数组](https://leetcode-cn.com/problems/gou-jian-cheng-ji-shu-zu-lcof)|[java](code/offer66.java)| 92 | |67|[把字符串转换成整数](https://leetcode-cn.com/problems/ba-zi-fu-chuan-zhuan-huan-cheng-zheng-shu-lcof)|[java](code/offer67.java)| 93 | |68-I|[二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof)|[java](code/offer68.java)| 94 | |68-II|[二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof)|[java](code/offer682.java)| -------------------------------------------------------------------------------- /code/ListNode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * @author qhhu 3 | * @date 2020/6/11 - 18:20 4 | */ 5 | public class ListNode { 6 | int val; 7 | ListNode next; 8 | 9 | ListNode(int x) { 10 | val = x; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /code/TreeNode.java: -------------------------------------------------------------------------------- 1 | /** 2 | * 树的节点类 3 | * 4 | * @author qhhu 5 | * @date 2020/2/13 - 13:19 6 | */ 7 | public class TreeNode { 8 | int val; 9 | TreeNode left; 10 | TreeNode right; 11 | 12 | TreeNode(int x) { 13 | val = x; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /code/offer03.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | 3 | /** 4 | * [03] 数组中重复的数字 5 | * 6 | * 题目:找出数组中重复的数字 7 | * 8 | * 在一个长度为 n 的数组里的所有数字都在 0 到 n - 1 的范围内,数组中某些数字是重复的,找出数组中任意一个重复的数字。 9 | * (不知道有几个数字是重复的;也不知道每个数字重复几次) 10 | * 11 | * 思路:1。将输入数组排序,从排序的数组中找到重复的数字。 12 | * 2。使用哈希表(字符集小的时候可以使用数组)存储已遍历过的数字,若当前遍历的数字出现过,则找到重复数字。 13 | * 3。遍历数组,将数字按归位(即将数字 i 放在下标为 i 的位置),若同一个位置上存在多个数字,则找到重复数字。 14 | */ 15 | class Solution { 16 | /** 17 | * 时间复杂度:O(n * logn) 18 | * 空间复杂度:O(1) 19 | */ 20 | public int findRepeatNumber1(int[] nums) { 21 | if (nums == null || nums.length == 0) { 22 | return -1; 23 | } 24 | // sort array, make same elements to adjacent. 25 | Arrays.sort(nums); 26 | // traverse array to judge two adjacent elements is same or not. 27 | for (int i = 1; i < nums.length; i++) { 28 | if (nums[i] == nums[i - 1]) { 29 | return nums[i]; 30 | } 31 | } 32 | 33 | // none of duplication. 34 | return -1; 35 | } 36 | 37 | /** 38 | * 时间复杂度:O(n) 39 | * 空间复杂度:O(n) 40 | */ 41 | public int findRepeatNumber2(int[] nums) { 42 | if (nums == null || nums.length == 0) { 43 | return -1; 44 | } 45 | // use array to storage number had appeared or not. 46 | boolean[] appeared = new boolean[nums.length]; 47 | // traverse array nums. 48 | for (int num : nums) { 49 | // if current number had appeared, find the duplication. 50 | if (appeared[num]) { 51 | return num; 52 | } 53 | // use array appeared mark number had appeared. 54 | appeared[num] = true; 55 | } 56 | 57 | // none of duplication. 58 | return -1; 59 | } 60 | 61 | /** 62 | * 时间复杂度: O(n) 63 | * 空间复杂度: O(1) 64 | */ 65 | public int findRepeatNumber3(int[] nums) { 66 | if (nums == null || nums.length == 0) { 67 | return -1; 68 | } 69 | // traverse array nums. 70 | for (int i = 0; i < nums.length; i++) { 71 | // if current number isn't in it's right position, 72 | // judge right position's element is same of its or not. 73 | // if same, find the duplication; 74 | // if not, swap its and the right position element, 75 | // then continue while process(judge current element is in it's right position or not). 76 | while (i != nums[i]) { 77 | if (nums[nums[i]] == nums[i]) { 78 | return nums[i]; 79 | } else { 80 | swap(nums, i, nums[i]); 81 | } 82 | } 83 | } 84 | 85 | // none of duplication. 86 | return -1; 87 | } 88 | 89 | private void swap(int[] arr, int a, int b) { 90 | int temp = arr[b]; 91 | arr[b] = arr[a]; 92 | arr[a] = temp; 93 | } 94 | } -------------------------------------------------------------------------------- /code/offer032.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [3] 不修改数组找到重复的数字 3 | * 4 | * 题目: 在一个长度为 n + 1 的数组里的所有数字都在 [1, n]的范围内, 所以数组中至少有一个数字是重复的. 请找出数组中任意一个重复的数字. 5 | * 但不能修改输入的数组. 6 | * 7 | * 思路: 类二分查找, 将数组分为含*一定*包含重复元素部分和*不一定*包含重复元素部分 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(n * logn) 12 | * 空间复杂度: O(1) 13 | */ 14 | public int findDuplicate(int[] nums) { 15 | if (nums.length == 0) return -1; 16 | int n = nums.length; 17 | // from [1, n] to find duplication 18 | int l = 1, r = n; 19 | while (l < r) { 20 | int mid = l + r >> 1; 21 | // duplication is in left part 22 | if (check(nums, l, mid)) r = mid; 23 | // duplication is in right part 24 | else l = mid + 1; 25 | } 26 | return l; 27 | } 28 | 29 | // judge the range of [start, end] has duplication or not 30 | private boolean check(int[] nums, int st, int ed) { 31 | int cnt = 0; 32 | for (int num : nums) { 33 | if (num >= st && num <= ed) cnt++; 34 | } 35 | return cnt > ed - st + 1; 36 | } 37 | } -------------------------------------------------------------------------------- /code/offer04.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [04] 二维数组中的查找 3 | * 4 | * 题目:判断矩阵中是否含有整数 target (矩阵中每一行从左往右递增, 每一列从上往下递增)。 5 | * 6 | * 思路:从左下角往右上角判断(或从右上角往左下角判断)。 7 | * 1。若从左下角开始查找,小于当前元素的数一定不在在其右边,大于当前元素的数一定不在其上边,所以可以根据 target 和当前元素的大小关系 8 | * 来缩小查找区间。 9 | * 2。若从右上角往左下角判断同理。 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度:O(n + m) (n 为矩阵的行数,m 为矩阵的列数) 14 | * 空间复杂度:O(1) 15 | */ 16 | public boolean findNumberIn2DArray1(int[][] matrix, int target) { 17 | if (matrix == null || matrix.length == 0 || matrix[0].length == 0) { 18 | return false; 19 | } 20 | // search from left bottom to right top. 21 | int rows = matrix.length, cols = matrix[0].length; 22 | int row = rows - 1, col = 0; 23 | while (row >= 0 && col < cols) { 24 | if (matrix[row][col] == target) { 25 | // trap target. 26 | return true; 27 | } else if (matrix[row][col] < target) { 28 | // if current element is smaller than target, move right. 29 | col++; 30 | } else { 31 | // if current element is bigger than target, move up. 32 | row--; 33 | } 34 | } 35 | 36 | return false; 37 | } 38 | } -------------------------------------------------------------------------------- /code/offer05.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [05] 替换空格 3 | * 4 | * 题目:将给定字符串 s 中的每个空格都替换成 "%20"。 5 | * 6 | * 思路:1。初始化一个 StringBuilder,遍历字符串,当前字符为 ' ' 时,向 StringBuilder 中加入 "%20";当前字符不为 ' ' 时, 7 | * 向 StringBuilder 中加入该字符。 8 | * 9 | * 其实原题想考察的是:给定一个包含空格的字符数组,将数组中的空格替换为 "%20" 三个字符,要求额外空间复杂度为 O(1)。 10 | * (假设数组中有足够的的空间来保存新添加的字符) 11 | * 因为要求额外空间复杂度为 O(1),所以就在原字符数组上进行操作: 12 | * 2。从前往后遍历字符串,每次遇到空格时需要先将空格后的字符全部后移两个字节,再将空格替换为 "%20"。时间复杂度:O(n ^ 2)。 13 | * 3。令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要 14 | * 令 P2 指向的位置依次填充 "20%" (注意是逆序的),否则就填充上 P1 指向字符的值。从后向前遍是为了在改变 P2 所指向的内容 15 | * 时,不会影响到 P1 遍历原来字符串的内容。这样就避免多次重复移动字符。 16 | */ 17 | class Solution { 18 | /** 19 | * 时间复杂度:O(n) 20 | * 空间复杂度:O(n) 21 | */ 22 | public String replaceSpace1(String s) { 23 | StringBuilder sb = new StringBuilder(); 24 | for (char chr : s.toCharArray()) { 25 | if (chr == ' ') { 26 | // replace all ' ' with "%20". 27 | sb.append("%20"); 28 | } else { 29 | sb.append(chr); 30 | } 31 | } 32 | 33 | return sb.toString(); 34 | } 35 | } -------------------------------------------------------------------------------- /code/offer06.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | 3 | /** 4 | * [06] 从尾到头打印链表 5 | * 6 | * 题目: 按链表值从尾到头的顺序返回一个整数类型的数组. 7 | * 8 | * 思路: 1. 反转链表后再从头到尾输出. 9 | * 2. 从头到尾遍历链表并将值存入栈中, 再从栈中依次弹出元素, 输出的节点顺序即为从尾到头. 10 | * 3. 使用递归栈完成上方法2: 每次访问一个节点时, 先递归输出它后面的节点, 再输出该节点. 11 | * 4. 从头到尾遍历链表, 每次将节点值放在LinkedList第一个节点位置. 12 | */ 13 | 14 | /** 15 | * Definition for singly-linked list. 16 | * public class ListNode { 17 | * int val; 18 | * ListNode next; 19 | * ListNode(int x) { val = x; } 20 | * } 21 | */ 22 | class Solution { 23 | /** 24 | * 时间复杂度: O(n) 25 | * 空间复杂度: O(1) 26 | */ 27 | public int[] reversePrint1(ListNode head) { 28 | if (head == null) { 29 | return new int[0]; 30 | } 31 | // count list's node number. 32 | int len = 0; 33 | ListNode cur = head; 34 | while (cur != null) { 35 | len++; 36 | cur = cur.next; 37 | } 38 | // first, reverse list. 39 | // second, traverse list to storage value. 40 | // finally, reverse list again to recover list. 41 | head = reverseList(head); 42 | int[] ret = new int[len]; 43 | int index = 0; 44 | cur = head; 45 | while (cur != null) { 46 | ret[index++] = cur.val; 47 | cur = cur.next; 48 | } 49 | reverseList(head); 50 | 51 | return ret; 52 | } 53 | 54 | private ListNode reverseList(ListNode head) { 55 | if (head == null) { 56 | return null; 57 | } 58 | ListNode pre = null, cur = head; 59 | while (head != null) { 60 | ListNode succ = head.next; 61 | cur.next = pre; 62 | pre = cur; 63 | cur = succ; 64 | } 65 | 66 | return pre; 67 | } 68 | 69 | /** 70 | * 时间复杂度: O(n) 71 | * 空间复杂度: O(n) 72 | */ 73 | public int[] reversePrint2(ListNode head) { 74 | if (head == null) { 75 | return new int[0]; 76 | } 77 | // traverse list and use stack to storage value. 78 | Deque stack = new ArrayDeque<>(); 79 | while (head != null) { 80 | stack.push(head.val); 81 | head = head.next; 82 | } 83 | int[] ret = new int[stack.size()]; 84 | int index = 0; 85 | // pop element from stack and storage its into array ret. 86 | // the order is from end to first. 87 | while (!stack.isEmpty()) { 88 | ret[index++] = stack.pop(); 89 | } 90 | 91 | return ret; 92 | } 93 | 94 | /** 95 | * 时间复杂度: O(n) 96 | * 空间复杂度: O(n) 97 | */ 98 | private int[] ret; 99 | private int index = 0; 100 | 101 | public int[] reversePrint3(ListNode head) { 102 | if (head == null) { 103 | return new int[0]; 104 | } 105 | // count list's node number. 106 | int len = 0; 107 | ListNode cur = head; 108 | while (cur != null) { 109 | len++; 110 | cur = cur.next; 111 | } 112 | // use recursive stack to collect element. 113 | ret = new int[len]; 114 | dfs(head); 115 | 116 | return ret; 117 | } 118 | 119 | private void dfs(ListNode head) { 120 | if (head == null) { 121 | return; 122 | } 123 | // recursive call dfs to collect the others listNode from end to first. 124 | dfs(head.next); 125 | // then add current listNode value. 126 | // because its is the first node of list, 127 | // will be put into last index in the array ret. 128 | ret[index++] = head.val; 129 | } 130 | 131 | /** 132 | * 时间复杂度: O(n) 133 | * 空间复杂度: O(n) 134 | */ 135 | public int[] reversePrint4(ListNode head) { 136 | LinkedList list = new LinkedList<>(); 137 | while (head != null) { 138 | // every element will be put in the first of LinkedList, 139 | // finally the element order is from end to first. 140 | list.addFirst(head.val); 141 | head = head.next; 142 | } 143 | // use array ret to storage value in LinkedList for return. 144 | int[] ret = new int[list.size()]; 145 | int index = 0; 146 | for (int num : list) { 147 | ret[index++] = num; 148 | } 149 | 150 | return ret; 151 | } 152 | } -------------------------------------------------------------------------------- /code/offer07.java: -------------------------------------------------------------------------------- 1 | import java.util.HashMap; 2 | 3 | /** 4 | * [07] 重建二叉树 5 | * 6 | * 题目: 根据二叉树的前序遍历和中序遍历的序列, 重构二叉树(不含重复元素). 7 | * 8 | * 思路: 前序遍历序列为, 中-左-右, 9 | * 中序遍历序列为, 左-中-右. 10 | * 前序遍历序列的第一个节点值为根节点的值, 通过这个根结点的值将前序和中序遍历序列都分为左子树和右子树部分, 然后分别对左右子树递归地求解. 11 | * (注意: 是通过左右子树的节点数来划分前序和中序遍历序列, 而不是异想) 12 | */ 13 | 14 | /** 15 | * Definition for a binary tree node. 16 | * public class TreeNode { 17 | * int val; 18 | * TreeNode left; 19 | * TreeNode right; 20 | * TreeNode(int x) { val = x; } 21 | * } 22 | */ 23 | class Solution { 24 | /** 25 | * 时间复杂度: O(n) 26 | * 空间复杂度: O(n) 27 | */ 28 | private HashMap inMap; 29 | 30 | public TreeNode buildTree(int[] preorder, int[] inorder) { 31 | // use map to record inorder's value and it's index, 32 | // then you can quickly search value's index in inorder. 33 | inMap = new HashMap<>(inorder.length); 34 | for (int i = 0; i < inorder.length; i++) { 35 | inMap.put(inorder[i], i); 36 | } 37 | 38 | return buildTreeCore(preorder, 0, preorder.length - 1, 39 | inorder, 0, inorder.length - 1); 40 | } 41 | 42 | private TreeNode buildTreeCore(int[] preorder, int preStart, int preEnd, 43 | int[] inorder, int inStart, int inEnd) { 44 | // base case. 45 | if (preStart > preEnd) { 46 | return null; 47 | } 48 | // according to the preorder's first value creat the root TreeNode. 49 | int value = preorder[preStart]; 50 | TreeNode root = new TreeNode(value); 51 | // search the root's index in inorder by inMap, 52 | // and use this index to calculate the number of left subtree sequence, 53 | // for division origin sequence to left subtree part and right subtree part. 54 | int indexInInorder = inMap.get(value); 55 | int leftSubTreeSize = indexInInorder - inStart; 56 | // recursive call buildTreeCore to construct current node's left subtree and right subtree. 57 | root.left = buildTreeCore(preorder, preStart + 1, preStart + leftSubTreeSize, 58 | inorder, inStart, indexInInorder - 1); 59 | root.right = buildTreeCore(preorder, preStart + leftSubTreeSize + 1, preEnd, 60 | inorder, indexInInorder + 1, inEnd); 61 | 62 | return root; 63 | } 64 | } -------------------------------------------------------------------------------- /code/offer08.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [8] 二叉树的下一个节点 3 | * 4 | * 题目: 给定一个二叉树和其中的一个结点, 请找出中序遍历顺序的下一个结点并且返回. 注意, 树中的结点不仅包含左右子结点, 同时包含指向父结点的指针. 5 | * 6 | * 思路: 中序遍历顺序为左-中-右. 7 | * a. 当前节点有右节点时, 其后继节点为其右子树的最左节点. 8 | * b. 当前节点无右节点时, 则判断其是否为其父节点的左子节点. 若是, 则其后继节点为其父节点; 若不是, 继续向上寻找. 9 | */ 10 | 11 | /* 12 | public class TreeLinkNode { 13 | int val; 14 | TreeLinkNode left = null; 15 | TreeLinkNode right = null; 16 | TreeLinkNode next = null; 17 | 18 | TreeLinkNode(int val) { 19 | this.val = val; 20 | } 21 | } 22 | */ 23 | public class Solution { 24 | /** 25 | * 时间复杂度: O(n) 26 | * 空间复杂度: O(1) 27 | */ 28 | public TreeLinkNode GetNext(TreeLinkNode pNode) { 29 | if (pNode == null) { 30 | return null; 31 | } 32 | if (pNode.right != null) { 33 | // if current node have right subtree, 34 | // looking for succeed node in it's right subtree. 35 | pNode = pNode.right; 36 | while (pNode.left != null) { 37 | pNode = pNode.left; 38 | } 39 | return pNode; 40 | } else { 41 | // if current node haven't right subtree, 42 | // judge it is its parent node's left child node or not. 43 | // if not, continue looking for upward. 44 | TreeLinkNode parent = pNode.next; 45 | while (parent != null && pNode != parent.left) { 46 | pNode = parent; 47 | parent = pNode.next; 48 | } 49 | return parent; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /code/offer09.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.Deque; 3 | 4 | /** 5 | * [09] 用两个栈实现队列 6 | * 7 | * 题目: 用两个栈来实现一个队列. 8 | * 9 | * 思路: 使用两个辅助栈, 一个输入栈IN和一个输出栈OUT. 输入时总是压入栈IN, 输出时将栈IN中的元素全部弹出并压入栈OUT后再从OUT栈中弹出, 10 | * 这样出栈顺序就和最开始入栈顺序是相同的, 即先进入的元素先退出, 这就是队列的顺序. 11 | */ 12 | class CQueue { 13 | private Deque in; 14 | private Deque out; 15 | 16 | public CQueue() { 17 | in = new ArrayDeque<>(); 18 | out = new ArrayDeque<>(); 19 | } 20 | 21 | public void appendTail(int value) { 22 | // node always push into stack IN. 23 | in.push(value); 24 | } 25 | 26 | public int deleteHead() { 27 | if (out.isEmpty()) { 28 | if (in.isEmpty()) { 29 | // when queue is empty, return -1. 30 | return -1; 31 | } else { 32 | // when stack IN is empty but stack OUT have elements, 33 | // push all stack OUT's element into stack IN. 34 | while (!in.isEmpty()) { 35 | out.push(in.pop()); 36 | } 37 | } 38 | } 39 | 40 | // pop operate always use to stack OUT. 41 | return out.pop(); 42 | } 43 | } 44 | 45 | /** 46 | * Your CQueue object will be instantiated and called as such: 47 | * CQueue obj = new CQueue(); 48 | * obj.appendTail(value); 49 | * int param_2 = obj.deleteHead(); 50 | */ -------------------------------------------------------------------------------- /code/offer10.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [10-I] 斐波那切数列 3 | * 4 | * 题目: 返回斐波那契数列的第 n 项(斐波那契数列由 0 和 1 开始). 5 | * (答案需要取模 1e9 + 7 (1000000007)) 6 | * 7 | * 思路: 动态规划, f(i) 表示第 i 项斐波那契数列; 8 | * 状态转移方程: f(i) = f(i - 1) + f(i - 2). 9 | */ 10 | class Solution { 11 | /** 12 | * 时间复杂度: O(n) 13 | * 空间复杂度: O(1) 14 | */ 15 | public int fib(int n) { 16 | if (n <= 1) { 17 | return n; 18 | } 19 | int pre = 0, succ = 1; 20 | for (int i = 2; i <= n; i++) { 21 | // state transition equation: f(i) = f(i - 1) + f(i - 2). 22 | int fib = (pre + succ) % (int) (1e9 + 7); 23 | pre = succ; 24 | succ = fib; 25 | } 26 | 27 | return succ; 28 | } 29 | } -------------------------------------------------------------------------------- /code/offer102.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [10-II] 青蛙跳台阶问题 3 | * 4 | * 题目: 一只青蛙一次可以跳上 1 级台阶, 也可以跳上 2 级台阶. 求该青蛙跳上一个 n 级的台阶总共有多少种跳法. 5 | * (答案需要取模 1e9 + 7 (1000000007)) 6 | * 7 | * 思路: 动态规划, f(i) 表示 i 个台阶一共有多少种跳法; 8 | * 状态转移方程: f(i) = f(i - 1) + f(i - 2). 9 | * 与 [10-I] 斐波那切数列的不同在于, 此题的 f(0) = 1, f(1) = 1(斐波那切数列的 f(0) = 0, f(1) = 1). 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(n) 14 | * 空间复杂度: O(1) 15 | */ 16 | public int numWays(int n) { 17 | if (n <= 1) { 18 | return 1; 19 | } 20 | int pre = 1, succ = 1; 21 | for (int i = 2; i <= n; i++) { 22 | // state transition equation: f(i) = f(i - 1) + f(i - 2). 23 | int fib = (pre + succ) % (int) (1e9 + 7); 24 | pre = succ; 25 | succ = fib; 26 | } 27 | 28 | return succ; 29 | } 30 | } -------------------------------------------------------------------------------- /code/offer103.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [10-III] 变态跳台阶 3 | * 4 | * 题目: 一只青蛙一次可以跳上 1 级台阶, 也可以跳上 2 级 ... 它也可以跳上 n 级. 求该青蛙跳上一个 n 级的台阶总共有多少种跳法. 5 | * 6 | * 思路: 动态规划, f(n) 表示 n 个台阶总共有多少种跳法; 7 | * 状态转移方程: f(n) = f(n - 1) + f(n - 2) + ... + f(0) 8 | * ==> f(n) = 2 * f(n - 1) 9 | * f(n - 1) = f(n - 2) + f(n - 3) + ... + f(0) 10 | * 11 | */ 12 | public class Solution { 13 | public int JumpFloorII(int n) { 14 | int dp = 1; 15 | for (int i = 2; i <= n; i++) { 16 | // state transition equation: f(n) = 2 * f(n - 1). 17 | dp = 2 * dp; 18 | } 19 | 20 | return dp; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /code/offer104.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [10-IV] 矩形覆盖 3 | * 4 | * 题目: 可以用 2 * 1 的小矩形横着或者竖着去覆盖更大的矩形. 请问用 n 个 2 * 1 的小矩形无重叠地覆盖一个 2 * n 的大矩形, 总共有多少种方法. 5 | * 6 | * 思路: 要覆盖 2 * n 的大矩形, 可以先竖着覆盖一个 2 * 1 的矩形, 再覆盖 2 * (n - 1) 的矩形; 7 | * 或者可以先横着覆盖两个 2 * 1 的矩形, 再覆盖 2 * (n - 2) 的矩形. 8 | * 即 f(n) = f(n - 1) + f(n - 2), 实际上就是斐波那切数列. 9 | */ 10 | public class Solution { 11 | /** 12 | * 时间复杂度: O(n) 13 | * 空间复杂度: O(1) 14 | */ 15 | public int RectCover(int target) { 16 | if (target <= 1) { 17 | return target; 18 | } 19 | int pre = 0, succ = 1; 20 | for (int i = 2; i <= target; i++) { 21 | // state transition equation: f(i) = f(i - 1) + f(i - 2). 22 | int fib = (pre + succ) % (int) (1e9 + 7); 23 | pre = succ; 24 | succ = fib; 25 | } 26 | 27 | return succ; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /code/offer11.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [11] 旋转数组的最小数字 3 | * 4 | * 题目: 输出给定非递减排序的数组的旋转数组的最小元素. 5 | * (给出的所有元素都大于0) 6 | * 7 | * 思路: 二分查找, 旋转排序数组可以拆分为两个排序数组 nums1 和 nums2, 并且 nums1任意元素 >= nums2 任意元素, 8 | * 使用二分查找这两个数组的分界点(nums2 的首元素)即最小元素. 9 | * 1. 将原数组划分为大于 nums[hi] 部分和小于等于 nums[i] 部分, 最后返回小于等于部分的第一个个元素. 但若数组元素允许重复, 会出现 10 | * 一个特殊的情况: nums[lo] == nums[mid] == nums[hi], 此时无法确定解在哪个区间, 直接顺序查找. 11 | * 2. 根据 nums[mid] 和 nums[hi] 的大小判断最小元素所在区间. 但若数组元素允许重复, 会出现 nums[mid] == nums[hi] 的情况, 此 12 | * 时无法判断最小元素所在区间(例: [1, 0, 1, 1, 1], [1, 1, 1, 0, 1]). 此时采用 hi = hi - 1 的解决方法, 此操作不会丢失最 13 | * 小值. 14 | */ 15 | class Solution { 16 | /** 17 | * 时间复杂度: O(logn) (在特例情况下会退化到 O(n) (如[1, 1, 1, 1])) 18 | * 空间复杂度: O(1) 19 | */ 20 | public int minArray1(int[] numbers) { 21 | if (numbers.length == 0) { 22 | return 0; 23 | } 24 | int lo = 0, hi = numbers.length - 1; 25 | while (lo < hi) { 26 | int mid = lo + ((hi - lo) >> 1); 27 | // divide section [lo, hi] to bigger than hi part and smaller or equal hi part. 28 | // when numbers[lo] == numbers[mid] numbers[hi], 29 | // can't judge, only to traverse section to find smallest element. 30 | if (numbers[lo] == numbers[mid] && numbers[mid] == numbers[hi]) { 31 | return minNumber(numbers, lo, hi); 32 | } else if (numbers[mid] <= numbers[hi]) { 33 | hi = mid; 34 | } else { 35 | lo = mid + 1; 36 | } 37 | } 38 | return numbers[lo]; 39 | } 40 | 41 | private int minNumber(int[] numbers, int lo, int hi) { 42 | for (int i = lo; i < hi; i++) { 43 | if (numbers[i] > numbers[i + 1]) { 44 | return numbers[i + 1]; 45 | } 46 | } 47 | return numbers[lo]; 48 | } 49 | 50 | /** 51 | * 时间复杂度: O(logn) (在特例情况下会退化到 O(n) (如[1, 1, 1, 1])) 52 | * 空间复杂度: O(1) 53 | */ 54 | public int minArray2(int[] numbers) { 55 | int lo = 0, hi = numbers.length - 1; 56 | while (lo < hi) { 57 | int mid = lo + ((hi - lo) >> 1); 58 | // when mid's value is bigger than hi, 59 | // mid must be in left section and the smallest element is in mid's right. 60 | if (numbers[mid] > numbers[hi]) { 61 | lo = mid + 1; 62 | } else if (numbers[mid] < numbers[hi]) { 63 | // when mid's value is smaller than hi, 64 | // mid must be in right section and the smallest element maybe is mid. 65 | hi = mid; 66 | } else { 67 | // when mid's value is equal with hi, 68 | // we can't judge mid is belong to which section, such as [1, 0, 1, 1, 1], [1, 1, 1, 0, 1]. 69 | // so let hi = hi - 1 to solve this problem. 70 | hi--; 71 | } 72 | } 73 | 74 | return numbers[lo]; 75 | } 76 | } -------------------------------------------------------------------------------- /code/offer12.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [12] 矩阵中的路径 3 | * 4 | * 题目: 设计一个函数, 用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径. 5 | * 6 | * 思路: 回溯算法. 7 | */ 8 | class Solution { 9 | /** 10 | * 时间复杂度: O((m * n) ^ 2) 11 | * 空间复杂度: O(m * n) 12 | */ 13 | private int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; 14 | 15 | public boolean exist(char[][] board, String word) { 16 | if (board == null || board.length == 0 || board[0].length == 0) { 17 | return false; 18 | } 19 | int rows = board.length, cols = board[0].length; 20 | // use visited array to mark matrix's position which has been visited in case revisited. 21 | boolean[][] visited = new boolean[rows][cols]; 22 | // traverse every position of matrix. 23 | // if current position's char is equal with word's first char, 24 | // then start to find path from current position. 25 | for (int i = 0; i < rows; i++) { 26 | for (int j = 0; j < cols; j++) { 27 | if (board[i][j] == word.charAt(0)) { 28 | if (dfs(board, visited, i, j, word, 0)) { 29 | return true; 30 | } 31 | } 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | private boolean dfs(char[][] board, boolean[][] visited, int row, int col, String word, int index) { 38 | // all chars in word had been find. 39 | if (index == word.length()) { 40 | return true; 41 | } 42 | // when current position is out of matrix, has been visited; 43 | // or current position char is's same to current char in word. 44 | if (row < 0 || row >= board.length || col < 0 || col >= board[0].length 45 | || visited[row][col] || board[row][col] != word.charAt(index)) { 46 | return false; 47 | } 48 | visited[row][col] = true; 49 | // continue to find path in four directions. 50 | for (int[] dir : direction) { 51 | if (dfs(board, visited, row + dir[0], col + dir[1], word, index + 1)) { 52 | return true; 53 | } 54 | } 55 | // backtrack: 56 | // before return upper recursion, must recover visited array as it was. 57 | visited[row][col] = false; 58 | return false; 59 | } 60 | } -------------------------------------------------------------------------------- /code/offer13.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [13] 机器人的运动范围 3 | * 4 | * 题目: 地上有一个m行和n列的方格. 一个机器人从坐标 (0, 0) 的格子开始移动, 每一次只能向左, 右, 上, 下四个方向移动一格, 5 | * 但是不能进入行坐标和列坐标的数位之和大于k的格子. 6 | * 7 | * 思路: 深度优先遍历, 注意这是计算一共可以到达多少格子, 而不是最远路径. 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(m * n) 12 | * 空间复杂度: O(m * n) 13 | */ 14 | private int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; 15 | 16 | public int movingCount(int m, int n, int k) { 17 | if (k < 0 || m <= 0 || n <= 0) { 18 | return -1; 19 | } 20 | // use visited array to mark matrix's position which has been visited in case revisited. 21 | boolean[][] visited = new boolean[m][n]; 22 | return dfs(m, n, k, visited, 0, 0); 23 | } 24 | 25 | private int dfs(int rows, int cols, int k, boolean[][] visited, int row, int col) { 26 | if (row < 0 || row >= rows || col < 0 || col >= cols 27 | || visited[row][col] || !check(row, col, k)) { 28 | return 0; 29 | } 30 | visited[row][col] = true; 31 | // use integer cnt to storage how much grid can be visited. 32 | int cnt = 1; 33 | // visited current position and continue calculate can visited position count from four directions. 34 | for (int[] dir : direction) { 35 | cnt += dfs(rows, cols, k, visited, row + dir[0], col + dir[1]); 36 | } 37 | // needn't recover visited array. 38 | // because this question is find how much position can be visited, 39 | // every position will be allow visited one time most. 40 | return cnt; 41 | } 42 | 43 | // check can visited current position or not. 44 | private boolean check(int a, int b, int k) { 45 | int sum = 0; 46 | while (a != 0) { 47 | sum += a % 10; 48 | a /= 10; 49 | } 50 | while (b != 0) { 51 | sum += b % 10; 52 | b /= 10; 53 | } 54 | 55 | return sum <= k; 56 | } 57 | } -------------------------------------------------------------------------------- /code/offer14.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [14-I] 剪绳子 3 | * 4 | * 题目: 给你一个长度为 n 的绳子, 请将绳子剪成整数长度的 m 段(m, n都是整数, n > 1 且 m > 1即至少剪一刀), 返回每段绳子长度的最大乘积. 5 | * (与[面试题14-II] 剪绳子不同的是: 2 <= n <= 58) 6 | * 7 | * 思路: 动态规划, f(i)表示将长度 i 的绳子剪成若干段后各段乘积的最大值; 8 | * 在剪第一刀的时候有 i - 1 种可能的选择, 即剪出的第一段为 1, 2, ..., i - 1, 9 | * 所以状态转移方程: f(i) = max(f(j) + f(i - j)) j ∈ [1, i). 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(n ^ 2) 14 | * 空间复杂度: O(n) 15 | */ 16 | public int cuttingRope(int n) { 17 | int[] dp = new int[n + 1]; 18 | // dp[0] = 0; 19 | // dp[1] = 0; 20 | // from down to up. 21 | for (int i = 2; i <= n; i++) { 22 | // just need loop to i / 2. 23 | int mid = i >> 1; 24 | // state transition equation is f(i) = max(f(j) + f(i - j)). 25 | for (int j = 1; j <= mid; j++) { 26 | // divide current string to two parts, 27 | // and calculate product what current can be get most. 28 | // Math.max(dp[j], j) is mean cut current length j string or don't. 29 | int product = Math.max(dp[j], j) * Math.max(dp[i - j], i - j); 30 | dp[i] = Math.max(dp[i], product); 31 | } 32 | } 33 | 34 | return dp[n]; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /code/offer142.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [14-II] 剪绳子 3 | * 4 | * 题目: 给你一个长度为 n 的绳子, 请将绳子剪成整数长度的 m 段(m, n都是整数, n > 1 且 m > 1即至少剪一刀), 返回每段绳子长度的最大乘积. 5 | * (答案需要取模 1e9 + 7 (1000000007)) 6 | * (与[面试题14-I] 剪绳子不同的是: 2 <= n <= 1000) 7 | * 8 | * 思路: 贪心策略, 当 n >= 5 时, 尽可能地多剪长度为 3 的绳子; 9 | * 当剩下的绳子长度为 4 时, 把绳子剪成两段长度为 2 的绳子. 10 | * 证明: 当 n >= 5 时, 3(n - 3) - n = 2n - 9 > 0, 且 2(n - 2) - n = n - 4 > 0. 因此在 n >= 5 的情况下, 将绳子剪成一段 11 | * 为 2 或者 3, 得到的乘积会更大. 又因为 3(n - 3) - 2(n - 2) = n - 5 >= 0,所以剪成一段长度为 3 比长度为 2 得到的乘积更大. 12 | */ 13 | class Solution { 14 | /** 15 | * 时间复杂度: O(logn) 16 | * 空间复杂度: O(1) 17 | */ 18 | public int cuttingRope(int n) { 19 | if (n < 2) { 20 | return 0; 21 | } 22 | if (n == 2) { 23 | return 1; 24 | } 25 | if (n == 3) { 26 | return 2; 27 | } 28 | int mod = (int) (1e9 + 7); 29 | long ret = 1; 30 | // when current n's value is bigger or equal with 5, cut a length 3 part. 31 | while (n >= 5) { 32 | ret *= 3; 33 | ret %= mod; 34 | n -= 3; 35 | } 36 | 37 | // rest length is 4, its should be divide to 2 and 2. 38 | // means when n == 4, max product == 2 * 2 == 4. 39 | return (int) (ret * n % mod); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /code/offer15.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [15] 二进制中 1 的个数 3 | * 4 | * 题目: 输入一个整数, 输出该数二进制表示中 1 的个数. 5 | * 6 | * 思路: 1. 使用无符号右移操作, 从右到左判断二进制表示的每一位上的数是否为 1. 7 | * 2. (n - 1) & n操作可以将 n 的最右边的 1 变为 0, 所以计算可以进行多少次此操作, 就有多少个1. 8 | * 如: (1100 - 1) & 1100 = 1011 & 1100 = 1000. 9 | * (减 1 操作将原数字最右边的 1 变为 0, 其后所有位变为 1 与原数字 & 后, 即将原数字最右边的 1 变为 0) 10 | */ 11 | public class Solution { 12 | /** 13 | * 时间复杂度: O(m) (m 表示 n 的二进制位数) 14 | * 空间复杂度: O(1) 15 | */ 16 | // you need to treat n as an unsigned value 17 | public int hammingWeight1(int n) { 18 | int cnt = 0; 19 | while (n != 0) { 20 | // judge every position of n's binary number is 1 or not, 21 | // and count 1's appeared times. 22 | cnt += n & 1; 23 | n >>>= 1; 24 | } 25 | 26 | return cnt; 27 | } 28 | 29 | /** 30 | * 时间复杂度: O(m) (m 表示 n 的二进制中 1 的个数) 31 | * 空间复杂度: O(1) 32 | */ 33 | // you need to treat n as an unsigned value 34 | public int hammingWeight2(int n) { 35 | int cnt = 0; 36 | // when n not equal 0, explain n have one 1 at last, cnt plus one. 37 | // then n = (n - 1) & n operation let n lost this one 1 which Just accumulated, 38 | // and continue judge n is equal 0 or not. 39 | while (n != 0) { 40 | cnt++; 41 | n = (n - 1) & n; 42 | } 43 | 44 | return cnt; 45 | } 46 | } -------------------------------------------------------------------------------- /code/offer16.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [16] 数值的整数次方 3 | * 4 | * 题目: 给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent. 求 base 的 exponent 次方. 5 | * 6 | * 思路: 1. 快幂算法: a ^ n = (a ^ (n / 2)) * (a ^ (n / 2)) if n 为偶数; 7 | * = (a ^ ((n - 1) / 2)) * (a ^ ((n - 1) / 2)) * a if n 为奇数. 8 | * 2. 位运算计算幂: 二进制表示 exponent ,若当前位置上的值为1, 表示当前位置有值. 9 | * 二进制的最后一位对应的值是 base; 倒数第二位对应的是 base平方; 倒数第三位 base 平方的平方, 以此类推. 10 | * 设结果的初始值为 res = 1, 发现对应位置上的数为 1, 就将 res 乘以对应的值. 11 | * 12 | */ 13 | class Solution { 14 | /** 15 | * 时间复杂度: O(logn) 16 | * 空间复杂度: O(logn) 17 | */ 18 | public double myPow1(double x, int n) { 19 | if (n == 0) { 20 | return 1; 21 | } 22 | if (n == 1) { 23 | return x; 24 | } 25 | if (n == -1) { 26 | return 1 / x; 27 | } 28 | // calculate a ^ (n / 2)) * (a ^ (n / 2). 29 | double half = myPow1(x, n / 2); 30 | // if n is odd number n / 2 will rest 1 or -1, 31 | // then calculate the rest part a ^ (n % 2)). 32 | double mod = myPow1(x, n % 2); 33 | return half * half * mod; 34 | } 35 | 36 | /** 37 | * 时间复杂度: O(m) (m 为 n 的二进制位数) 38 | * 空间复杂度: O(1) 39 | */ 40 | public double myPow2(double x, int n) { 41 | if (n == 0) { 42 | return 1; 43 | } 44 | if (n == 1) { 45 | return x; 46 | } 47 | if (n == -1) { 48 | return 1 / x; 49 | } 50 | boolean flag = n > 0 ? true : false; 51 | // if x is negative number. 52 | // first calculate the value when it is positive, 53 | // then just return 1 divide this value. 54 | n = Math.abs(n); 55 | // initialize res as 1. 56 | double ret = 1; 57 | while (n != 0) { 58 | // from back to front, judge every position is 1 or 0. 59 | // if it's 1, ret multiply current x. 60 | if ((n & 1) == 1) { 61 | ret *= x; 62 | } 63 | // calculate next position's x. 64 | x *= x; 65 | // move to next position. 66 | n >>>= 1; 67 | } 68 | 69 | // set return value according to flag. 70 | return flag ? ret : 1 / ret; 71 | } 72 | } -------------------------------------------------------------------------------- /code/offer17.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | 3 | /** 4 | * [17] 打印从 1 到最大的 n 位数 5 | * 6 | * 题目: 输入数字 n, 按顺序打印出从 1 到最大的 n 位十进制数. 比如输入 3, 则打印出 1, 2, 3 一直到最大的 3 位数即 999. 7 | * 8 | * 思路: 1. 先求出最大的 n 位数, 再从 1 开始逐个打印. 9 | * 10 | * 若最大的 n 位数使用整数和长整数都会溢出, 可以使用 char 数组进行储存. 11 | * 2. 模拟整数的加法: 初始化字符数组表示 0, 然后每次将字符数组表示的数字加一. 12 | * 3. 全排列: 把数字的每一位都从 0 到 9 排列一遍, 就得到了所有的十进制数. 13 | */ 14 | class Solution { 15 | /** 16 | * 时间复杂度: O(m) (m 为最大的 n 位数) 17 | * 空间复杂度: O(1) 18 | */ 19 | public int[] printNumbers1(int n) { 20 | int len = 0; 21 | while (n-- > 0) { 22 | len = len * 10 + 9; 23 | } 24 | int[] ret = new int[len]; 25 | for (int i = 1; i <= len; i++) { 26 | ret[i - 1] = i; 27 | } 28 | 29 | return ret; 30 | } 31 | 32 | /** 33 | * 时间复杂度: O(m) (m 为最大的 n 位数) 34 | * 空间复杂度: O(n) 35 | */ 36 | public void print1ToMaxOfNDigits2(int n) { 37 | if (n < 0) { 38 | return; 39 | } 40 | // initialize number to 0. 41 | char[] number = new char[n]; 42 | Arrays.fill(number, '0'); 43 | 44 | // plus one to the number every time, 45 | // if number isn't overflow, print this number. 46 | while (!isOverflowAfterIncrement(number)) { 47 | printNumber(number); 48 | } 49 | } 50 | 51 | private boolean isOverflowAfterIncrement(char[] number) { 52 | int len = number.length; 53 | boolean isOverflow = false; 54 | int carry = 0; 55 | for (int i = len - 1; i >= 0; i--) { 56 | int curSum = number[i] - '0' + carry; 57 | // plus one. 58 | if (i == len - 1) { 59 | curSum++; 60 | } 61 | if (curSum >= 10) { 62 | // the highest position carry one, 63 | // means current number is the biggest value of n position. 64 | if (i == 0) { 65 | isOverflow = true; 66 | } else { 67 | carry = curSum / 10; 68 | curSum = curSum % 10; 69 | number[i] = (char) ('0' + curSum); 70 | } 71 | } else { 72 | // if haven't carry, 73 | // means current number finish plus one. 74 | number[i] = (char) ('0' + curSum); 75 | break; 76 | } 77 | } 78 | 79 | return isOverflow; 80 | } 81 | 82 | // print number 83 | private void printNumber(char[] number) { 84 | int len = number.length; 85 | int index = 0; 86 | // skip 0 which in the front. 87 | while (index < len && number[index] == '0') { 88 | index++; 89 | } 90 | while (index < len) { 91 | System.out.print(number[index++]); 92 | } 93 | System.out.println(); 94 | } 95 | 96 | /** 97 | * 时间复杂度: O(m) (m 为最大的 n 位数) 98 | * 空间复杂度: O(n) 99 | */ 100 | public void print1ToMaxOfNDigits3(int n) { 101 | if (n < 0) { 102 | return; 103 | } 104 | process(n, new char[n], 0); 105 | } 106 | 107 | private void process(int n, char[] number, int index) { 108 | if (index == n) { 109 | printNumber(number); 110 | return; 111 | } 112 | // every position will put char from 0 to 9. 113 | for (int i = 0; i <= 9; i++) { 114 | number[index] = (char) ('0' + i); 115 | process(n, number, index + 1); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /code/offer18.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [18-I] 删除链表的节点 3 | * 4 | * 题目: 删除链表的节点 5 | * 6 | * 给定单向链表的头节点和一个要删除的节点值, 返回删除后的节点的头节点. 7 | * (题目保证链表中节点的值互不相同) 8 | * 9 | * 思路: 1. 遍历链表同时保存当前节点的前驱节点, 当遇到值等于给定值的节点时, 将其删除. 10 | * 11 | * 原题是在 O(1) 时间内删除链表节点: 给定单向链表的头节点和一个节点指针, 在O(1)的时间内删除该节点. 12 | * 2. 把下一个节点的值复制到需要删除的节点上, 再把下一个节点删除, 相当于将当前需要删除的节点删除了. 13 | * (需要额外考虑需要删除的节点是否是尾节点, 当前链表是否只有一个节点) 14 | */ 15 | 16 | /** 17 | * Definition for singly-linked list. 18 | * public class ListNode { 19 | * int val; 20 | * ListNode next; 21 | * ListNode(int x) { val = x; } 22 | * } 23 | */ 24 | class Solution { 25 | /** 26 | * 时间复杂度: O(n) 27 | * 空间复杂度: O(1) 28 | */ 29 | public ListNode deleteNode1(ListNode head, int val) { 30 | ListNode dummy = new ListNode(-1); 31 | dummy.next = head; 32 | // storage current node's previous node. 33 | ListNode pre = dummy; 34 | while (head != null) { 35 | // when meet node which value equal with val, delete its. 36 | if (head.val == val) { 37 | pre.next = head.next; 38 | return dummy.next; 39 | } 40 | pre = head; 41 | head = head.next; 42 | } 43 | 44 | throw new IllegalArgumentException("Haven't a node which value equal val"); 45 | } 46 | 47 | /** 48 | * 时间复杂度: O(1) (平均时间复杂度) 49 | * 空间复杂度: O(1) 50 | */ 51 | public ListNode deleteNode2(ListNode head, ListNode toBeDeleted) { 52 | if (head == null || toBeDeleted == null) { 53 | return null; 54 | } 55 | if (toBeDeleted.next != null) { 56 | // toBeDeleted isn't tail node. 57 | toBeDeleted.val = toBeDeleted.next.val; 58 | toBeDeleted.next = toBeDeleted.next.next; 59 | } else { 60 | if (head == toBeDeleted) { 61 | // toBeDeleted is tail node and list just have one node, 62 | // delete the head node. 63 | head = null; 64 | } else { 65 | // toBeDeleted is tail node and list have many node, 66 | // can only traverse list to delete tail node. 67 | ListNode cur = head; 68 | while (cur.next != toBeDeleted) { 69 | cur = cur.next; 70 | } 71 | cur.next = null; 72 | } 73 | } 74 | 75 | return head; 76 | } 77 | } -------------------------------------------------------------------------------- /code/offer182.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [18-II] 删除链表的节点 3 | * 4 | * 题目: 删除链表中的重复节点 5 | * 6 | * 在一个排序的链表中, 删除该链表中重复的节点, 返回链表头指针. 例如: 链表 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5 处理后为 1 -> 2 -> 5. 7 | * 8 | * 思路: 1. 迭代: 遍历链表, 遇到重复节点则将其删除. 9 | * 1 -> 2 -> 2 -> 3 10 | * pre cur 11 | * pre cur 12 | * pre cur 13 | * pre -> cur continue 14 | * 2. 递归(减而治之): f(n) = head + f(n - 1). 15 | * 16 | * 原题是去重节点, 例如: 链表 1 -> 2 -> 3 -> 3 -> 4 -> 4 -> 5 处理后为 1 -> 2 -> 3 -> 4 -> 5. 17 | * 3. 同方法1: 1 -> 2 -> 2 -> 3 18 | * cur succ 19 | * cur succ 20 | * cur -> succ and cur == succ continue 21 | */ 22 | 23 | /* 24 | public class ListNode { 25 | int val; 26 | ListNode next = null; 27 | 28 | ListNode(int val) { 29 | this.val = val; 30 | } 31 | } 32 | */ 33 | public class Solution { 34 | /** 35 | * 时间复杂度: O(n) 36 | * 空间复杂度: O(1) 37 | */ 38 | public ListNode deleteDuplication1(ListNode head) { 39 | // head node may be delete, 40 | // use dummy node to convenience delete head node. 41 | ListNode dummy = new ListNode(-1); 42 | dummy.next = head; 43 | 44 | var pre = dummy; 45 | while (pre.next != null) { 46 | // if current element have duplication, 47 | // skip all duplication element to find the last duplication element, 48 | ListNode cur = pre.next; 49 | while (cur.next != null && cur.val == cur.next.val) cur = cur.next; 50 | // can judge have duplication element or not in range from pre to cur by if. 51 | // if pre's successor node is cur, it means haven't duplication. 52 | // if current element haven't duplication, move previous to the next element and continue loop. 53 | if (pre.next == cur) pre = cur; 54 | // if pre's successor node is'n cur, it means haven duplication. 55 | // if current element have duplication, delete duplication and continue loop 56 | else pre.next = cur.next; 57 | } 58 | 59 | return dummy.next; 60 | } 61 | 62 | /** 63 | * 时间复杂度: O(n) 64 | * 空间复杂度: O(n) 65 | */ 66 | public ListNode deleteDuplication2(ListNode pHead) { 67 | if (pHead == null || pHead.next == null) { 68 | return pHead; 69 | } 70 | ListNode succ = pHead.next; 71 | if (succ.val == pHead.val) { 72 | // if current element is duplication, 73 | // skip all duplication element find the first not duplication element, 74 | // then recursive call deleteDuplication to handle other nodes. 75 | while (succ != null && succ.val == pHead.val) { 76 | succ = succ.next; 77 | } 78 | return deleteDuplication2(succ); 79 | } else { 80 | // if current element isn't duplication, 81 | // just continue recursive call deleteDuplication to handle other nodes. 82 | pHead.next = deleteDuplication2(succ); 83 | return pHead; 84 | } 85 | } 86 | 87 | /** 88 | * 时间复杂度: O(n) 89 | * 空间复杂度: O(1) 90 | */ 91 | public ListNode deleteDuplication3(ListNode pHead) { 92 | ListNode cur = pHead; 93 | ListNode succ = null; 94 | while (cur != null) { 95 | succ = cur.next; 96 | // if current element have duplication, 97 | // skip all duplication element find the first not duplication element, 98 | while (succ != null && succ.val == cur.val) { 99 | succ = succ.next; 100 | } 101 | // connect current node and first not duplication element for delete duplication. 102 | cur.next = succ; 103 | cur = succ; 104 | } 105 | 106 | return pHead; 107 | } 108 | } -------------------------------------------------------------------------------- /code/offer19.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [19] 正则表达式匹配 3 | * 4 | * 题目: 实现一个函数用来匹配包括 '.' 和 '*' 的正则表达式. 在本题中, 匹配是指字符串的所有字符匹配整个模式. 5 | * (模式中的字符 '.' 表示任意一个字符, 而 '*' 表示它前面的字符可以出现任意次(包含 0 次)) 6 | * 7 | * 思路: 1. 回溯所有情况. 8 | * 2. 动态规划, dp[i][j] 表示 s 的前 i 个是否能被 p 的前 j 个匹配. 9 | * https://leetcode-cn.com/problems/regular-expression-matching/solution/dong-tai-gui-hua-zen-yao-cong-0kai-shi-si-kao-da-b/ 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O() 14 | * 空间复杂度: O() 15 | */ 16 | public boolean isMatch1(String s, String p) { 17 | if (s == null || p == null) { 18 | return false; 19 | } 20 | return isMatchCore(s, 0, p, 0); 21 | } 22 | 23 | private boolean isMatchCore(String s, int indexOfS, String p, int indexOfP) { 24 | if (indexOfS == s.length() && indexOfP == p.length()) { 25 | return true; 26 | } 27 | if (indexOfS != s.length() && indexOfP == p.length()) { 28 | return false; 29 | } 30 | // can't estimate match condition: 31 | // third case, str is end but pattern maybe remain char* could be delete, continue judge.(very important) 32 | // fourth case, str isn't end and pattern isn't end, continue judge. 33 | 34 | // if next char is '*'. 35 | if (indexOfP + 1 < p.length() && p.charAt(indexOfP + 1) == '*') { 36 | if (indexOfS != s.length() 37 | // if current str's char can match pattern's char. 38 | && (s.charAt(indexOfS) == p.charAt(indexOfP) || p.charAt(indexOfP) == '.')) { 39 | // pattern's char match zero str's char. 40 | return isMatchCore(s, indexOfS, p, indexOfP + 2) 41 | // pattern's char match one str's char. 42 | || isMatchCore(s, indexOfS + 1, p, indexOfP + 2) 43 | // pattern's char match multiple str's char. 44 | || isMatchCore(s, indexOfS + 1, p, indexOfP); 45 | } else { 46 | // two different case(very important): 47 | // if current str's char can't match pattern's char or str is end, 48 | // should ignore pattern current char and '*'. 49 | return isMatchCore(s, indexOfS, p, indexOfP + 2); 50 | } 51 | } 52 | 53 | // if next char isn't '*', str current character must match pattern current char or false. 54 | if (indexOfS != s.length() 55 | && (s.charAt(indexOfS) == p.charAt(indexOfP) || p.charAt(indexOfP) == '.')) { 56 | return isMatchCore(s, indexOfS + 1, p, indexOfP + 1); 57 | } else { 58 | // two different case(very important): 59 | // if current str's char can't match pattern's char or str is end, 60 | // return false. 61 | return false; 62 | } 63 | } 64 | 65 | /** 66 | * 时间复杂度: O(m * n) 67 | * 空间复杂度: O(m * n) 68 | */ 69 | public boolean isMatch2(String s, String p) { 70 | char[] str = s.toCharArray(); 71 | char[] pattern = p.toCharArray(); 72 | int m = str.length, n = pattern.length; 73 | boolean[][] dp = new boolean[m + 1][n + 1]; 74 | 75 | // base case. 76 | dp[0][0] = true; 77 | for (int i = 1; i <= n; i++) 78 | if (pattern[i - 1] == '*') 79 | dp[0][i] = dp[0][i - 2]; 80 | 81 | for (int i = 1; i <= m; i++) 82 | for (int j = 1; j <= n; j++) 83 | if (str[i - 1] == pattern[j - 1] || pattern[j - 1] == '.') 84 | dp[i][j] = dp[i - 1][j - 1]; 85 | else if (pattern[j - 1] == '*') 86 | if (pattern[j - 2] == str[i - 1] || pattern[j - 2] == '.') { 87 | dp[i][j] |= dp[i][j - 1]; // a* counts as single a 88 | dp[i][j] |= dp[i - 1][j]; // a* counts as multiple a 89 | dp[i][j] |= dp[i][j - 2]; // a* counts as empty 90 | } else 91 | dp[i][j] = dp[i][j - 2]; // a* only counts as empty 92 | 93 | return dp[m][n]; 94 | } 95 | } -------------------------------------------------------------------------------- /code/offer20.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [20] 表示数值的字符串 3 | * 4 | * 题目: 实现一个函数用来判断字符串是否表示数值(包括整数和小数) 5 | * (字符串 "+100", "5e2", "-123", "3.1416" 和 "-1E-16" 都表示数值, 6 | * 但是 "12e", "1a3.14", "1.2.3", "+-5" 和 "12e+4.3" 都不是) 7 | * 8 | * 思路: 1. 将字符串分为三部分判断, A.Be|EC: A 为数值的整数部分, B 为数值的小数部分, C 为数值的指数部分. 9 | * 其中 A 和 C 都是整数(可以有正负号), 而 B 是一个无符号整数. 10 | * 2. 使用正则表达式进行匹配: [] : 字符集合 11 | * () : 分组 12 | * ? : 重复 0 ~ 1 次 13 | * + : 重复 1 ~ n 次 14 | * * : 重复 0 ~ n 次 15 | * . : 任意字符 16 | * \\. : 转义后的 . 17 | * \\d : 数字 18 | */ 19 | class Solution { 20 | /** 21 | * 时间复杂度: O(n) 22 | * 空间复杂度: O(1) 23 | */ 24 | // because index is same one in this method, 25 | // so use global variable to storage index. 26 | private int index = 0; 27 | 28 | public boolean isNumber1(String s) { 29 | if (s == null) { 30 | return false; 31 | } 32 | // remove string's space which on both sides. 33 | s = s.trim(); 34 | boolean isNum = scanInteger(s); 35 | // if current char is '.', means the next part is decimal. 36 | if (index < s.length() && s.charAt(index) == '.') { 37 | index++; 38 | // .123 is legal; 123. is legal; 123.123 legal, 39 | // so use (scanUnsignedInteger(s) || isNum) to judge. 40 | // but can't write like this (isNum || scanUnsignedInteger(s))(important), 41 | // because if put isNum in the front. 42 | // when isNum is right scanUnsignedInteger(s) will not be execute, 43 | // means the part B will not calculate. 44 | isNum = scanUnsignedInteger(s) || isNum; 45 | } 46 | // if current char is 'e' or 'E', means the next part is exponent. 47 | if (index < s.length() && (s.charAt(index) == 'e' || s.charAt(index) == 'E')) { 48 | index++; 49 | // .e1 and e1 is illegal; 12e and 12e+5.4 is illegal. 50 | // so use (isNum && scanInteger(s)) to judge. 51 | isNum = isNum && scanInteger(s); 52 | } 53 | 54 | // isNum just can imply string have number as start, 55 | // only when string's all chars are used and isNum is right can imply string is number. 56 | return isNum && index == s.length(); 57 | } 58 | 59 | // take away unsigned integer part. 60 | private boolean scanUnsignedInteger(String s) { 61 | int i = index; 62 | while (index < s.length() && (s.charAt(index) >= '0' && s.charAt(index) <= '9')) { 63 | index++; 64 | } 65 | 66 | return index > i; 67 | } 68 | 69 | // take away signed integer part. 70 | private boolean scanInteger(String s) { 71 | if (index < s.length() && (s.charAt(index) == '-' || s.charAt(index) == '+')) { 72 | index++; 73 | } 74 | 75 | return scanUnsignedInteger(s); 76 | } 77 | 78 | /** 79 | * 时间复杂度: O() 80 | * 空间复杂度: O() 81 | */ 82 | public boolean isNumber2(String s) { 83 | if (s == null || s.length() == 0) { 84 | return false; 85 | } 86 | return s.matches("[+-]?\\d*(\\.\\d+)?([eE][+-]?\\d+)?"); 87 | } 88 | } -------------------------------------------------------------------------------- /code/offer21.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [21] 调整数组顺序使奇数位于偶数前面 3 | * 4 | * 题目: 输入一个整数数组, 实现一个函数来调整该数组中数字的顺序, 使得所有奇数位于数组的前半部分, 所有偶数位于数组的后半部分. 5 | * (不需要保证奇数和奇数, 偶数和偶数之间的相对位置不变) 6 | * 7 | * 思路: 类似快速排序的 partition 过程: 将当前区间分为奇数部分和偶数部分. 遍历数组, 若当前元素是奇数则将其放入奇数区间, 若不是则继续遍历. 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(n) 12 | * 空间复杂度: O(1) 13 | */ 14 | public int[] exchange(int[] nums) { 15 | // divide current array to odd section and even section. 16 | int odd = -1; 17 | for (int i = 0; i < nums.length; i++) { 18 | // if current element is odd number, 19 | // put current element to odd section. 20 | if ((nums[i] & 1) == 1) { 21 | swap(nums, i, ++odd); 22 | } 23 | } 24 | 25 | return nums; 26 | } 27 | 28 | private void swap(int[] arr, int a, int b) { 29 | int temp = arr[b]; 30 | arr[b] = arr[a]; 31 | arr[a] = temp; 32 | } 33 | } -------------------------------------------------------------------------------- /code/offer22.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [22] 链表中倒数第 k 个节点 3 | * 4 | * 题目: 输入一个链表, 输出该链表中倒数第 k 个节点. 5 | * (链表的尾节点是倒数第1个节点) 6 | * 7 | * 思路: 快慢指针: 设链表的长度为 n. 设置两个指针 fast 和 slow, 先让 fast 移动 k 个节点, 然后让 fast 和 slow 同时移动, 可以知道 8 | * 当 fast 移动到链表结尾(null)时, fast 与 slow 相距 k 个节点, 此时 slow 所在位置就是倒数第 k 个节点. 9 | */ 10 | 11 | /** 12 | * Definition for singly-linked list. 13 | * public class ListNode { 14 | * int val; 15 | * ListNode next; 16 | * ListNode(int x) { val = x; } 17 | * } 18 | */ 19 | class Solution { 20 | /** 21 | * 时间复杂度: O(n) 22 | * 空间复杂度: O(1) 23 | */ 24 | public ListNode getKthFromEnd(ListNode head, int k) { 25 | if (head == null || k <= 0) { 26 | return null; 27 | } 28 | ListNode fast = head, slow = head; 29 | // fast pointer is faster than slow about k step. 30 | while (fast != null && k-- > 0) { 31 | fast = fast.next; 32 | } 33 | // when fast pointer is null but k also bigger than 0, 34 | // it means this list haven't the countdown k's node so return null. 35 | if (k > 0) { 36 | return null; 37 | } 38 | // fast and slow pointer move together, 39 | // until fast pointer move to null, 40 | // now, slow pointer is the countdown k's node. 41 | while (fast != null) { 42 | fast = fast.next; 43 | slow = slow.next; 44 | } 45 | 46 | return slow; 47 | } 48 | } -------------------------------------------------------------------------------- /code/offer23.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [23] 链表中环的入口节点 3 | * 4 | * 题目: 给一个链表, 若其中包含环, 返回该链表的环的入口结点, 否则, 输出 null. 5 | * 6 | * 思路: Floyd 算法, 找出列表中是否有环, 如果没有环, 可以直接返回 null 并退出. 否则, 用相遇节点来找到环的入口. 7 | */ 8 | 9 | /* 10 | public class ListNode { 11 | int val; 12 | ListNode next = null; 13 | 14 | ListNode(int val) { 15 | this.val = val; 16 | } 17 | } 18 | */ 19 | public class Solution { 20 | /** 21 | * 时间复杂度: O(n) 22 | * 空间复杂度: O(1) 23 | */ 24 | public ListNode EntryNodeOfLoop(ListNode pHead) { 25 | if (pHead == null || pHead.next == null) { 26 | return pHead; 27 | } 28 | ListNode fast = pHead, slow = pHead; 29 | // judge list have cycle or not. 30 | while (fast != null && fast.next != null) { 31 | fast = fast.next.next; 32 | slow = slow.next; 33 | if (fast == slow) { 34 | break; 35 | } 36 | } 37 | // if fast pointer point the end of list, means list haven't cycle. 38 | if (fast == null || fast.next == null) { 39 | return null; 40 | } 41 | // if list have cycle, find into cycle's node. 42 | fast = pHead; 43 | while (fast != slow) { 44 | fast = fast.next; 45 | slow = slow.next; 46 | } 47 | 48 | return fast; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /code/offer24.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [24] 反转链表 3 | * 4 | * 题目: 输入一个链表的头节点, 反转该链表并输出反转后链表的头节点. 5 | * 6 | * 思路: 反转链表, 1. 迭代. 7 | * 2. 递归. 8 | */ 9 | 10 | /** 11 | * Definition for singly-linked list. 12 | * public class ListNode { 13 | * int val; 14 | * ListNode next; 15 | * ListNode(int x) { val = x; } 16 | * } 17 | */ 18 | class Solution { 19 | /** 20 | * 时间复杂度: O(n) 21 | * 空间复杂度: O(1) 22 | */ 23 | public ListNode reverseList(ListNode head) { 24 | if (head == null || head.next == null) return head; 25 | // when reverse current listNode's next point, 26 | // use succ to storage current listNode's next listNode, 27 | // in case break list. 28 | ListNode pre = null, cur = head, succ = null; 29 | while (cur != null) { 30 | succ = cur.next; 31 | cur.next = pre; 32 | pre = cur; 33 | cur = succ; 34 | } 35 | return pre; 36 | } 37 | 38 | /** 39 | * 时间复杂度: O(n) 40 | * 空间复杂度: O(n) 41 | */ 42 | public ListNode reverseList2(ListNode head) { 43 | if (head == null || head.next == null) return head; 44 | ListNode succ = head.next; 45 | // recursive call reverseList2 to solve rest list, 46 | // then reverse current head node. 47 | ListNode newHead = reverseList2(succ); 48 | succ.next = head; 49 | head.next = null; 50 | return newHead; 51 | } 52 | } -------------------------------------------------------------------------------- /code/offer25.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [25] 合并两个排序的链表 3 | * 4 | * 题目: 输入两个递增排序的链表, 合并这两个链表并使新链表中的节点仍然是递增排序的. 5 | * 6 | * 思路: 类似归并排序的 merge 过程, 1. 迭代: 合并两链表, 当其中一个链表中所有节点合并完成后, 将另一个链表剩余节点一并合并. 7 | * 2. 迭代: 合并两链表, 直到所有节点都合并完成. 8 | * 3. 递归(减而治之): f(n, m) = 1 + (f(n - 1, m) or f(n, m - 1)). 9 | */ 10 | 11 | /** 12 | * Definition for singly-linked list. 13 | * public class ListNode { 14 | * int val; 15 | * ListNode next; 16 | * ListNode(int x) { val = x; } 17 | * } 18 | */ 19 | class Solution { 20 | /** 21 | * 时间复杂度: O(m + n) 22 | * 空间复杂度: O(1) 23 | */ 24 | public ListNode mergeTwoLists1(ListNode l1, ListNode l2) { 25 | if (l1 == null) return l2; 26 | if (l2 == null) return l1; 27 | // use dummy for easy return merged list's head. 28 | // tail always be the end of current merged list. 29 | ListNode dummy = new ListNode(-1), tail = dummy; 30 | // merge l1 and l2, 31 | // until all of them had run out. 32 | while (l1 != null || l2 != null) { 33 | int v1 = l1 != null ? l1.val : Integer.MAX_VALUE; 34 | int v2 = l2 != null ? l2.val : Integer.MAX_VALUE; 35 | if (v1 <= v2) { 36 | tail.next = l1; 37 | l1 = l1.next; 38 | } else { 39 | tail.next = l2; 40 | l2 = l2.next; 41 | } 42 | tail = tail.next; 43 | } 44 | return dummy.next; 45 | } 46 | 47 | /** 48 | * 时间复杂度: O(m + n) 49 | * 空间复杂度: O(1) 50 | */ 51 | public ListNode mergeTwoLists2(ListNode l1,ListNode l2) { 52 | if (l1 == null) { 53 | return l2; 54 | } 55 | if (l2 == null) { 56 | return l1; 57 | } 58 | // use dummy for easy return merged list's head. 59 | // tail always be the end of current merged list. 60 | ListNode dummy = new ListNode(-1); 61 | ListNode tail = dummy; 62 | // merge l1 and l2, 63 | // until one of them had run out. 64 | while (l1 != null && l2 != null) { 65 | if (l1.val <= l2.val) { 66 | tail.next = l1; 67 | l1 = l1.next; 68 | } else { 69 | tail.next = l2; 70 | l2 = l2.next; 71 | } 72 | tail = tail.next; 73 | } 74 | // merge the rest of l1 or l2. 75 | if (l1 == null) { 76 | tail.next = l2; 77 | } else { 78 | tail.next = l1; 79 | } 80 | 81 | return dummy.next; 82 | } 83 | 84 | /** 85 | * 时间复杂度: O(m + n) 86 | * 空间复杂度: O(m + n) 87 | */ 88 | public ListNode mergeTwoLists3(ListNode l1,ListNode l2) { 89 | if (l1 == null) { 90 | return l2; 91 | } 92 | if (l2 == null) { 93 | return l1; 94 | } 95 | if (l1.val <= l2.val) { 96 | l1.next = mergeTwoLists3(l1.next, l2); 97 | return l1; 98 | } else { 99 | l2.next = mergeTwoLists3(l1, l2.next); 100 | return l2; 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /code/offer26.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [26] 树的子结构 3 | * 4 | * 题目: 判断给定二叉树 B 是不是 A 的子结构. (约定空树不是任意一个树的子结构) 5 | * (B 是 A 的子结构,, 即 A 中有出现和 B 相同的结构和节点值) 6 | * 7 | * 思路: node1 node2 8 | * 1 1 9 | * 3 5 3 5 10 | * 2 4 6 7 11 | * 遍历 A 树, 将以每个节点为根节点的子树和 B 树比较, 判断 B 是否为其子树. 12 | * 若 node2 遍历完, 则 node2 是 node1 的子树, node1 遍历完 node2 还没遍历完, 则不是. 13 | */ 14 | 15 | /** 16 | * Definition for a binary tree node. 17 | * public class TreeNode { 18 | * int val; 19 | * TreeNode left; 20 | * TreeNode right; 21 | * TreeNode(int x) { val = x; } 22 | * } 23 | */ 24 | class Solution { 25 | /** 26 | * 时间复杂度: O(m * n) (m 为 A 的节点数, n 为 B 的节点数) 27 | * 空间复杂度: O(n) 28 | */ 29 | public boolean isSubStructure(TreeNode A, TreeNode B) { 30 | if (A == null || B == null) return false; 31 | return isSubStructureCore(A, B) 32 | || isSubStructure(A.left, B) || isSubStructure(A.right, B); 33 | } 34 | 35 | private boolean isSubStructureCore(TreeNode root1, TreeNode root2) { 36 | // root1 == null && root2 == null, 37 | // or root1 != null && root2 == null. 38 | if (root2 == null) return true; 39 | // root1 == null & root2 != null, 40 | // or root1 noe equal root2. 41 | if (root1 == null || root1.val != root2.val) return false; 42 | return isSubStructureCore(root1.left, root2.left) && isSubStructureCore(root1.right, root2.right); 43 | } 44 | } -------------------------------------------------------------------------------- /code/offer27.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [27] 二叉树的镜像 3 | * 4 | * 题目: 返回给定二叉树镜像. 5 | * 6 | * 思路: 遍历给定二叉树的每个节点,并翻转当前遍历的节点的左右子树 7 | */ 8 | 9 | /** 10 | * Definition for a binary tree node. 11 | * public class TreeNode { 12 | * int val; 13 | * TreeNode left; 14 | * TreeNode right; 15 | * TreeNode(int x) { val = x; } 16 | * } 17 | */ 18 | class Solution { 19 | /** 20 | * 时间复杂度: O(n) 21 | * 空间复杂度: O(n) 22 | */ 23 | public TreeNode mirrorTree(TreeNode root) { 24 | if (root == null) return null; 25 | // change left and right subtree position, 26 | TreeNode tmp = root.right; 27 | root.right = root.left; 28 | root.left = tmp; 29 | // recursive call mirrorTree to handle left and right subtree. 30 | mirrorTree(root.left); 31 | mirrorTree(root.right); 32 | 33 | return root; 34 | } 35 | } -------------------------------------------------------------------------------- /code/offer28.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.Queue; 3 | 4 | /** 5 | * [28] 对称的二叉树 6 | * 7 | * 题目: 判断给定二叉树是不是对称的. 如果一棵二叉树和它的镜像一样, 那么它是对称的. 8 | * 9 | * 思路: 判断给定的二叉树的左子树与右子树是否为镜像二叉树即可. 10 | * 1. 递归, dfs. 11 | * 2. 迭代, bfs. 12 | */ 13 | 14 | /** 15 | * Definition for a binary tree node. 16 | * public class TreeNode { 17 | * int val; 18 | * TreeNode left; 19 | * TreeNode right; 20 | * TreeNode(int x) { val = x; } 21 | * } 22 | */ 23 | class Solution { 24 | /** 25 | * 时间复杂度: O(n) 26 | * 空间复杂度: O(n) 27 | */ 28 | public boolean isSymmetric1(TreeNode root) { 29 | if (root == null) return true; 30 | return isMirror(root.left, root.right); 31 | } 32 | 33 | // if root's left subtree and right subtree is mirroring tree, 34 | // means the tree root is symmetry binary tree. 35 | private boolean isMirror(TreeNode left, TreeNode right) { 36 | if (left == null && right == null) return true; 37 | if (left == null || right == null) return false; 38 | return left.val == right.val 39 | && isMirror(left.left, right.right) && isMirror(left.right, right.left); 40 | } 41 | 42 | /** 43 | * 时间复杂度: O(n) 44 | * 空间复杂度: O(n) 45 | */ 46 | public boolean isSymmetric2(TreeNode root) { 47 | if (root == null) { 48 | return true; 49 | } 50 | return bfs(root.left, root.right); 51 | } 52 | 53 | // if root's left subtree and right subtree is mirroring tree, 54 | // means the tree root is symmetry binary tree. 55 | private boolean bfs(TreeNode left, TreeNode right) { 56 | Queue queue = new ArrayDeque<>(); 57 | queue.offer(left); 58 | queue.offer(right); 59 | while (!queue.isEmpty()) { 60 | TreeNode cur1 = queue.poll(); 61 | TreeNode cur2 = queue.poll(); 62 | // judge current left subtree node and right one is equal or not. 63 | if (cur1 == null && cur2 == null) { 64 | continue; 65 | } 66 | if (cur1 == null || cur2 == null || cur1.val != cur2.val) { 67 | return false; 68 | } 69 | // make the should equal nodes put close, 70 | // so in the next iteration will be judge them is equal or not. 71 | // if current node is null, also should be add into queue, 72 | // in case mix the should equal pair(very important). 73 | queue.offer(cur1.left); 74 | queue.offer(cur2.right); 75 | queue.offer(cur1.right); 76 | queue.offer(cur2.left); 77 | } 78 | 79 | return true; 80 | } 81 | } -------------------------------------------------------------------------------- /code/offer29.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [29] 顺时针打印矩阵 3 | * 4 | * 题目: 按照从外向里以顺时针的顺序依次打印出给定矩阵中的每一个数字. 5 | * 6 | * 思路: 我们顺时针定义四个方向:右下左上。 7 | * 从左上角开始遍历,先往右走,走到不能走为止,然后更改到下个方向,再走到不能走为止,依次类推,直到遍历 n ^2 个格子后停止。 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(m * n) 12 | * 空间复杂度: O(1) 13 | */ 14 | // four directions: right, down, left, up 15 | private int[][] dir = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; 16 | 17 | public int[] spiralOrder(int[][] matrix) { 18 | if (matrix.length == 0) return new int[] {}; 19 | int n = matrix.length, m = matrix[0].length; 20 | int[] ret = new int[n * m]; 21 | boolean[][] st = new boolean[n][m]; 22 | // from (0, 0) to traverse matrix until all position is done 23 | for (int i = 0, j = 0, idx = 0, d = 0; idx < n * m;) { 24 | ret[idx++] = matrix[i][j]; 25 | st[i][j] = true; 26 | int x = i + dir[d][0], y = j + dir[d][1]; 27 | // if current position is out of border 28 | // change direction to next one and continue traverse 29 | if (x < 0 || x >= n || y < 0 || y >= m || st[x][y]) { 30 | d = (d + 1) % 4; 31 | x = i + dir[d][0]; 32 | y = j + dir[d][1]; 33 | } 34 | i = x; 35 | j = y; 36 | } 37 | return ret; 38 | } 39 | } -------------------------------------------------------------------------------- /code/offer30.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.Deque; 3 | 4 | /** 5 | * [30] 包含 min 函数的栈 6 | * 7 | * 题目: 定义栈的数据结构, 请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中, 调用 min, push 及 pop 的时间复杂度都是 O(1). 8 | * 9 | * 思路: 使用辅助栈 min 存储当前的最小值. 10 | */ 11 | class MinStack { 12 | 13 | private Deque data; 14 | private Deque min; 15 | 16 | /** initialize your data structure here. */ 17 | public MinStack() { 18 | data = new ArrayDeque<>(); 19 | min = new ArrayDeque<>(); 20 | } 21 | 22 | /** 23 | * 方法一: 24 | * 数据栈和最小值栈中元素个数相同. 25 | */ 26 | public void push1(int x) { 27 | data.push(x); 28 | // always push current minimum element into min stack. 29 | if (!min.isEmpty() && min.peek() < x) min.push(min.peek()); 30 | else min.push(x); 31 | } 32 | 33 | public void pop1() { 34 | data.pop(); 35 | min.pop(); 36 | } 37 | 38 | /** 39 | * 方法二: 40 | * 数据栈中元素大于等于最小值栈中元素个数. 41 | */ 42 | public void push2(int x) { 43 | data.push(x); 44 | // push x into min stack, 45 | // only when current insert value x is smaller or equal current minimum element. 46 | if (min.isEmpty()) { 47 | min.push(x); 48 | } else if (x <= min.peek()) { 49 | min.push(x); 50 | } 51 | } 52 | 53 | public void pop2() { 54 | int top = data.pop(); 55 | // when current pop element is equal min stack's top, 56 | // pop min stack top element. 57 | if (top == min.peek()) { 58 | min.pop(); 59 | } 60 | } 61 | 62 | public int top() { 63 | return data.peek(); 64 | } 65 | 66 | public int min() { 67 | return min.peek(); 68 | } 69 | } 70 | 71 | /** 72 | * Your MinStack object will be instantiated and called as such: 73 | * MinStack obj = new MinStack(); 74 | * obj.push(x); 75 | * obj.pop(); 76 | * int param_3 = obj.top(); 77 | * int param_4 = obj.min(); 78 | */ -------------------------------------------------------------------------------- /code/offer31.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.Deque; 3 | 4 | /** 5 | * [31] 栈的压入, 弹出序列 6 | * 7 | * 题目: 输入两个整数序列, 第一个序列表示栈的压入顺序, 请判断第二个序列是否为该栈的弹出顺序.假设压入栈的所有数字均不相等. 8 | * (鉴别栈混洗) 9 | * 10 | * 思路: 模拟栈混洗: 使用一个辅助栈, 把输入的第一个序列中的数字依次压入该辅助栈, 并按照第二个序列的顺序依次从该栈中弹出数字. 11 | */ 12 | class Solution { 13 | /** 14 | * 时间复杂度: O(m + n) 15 | * 空间复杂度: O(m + n) 16 | */ 17 | public boolean validateStackSequences(int[] pushed, int[] popped) { 18 | int pushLen = pushed.length, popLen = popped.length; 19 | if (pushLen != popLen) return false; 20 | if (pushLen == 0 && popLen == 0) return true; 21 | Deque stk = new ArrayDeque<>(); 22 | for (int pushIndex = 0, popIndex = 0; pushIndex < pushLen; pushIndex++) { 23 | // push 'pushed' array's element into stack in order. 24 | stk.push(pushed[pushIndex]); 25 | // when stack's top equal with 'popped' array's current element, 26 | // pop stack's top and 'popped' array's current index plus one for point the next element. 27 | // until this two element don't equal again. 28 | while (!stk.isEmpty() && stk.peek() == popped[popIndex]) { 29 | stk.pop(); 30 | popIndex++; 31 | } 32 | } 33 | return stk.isEmpty(); 34 | } 35 | } -------------------------------------------------------------------------------- /code/offer32.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.ArrayList; 3 | import java.util.Deque; 4 | import java.util.List; 5 | 6 | /** 7 | * [32-I] 从上到下打印二叉树 8 | * 9 | * 题目: 从上到下打印出二叉树的每个节点, 同一层的节点按照从左到右的顺序打印. 10 | * 11 | * 思路: 使用队列来进行层次遍历. 12 | */ 13 | 14 | /** 15 | * Definition for a binary tree node. 16 | * public class TreeNode { 17 | * int val; 18 | * TreeNode left; 19 | * TreeNode right; 20 | * TreeNode(int x) { val = x; } 21 | * } 22 | */ 23 | class Solution { 24 | /** 25 | * 时间复杂度: O(n) 26 | * 空间复杂度: O(n) 27 | */ 28 | public int[] levelOrder(TreeNode root) { 29 | if (root == null) return new int[0]; 30 | List list = new ArrayList<>(); 31 | Deque queue = new ArrayDeque<>(); 32 | queue.offer(root); 33 | while (!queue.isEmpty()) { 34 | TreeNode cur = queue.poll(); 35 | list.add(cur.val); 36 | if (cur.left != null) queue.offer(cur.left); 37 | if (cur.right != null) queue.offer(cur.right); 38 | } 39 | 40 | return list.stream().mapToInt(Integer::intValue).toArray(); 41 | } 42 | } -------------------------------------------------------------------------------- /code/offer322.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | 3 | /** 4 | * [32-II] 分行从上到下打印二叉树 5 | * 6 | * 题目: 从上到下按层打印二叉树, 同一层的节点按从左到右的顺序打印, 每一层打印到一行. 7 | * 8 | * 思路: 使用队列来进行层次遍历. 9 | */ 10 | 11 | /** 12 | * Definition for a binary tree node. 13 | * public class TreeNode { 14 | * int val; 15 | * TreeNode left; 16 | * TreeNode right; 17 | * TreeNode(int x) { val = x; } 18 | * } 19 | */ 20 | class Solution { 21 | /** 22 | * 时间复杂度: O(n) 23 | * 空间复杂度: O(n) 24 | */ 25 | public List> levelOrder(TreeNode root) { 26 | if (root == null) return Collections.emptyList(); 27 | List> res = new ArrayList<>(); 28 | Deque queue = new ArrayDeque<>(); 29 | queue.offer(root); 30 | while (!queue.isEmpty()) { 31 | // number of elements in the current level. 32 | int levelSize = queue.size(); 33 | List sublist = new ArrayList<>(); 34 | while (levelSize-- > 0) { 35 | TreeNode cur = queue.poll(); 36 | // fulfill the current level's node list. 37 | sublist.add(cur.val); 38 | // add exist child nodes of the current level in the queue, 39 | // for the next level traverse. 40 | if (cur.left != null) queue.offer(cur.left); 41 | if (cur.right != null) queue.offer(cur.right); 42 | } 43 | res.add(sublist); 44 | } 45 | 46 | return res; 47 | } 48 | } -------------------------------------------------------------------------------- /code/offer323.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | 3 | /** 4 | * [32-III] 之字形打印二叉树 5 | * 6 | * 题目: 按照之字形顺序打印二叉树, 即第一行按照从左到右的顺序打印, 第二层按照从右到左的顺序打印, 第三行再按照从左到右的顺序打印, 7 | * 其他行以此类推. 8 | * 9 | * 思路: 使用队列来进行层次遍历. 10 | */ 11 | 12 | /** 13 | * Definition for a binary tree node. 14 | * public class TreeNode { 15 | * int val; 16 | * TreeNode left; 17 | * TreeNode right; 18 | * TreeNode(int x) { val = x; } 19 | * } 20 | */ 21 | class Solution { 22 | /** 23 | * 时间复杂度: O(n) 24 | * 空间复杂度: O(n) 25 | */ 26 | public List> levelOrder(TreeNode root) { 27 | if (root == null) return Collections.emptyList(); 28 | List> res = new ArrayList<>(); 29 | Deque queue = new ArrayDeque<>(); 30 | queue.offer(root); 31 | boolean flag = true; 32 | while (!queue.isEmpty()) { 33 | // number of elements in the current level. 34 | int levelSize = queue.size(); 35 | LinkedList sublist = new LinkedList<>(); 36 | while (levelSize-- > 0) { 37 | TreeNode cur = queue.poll(); 38 | // according to flag to decide storage order. 39 | // for fulfill the current level's node list. 40 | if (flag) sublist.add(cur.val); 41 | else sublist.addFirst(cur.val); 42 | // add exist child nodes of the current level in the queue, 43 | // for the next level traverse. 44 | if (cur.left != null) queue.offer(cur.left); 45 | if (cur.right != null) queue.offer(cur.right); 46 | } 47 | res.add(sublist); 48 | // when traverse the next level, 49 | // change the direction of storage order. 50 | flag = !flag; 51 | } 52 | 53 | return res; 54 | } 55 | } -------------------------------------------------------------------------------- /code/offer33.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [33] 二叉搜索树的后序遍历序列 3 | * 4 | * 题目: 判断给定数组是不是某二叉搜索树的后序遍历结果. 假设输入的数组的任意两个数字都互不相同. 5 | * 6 | * 思路: 后序遍历序列中的数字可以分为三部分: 第一部分是左子树节点的值, 它们都比根节点的值小; 第二部分是右子树节点的值, 它们都比根节点的值大; 7 | * 第三部分就是最后一个数字即树的根节点的值. 8 | * 根据根节点的大小, 将序列分为左子树和右子树部分后, 再判断根节点和左右子树的序列是否符合二叉搜索树的特性(即左子树序列中元素都小于根节 9 | * 点, 右子树序列中元素都大于根节点), 最后递归判断左右子树的序列是否是二叉搜索树. 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(n ^ 2) 14 | * 空间复杂度: O(n) 15 | */ 16 | public boolean verifyPostorder(int[] postorder) { 17 | if (postorder.length == 0) return true; 18 | return verifyPostorderCore(postorder, 0, postorder.length - 1); 19 | } 20 | 21 | private boolean verifyPostorderCore(int[] postorder, int start, int end) { 22 | // when subtree only have one or none node, the subtree is binary search tree. 23 | if (start >= end) return true; 24 | // last element of sequence is root. 25 | // though root value split sequence to left subtree sequence and right subtree sequence, 26 | // and judge the root and those two subtree sequence conform to binary search tree or not. 27 | int root = postorder[end]; 28 | int i = start; 29 | for (; i < end; i++) { 30 | if (postorder[i] > root) break; 31 | } 32 | for (int j = i; j < end; j++) { 33 | if (postorder[j] < root) return false; 34 | } 35 | // recursive call verifyPostorderCore to judge those two subtree sequence is binary search tree or not. 36 | return verifyPostorderCore(postorder, start, i - 1) 37 | && verifyPostorderCore(postorder, i, end - 1); 38 | } 39 | } -------------------------------------------------------------------------------- /code/offer34.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.Collections; 3 | import java.util.List; 4 | 5 | /** 6 | * [34] 二叉树中和为某一值的路径 7 | * 8 | * 题目: 返回给定二叉树中节点值的和为输入整数的所有路径. 从树的根节点开始往下一直到叶节点所经过的节点形成一条路径. 9 | * 10 | * 思路: 回溯算法. 11 | */ 12 | 13 | /** 14 | * Definition for a binary tree node. 15 | * public class TreeNode { 16 | * int val; 17 | * TreeNode left; 18 | * TreeNode right; 19 | * TreeNode(int x) { val = x; } 20 | * } 21 | */ 22 | class Solution { 23 | /** 24 | * 时间复杂度: O(n) 25 | * 空间复杂度: O(n) 26 | */ 27 | private List> res = new ArrayList<>(); 28 | 29 | public List> pathSum(TreeNode root, int sum) { 30 | if (root == null) return Collections.emptyList(); 31 | dfs(root, new ArrayList<>(), sum); 32 | 33 | return res; 34 | } 35 | 36 | private void dfs(TreeNode root, List path, int sum) { 37 | if (root == null) return; 38 | // calculate current node value to current sum, 39 | // and add current node to path. 40 | sum -= root.val; 41 | path.add(root.val); 42 | if (root.left == null && root.right == null && sum == 0) { 43 | // if current node is leaf and current sum is equal to target, 44 | // get one path which path's sum equal sum. 45 | res.add(new ArrayList(path)); 46 | } else { 47 | // if current node isn't leaf or current sum isn't equal to target, 48 | // continue recursive call dfs to find path. 49 | dfs(root.left, path, sum); 50 | dfs(root.right, path, sum); 51 | } 52 | // backtrack: when back to upper should remove current node(very important). 53 | path.remove(path.size() - 1); 54 | } 55 | } -------------------------------------------------------------------------------- /code/offer35.java: -------------------------------------------------------------------------------- 1 | import java.util.HashMap; 2 | 3 | /** 4 | * [35] 复杂链表的复制 5 | * 6 | * 题目: 复制含有 next 和 random 指针的复杂链表. 7 | * 8 | * 思路: 1. 维护一个哈希表存储, 原节点和拷贝节点. 然后根据原节点连接各拷贝节点. 9 | * 2. 在每个链表原始节点后创建新节点后, 复制 random 指针, 最后将这个长链拆分为两个链表. 10 | */ 11 | 12 | /* 13 | // Definition for a Node. 14 | class Node { 15 | int val; 16 | Node next; 17 | Node random; 18 | 19 | public Node(int val) { 20 | this.val = val; 21 | this.next = null; 22 | this.random = null; 23 | } 24 | } 25 | */ 26 | class Solution { 27 | /** 28 | * 时间复杂度: O(n) 29 | * 空间复杂度: O(n) 30 | */ 31 | public Node copyRandomList1(Node head) { 32 | if (head == null) { 33 | return null; 34 | } 35 | HashMap map = new HashMap<>(); 36 | Node cur = head; 37 | // clone every node in original list. 38 | while (cur != null) { 39 | map.put(cur, new Node(cur.val)); 40 | cur = cur.next; 41 | } 42 | // according to original list to connect clone node. 43 | cur = head; 44 | while (cur != null) { 45 | Node clone = map.get(cur); 46 | clone.next = map.get(cur.next); 47 | clone.random = map.get(cur.random); 48 | cur = cur.next; 49 | } 50 | 51 | return map.get(head); 52 | } 53 | 54 | /** 55 | * 时间复杂度: O(n) 56 | * 空间复杂度: O(1) 57 | */ 58 | public Node copyRandomList2(Node head) { 59 | if (head == null) { 60 | return null; 61 | } 62 | // put current node's clone node behind current node. 63 | Node cur = head; 64 | while (cur != null) { 65 | Node clone = new Node(cur.val); 66 | clone.next = cur.next; 67 | cur.next = clone; 68 | cur = clone.next; 69 | } 70 | // connect clone's random point. 71 | cur = head; 72 | while (cur != null) { 73 | Node clone = cur.next; 74 | if (cur.random != null) { 75 | // if current node have random point, 76 | // so copy random point. 77 | // clone's random is behind current node's random. 78 | clone.random = cur.random.next; 79 | } 80 | cur = clone.next; 81 | } 82 | // divide long list to before and copy one. 83 | cur = head; 84 | Node cloneHead = cur.next; 85 | // niubility operation(respect). 86 | while (cur.next != null) { 87 | Node next = cur.next; 88 | cur.next = next.next; 89 | cur = next; 90 | } 91 | 92 | return cloneHead; 93 | } 94 | } -------------------------------------------------------------------------------- /code/offer36.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [36] 二叉搜索树与双向链表 3 | * 4 | * 题目: 将给定二叉搜索树转换成一个排序的循环双向链表. 要求不能创建任何新的节点, 只能调整树中节点指针的指向. 5 | * 6 | * 思路: 因为要创建排序的链表, 所以中序遍历给定二叉树, 依次将遍历到的节点连接起来. 7 | */ 8 | 9 | /* 10 | // Definition for a Node. 11 | class Node { 12 | public int val; 13 | public Node left; 14 | public Node right; 15 | 16 | public Node() {} 17 | 18 | public Node(int _val) { 19 | val = _val; 20 | } 21 | 22 | public Node(int _val,Node _left,Node _right) { 23 | val = _val; 24 | left = _left; 25 | right = _right; 26 | } 27 | }; 28 | */ 29 | class Solution { 30 | /** 31 | * 时间复杂度: O(n) 32 | * 空间复杂度: O(n) 33 | */ 34 | // use dummy as the header of the list. 35 | private Node dummy = new Node(-1); 36 | // use tail point current list's last node, 37 | // for connect node to list. 38 | private Node tail = dummy; 39 | 40 | public Node treeToDoublyList(Node root) { 41 | if (root == null) { 42 | return null; 43 | } 44 | dfs(root); 45 | 46 | // make list to be a circle. 47 | Node head = dummy.right; 48 | head.left = tail; 49 | tail.right = head; 50 | return head; 51 | } 52 | 53 | private void dfs(Node root) { 54 | if (root == null) { 55 | return; 56 | } 57 | dfs(root.left); 58 | // connect current node to list. 59 | tail.right = root; 60 | root.left = tail; 61 | tail = root; 62 | dfs(root.right); 63 | } 64 | } -------------------------------------------------------------------------------- /code/offer37.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [37] 序列化二叉树 3 | * 4 | * 题目: 实现两个函数, 分别用来序列化和反序列化二叉树. 5 | * 6 | * 思路: 怎么序列化就怎么反序列化. 7 | * https://github.com/A11Might/practicecode/blob/master/learningCode/SerializeAndReconstructTree.java 8 | */ 9 | 10 | import java.util.ArrayDeque; 11 | import java.util.Deque; 12 | 13 | /** 14 | * Definition for a binary tree node. 15 | * public class TreeNode { 16 | * int val; 17 | * TreeNode left; 18 | * TreeNode right; 19 | * TreeNode(int x) { val = x; } 20 | * } 21 | */ 22 | public class Codec { 23 | 24 | /** 25 | * 先序遍历序列化与反序列化. 26 | */ 27 | // when deserializer, the string always used this. 28 | private String data; 29 | 30 | // Encodes a tree to a single string. 31 | // serialize to 1_2_3_#_#_4_5_#_#_#_# 32 | public String serialize(TreeNode root) { 33 | if (root == null) { 34 | return "#"; 35 | } 36 | return root.val + "_" + serialize(root.left) + "_" + serialize(root.right); 37 | } 38 | 39 | // Decodes your encoded data to tree. 40 | public TreeNode deserialize(String data) { 41 | this.data = data; 42 | return deserializeCore(); 43 | } 44 | 45 | private TreeNode deserializeCore() { 46 | if (data.length() == 0) { 47 | return null; 48 | } 49 | int index = data.indexOf("_"); 50 | String value = index == -1 ? data : data.substring(0, index); 51 | data = index == -1 ? "" : data.substring(index + 1); 52 | if (value.equals("#")) { 53 | return null; 54 | } 55 | TreeNode root = new TreeNode(Integer.valueOf(value)); 56 | root.left = deserializeCore(); 57 | root.right = deserializeCore(); 58 | 59 | return root; 60 | } 61 | 62 | /** 63 | * 层次遍历实现序列化和反序列化. 64 | */ 65 | // Encodes a tree to a single string. 66 | // serialize to 1_2_3_#_#_4_5_#_#_#_#_ in the last have a "_" 67 | public String serialize2(TreeNode root) { 68 | if (root == null) { 69 | return "#_"; 70 | } 71 | String ret = root.val + "_"; 72 | Deque queue = new ArrayDeque<>(); 73 | queue.offer(root); 74 | while (!queue.isEmpty()) { 75 | // queue couldn't add null element, 76 | // so node transform to string didn't take place poll operation. 77 | TreeNode cur = queue.poll(); 78 | if (cur.left != null) { 79 | ret += cur.left.val + "_"; 80 | queue.offer(cur.left); 81 | } else { 82 | ret += "#_"; 83 | } 84 | if (cur.right != null) { 85 | ret += cur.right.val + "_"; 86 | queue.offer(cur.right); 87 | } else { 88 | ret += "#_"; 89 | } 90 | } 91 | 92 | return ret; 93 | } 94 | 95 | // Decodes your encoded data to tree. 96 | public TreeNode deserialize2(String data) { 97 | this.data = data; 98 | TreeNode root = generateTreeNode(); 99 | if (root == null) { 100 | return null; 101 | } 102 | Deque queue = new ArrayDeque<>(); 103 | queue.offer(root); 104 | while (!queue.isEmpty()) { 105 | TreeNode cur = queue.poll(); 106 | cur.left = generateTreeNode(); 107 | cur.right = generateTreeNode(); 108 | if (cur.left != null) { 109 | queue.offer(cur.left); 110 | } 111 | if (cur.right != null) { 112 | queue.offer(cur.right); 113 | } 114 | } 115 | 116 | return root; 117 | } 118 | 119 | private TreeNode generateTreeNode() { 120 | int index = data.indexOf("_"); 121 | String value = data.substring(0, index); 122 | data = index == data.length() - 1 ? "" : data.substring(index + 1); 123 | if (value.equals("#")) { 124 | return null; 125 | } 126 | return new TreeNode(Integer.valueOf(value)); 127 | } 128 | } 129 | 130 | // Your Codec object will be instantiated and called as such: 131 | // Codec codec = new Codec(); 132 | // codec.deserialize(codec.serialize(root)); -------------------------------------------------------------------------------- /code/offer38.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.HashSet; 3 | import java.util.List; 4 | 5 | /** 6 | * [38] 字符串的排列 7 | * 8 | * 题目: 返回给定字符串中字符的所有排列. 可以以任意顺序返回这个字符串数组, 但里面不能有重复元素. 9 | * 10 | * 思路: 减而治之(回溯算法): 先在给定序列中选择一个数字放在当前位置, 再对剩余的数字进行全排列, 即 f(n) = 1 + f(n - 1). 11 | */ 12 | class Solution { 13 | /** 14 | * 时间复杂度: O(A _n ^n) 15 | * 空间复杂度: O(n) 16 | */ 17 | public String[] permutation(String s) { 18 | char[] chrs = s.toCharArray(); 19 | List ret = new ArrayList<>(); 20 | process(chrs, ret, 0); 21 | return ret.toArray(new String[ret.size()]); 22 | } 23 | 24 | private void process(char[] chrs, List ret, int index) { 25 | if (index == chrs.length) { 26 | ret.add(new String(chrs)); 27 | } 28 | // use set to judge is current char used or not. 29 | HashSet set = new HashSet<>(); 30 | for (int i = index; i < chrs.length; i++) { 31 | if (set.contains(chrs[i])) { 32 | continue; 33 | } 34 | set.add(chrs[i]); 35 | swap(chrs, index, i); 36 | process(chrs, ret, index + 1); 37 | // backtrack: recover char array after recursion(very important). 38 | swap(chrs, index, i); 39 | } 40 | } 41 | 42 | private void swap(char[] chrs, int a, int b) { 43 | char temp = chrs[b]; 44 | chrs[b] = chrs[a]; 45 | chrs[a] = temp; 46 | } 47 | } -------------------------------------------------------------------------------- /code/offer39.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [39] 数组中出现次数超过一半的数字 3 | * 4 | * 题目: 找出给定数组中出现的次数超过数组长度的一半的数字. 5 | * (假设数组是非空的, 并且给定的数组总是存在多数元素) 6 | * 7 | * 思路: 1. 改变原数组: 8 | * 数组排序后位于数组中间的数字(数组中第 n / 2 大的数字)一定为数组中出现的次数超过数组长度的一半的数字. 9 | * 快速排序的 partition() 方法, 会返回一个整数 j 使得 a[lo ... j - 1] 小于等于 a[j], 且 a[j + 1 ... hi] 大于等于 a[j], 10 | * 此时 a[j] 就是数组的第 j 大元素. 可以利用这个特性找出数组的第 n / 2 个元素. 11 | * 2. 不改变原数组: 12 | * 多数投票问题, 可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题, 使得时间复杂度为 O(n). 13 | * 使用 cnt 来统计一个元素出现的次数, 当遍历到的元素和统计元素相等时, 令 cnt++, 否则令 cnt--. 如果前面查找了 i 个元素, 14 | * 且 cnt == 0, 说明前 i 个元素没有 majority, 或者有 majority, 但是出现的次数少于 i / 2, 因为如果多于 i / 2 的话 15 | * cnt 就一定不会为 0. 此时剩下的 n - i 个元素中, majority 的数目依然多于 (n - i) / 2, 因此继续查找就能找出 majority. 16 | */ 17 | class Solution { 18 | /** 19 | * 时间复杂度: O(n) 20 | * 空间复杂度: O(1) 21 | */ 22 | public int majorityElement1(int[] nums) { 23 | int len = nums.length; 24 | int lo = 0, hi = len - 1; 25 | while (lo <= hi) { 26 | int index = partition(nums, lo, hi); 27 | if (index == len / 2) { 28 | // find the (n / 2)th element. 29 | break; 30 | } else if (index < len / 2) { 31 | lo = index + 1; 32 | } else { 33 | hi = index - 1; 34 | } 35 | } 36 | 37 | return judgeResult(nums, nums[len / 2]); 38 | } 39 | 40 | // divide target range array to smaller hi value part and other part. 41 | private int partition(int[] nums, int start, int end) { 42 | int pivot = nums[end]; 43 | int smaller = start - 1; 44 | while (start < end) { 45 | if (nums[start] < pivot) { 46 | swap(nums, start++, ++smaller); 47 | } else { 48 | start++; 49 | } 50 | } 51 | swap(nums, end, ++smaller); 52 | return smaller; 53 | } 54 | 55 | private void swap(int[] arr, int a, int b) { 56 | int temp = arr[b]; 57 | arr[b] = arr[a]; 58 | arr[a] = temp; 59 | } 60 | 61 | // judge result right or not 62 | private int judgeResult(int[] arr, int res) { 63 | int n = arr.length; 64 | int count = 0; 65 | for (int num : arr) { 66 | if (num == res) { 67 | count++; 68 | } 69 | } 70 | return count > (n >> 1) ? res : 0; 71 | } 72 | 73 | /** 74 | * 时间复杂度: O(n) 75 | * 空间复杂度: O(1) 76 | */ 77 | public int majorityElement2(int[] nums) { 78 | int majority = -1; 79 | int cnt = 0; 80 | for (int num : nums) { 81 | if (cnt == 0) { 82 | majority = num; 83 | } 84 | cnt = num == majority ? cnt + 1 : cnt - 1; 85 | } 86 | // judge majority's size is bigger than half or not. 87 | cnt = 0; 88 | for (int val : nums) { 89 | if (val == majority) { 90 | cnt++; 91 | } 92 | } 93 | return cnt > nums.length / 2 ? majority : 0; 94 | } 95 | } -------------------------------------------------------------------------------- /code/offer40.java: -------------------------------------------------------------------------------- 1 | import java.util.PriorityQueue; 2 | 3 | /** 4 | * [40] 最小的 k 个数 5 | * 6 | * 题目: 输入整数数组 arr, 找出其中最小的 k 个数. 7 | * 8 | * 思路: 1. 同 39. 数组中出现次数超过一半的数字, 利用快排的 partition 函数: 9 | * 快速排序的 partition() 方法, 会返回一个整数 j 使得 a[lo ... j - 1] 小于等于 a[j], 且 a[j + 1 ... hi] 大于等于 a[j], 10 | * 此时 a[j] 就是数组的第 j 大元素. 可以利用这个特性找出数组的第 K 个元素. 11 | * 2. 遍历数组, 使用大根堆维护到目前为止最小的 k 个数, 最后返回这 k 个元素即可. 12 | */ 13 | class Solution { 14 | /** 15 | * 时间复杂度: O(n) 16 | * 空间复杂度: O(1) 17 | */ 18 | public int[] getLeastNumbers1(int[] arr, int k) { 19 | if (arr == null || arr.length == 0 || k == 0) { 20 | return new int[0]; 21 | } 22 | int lo = 0, hi = arr.length - 1; 23 | while (lo <= hi) { 24 | int index = partition(arr, lo, hi); 25 | if (index == k - 1) { 26 | // find the kth smallest element. 27 | break; 28 | } else if (index < k - 1) { 29 | lo = index + 1; 30 | } else { 31 | hi = index - 1; 32 | } 33 | } 34 | 35 | // partition function will change the array, 36 | // let k elements in the front is the smallest k elements. 37 | int[] ret = new int[k]; 38 | for (int i = 0; i < k; i++) { 39 | ret[i] = arr[i]; 40 | } 41 | return ret; 42 | } 43 | 44 | // divide target range array to smaller hi value part and other part. 45 | private int partition(int[] arr, int start, int end) { 46 | int pivot = arr[end]; 47 | int smaller = start - 1; 48 | while (start < end) { 49 | if (arr[start] < pivot) { 50 | swap(arr, start++, ++smaller); 51 | } else { 52 | start++; 53 | } 54 | } 55 | swap(arr, end, ++smaller); 56 | return smaller; 57 | } 58 | 59 | private void swap(int[] arr, int a, int b) { 60 | int temp = arr[b]; 61 | arr[b] = arr[a]; 62 | arr[a] = temp; 63 | } 64 | 65 | /** 66 | * 时间复杂度: O(n * logk) 67 | * 空间复杂度: O(k) 68 | */ 69 | public int[] getLeastNumbers2(int[] arr, int k) { 70 | if (arr == null || arr.length == 0 || k == 0) { 71 | return new int[0]; 72 | } 73 | PriorityQueue bigHeap = new PriorityQueue<>( 74 | (o1, o2) -> o2 - o1 75 | ); 76 | for (int num : arr) { 77 | bigHeap.add(num); 78 | // keep the size of big root heap is k. 79 | if (bigHeap.size() > k) { 80 | bigHeap.poll(); 81 | } 82 | } 83 | 84 | return bigHeap.stream().mapToInt(Integer::intValue).toArray(); 85 | } 86 | } -------------------------------------------------------------------------------- /code/offer41.java: -------------------------------------------------------------------------------- 1 | import java.util.PriorityQueue; 2 | 3 | /** 4 | * [41] 数据流中的中位数 5 | * 6 | * 题目: 返回一个数据流中的中位数. 如果从数据流中读出奇数个数值, 那么中位数就是所有数值排序之后位于中间的数值. 如果从数据流中读出偶数个数值, 7 | * 那么中位数就是所有数值排序之后中间两个数的平均值. 8 | * 9 | * 思路: 分别用大根堆和小根堆来存储数据流左半边的数和右半边的数, 并使用数据流读取的元素个数来平衡两个堆中元素的平衡(size 为偶数时插入大根堆; 10 | * size 为奇数时插入小根堆). 返回中位数时若从数据流中读出奇数个数值, 则返回大根堆的堆顶; 若从数据流中读出偶数个数值, 则返回两个堆顶的 11 | * 平均值. 12 | */ 13 | class MedianFinder { 14 | 15 | // 'bigHeap' to storage left half of data flow . 16 | private PriorityQueue bigHeap; 17 | // 'bigHeap' to storage right half of data flow . 18 | private PriorityQueue smallHeap; 19 | // 'size' storage current number of read from data flow. 20 | private int size; 21 | 22 | /** initialize your data structure here. */ 23 | public MedianFinder() { 24 | bigHeap = new PriorityQueue<>((o1, o2) -> o2 - o1); 25 | smallHeap = new PriorityQueue<>((o1, o2) -> o1 - o2); 26 | size = 0; 27 | } 28 | 29 | public void addNum(int num) { 30 | // should insure balance of two heaps's size when insert element. 31 | if ((size & 1) == 0) { 32 | // when current element is the number of even, should add into bigHeap. 33 | // because of all elements in bigHeap is smaller than smallHeap, 34 | // but current insert element isn't sure smaller than every element in smallHeap. 35 | // so first put current insert element into smallHeap, 36 | // then poll smallHeap top element which is smallest element in smallHeap, 37 | // and insert into bigHeap. 38 | smallHeap.add(num); 39 | bigHeap.add(smallHeap.poll()); 40 | } else { 41 | // when current element is the number of odd, should add into smallHeap. 42 | // in the same way. 43 | bigHeap.add(num); 44 | smallHeap.add(bigHeap.poll()); 45 | } 46 | size++; 47 | } 48 | 49 | public double findMedian() { 50 | if ((size & 1) == 0) { 51 | return (bigHeap.peek() + smallHeap.peek()) / 2.0; 52 | } else { 53 | return (double) bigHeap.peek(); 54 | } 55 | } 56 | } 57 | 58 | /** 59 | * Your MedianFinder object will be instantiated and called as such: 60 | * MedianFinder obj = new MedianFinder(); 61 | * obj.addNum(num); 62 | * double param_2 = obj.findMedian(); 63 | */ -------------------------------------------------------------------------------- /code/offer42.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [42] 连续子数组的最大和 3 | * 4 | * 题目: 给定一个数组里有正数也有负数. 数组中的一个或连续多个整数组成一个子数组. 求所有子数组的和的最大值.(要求时间复杂度为 O(n)) 5 | * 6 | * 思路: 动态规划: f(i) 表示以 i 个元素结尾的连续子数组的最大和, 7 | * 状态转移方程: array[i] if i == 0 or f(i - 1) <= 0; 8 | * f(i) = 9 | * array[i] + f(i - 1) if i != 0 and f(i - 1) > 0. 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(n) 14 | * 空间复杂度: O(n) 15 | */ 16 | public int maxSubArray1(int[] nums) { 17 | if (nums == null || nums.length == 0) { 18 | return 0; 19 | } 20 | int len = nums.length; 21 | int[] dp = new int[len + 1]; 22 | // dp[0] = 0; 23 | int max = Integer.MIN_VALUE; 24 | for (int i = 1; i <= len; i++) { 25 | // f(i) = array[i] + f(i - 1) if f(i - 1) > 0 26 | dp[i] = nums[i - 1]; 27 | if (dp[i - 1] > 0) { 28 | dp[i] += dp[i - 1]; 29 | } 30 | // find largest continuous subarray sum. 31 | max = Math.max(max, dp[i]); 32 | } 33 | 34 | return max; 35 | } 36 | 37 | /** 38 | * 时间复杂度: O(n) 39 | * 空间复杂度: O(1) 40 | */ 41 | public int maxSubArray(int[] nums) { 42 | if (nums == null || nums.length == 0) { 43 | return 0; 44 | } 45 | int sum = 0; 46 | int max = Integer.MIN_VALUE; 47 | for (int num : nums) { 48 | sum = sum <= 0 ? num : sum + num; 49 | max = Math.max(max, sum); 50 | } 51 | 52 | return max; 53 | } 54 | } -------------------------------------------------------------------------------- /code/offer43.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [43] 1 ~ n 整数中 1 出现的次数 3 | * 4 | * 题目: 返回 1 ~ n 这 n 个整数的十进制表示中 1 出现的次数. 5 | * 6 | * 思路: http://blog.huqihh.com/2019-09-04-Number1times/ 7 | * (算每一位出现 1 的次数, 如 11 个位上算一次, 十位上再算一次) 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(logn) 12 | * 空间复杂度: O(1) 13 | */ 14 | public int countDigitOne(int n) { 15 | // calculate every position's 1 take palace times from lowest to highest position. 16 | // 'weight' is number on one position. 17 | // 'pre' is current weight's previous part. 18 | // 'base' is current weight's position such as lowest is 1, higher is 10. 19 | // 'cnt' is current 1 take place times 20 | int pre = n, weight = 0, base = 1, cnt = 0; 21 | while (pre > 0) { 22 | weight = pre % 10; 23 | pre = pre / 10; 24 | // when weight equal 1, 25 | // weight's previous part has pre + 1 conditions and weight's succeed part has 'base' conditions. 26 | // so when weight equal 1 will have pre * base conditions at last, 27 | // because when previous part equal previous part's maximum value, 28 | // should according weight to judge have other conditions or not(very important). 29 | cnt += pre * base; 30 | // above 'cnt' had include (0 - pre0base) all conditions what weight's position equal 1. 31 | // like 245, above just count 0 to 200, hundred position's take place 1 conditions, 200 to 245 hasn't count. 32 | // when weight equal 1, can continue count succeed part + 1 times, 33 | // when weight bigger than 1 , can continue count base times, 34 | // when weight equal 0, continue count. 35 | if (weight == 1) { 36 | cnt += (n % base) + 1; 37 | } else if (weight > 1) { 38 | cnt += base; 39 | } 40 | // move to higher position. 41 | base *= 10; 42 | } 43 | 44 | return cnt; 45 | } 46 | } -------------------------------------------------------------------------------- /code/offer44.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [44] 数字序列中某一位的数字 3 | * 4 | * 题目: 数字以 0123456789101112131415... 的格式序列化到一个字符序列中. 在这个序列中, 第 5 位(从下标 0 开始计数)是 5, 第 13 位是 1, 5 | * 第 19 位是 4, 等等. 返回任意第 n 位对应的数字. 6 | * 7 | * 思路: 找规律. 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O() 12 | * 空间复杂度: O() 13 | */ 14 | public int findNthDigit(int n) { 15 | if (n < 0) { 16 | return -1; 17 | } 18 | // place represent digit's position numbers. 19 | int place = 1; 20 | while (true) { 21 | int numbers = countNumbers(place) * place; 22 | if (n < numbers) { 23 | return getDigitAtN(n, place); 24 | } 25 | n -= numbers; 26 | place++; 27 | } 28 | } 29 | 30 | // get 'place' positions digit form string's length. 31 | // 10, 90, 900, ... 32 | private int countNumbers(int place) { 33 | if (place == 1) { 34 | return 10; 35 | } 36 | 37 | return (int) Math.pow(10, place - 1) * 9; 38 | } 39 | 40 | // find nth number in 'place' position digit form string. 41 | private int getDigitAtN(int n, int place) { 42 | int beginNum = getBeginNum(place); 43 | int offset = n / place; 44 | String number = beginNum + offset + ""; 45 | int position = n % place; 46 | 47 | return number.charAt(position) - '0'; 48 | } 49 | 50 | // get 'place' positions digit's first digit. 51 | // 0, 10, 100 52 | private int getBeginNum(int place) { 53 | if (place == 1) { 54 | return 0; 55 | } 56 | 57 | return (int) Math.pow(10, place - 1); 58 | } 59 | } -------------------------------------------------------------------------------- /code/offer45.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | import java.util.stream.Collectors; 3 | 4 | /** 5 | * [45] 把数组排成最小的数 6 | * 7 | * 题目: 将给定数组里所有数字拼接起来排成一个数, 返回能拼接出的所有数字中最小的一个. 8 | * 9 | * 思路: 按排序规则(若 str1 + str2 < str2 + str1, 则str1 排在在前)排序给定数组. 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(n * logn) 14 | * 空间复杂度: O(n) 15 | */ 16 | public String minNumber(int[] nums) { 17 | if (nums == null || nums.length == 0) { 18 | return ""; 19 | } 20 | return Arrays.stream(nums).mapToObj(String::valueOf).sorted( 21 | (str1, str2) -> (str1 + str2).compareTo(str2 + str1) 22 | ).collect(Collectors.joining()); 23 | } 24 | } -------------------------------------------------------------------------------- /code/offer46.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [46] 把数字翻译成字符串 3 | * 4 | * 题目: 将给定数字按照如下规则把它翻译为字符串: 0 翻译成 “a”, 1 翻译成 “b”, ... , 25 翻译成 “z”. 返回有多少种不同的翻译方法. 5 | * 6 | * 思路: 动态规划: f(i) 表示 i 位置到数字结尾的数字有多少种翻译方法. 7 | * 状态转移方程: f(i) = f(i - 1) + f(i - 2). 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(n) 12 | * 空间复杂度: O(n) 13 | */ 14 | public int translateNum(int num) { 15 | String str = String.valueOf(num); 16 | int len = str.length(); 17 | int[] dp = new int[len + 1]; 18 | // dynamic programing's base case. 19 | dp[len] = 1; 20 | for (int i = len - 1; i >= 0; i--) { 21 | // state transfer equation: f(i) = f(i - 1) + f(i - 2). 22 | dp[i] = dp[i + 1]; 23 | // if current number start with '0', 24 | // it can't take two place in the front to translate to string. 25 | if (str.charAt(i) != '0') { 26 | // if current number take two place is bigger than 25, 27 | // it also can't translate to string. 28 | if (i + 2 <= len && Integer.valueOf(str.substring(i, i + 2)) <= 25) { 29 | dp[i] += dp[i + 2]; 30 | } 31 | } 32 | } 33 | 34 | return dp[0]; 35 | } 36 | } -------------------------------------------------------------------------------- /code/offer47.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [47] 礼物的最大价值 3 | * 4 | * 题目: 给定一个 m * n 的棋盘的每一格都放有一个礼物(价值大于 0), 从棋盘的左上角开始拿格子里的礼物, 并每次向右或者向下移动一格, 5 | * 直到到达棋盘的右下角. 返回最多能拿到多少价值的礼物. 6 | * 7 | * 思路: 应该用动态规划求解, 而不是深度优先搜索, 深度优先搜索过于复杂, 不是最优解. 8 | * 动态规划: f(i, j) 表示走到 (i, j) 位置可以获得的最大价值. 9 | * 状态转移方程: f(i, j) = max(f(i - 1, j), f(i, j - 1)) + grid[i][j]. 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(m * n) 14 | * 空间复杂度: O(m * n) 15 | */ 16 | public int maxValue1(int[][] grid) { 17 | if (grid == null || grid.length == 0 || grid[0].length == 0) { 18 | return 0; 19 | } 20 | int rows = grid.length, cols = grid[0].length; 21 | int[][] dp = new int[rows][cols]; 22 | dp[0][0] = grid[0][0]; 23 | // initialize first row and column. 24 | for (int j = 1; j < cols; j++) { 25 | dp[0][j] = dp[0][j - 1] + grid[0][j]; 26 | } 27 | for (int i = 1; i < rows; i++) { 28 | dp[i][0] = dp[i - 1][0] + grid[i][0]; 29 | } 30 | // fill in the dynamic programing form by state transfer equation. 31 | // state transfer equation: f(i, j) = max(f(i - 1, j), f(i, j - 1)) + grid[i][j]. 32 | for (int i = 1; i < rows; i++) { 33 | for (int j = 1; j < cols; j++) { 34 | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; 35 | } 36 | } 37 | 38 | return dp[rows - 1][cols - 1]; 39 | } 40 | 41 | /** 42 | * 时间复杂度: O(m * n) 43 | * 空间复杂度: O(n) (n 为矩阵的列数) 44 | */ 45 | public int maxValue2(int[][] grid) { 46 | if (grid == null || grid.length == 0 || grid[0].length == 0) { 47 | return 0; 48 | } 49 | int rows = grid.length, cols = grid[0].length; 50 | // because of f(i, j) only depend on f(i - 1, j) or f(i, j - 1), 51 | // so can reuse one row array to storage dynamic programing result. 52 | int[] dp = new int[cols]; 53 | for (int[] values : grid) { 54 | // the first column position only can come from upper cell, 55 | // so f(i, 0) = f(i - 1, 0) + grid[i][0], no double. 56 | dp[0] += values[0]; 57 | // f(i, j) = max(f(i - 1, j), f(i, j - 1)) + grid[i][j] if j > 0. 58 | for (int j = 1; j < values.length; j++) { 59 | dp[j] = Math.max(dp[j], dp[j - 1]) + values[j]; 60 | } 61 | } 62 | 63 | return dp[cols - 1]; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /code/offer48.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | 3 | /** 4 | * [48] 最长不含重复字符的子字符串 5 | * 6 | * 题目: 返回给定字符串中最长的不包含重复字符的子字符串的长度. 7 | * 8 | * 思路: 滑动窗口. 9 | */ 10 | class Solution { 11 | /** 12 | * 时间复杂度: O(n) 13 | * 空间复杂度: O(1) 14 | */ 15 | public int lengthOfLongestSubstring(String s) { 16 | int[] preIndex = new int[128]; 17 | Arrays.fill(preIndex, -1); 18 | // initial window size is zero, [0, -1]. 19 | int left = 0, right = -1; 20 | int maxLength = 0; 21 | // expand the right edge, 22 | // and keep the characters in window don't repeat. 23 | while (right + 1 < s.length()) { 24 | char chr = s.charAt(++right); 25 | // if current character is repeat, 26 | // update left edge. 27 | if (preIndex[chr] != -1) { 28 | left = Math.max(left, preIndex[chr] + 1); 29 | } 30 | maxLength = Math.max(maxLength, right - left + 1); 31 | preIndex[chr] = right; 32 | } 33 | 34 | return maxLength; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /code/offer49.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [49] 丑数 3 | * 4 | * 题目: 把只包含因子 2, 3 和 5 的数称作丑数(Ugly Number). 返回按从小到大的顺序的第 n 个丑数. 5 | * (1 是第一个丑数) 6 | * 7 | * 思路: 丑数的质因子只有 2, 3 和 5, 所以每个丑数都是由另一个较小的丑数乘以 2, 3 和 5 所得, 所以从小到大按顺序存储乘积所得丑数即可. 8 | * 如下: 1 是第一个丑数 9 | * (2 * 1) 3 * 1 5 * 1 10 | * 2 * 2 (3 * 1) 5 * 1 11 | * (2 * 2) 3 * 2 5 * 1 12 | */ 13 | class Solution { 14 | /** 15 | * 时间复杂度: O(n) 16 | * 空间复杂度: O(n) 17 | */ 18 | public int nthUglyNumber(int n) { 19 | if (n < 7) { 20 | return n; 21 | } 22 | int[] res = new int[n]; 23 | // first ugly number. 24 | res[0] = 1; 25 | // use three variable 'two', 'three' and 'five', 26 | // respective represent current minimum haven't use ugly number, 27 | // which will product 2, product 3 and product 5 for gain new ugly number. 28 | int two = 0, three = 0, five = 0; 29 | for (int i = 1; i < n; i++) { 30 | // storage the minimum number in current gained new ugly numbers. 31 | res[i] = Math.min(Math.min(res[two] * 2, res[three] * 3), res[five] * 5); 32 | // move current used ugly number's variable to point the next ugly number. 33 | if (res[i] == res[two] * 2) { 34 | two++; 35 | } 36 | if (res[i] == res[three] * 3) { 37 | three++; 38 | } 39 | if (res[i] == res[five] * 5) { 40 | five++; 41 | } 42 | } 43 | 44 | return res[n - 1]; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /code/offer50.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [50-I] 第一个只出现一次的字符 3 | * 4 | * 题目: 在字符串 s 中找出第一个只出现一次的字符. 如果没有, 返回一个单空格. 5 | * 6 | * 思路: 遍历字符串, 使用哈希表或数组记录字符串中每个字符的出现次数, 然后再次遍历字符串, 当当前字符出现次数为 1 时, 返回即可. 7 | */ 8 | class Solution { 9 | /** 10 | * 时间复杂度: O(n) 11 | * 空间复杂度: O(1) 12 | */ 13 | public char firstUniqChar(String s) { 14 | if (s == null || s.length() == 0) { 15 | return ' '; 16 | } 17 | // use array to store char appear times. 18 | int[] freq = new int[128]; 19 | // first traverse for statistics char appear times. 20 | for (char chr : s.toCharArray()) { 21 | freq[chr]++; 22 | } 23 | // second traverse for select the first appear ont time char. 24 | for (char chr : s.toCharArray()) { 25 | if (freq[chr] == 1) { 26 | return chr; 27 | } 28 | } 29 | 30 | return ' '; 31 | } 32 | } -------------------------------------------------------------------------------- /code/offer502.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.Queue; 3 | 4 | /** 5 | * [50-II] 字符流中第一个不重复的字符 6 | * 7 | * 题目: 实现一个函数用来找出字符流中第一个只出现一次的字符. 如果当前字符流没有存在出现一次的字符, 返回 '#' 字符. 8 | * 9 | * 思路: 1. 用数组存储每个从数据流中读取的字符的索引(从 1 开始, 0 代表当前字符未出现过; 不同字符使用 ascii码 一一映射到整数数组中). 10 | * 当字符重复出现时, 将数组中的值置为 -1. 这样数组中就保存了只出现一次的字符的索引, 遍历数组找到第一个只出现一次的字符. 11 | * 2. 用数组统计从字符流中读取的每个字符的出现次数(不同字符使用 ascii码 一一映射到整数数组中), 并将遍历到的字符依次加入到队列中. 12 | * 不断维护队首元素为第一个只出现一次的字符(不断出队出现多次的字符). 13 | */ 14 | public class Solution { 15 | /** 16 | * 方法一 17 | */ 18 | private int[] occurrence = new int[128]; 19 | // index is the index of current character in stringstream. 20 | private int index = 1; 21 | 22 | //Insert one char from stringstream 23 | public void Insert1(char ch) { 24 | // record current character's first appeared index in stringstream. 25 | // if current character appeared one more time, record -1 for mark. 26 | if (occurrence[ch] == 0) { 27 | occurrence[ch] = index; 28 | } else if (occurrence[ch] > 0) { 29 | occurrence[ch] = -1; 30 | } 31 | // move index to point next character. 32 | index++; 33 | } 34 | 35 | //return the first appearence once char in current stringstream 36 | public char FirstAppearingOnce1() { 37 | char res = '#'; 38 | int minIndex = Integer.MAX_VALUE; 39 | // traverse occurrence array for find earliest index of char which appear only one time 40 | for (int i = 0; i < occurrence.length; i++) { 41 | if (occurrence[i] > 0 && occurrence[i] < minIndex) { 42 | res = (char) i; 43 | minIndex = occurrence[i]; 44 | } 45 | } 46 | 47 | return res; 48 | } 49 | 50 | /** 51 | * 方法二 52 | */ 53 | private int[] cnts = new int[128]; 54 | private Queue queue = new ArrayDeque<>(); 55 | 56 | //Insert one char from stringstream 57 | public void Insert(char ch) 58 | { 59 | cnts[ch]++; 60 | queue.offer(ch); 61 | // maintenance queue head element is the character which first appear only one time. 62 | while (!queue.isEmpty() && cnts[queue.peek()] > 1) { 63 | queue.poll(); 64 | } 65 | } 66 | //return the first appearence once char in current stringstream 67 | public char FirstAppearingOnce() 68 | { 69 | return queue.isEmpty() ? '#' : queue.peek(); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /code/offer51.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [51] 数组中的逆序对 3 | * 4 | * 题目: 若数组中前一个数字大于后面的数字, 则这两个数字组成一个逆序对. 返回给定数组中逆序对的总数. 5 | * 6 | * 思路: 先把数组分隔成子数组, 统计出子数组内部的逆序对数目, 然后在统计出两相邻子数组之间的逆序对的数目. 在统计逆序对的过程中对数组进行排序, 7 | * 可以加快统计速度(统计两有序相邻子数组之间的逆序对的时间复杂度为 O(m + n), 如下 merge 过程). 这个整个过程其实就是归并排序. 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(n * logn) 12 | * 空间复杂度: O(n) 13 | */ 14 | private int cnt = 0; 15 | 16 | public int reversePairs(int[] nums) { 17 | if (nums == null || nums.length == 0) { 18 | return 0; 19 | } 20 | mergeSort(nums, 0, nums.length - 1); 21 | return cnt; 22 | } 23 | 24 | private void mergeSort(int[] nums, int lo, int hi) { 25 | if (lo >= hi) { 26 | return; 27 | } 28 | int mid = lo + ((hi - lo) >> 1); 29 | mergeSort(nums, lo, mid); 30 | mergeSort(nums, mid + 1, hi); 31 | merge(nums, lo, mid, hi); 32 | } 33 | 34 | private void merge(int[] nums, int lo, int mid, int hi) { 35 | int[] temp = new int[hi - lo + 1]; 36 | int index = 0; 37 | int p1 = lo, p2 = mid + 1; 38 | while (p1 <= mid && p2 <= hi) { 39 | if (nums[p1] <= nums[p2]) { 40 | temp[index++] = nums[p1++]; 41 | } else { 42 | // when left part current element p1 is bigger than right part one p2, 43 | // we can get mid - p1 + 1 inverse pairs in one time. 44 | // because left part is in order, 45 | // it means elements which index is bigger than p1 in left part, 46 | // it's value is also bigger than p1. 47 | // so all of elements from p1 to mid are bigger than p2, 48 | // we got mid - p1 + 1 inverse pairs. 49 | cnt += mid - p1 + 1; 50 | temp[index++] = nums[p2++]; 51 | } 52 | } 53 | while (p1 <= mid) { 54 | temp[index++] = nums[p1++]; 55 | } 56 | while (p2 <= hi) { 57 | temp[index++] = nums[p2++]; 58 | } 59 | for (int i = 0; i < temp.length; i++) { 60 | nums[lo++] = temp[i]; 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /code/offer52.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [52] 两个链表的第一个公共节点 3 | * 4 | * 题目: 返回给定两个链表的第一个公共节点. 如果两个链表没有交点,返回 null. 5 | * (可假定整个链表结构中没有循环) 6 | * 7 | * 思路: 1. 修正长链使得两链表剩余节点数相同, 然后同时遍历两链表, 来找到公共节点. 8 | * 1 -> 2 -> 3 9 | * ^ -> 4 -> 5 10 | * 1 -> 2 11 | * ^ 12 | * 2. 同时遍历两链表, 当其中一条链表到达链尾时, 将其指向另一条链表的头节点继续遍历, 这样就能同时遍历到公共节点. 13 | * 原链表有公共节点时: 14 | * 1 -> 2 -> 3 15 | * -> 4 -> 5 16 | * 1 -> 2 17 | * 18 | * 遍历顺序如下: 19 | * 1 -> 2 -> 3 -> 4 -> 5 -> null -> 1 -> 2 -> 4 -> 5 20 | * ^ 21 | * 1 -> 2 -> 4 -> 5 -> null -> 1 -> 2 -> 3 -> 4 -> 5 22 | * ^ 23 | * 原链表没有公共节点时: 24 | * 1 -> 2-> 3 25 | * 26 | * 1 -> 2 27 | * 28 | * 遍历顺序为: 29 | * 1 -> 2 -> 3 -> null -> 1 -> 2 -> null 30 | * ^ 31 | * 1 -> 2 -> null -> 1 -> 2 -> 3 -> null 32 | * ^ 33 | */ 34 | 35 | /** 36 | * Definition for singly-linked list. 37 | * public class ListNode { 38 | * int val; 39 | * ListNode next; 40 | * ListNode(int x) { 41 | * val = x; 42 | * next = null; 43 | * } 44 | * } 45 | */ 46 | public class Solution { 47 | /** 48 | * 时间复杂度: O(m + n) 49 | * 空间复杂度: O(1) 50 | */ 51 | public ListNode getIntersectionNode1(ListNode headA, ListNode headB) { 52 | int offset = 0; 53 | ListNode cur = headA; 54 | while (cur != null) { 55 | offset++; 56 | cur = cur.next; 57 | } 58 | cur = headB; 59 | while (cur != null) { 60 | offset--; 61 | cur = cur.next; 62 | } 63 | ListNode longList = headA, shortList = headB; 64 | if (offset < 0) { 65 | longList = headB; 66 | shortList = headA; 67 | } 68 | // offset longer list. 69 | offset = Math.abs(offset); 70 | while (offset-- > 0) { 71 | longList = longList.next; 72 | } 73 | // traverse long list and short list together for find common node. 74 | while (longList != null && shortList != null 75 | && longList != shortList) { 76 | longList = longList.next; 77 | shortList = shortList.next; 78 | } 79 | 80 | if (longList == null || shortList == null) { 81 | return null; 82 | } 83 | 84 | return longList; 85 | } 86 | 87 | /** 88 | * 时间复杂度: O(m + n) 89 | * 空间复杂度: O(1) 90 | */ 91 | public ListNode getIntersectionNode2(ListNode headA, ListNode headB) { 92 | ListNode l1 = headA, l2 = headB; 93 | while (l1 != l2) { 94 | // when current pointer arrive the end of list, 95 | // point current pointer to the other list's head, 96 | // and continue traverse. 97 | l1 = l1 == null ? headB : l1.next; 98 | l2 = l2 == null ? headA : l2.next; 99 | } 100 | 101 | return l1; 102 | } 103 | } -------------------------------------------------------------------------------- /code/offer53.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [53-I] 在排序数组中查找数字 3 | * 4 | * 题目: 统计一个数字在排序数组中出现的次数. 5 | * 6 | * 思路: 二分查找返回不大于目标元素的最后一个元素, 即将数组分为小于等于目标元素的区间和大于目标元素的区间, 返回前一个区间的最后一个元素. 7 | * 语义约定: a. 当有多个目标元素时, 必须返回最靠后的元素. 8 | * b. 失败时, 应返回小于目标元素的最大者(含哨兵 (lo - 1)). 9 | * 使用二分查找目标元素 -1 得到的下标 +1 和目标元素的下标之间即为目标数字出现范围. 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(logn) 14 | * 空间复杂度: O(1) 15 | */ 16 | public int search(int[] nums , int target) { 17 | if (nums == null || nums.length == 0) { 18 | return 0; 19 | } 20 | // use binary search to find target element, 21 | // if success, will return right lastPos, 22 | // if failure, will return -1 or n - 1, 23 | // so need judge current return index is right or not. 24 | int lastPos = binarySearch(nums, target); 25 | if (lastPos == -1 || nums[lastPos] != target) { 26 | return 0; 27 | } 28 | // find target - 1 for get the first element index in front of target. 29 | int firstPosPre = binarySearch(nums, target - 1); 30 | 31 | return lastPos - firstPosPre; 32 | } 33 | 34 | // return the last target element. 35 | private int binarySearch(int[] nums, int target) { 36 | int lo = 0, hi = nums.length - 1; 37 | while (lo <= hi) { 38 | int mid = lo + ((hi - lo) >> 1); 39 | if (nums[mid] <= target) { 40 | lo = mid + 1; 41 | } else { 42 | hi = mid - 1; 43 | } 44 | } 45 | 46 | return hi; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /code/offer532.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [53-II] 0 ~ n - 1 中缺失的数字 3 | * 4 | * 题目: 一个长度为 n - 1 的递增排序数组中的所有数字都是唯一的, 并且每个数字都在范围 0 ~ n - 1 之内. 在范围 0 ~ n - 1 内的 n 个数字中 5 | * 有且只有一个数字不在该数组中, 返回这个数字. 6 | * 7 | * 思路: 因为数组中的元素是排序的, 所以将 0 ~ n - 1 这些数字依次放入数组中, 那么它们的下标与它们的值是相同. 假设此时数字 m 不在数组中, 则 8 | * m 之前的数字的下标与它们的值依然相等, 但 m + 1 处在下标为 m 的位置, m + 2 处在下标 m + 1 的位置, 以此类推. 因此问题转化为在排 9 | * 序数组中找到第一个值和下标不相等的元素的下标. 10 | * 使用二分查找, 根据中间元素的值与下标是否相等, 将数组分为相等区间和不相等区间, 返回不相等区间的第一个元素的下标. 11 | */ 12 | class Solution { 13 | /** 14 | * 时间复杂度: O(logn) 15 | * 空间复杂度: O(1) 16 | */ 17 | public int missingNumber(int[] nums) { 18 | int lo = 0, hi = nums.length - 1; 19 | while (lo <= hi) { 20 | int mid = lo + ((hi - lo) >> 1); 21 | // divide array to equal section and not equal section. 22 | if (nums[mid] == mid) { 23 | lo = mid + 1; 24 | } else { 25 | hi = mid - 1; 26 | } 27 | } 28 | 29 | return lo; 30 | } 31 | } -------------------------------------------------------------------------------- /code/offer54.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [54] 二叉搜素树的第k大节点 3 | * 4 | * 题目: 返回给定二叉搜索树中第 k 大的节点. 5 | * 6 | * 思路: 二叉搜索树的中序遍历序列是升序的, 所以只要将其左中右的遍历顺序改为右中左, 其遍历序列就会变成降序, 7 | * 此时遍历 k 个节点即可得到第 k 大的节点. 8 | */ 9 | 10 | /** 11 | * Definition for a binary tree node. 12 | * public class TreeNode { 13 | * int val; 14 | * TreeNode left; 15 | * TreeNode right; 16 | * TreeNode(int x) { val = x; } 17 | * } 18 | */ 19 | class Solution { 20 | /** 21 | * 时间复杂度: O(n) 22 | * 空间复杂度: O(n) 23 | */ 24 | private int cnt = 0; 25 | private int ret = 0; 26 | 27 | public int kthLargest(TreeNode root, int k) { 28 | inorder(root, k); 29 | return ret; 30 | } 31 | 32 | private void inorder(TreeNode root, int k) { 33 | if (root == null) { 34 | return; 35 | } 36 | // traversal order is right to mid to left. 37 | inorder(root.right, k); 38 | cnt++; 39 | if (cnt == k) { 40 | ret = root.val; 41 | return; 42 | } 43 | inorder(root.left, k); 44 | } 45 | } -------------------------------------------------------------------------------- /code/offer55.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [55-I] 二叉树的深度 3 | * 4 | * 题目: 返回给定二叉树的深度. 从根节点到叶节点依次经过的节点(含根, 叶节点)形成树的一条路径, 最长路径的长度为树的深度. 5 | * 6 | * 思路: 树型 DP. 7 | */ 8 | 9 | /** 10 | * Definition for a binary tree node. 11 | * public class TreeNode { 12 | * int val; 13 | * TreeNode left; 14 | * TreeNode right; 15 | * TreeNode(int x) { val = x; } 16 | * } 17 | */ 18 | class Solution { 19 | /** 20 | * 时间复杂度: O(n) 21 | * 空间复杂度: O(n) 22 | */ 23 | public int maxDepth(TreeNode root) { 24 | if (root == null) { 25 | return 0; 26 | } 27 | // current tree's depth equal the larger of the left and right subtrees plus one. 28 | return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1; 29 | } 30 | } -------------------------------------------------------------------------------- /code/offer552.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [55-II] 平衡二叉树 3 | * 4 | * 题目: 判断给定二叉树树是不是平衡二叉树. 如果某二叉树中任意节点的左右子树的深度相差不超过 1, 那么它就是一棵平衡二叉树. 5 | * 6 | * 思路: 树型 DP. 7 | */ 8 | 9 | /** 10 | * Definition for a binary tree node. 11 | * public class TreeNode { 12 | * int val; 13 | * TreeNode left; 14 | * TreeNode right; 15 | * TreeNode(int x) { val = x; } 16 | * } 17 | */ 18 | class Solution { 19 | /** 20 | * 时间复杂度: O(n) 21 | * 空间复杂度: O(logn) 22 | */ 23 | public boolean isBalanced(TreeNode root) { 24 | // use a two element array to storage current tree's message. 25 | // array[0] represent is balance, 0: is't balance; 1: is balance. 26 | // array[1] represent tree's height. 27 | int[] ret = isBalancedCore(root); 28 | return ret[0] == 1; 29 | } 30 | 31 | private int[] isBalancedCore(TreeNode root) { 32 | if (root == null) { 33 | return new int[] {1, 0}; 34 | } 35 | // recursive call isBalancedCore to collect left subtree's message, 36 | // if right subtree isn't balance direct return. 37 | int[] left = isBalancedCore(root.left); 38 | if (left[0] == 0) { 39 | return new int[] {0, 0}; 40 | } 41 | // same way to right subtree. 42 | int[] right = isBalancedCore(root.right); 43 | if (right[0] == 0 44 | // judge current tree is balance or not. 45 | || Math.abs(left[1] - right[1]) > 1) { 46 | return new int[] {0, 0}; 47 | } 48 | 49 | // make current tree's message. 50 | return new int[] {1, Math.max(left[1], right[1]) + 1}; 51 | } 52 | } -------------------------------------------------------------------------------- /code/offer56.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [56-I] 数组中数字出现的次数 3 | * 4 | * 题目: 给定一个整型数组 nums, 其中除两个数字之外, 其他数字都出现了两次. 返回这两个只出现一次的数字. 5 | * (要求时间复杂度是 O(n), 空间复杂度是 O(1)) 6 | * 7 | * 思路: a. 对所有数字求异或, 相同的数字会抵消, 最后剩下两个只出现 1 次的数字的异或和 xor. 8 | * b. 找到 xor 中为 1 的位, 该位置 n 为两数不同的地方. 9 | * c. 使用位置 n, 将数组分为 n 位置等于 0 和等于 1 的两组, 同时两个不一样的数字也分别在两组. 10 | * d. 分别求两组所有数的异或和, 最终结果即为两个只出现一次的数. 11 | */ 12 | class Solution { 13 | /** 14 | * 时间复杂度: O(n) 15 | * 空间复杂度: O(1) 16 | */ 17 | public int[] singleNumbers(int[] nums) { 18 | int xor = 0; 19 | // calculate the Exclusive OR of two numbers that appears only once. 20 | for (int num : nums) { 21 | xor ^= num; 22 | } 23 | // use below way to find the last 1's position in xor. 24 | xor = (xor & (xor - 1)) ^ xor; 25 | // use last different position split array to two parts, 26 | // one is equal with this position and the other is not. 27 | // then respective calculate two parts' sum of Exclusive OR, 28 | // the finally result is answer. 29 | int num1 = 0, num2 = 0; 30 | for (int num : nums) { 31 | if ((xor & num) == 0) { 32 | num1 ^= num; 33 | } else { 34 | num2 ^= num; 35 | } 36 | } 37 | 38 | return new int[] {num1, num2}; 39 | } 40 | } -------------------------------------------------------------------------------- /code/offer562.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [56-II] 数组中数字出现的次数 II 3 | * 4 | * 题目: 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。 5 | * 6 | * 思路: 将数组中所有数字的二进制表示的每一位都加起来. 若某一位的和能被 3 整除, 那么那个只出现一次的数字二进制表示中对应的那一位是 0; 7 | * 否则就是 1. 8 | * 例如: 1110 9 | * 1110 10 | * 1110 11 | * 1101 12 | * ---- 13 | * 4431 每位都 % 3 = 1101 14 | */ 15 | class Solution { 16 | /** 17 | * 时间复杂度: O(n) 18 | * 空间复杂度: O(1) 19 | */ 20 | public int singleNumber(int[] nums) { 21 | int ret = 0; 22 | // int number has 32 position in binary system. 23 | for (int i = 0; i < 32; i++) { 24 | // count the number of ones in each position. 25 | int cnt = 0; 26 | for (int num : nums) { 27 | num >>>= i; 28 | if ((num & 1) == 1) { 29 | cnt++; 30 | } 31 | } 32 | // use cnt to restore the number that appears only once. 33 | if (cnt % 3 != 0) { 34 | ret += (1 << i); 35 | } 36 | } 37 | 38 | return ret; 39 | } 40 | } -------------------------------------------------------------------------------- /code/offer57.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [57-I] 和为 s 的数字 3 | * 4 | * 题目: 给定一个递增排序的数组和一个数字 s, 在数组中查找两个数, 使得它们的和正好是 s. 如果有多对数字的和等于 s, 则输出任意一对即可. 5 | * 6 | * 思路: 由于是递增排序数组, 使用头尾双指针分别指向数组中的最小值和最大值, 假设两指针指向的元素和为 sum, 若 sum == target, 则找到一对; 7 | * 若 sum > target, 则移动尾指针使 sum 变小; 若 sum < target, 则移动头指针使 sum 变大. 8 | */ 9 | class Solution { 10 | /** 11 | * 时间复杂度: O(n) 12 | * 空间复杂度: O(1) 13 | */ 14 | public int[] twoSum(int[] nums, int target) { 15 | int left = 0, right = nums.length - 1; 16 | while (left < right) { 17 | int sum = nums[left] + nums[right]; 18 | if (sum == target) { 19 | // when head pointer value plus tail pointer value equal with sum, 20 | // we found the number pair. 21 | return new int[] {nums[left], nums[right]}; 22 | } else if (sum > target) { 23 | // when head pointer value plus tail pointer value bigger than sum, 24 | // move tail point for decrease current sum. 25 | right--; 26 | } else { 27 | // when head pointer value plus tail pointer value smaller than sum, 28 | // move tail point for increase current sum. 29 | left++; 30 | } 31 | } 32 | 33 | throw new IllegalArgumentException("No Solution"); 34 | } 35 | } -------------------------------------------------------------------------------- /code/offer572.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.List; 3 | 4 | /** 5 | * [57-II] 和为 s 的连续正数序列 6 | * 7 | * 题目: 给定一个正整数 target, 输出所有和为 target 的连续正整数序列(至少含有两个数). 8 | * (序列内的数字由小到大排列, 不同序列按照首个数字从小到大排列.) 9 | * 10 | * 思路: 从最小序列开始寻找, 使用 left 和 right 分别表示当前序列中的最小值和最大值, 因为序列至少需要两个数字, 所以初始化 left 为 1, 11 | * right 为 2. 然后不快扩大或减小序列(增加 right 或 增加 left): 12 | * a. 当前序列和等于 target 时, 同时增大 left 和 right, 继续查找. 13 | * (此时增大 right 或 增大 left 都不可能再次使当前序列和等于 target, 所以同时增大 left 和 right) 14 | * b. 当前序列和小于 target 时, 增大 right 来增加序列和. 15 | * c. 当前序列和大于 target 时, 增加 left 来减小序列和. 16 | * 直至 left < (1 + sum) / 2 为止, 因为当 left == (1 + sum) / 2时, right == (1 + sum) / 2 + 1, 此时最小的序列和都大于 17 | * target, 所以终止查找. 18 | * 19 | */ 20 | class Solution { 21 | /** 22 | * 时间复杂度: O(n ^ 2) 23 | * 空间复杂度: O(n ^ 2) 24 | */ 25 | private List ret = new ArrayList<>(); 26 | 27 | public int[][] findContinuousSequence(int target) { 28 | int left = 1, right = 2; 29 | int sum = left + right; 30 | int threshold = (target + 1) / 2; 31 | while (left < threshold) { 32 | if (sum == target) { 33 | // when current sequence's sum equal with target, 34 | // we got one sequence. 35 | addNum(left, right); 36 | sum -= left; 37 | left++; 38 | right++; 39 | sum += right; 40 | } else if (sum < target) { 41 | // when current sequence's sum is smaller than target, 42 | // move right pointer to increase sum. 43 | right++; 44 | sum += right; 45 | } else { 46 | // when current sequence's sum is bigger than target, 47 | // move right pointer to reduce sum. 48 | sum -= left; 49 | left++; 50 | } 51 | } 52 | 53 | return ret.toArray(new int[ret.size()][]); 54 | } 55 | 56 | // add current sequence to result. 57 | private void addNum(int left, int right) { 58 | int[] arr = new int[right - left + 1]; 59 | int index = 0; 60 | for (int i = left; i <= right; i++) { 61 | arr[index++] = i; 62 | } 63 | ret.add(arr); 64 | } 65 | } -------------------------------------------------------------------------------- /code/offer58.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [58-I] 翻转单词顺序 3 | * 4 | * 题目: 输入一个英文句子, 翻转句子中单词的顺序, 但单词内字符的顺序不变. 为简单起见, 标点符号和普通字母一样处理. 5 | * (无空格字符构成一个单词; 6 | * 输入字符串可以在前面或者后面包含多余的空格, 但是反转后的字符不能包括; 7 | * 如果两个单词间有多余的空格, 将反转后单词间的空格减少到只含一个.) 8 | * 9 | * 思路: 1. 拆分出每个单词后, 逆序组成字符串. 10 | * 11 | * 原题想考察的应该是: 给定一个字符数组, 翻转字符数组中单词的顺序, 标点同普通字母一样处理. 要求额外空间复杂度为 O(1). 12 | * 2. 两次翻转字符串: a. 反转句子中所有的字符; b. 反转每个单词中字符的顺序. 13 | */ 14 | class Solution { 15 | /** 16 | * 时间复杂度: O() 17 | * 空间复杂度: O() 18 | */ 19 | public String reverseWords1(String s) { 20 | if (s == null || s.length() == 0) { 21 | return ""; 22 | } 23 | // split up words in the string. 24 | String[] strs = s.trim().split(" +"); 25 | // joint this words in reverse order for get a string. 26 | StringBuilder sb = new StringBuilder(); 27 | for (int i = strs.length - 1; i >= 0; i--) { 28 | sb.append(strs[i]); 29 | if (i != 0) { 30 | sb.append(" "); 31 | } 32 | } 33 | 34 | return sb.toString(); 35 | } 36 | 37 | /** 38 | * 时间复杂度: O() 39 | * 空间复杂度: O() 40 | */ 41 | public String reverseWords2(String s) { 42 | if (s == null || s.length() == 0) { 43 | return s; 44 | } 45 | // remove spaces on both sides of the string, 46 | // and replace multiple spaces in a string with one. 47 | char[] chrs = s.trim().replaceAll(" +", " ").toCharArray(); 48 | int len = chrs.length; 49 | // first, reverse whole string's characters. 50 | reverse(chrs, 0, len - 1); 51 | // second, reverse each word's characters. 52 | int start = 0, end = 0; 53 | while (end <= len) { 54 | // tips: end equal n should put in front of end pointer point character is space in case out of index error. 55 | if (end == len || chrs[end] == ' ') { 56 | // when end pointer point character is space or end pointer's index equal n, 57 | // found one word and reverse its characters, 58 | // then move pointers to find the next word. 59 | reverse(chrs, start, end - 1); 60 | start = end + 1; 61 | } 62 | end++; 63 | } 64 | 65 | return new String(chrs); 66 | } 67 | 68 | // reverse character array from start to end. 69 | private void reverse(char[] chrs, int start, int end) { 70 | while (start < end) { 71 | char temp = chrs[end]; 72 | chrs[end--] = chrs[start]; 73 | chrs[start++] = temp; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /code/offer582.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [58-II] 左旋转字符串 3 | * 4 | * 题目: 字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部. 请定义一个函数实现字符串左旋转操作的功能. 5 | * 6 | * 思路: 1. (str + str).substring(n, len + n)即为要求的结果. 7 | * 8 | * 原题想考察的应该是: 给定一个字符数组, 对这个字符数组进行左旋转操作. 要求额外空间复杂度为 O(1). 9 | * 2. 将原字符串分为两部分, 第一部分为需要移到后面的字符, 第二部分为剩余的字符. 10 | * 然后进行三次反转字符串的操作: a. 反转第一部分; b. 反转第二部分; c. 反转整个字符串, 即可实现左旋功能. 11 | */ 12 | class Solution { 13 | /** 14 | * 时间复杂度: O() 15 | * 空间复杂度: O() 16 | */ 17 | public String reverseLeftWords1(String s, int n) { 18 | if (s.length() == 0 || n == 0) { 19 | return s; 20 | } 21 | int len = s.length(); 22 | // actually move step. 23 | n %= len; 24 | // (str + str).substring(n, len + n). 25 | s += s; 26 | return s.substring(n, n + len); 27 | } 28 | 29 | /** 30 | * 时间复杂度: O() 31 | * 空间复杂度: O() 32 | */ 33 | public String reverseLeftWords2(String s, int n) { 34 | if (s.length() == 0 || n == 0) { 35 | return s; 36 | } 37 | int len = s.length(); 38 | // actually move step. 39 | n %= len; 40 | // divide string to two parts, 41 | // and respectively reverse these two parts, 42 | // finally reverse whole string. 43 | char[] chrs = s.toCharArray(); 44 | reverse(chrs, 0, n - 1); 45 | reverse(chrs, n, len - 1); 46 | reverse(chrs, 0, len - 1); 47 | return new String(chrs); 48 | } 49 | 50 | // reverse character array from start to end. 51 | private void reverse(char[] chrs, int start, int end) { 52 | while (start < end) { 53 | char temp = chrs[end]; 54 | chrs[end--] = chrs[start]; 55 | chrs[start++] = temp; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /code/offer59.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.Deque; 3 | 4 | /** 5 | * [59-I] 滑动窗口的最大值 6 | * 7 | * 题目: 给定一个数组 nums 和滑动窗口的大小 k, 返回所有滑动窗口中的最大值. 8 | * 9 | * 思路: 使用双端队列存储有可能成为滑动窗口最大值的数值. 10 | */ 11 | class Solution { 12 | /** 13 | * 时间复杂度: O(n) 14 | * 空间复杂度: O(n) 15 | */ 16 | public int[] maxSlidingWindow(int[] nums, int k) { 17 | if (nums.length == 0 || k <= 0) { 18 | return new int[0]; 19 | } 20 | int len = nums.length; 21 | int[] ret = new int[len - k + 1]; 22 | int index = 0; 23 | Deque dq = new ArrayDeque<>(); 24 | // initialize the sliding window size to zero. 25 | int left = 0, right = -1; 26 | // the window sliding to the right. 27 | while (right + 1 < nums.length) { 28 | right++; 29 | // current element is bigger than its previous element in current window, 30 | // so previous element can't be the maximum in current window, remove it. 31 | while (!dq.isEmpty() && nums[right] > nums[dq.peekLast()]) { 32 | dq.pollLast(); 33 | } 34 | dq.addLast(right); 35 | // if current window has been formed, collect maximum value in the window. 36 | if (right - left + 1 == k) { 37 | ret[index++] = nums[dq.peekFirst()]; 38 | // move left border for form the next window. 39 | left++; 40 | } 41 | // because add window element is one by one, 42 | // so window's first element is in window or out window right now. 43 | // judge window first element is out or not, keep window's element is valid. 44 | if (dq.peekFirst() < left) { 45 | dq.pollFirst(); 46 | } 47 | } 48 | 49 | return ret; 50 | } 51 | } -------------------------------------------------------------------------------- /code/offer592.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayDeque; 2 | import java.util.Deque; 3 | import java.util.Queue; 4 | 5 | /** 6 | * [59-II] 队列的最大值 7 | * 8 | * 题目: 请定义一个队列并实现函数 max_value 得到队列里的最大值, 要求函数 max_value, push_back 和 pop_front 的时间复杂度都是O(1). 9 | * 若队列为空, pop_front 和 max_value 需要返回 -1. 10 | * 11 | * 思路: 本质上是一个求滑动窗口最大值的问题. 将这个队列可以看成是一个滑动窗口, 入队就是将窗口的右边界右移, 出队就是将窗口的左边界右移. 12 | */ 13 | class MaxQueue { 14 | private Queue data; 15 | private Deque max; 16 | 17 | public MaxQueue() { 18 | data = new ArrayDeque<>(); 19 | max = new ArrayDeque<>(); 20 | } 21 | 22 | public int max_value() { 23 | if (max.isEmpty()) { 24 | return -1; 25 | } 26 | return max.peekFirst(); 27 | } 28 | 29 | public void push_back(int value) { 30 | data.offer(value); 31 | // maintain max queue's head element is current data queue's maximum value. 32 | while (!max.isEmpty() && value > max.peekLast()) { 33 | max.pollLast(); 34 | } 35 | max.addLast(value); 36 | } 37 | 38 | public int pop_front() { 39 | if (data.isEmpty()) { 40 | return -1; 41 | } 42 | int ret = data.poll(); 43 | // maintain max queue's head element is current data queue's maximum value. 44 | if (ret == max.peekFirst()) { 45 | max.pollFirst(); 46 | } 47 | return ret; 48 | } 49 | } 50 | 51 | /** 52 | * Your MaxQueue object will be instantiated and called as such: 53 | * MaxQueue obj = new MaxQueue(); 54 | * int param_1 = obj.max_value(); 55 | * obj.push_back(value); 56 | * int param_3 = obj.pop_front(); 57 | */ -------------------------------------------------------------------------------- /code/offer60.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [60] n 个骰子的点数 3 | * 4 | * 题目: 把 n 个骰子扔在地上, 所有骰子朝上一面的点数之和为 s. 输入 n, 打印出 s 的所有可能的值出现的概率. 5 | * (你需要用一个浮点数数组返回答案, 其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率.) 6 | * 7 | * 思路: 1. 回溯所有情况, 统计不同骰子和出现次数. 8 | * 2. 动态规划, 当 i > 1 时, i 个骰子掷出的数字和由前 i - 1 个骰子掷出数字和和第 i 个骰子掷出的数字决定. 9 | * 状态表示: f(i, j) 表示前 i 个骰子产生点数 j 的次数; 10 | * 状态转移方程: f(i, j) = f(i - 1, j - 1) + f(i - 1, j - 2) + ... + f(i - 1, j - k), 其中 k <= 骰子的面数 && k <= j, 11 | * 因为不可能掷出负数和. 12 | */ 13 | class Solution { 14 | /** 15 | * 时间复杂度: O(6 ^ n) 16 | * 空间复杂度: O(n) 17 | */ 18 | private int[] freq; 19 | 20 | public double[] twoSum1(int n) { 21 | int face = 6; 22 | int maxSum = n * face; 23 | // use array to storage i dices' every points sum appeared times. 24 | freq = new int[maxSum - n + 1]; 25 | dfs(n, 0, 0); 26 | 27 | // calculate the probability of each points sum. 28 | double totalNum = Math.pow(face, n); 29 | double[] ret = new double[n * face - n + 1]; 30 | for (int i = 0; i < freq.length; i++) { 31 | ret[i] = freq[i] / totalNum; 32 | } 33 | 34 | return ret; 35 | } 36 | 37 | // backtrack i dices' every condition. 38 | private void dfs(int n, int index, int curSum) { 39 | if (index == n) { 40 | freq[curSum - n]++; 41 | return; 42 | } 43 | // current dice point can be 1 to 6, chose one, 44 | // then recursive call dfs to solve rest dices condition. 45 | for (int i = 1; i <= 6; i++) { 46 | dfs(n, index + 1, curSum + i); 47 | } 48 | } 49 | 50 | /** 51 | * 时间复杂度: O(n ^ 2) 52 | * 空间复杂度: O(n ^ 2) 53 | */ 54 | public double[] twoSum2(int n) { 55 | // current dice has six faces. 56 | int face = 6; 57 | int maxSum = n * face; 58 | int[][] dp = new int[n + 1][maxSum + 1]; 59 | // base case: 60 | // when just have one dice, every face only appeared one time. 61 | for (int j = 1; j <= face; j++) { 62 | dp[1][j] = 1; 63 | } 64 | for (int i = 2; i <= n; i++) { 65 | // i dices' points sum is equal to i at least. 66 | // i dices' points sum is equal to the number of dices product the number of faces at most. 67 | for (int j = i; j <= i * face; j++) { 68 | // k is current dice's point. 69 | for (int k = 1; k <= face && k <= j; k++) { 70 | // state transfer equation: 71 | // f(i, j) = f(i - 1, j - 1) + f(i - 1, j - 2) + ... + f(i - 1, j - k) 72 | dp[i][j] += dp[i - 1][j - k]; 73 | } 74 | } 75 | } 76 | 77 | // number of n dices' possible condition. 78 | double totalNum = Math.pow(face, n); 79 | // number of type of n dices' points sum. 80 | double[] ret = new double[n * face - n + 1]; 81 | // calculate the probability of each points sum. 82 | for (int j = n; j <= maxSum; j++) { 83 | ret[j - n] = dp[n][j] / totalNum; 84 | } 85 | 86 | return ret; 87 | } 88 | 89 | /** 90 | * 时间复杂度: O(n ^ 2) 91 | * 空间复杂度: O(n) 92 | */ 93 | public double[] twoSum3(int n) { 94 | int face = 6; 95 | int maxSum = n * face; 96 | // reuse array to storage dynamic programing result. 97 | int[][] dp = new int[2][maxSum + 1]; 98 | for (int j = 1; j <= face; j++) { 99 | dp[1][j] = 1; 100 | } 101 | for (int i = 2; i <= n; i++) { 102 | // use i % 2 to calculate row which will reuse. 103 | int curRow = i % 2; 104 | // before reuse, should clear array.(important) 105 | for (int j = 0; j <= n * face; j++) { 106 | dp[curRow][j] = 0; 107 | } 108 | // other same to upper method. 109 | for (int j = i; j <= i * face; j++) { 110 | for (int k = 1; k <= face && k <= j; k++) { 111 | dp[curRow][j] += dp[1 - curRow][j - k]; 112 | } 113 | } 114 | } 115 | 116 | double totalNum = Math.pow(face, n); 117 | double[] ret = new double[n * face - n + 1]; 118 | for (int j = n; j <= maxSum; j++) { 119 | // use n % 2 to calculate the row which current using. 120 | ret[j - n] = dp[n % 2][j] / totalNum; 121 | } 122 | 123 | return ret; 124 | } 125 | } -------------------------------------------------------------------------------- /code/offer61.java: -------------------------------------------------------------------------------- 1 | import java.util.Arrays; 2 | 3 | /** 4 | * [61] 扑克牌中的顺子 5 | * 6 | * 题目: 从扑克牌中随机抽 5 张牌, 判断是不是一个顺子, 即这 5 张牌是不是连续的. 2 ~ 10为数字本身, A 为 1, J 为 11, Q 为 12 K 为 13, 7 | * 而大, 小王为 0, 可以看成任意数字. A 不能视为 14. 8 | * 9 | * 思路: 1. 数组排序后, 判断 0 的个数是否等于其他相邻数字之间的空缺总数. 10 | * 2. 满足除零外 a. 无重复元素; b. 最大值与最小值之差小于 5, 即为顺子. 11 | */ 12 | class Solution { 13 | /** 14 | * 时间复杂度: O(n * logn) 15 | * 空间复杂度: O(1) 16 | */ 17 | public boolean isStraight1(int[] nums) { 18 | Arrays.sort(nums); 19 | int cnt = 0; 20 | // count the number of zeros. 21 | for (int num : nums) { 22 | if (num == 0) { 23 | cnt++; 24 | } 25 | } 26 | // traverse array element except zero, 27 | // and use zero to complete the continuous card. 28 | for (int i = cnt; i < nums.length - 1; i++) { 29 | if (nums[i] == nums[i + 1]) { 30 | return false; 31 | } 32 | cnt -= nums[i + 1] - nums[i] - 1; 33 | } 34 | 35 | return cnt >= 0; 36 | } 37 | 38 | /** 39 | * 时间复杂度: O(n) 40 | * 空间复杂度: O(1) 41 | */ 42 | public boolean isStraight2(int[] nums) { 43 | // use boolean array visited to mark element had visited or not. 44 | // use max and min to storage nums array's maximum and minimum value except zero. 45 | boolean[] visited = new boolean[14]; 46 | int max = 0, min = 14; 47 | // traverse array: 48 | // judge array have duplication or not except zero, 49 | // and judge array the different between maximum and minimum value is smaller than five or not. 50 | for (int num : nums) { 51 | if (num == 0) { 52 | continue; 53 | } 54 | if (visited[num]) { 55 | return false; 56 | } 57 | visited[num] = true; 58 | max = Math.max(max, num); 59 | min = Math.min(min, num); 60 | if (max - min >= 5) { 61 | return false; 62 | } 63 | } 64 | 65 | return true; 66 | } 67 | } -------------------------------------------------------------------------------- /code/offer62.java: -------------------------------------------------------------------------------- 1 | import java.util.ArrayList; 2 | import java.util.List; 3 | 4 | /** 5 | * [62] 圆圈中最后剩下的数字 6 | * 7 | * 题目: 约瑟夫环问题, 0, 1, ..., n-1 这 n 个数字排成一个圆圈, 从数字 0 开始, 每次从这个圆圈里删除第 m 个数字. 8 | * 返回圆圈中剩余的最后一个数字. 9 | * 10 | * 思路: 1. 用链表模拟整个过程. 11 | * 2. 约瑟夫环递推公式: f(n, m) = (f(n - 1, m) + m) % n. 12 | */ 13 | class Solution { 14 | /** 15 | * 时间复杂度: O(m * n) 16 | * 空间复杂度: O(n) 17 | */ 18 | public int lastRemaining1(int n, int m) { 19 | // structure the cycle from 0 to n - 1. 20 | List circle = new ArrayList<>(); 21 | for (int i = 0; i < n; i++) { 22 | circle.add(i); 23 | } 24 | // from 0 index to start this game. 25 | int cur = 0; 26 | while (circle.size() > 1) { 27 | // cur is the element's index which should be delete(not its value). 28 | cur = (cur + (m - 1)) % circle.size(); 29 | circle.remove(cur); 30 | } 31 | 32 | return circle.get(0); 33 | } 34 | 35 | /** 36 | * 时间复杂度: O(n) 37 | * 空间复杂度: O(1) 38 | */ 39 | public int lastRemaining2(int n, int m) { 40 | // base case: f(1) = 0. 41 | int res = 0; 42 | for (int i = 2; i <= n; i++) { 43 | // equation: f(i) = (f(i - 1) + m) % i. 44 | res = (res + m) % i; 45 | } 46 | 47 | return res; 48 | } 49 | } -------------------------------------------------------------------------------- /code/offer63.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [63] 股票的最大利润 3 | * 4 | * 题目: 假设把某股票的价格按照时间先后顺序存储在数组中, 返回买卖该股票一次可能获得的最大利润. 5 | * 6 | * 思路: 贪心策略, 假设第 i 天进行卖出操作, 买入操作应该在第 i 天之前并且买入价格为目前为止的最低价格. 7 | */ 8 | class Solution { 9 | /** 10 | * 时间复杂度: O(n) 11 | * 空间复杂度: O(1) 12 | */ 13 | public int maxProfit(int[] prices) { 14 | if (prices == null || prices.length == 0) { 15 | return 0; 16 | } 17 | int soFarMin = prices[0]; 18 | int maxProfit = 0; 19 | for (int i = 1; i < prices.length; i++) { 20 | // the lowest price so far. 21 | soFarMin = Math.min(soFarMin, prices[i]); 22 | // the maximum profit can be make so far. 23 | maxProfit = Math.max(maxProfit, prices[i] - soFarMin); 24 | } 25 | return maxProfit; 26 | } 27 | } -------------------------------------------------------------------------------- /code/offer64.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [64] 求 1 + 2 + ... + n 3 | * 4 | * 题目: 求 1 + 2 + ... + n, 要求不能使用乘除法, for, while, if, else, switch, case 等关键字及条件判断语句(A ? B : C). 5 | * 6 | * 思路: 不能使用乘除, 就是用加减; 不能使用迭代, 就用递归; 不能使用判断, 就用短路与来结束递归. 7 | * a. 需利用短路与的短路特性实现递归终止. 8 | * b. 当 n == 0 时, (n > 0) && ((sum += Sum_Solution(n - 1)) > 0)第一个条件为 false, 不会执行后面递归的主体部分, 递归终止. 9 | * c. 当 n > 0 时, 执行 sum += Sum_Solution(n - 1), 实现递归计算 Sum_Solution(n). 10 | */ 11 | public class Solution { 12 | /** 13 | * 时间复杂度: O(n) 14 | * 空间复杂度: O(n) 15 | */ 16 | public int Sum_Solution(int n) { 17 | int sum = n; 18 | // use short out and to stop recursion when n equal 0. 19 | boolean flag = (n > 0) && (sum += Sum_Solution(n - 1)) > 0; 20 | return sum; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /code/offer65.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [65] 不用加减乘除做加法 3 | * 4 | * 题目: 求两个整数之和, 要求在函数体内不得使用 "+", "-", "*", "/" 四则运算符号. 5 | * 6 | * 思路: 二进制下计算 num1 + num2, 得到 sum 和 carry: 7 | * a. sum = num1 ^ num2 表示没有考虑进位的情况下两数的和, 8 | * 例如: 0 ^ 0, 1 ^ 1 == 0; 9 | * 0 ^ 1, 1 ^ 0 == 1. 10 | * b. carry = (num1 & num2) << 1 表示两数和的所有进位, 11 | * 例如: 0 & 0, 0 & 1, 1 & 0 == 0 后 0 << 1 == 0; 12 | * 1 & 1 == 1 后 1 << 1 == 10. 13 | * c. 然后将 sum 和 carry 再使用相同的方法相加, 重复abc步骤直至没有进位(carry == 0), 则 sum 即为两数之和. 14 | */ 15 | class Solution { 16 | /** 17 | * 时间复杂度: O() 18 | * 空间复杂度: O() 19 | */ 20 | public int add(int a, int b) { 21 | int sum = 0 ,carry = 0; 22 | // constant calculate the sum of 'sum' and 'carry', 23 | // until carry is equal zero. 24 | do { 25 | sum = a ^ b; 26 | carry = (a & b) << 1; 27 | a = sum; 28 | b = carry; 29 | } while (carry != 0); 30 | 31 | return sum; 32 | } 33 | } -------------------------------------------------------------------------------- /code/offer66.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [66] 构建乘积数组 3 | * 4 | * 题目: 给定一个数组 A[0, 1, ..., n-1], 请构建一个数组 B[0, 1, ..., n - 1], 其中 B 中的元素 B[i] = A[0] × A[1] × ... 5 | * × A[i - 1] × A[i + 1] × ... × A[n - 1]. 不能使用除法. 6 | * 7 | * 思路: B[i] = (A[0] * A[1] * ... * A[i - 1]) * (A[i + 1] * ... * A[n - 1]) = C[i] * 1 * D[i] 8 | * 9 | * B[0] = 1 * A[1] * A[2] * ... * A[n - 2] * A[n - 1] 10 | * B[1] = A[0] * 1 * A[2] * ... * A[n - 2] * A[n - 1] 11 | * ... 12 | * B[n - 2] = A[0] * A[1] * A[2] * ... * 1 * A[n - 1] 13 | * B[n - 1] = A[0] * A[1] * A[2] * ... * A[n - 2] * 1 14 | */ 15 | class Solution { 16 | /** 17 | * 时间复杂度: O(n) 18 | * 空间复杂度: O(1) 19 | */ 20 | public int[] constructArr(int[] a) { 21 | if (a == null || a.length == 0) { 22 | return null; 23 | } 24 | int len = a.length; 25 | // ret array is B. 26 | int[] ret = new int[len]; 27 | // first, from 0 to len - 1 put C into B. 28 | // C[0] = 1. 29 | ret[0] = 1; 30 | for (int i = 1; i < len; i++) { 31 | // use previous value to speed up calculate current value process. 32 | ret[i] = ret[i - 1] * a[i - 1]; 33 | } 34 | // second, from len - 1 to 0 multiply D into B. 35 | // D[len - 1] = 1. 36 | int product = 1; 37 | for (int i = len - 2; i >= 0; i--) { 38 | // use previous value to speed up calculate current value process. 39 | // B[i] = C[i] * D[i] 40 | product *= a[i + 1]; 41 | ret[i] *= product; 42 | } 43 | 44 | return ret; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /code/offer67.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [67] 把字符串转换成整数 3 | * 4 | * 题目: 将给定字符串转换成一个整数. 5 | * 6 | * 思路: 考虑所有情况. 7 | */ 8 | class Solution { 9 | /** 10 | * 时间复杂度: O(n) (n 为 str 的长度) 11 | * 空间复杂度: O(1) 12 | */ 13 | public int strToInt(String str) { 14 | str = str.trim(); 15 | if (str.length() == 0) { 16 | return 0; 17 | } 18 | // mark number is negative or not. 19 | boolean isNegative = str.charAt(0) == '-'; 20 | int ret = 0; 21 | for (int i = 0; i < str.length(); i++) { 22 | char chr = str.charAt(i); 23 | // skip '+' or '-' in begin. 24 | if (i == 0 && (chr == '+' || chr == '-')) { 25 | continue; 26 | } 27 | // current character isn't valid, 28 | // stop transfer. 29 | if (chr < '0' || chr > '9') { 30 | break; 31 | } 32 | // if number is out of limit, directly return. 33 | if (ret > Integer.MAX_VALUE / 10 34 | || (ret == Integer.MAX_VALUE / 10 && chr - '0' > 7)) { 35 | return isNegative ? Integer.MIN_VALUE : Integer.MAX_VALUE; 36 | } 37 | // calculate number. 38 | ret = ret * 10 + (chr - '0'); 39 | } 40 | 41 | return isNegative ? -ret : ret; 42 | } 43 | } -------------------------------------------------------------------------------- /code/offer68.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [68-I] 二叉搜索树的最近公共祖先 3 | * 4 | * 题目: 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先. 5 | * (所有节点的值都是唯一的; p, q 为不同节点且均存在于给定的二叉搜索树中.) 6 | * 7 | * 思路: 利用二叉搜索树的特性来判断当前节点是否为公共祖先, 并向可能有公共祖先节点的方向遍历. 8 | * 两个节点 p, q 的公共祖先 root 满足 root.val >= p.val && root.val <= q.val. 9 | */ 10 | 11 | /** 12 | * Definition for a binary tree node. 13 | * public class TreeNode { 14 | * int val; 15 | * TreeNode left; 16 | * TreeNode right; 17 | * TreeNode(int x) { val = x; } 18 | * } 19 | */ 20 | class Solution { 21 | /** 22 | * 时间复杂度: O(n) 23 | * 空间复杂度: O(n) 24 | */ 25 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 26 | if (root == null) { 27 | return null; 28 | } 29 | // if both p and q are greater than parent. 30 | if (root.val > p.val && root.val > q.val) { 31 | return lowestCommonAncestor(root.left, p, q); 32 | } 33 | // if both p and q are lesser than parent. 34 | if (root.val < p.val && root.val < q.val) { 35 | return lowestCommonAncestor(root.right, p, q); 36 | } 37 | 38 | // we have found the split point, i.e. the LCA node. 39 | return root; 40 | } 41 | } -------------------------------------------------------------------------------- /code/offer682.java: -------------------------------------------------------------------------------- 1 | /** 2 | * [68-II] 二叉树的最近公共祖先 3 | * 4 | * 题目: 给定一个二叉树, 找到该树中两个指定节点的最近公共祖先. 5 | * (所有节点的值都是唯一的; p, q 为不同节点且均存在于给定的二叉树中.) 6 | * 7 | * 思路: 在左右子树中查找是否存在 p 或者 q, 如果 p 和 q 分别在两个子树中, 那么就说明根节点就是最低公共祖先. 8 | 9 | /** 10 | * Definition for a binary tree node. 11 | * public class TreeNode { 12 | * int val; 13 | * TreeNode left; 14 | * TreeNode right; 15 | * TreeNode(int x) { val = x; } 16 | * } 17 | */ 18 | class Solution { 19 | /** 20 | * 时间复杂度: O(n) 21 | * 空间复杂度: O(n) 22 | */ 23 | public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) { 24 | if (root == null || root == p || root == q) { 25 | return root; 26 | } 27 | TreeNode left = lowestCommonAncestor(root.left, p, q); 28 | TreeNode right = lowestCommonAncestor(root.right, p, q); 29 | if (left != null && right != null) { 30 | return root; 31 | } 32 | return left == null ? right : left; 33 | } 34 | } --------------------------------------------------------------------------------