- 递归和分治
- 剑指Offer 10- I.斐波那契数列 (简单)">剑指Offer 10- I.斐波那契数列 (简单)
- 剑指Offer 10- II.青蛙跳台阶问题(简单)">剑指Offer 10- II.青蛙跳台阶问题(简单)
- 面试题08.01.三步问题 (简单)">面试题08.01.三步问题 (简单)
- 剑指Offer 06.从尾到头打印链表 (简单)">剑指Offer 06.从尾到头打印链表 (简单)
- 剑指Offer 24.反转链表 (简单)">剑指Offer 24.反转链表 (简单)
- 剑指Offer 16.数值的整数次方 (中等)">剑指Offer 16.数值的整数次方 (中等)
- 面试题08.05.递归乘法 (中等)">面试题08.05.递归乘法 (中等)
- 排序
- 面试题10.01.合并排序的数组 (简单)">面试题10.01.合并排序的数组 (简单)
- 21.合并两个有序链表 (简单)">21.合并两个有序链表 (简单)
- 242.有效的字母异位词(简单)">242.有效的字母异位词(简单)
- 1502.判断能否形成等差数列(简单)">1502.判断能否形成等差数列(简单)
- 56.合并区间(中等)">56.合并区间(中等)
- 剑指Offer 21.调整数组顺序使奇数位于偶数前面 (简单)">剑指Offer 21.调整数组顺序使奇数位于偶数前面 (简单)
- 75.颜色分类(中等)">75.颜色分类(中等)
- 147.对链表进行插入排序(中等)">147.对链表进行插入排序(中等)
- 148.排序链表(中等) 链表上的归并排序">148.排序链表(中等) 链表上的归并排序
- 215.数组中的第K个最大元素(中等)">215.数组中的第K个最大元素(中等)
- 面试题17.14.最小K个数(中等)">面试题17.14.最小K个数(中等)
- 剑指Offer 51.数组中的逆序对(困难)">剑指Offer 51.数组中的逆序对(困难)
递归和分治
剑指Offer 10- I.斐波那契数列 (简单)
递归
class Solution {// 备忘录public int[] map = new int[101];public int fib(int n) {Arrays.fill(map, -1);return doFib(n);}private int doFib(int n) {// 找出重复子问题 => 写出递归公式 -> 写出终止条件// 终止条件if(n == 0 || n == 1) {return n;}// 剪枝if(map[n] != -1) {return map[n];}map[n] = (doFib(n - 1) + doFib(n - 2)) % 1000000007;return map[n];}}
动态规划
class Solution {public int fib(int n) {if(n == 0 || n == 1) {return n;}int[] sum = new int[n+1];sum[0] = 0;sum[1] = 1;for(int i = 2; i <= n; i++) {sum[i] = (sum[i - 1] + sum[i - 2]) % 1000000007;}return sum[n];}}
剑指Offer 10- II.青蛙跳台阶问题(简单)
递归
class Solution {private int[] map = new int[101];public int numWays(int n) {// 递归公式 f(n) = f(n - 1) + f(n - 2); f(0) = f(1) = 1, f(2) = 2;Arrays.fill(map, -1);return doNumWays(n);}public int doNumWays(int n) {// 递归公式 f(n) = f(n - 1) + f(n - 2); f(0) = f(1) = 1, f(2) = 2;if(n == 0 || n == 1) {return 1;}if(n == 2) {return 2;}if(map[n] != -1) {return map[n];}map[n] = (doNumWays(n -1) + doNumWays(n -2)) % 1000000007;return map[n];}}
动态规划
class Solution {public int numWays(int n) {if(n == 0 || n == 1) {return 1;}int n1 = 1, n2 = 1, sum = 1;for(int i = 2; i <= n; i++) {n1 = n2;n2 = sum;sum = (n1 + n2) % 1000000007;}return sum;}}
面试题08.01.三步问题 (简单)
递归+剪枝
class Solution {public int waysToStep(int n) {// 递归公式: f(0) = 1, f(1) = 1, f(2) = f(0) + f(1), f(3) = f(0) + f(1) + f(2), f(4) = f(3) + f(2) + f(1)// f(n) = f(n - 1) + f(n - 2) + f(n - 3)long[] map = new long[n+1];Arrays.fill(map, -1);return (int)doWaysToStep(n, map);}private long doWaysToStep(int n, long[] map) {if(n == 0) {return 1;}if(n == 1 || n == 2) {return n;}if(map[n] != -1) {return map[n];}map[n] = (doWaysToStep(n -1, map) + doWaysToStep(n -2, map) + doWaysToStep(n - 3, map)) % 1000000007;return map[n];}}
动态规划
class Solution {public int waysToStep(int n) {// 递归公式: f(0) = 1, f(1) = 1, f(2) = f(0) + f(1), f(3) = f(0) + f(1) + f(2), f(4) = f(3) + f(2) + f(1)// f(n) = f(n - 1) + f(n - 2) + f(n - 3)if(n == 1 || n == 2) {return n;}int n1 = 0, n2 = 1, sum = 2;for(int i = 3; i <= n; i++) {sum = (sum[i - 1] + sum[i - 2] + sum[i - 3]) % 1000000007;}return (int)sum[n];}}
剑指Offer 06.从尾到头打印链表 (简单)
递归
class Solution {public int[] reversePrint(ListNode head) {return reversePrint(head, 0);}public int[] reversePrint(ListNode node, int count) {if(node == null) {return new int[0];}if(node.next != null) {int[] array = reversePrint(node.next, count + 1);array[array.length - count - 1] = node.val;return array;} else {int[] array = new int[count + 1];array[0] = node.val;return array;}}}
剑指Offer 24.反转链表 (简单)
递归
class Solution {public ListNode reverseList(ListNode head) {// 注意边界条件if(head == null || head.next == null) {return head;}ListNode newHead = reverseList(head.next);head.next.next = head;head.next = null;return newHead;}}
剑指Offer 16.数值的整数次方 (中等)
快速幂解析
class Solution {public double myPow(double x, int n) {if(x == 0) {return 0;}long b = n;double res = 1.0;if(n < 0) {x = 1 / x;b = -b;}while(b > 0) {// (b & 1) == 1 表示b为奇数if((b & 1) == 1) {res *= x;}x *= x;// b 右移一位,等价于 b /= 2;b >>= 1;}return res;}}
递归
解题思路:
Java中因为n的最小值可以取到Integer.MIN_VALUE,如果直接取它的相反数的话还是它自己,会导致堆栈溢出,因此提一个x出来,具体看代码
class Solution {public double myPow(double x, int n) {if(n == 0) {return 1;} else if (n < 0) {return 1 / (x * myPow(x, -n - 1));} else if(n % 2 == 0) {return myPow(x * x, n / 2);} else {return x * myPow(x, n - 1);}}}
面试题08.05.递归乘法 (中等)
迭代+移位解法
class Solution {public int multiply(int A, int B) {if(A == 0 || B == 0) {return 0;}// 优化点:保持A为较大值,这样可以减少B向右移位的次数。if(A > B) {int t = A;A = B;B = t;}int res = 0;long b = A;// 利用移位操作来计算,将A作为基数,A * B,相当于A累加B次// 当B为偶数时,A * B 等价于 A 向左移动 B/2 位,B =/ 2 => B >>= 1// 当B为奇数时,等价于 A + A * (B-1),此时(B-1)为偶数while(B > 0) {if((B & 1) == 1) {res += b;}b <<= 1;B >>= 1;}return res;}}
递归+移位解法
class Solution {public int multiply(int A, int B) {if(A == 0 || B == 0) {return 0;}if((B & 1) == 1) {return A + multiply(A, B - 1);} else {return multiply(A <<= 1, B >>= 1);}}}
排序
面试题10.01.合并排序的数组 (简单)
逆向双指针
class Solution {public void merge(int[] A, int m, int[] B, int n) {// 边界条件if(m == 0) {for(int i = 0; i < n; i++) {A[i] = B[i];}}// 逆向双指针int aIdx = m - 1, bIdx = n - 1, i = (m + n) - 1;while(aIdx >= 0 && bIdx >= 0) {if(A[aIdx] >= B[bIdx]) {A[i--] = A[aIdx--];} else {A[i--] = B[bIdx--];}}while(bIdx >= 0) {A[i--] = B[bIdx--];}}}
迭代
class Solution {public void merge(int[] A, int m, int[] B, int n) {int[] tmp = new int[m + n];int aIdx = 0, bIdx = 0, i = 0;while(aIdx < m && bIdx < n) {while(aIdx < m && A[aIdx] <= B[bIdx]) {tmp[i++] = A[aIdx++];}while(bIdx < n && A[aIdx] > B[bIdx]) {tmp[i++] = B[bIdx++];}}while(aIdx < m) {tmp[i++] = A[aIdx++];}while(bIdx < n) {tmp[i++] = B[bIdx++];}for(int j = 0; j < m + n; j++) {A[j] = tmp[j];}}}
21.合并两个有序链表 (简单)
解题思路:虚拟头结点+迭代
class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {if(l1 == null) {return l2;}if(l2 == null) {return l1;}ListNode newHead = new ListNode(), p = newHead;while(l1 != null && l2 != null) {if(l1.val >= l2.val) {p.next = l2;l2 = l2.next;} else {p.next = l1;l1 = l1.next;}p = p.next;}if(l1 != null) {p.next = l1;}if(l2 != null) {p.next = l2;}return newHead.next;}}
242.有效的字母异位词(简单)
排序
解题思路:分别转成字符数组后,通过调用 Java API Arrays#sort() 进行排序,然后直接比对。
class Solution {public boolean isAnagram(String s, String t) {if(s.length() != t.length()) {return false;}char[] a = s.toCharArray();char[] b = t.toCharArray();Arrays.sort(a);Arrays.sort(b);return Arrays.equals(a, b);}}
时间复杂度:O(nlogn)
空间复杂度:O(logn)
哈希表
解题思路:因为输入的字符串只包含小写字母,利用哈希表的特性,遍历字符串s,记录字符串s中字母出现的次数,然后遍历字符串t,对哈希表中出现的字母次数减一,最后遍历哈希表,不存在非0的值,就是有效的字母异位词。
class Solution {public boolean isAnagram(String s, String t) {if(s.length() != t.length()) {return false;}int size = s.length();// 字符串只包含小写字母,那么可以定义一个大小为26的int类型的数组,构成一个哈希表int[] map = new int[26];for(int i = 0; i < size; i++) {map[s.charAt(i) - 'a']++;}for(int i = 0; i < size; i++) {map[t.charAt(i) - 'a']--;}for(int i = 0; i < map.length; i++) {if(map[i] > 0) {return false;}}return true;}}
进阶写法:
class Solution {public boolean isAnagram(String s, String t) {if (s.length() != t.length()) {return false;}char[] s1 = s.toCharArray();char[] t1 = t.toCharArray();char[] counters = new char[26];char[] countert = new char[26];for (int i = 0; i < s1.length; i++) {counters[s1[i] - 'a']++;countert[t1[i] - 'a']++;}for (int i = 0; i < counters.length; i++) {if (counters[i] != countert[i]) {return false;}}return true;}}
时间复杂度:O(n)
空间复杂度:O(1)
1502.判断能否形成等差数列(简单)
排序
解题思路:先排序,遍历计算任意相邻项的差,存在不同,则不是等差数列
class Solution {public boolean canMakeArithmeticProgression(int[] arr) {Arrays.sort(arr);int length = arr.length;int diff = arr[1] - arr[0];for(int i = 2; i < length; i++) {int tmp = arr[i] - arr[i -1];if(diff != tmp) {return false;}}return true;}}
时间复杂度:O(n)
空间复杂度:O(1)
等差数列公式
解题思路:先排序,等差数列需满足:a(i) * 2 = a(i -1) + a(i + 1)
class Solution {public boolean canMakeArithmeticProgression(int[] arr) {Arrays.sort(arr);for(int i = 1; i < arr.length - 1; i++) {// 等差数列数组应满足:a(i) * 2 = a(i -1) + a(i + 1)if(arr[i] * 2 != (arr[i -1] + arr[i + 1])) {return false;}}return true;}}
时间复杂度:O(n)
空间复杂度:O(1)
56.合并区间(中等)
解题思路:
class Solution {public int[][] merge(int[][] intervals) {// 边界条件if(intervals.length == 0) {return new int[0][2];}// 以左端点排序Arrays.sort(intervals, (interval1, interval2) -> interval1[0] - interval2[0]);List<int[]> merged = new ArrayList<>();for(int i = 0; i < intervals.length; i++) {int L = intervals[i][0], R = intervals[i][1];if(merged.size() == 0 || merged.get(merged.size() - 1)[1] < L) {merged.add(new int[]{L, R});} else {merged.get(merged.size() - 1)[1] = Math.max(merged.get(merged.size() - 1)[1], R);}}return merged.toArray(new int[merged.size()][]);}}
class Solution {public int[][] merge(int[][] intervals) {// 先按照区间起始位置排序Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);// 遍历区间int[][] res = new int[intervals.length][2];int idx = -1;for (int[] interval: intervals) {// 如果结果数组是空的,或者当前区间的起始位置 > 结果数组中最后区间的终止位置,// 则不合并,直接将当前区间加入结果数组。if (idx == -1 || interval[0] > res[idx][1]) {res[++idx] = interval;} else {// 反之将当前区间合并至结果数组的最后区间res[idx][1] = Math.max(res[idx][1], interval[1]);}}return Arrays.copyOf(res, idx + 1);}}
剑指Offer 21.调整数组顺序使奇数位于偶数前面 (简单)
首尾指针
解题思路:首尾指针,首指针遍历直至找到偶数,尾指针遍历直至找到奇数,然后交换首尾指针指向的数。
class Solution {public int[] exchange(int[] nums) {int p = 0, q = nums.length - 1;while(p <= q) {while(p <= q && (nums[p] & 1) == 1) {p++;}while(p <= q && (nums[q] & 1) != 1) {q--;}if(p > q) {break;}int tmp = nums[p];nums[p] = nums[q];nums[q] = tmp;p++;q--;}return nums;}}
时间复杂度:O(n/2) => O(n)
空间复杂度:O(1)
快慢指针
解题思路:设置快慢指针,快慢指针往前遍历,遇到奇数就交换,遇到偶数,快指针往前一步,直至快指针遍历完成。
class Solution {public int[] exchange(int[] nums) {// 快慢指针int slow = 0, fast = 0;while(fast < nums.length) {if((nums[fast] & 1) == 1) {int tmp = nums[slow];nums[slow] = nums[fast];nums[fast] = tmp;fast++;slow++;} else {fast++;}}return nums;}}
时间复杂度:O(n)
空间复杂度:O(1)
75.颜色分类(中等)
冒泡排序
class Solution {public void sortColors(int[] nums) {// 冒泡排序for(int i = 0; i < nums.length; i++) {// 标识有无数据交换boolean flag = false;for(int j = 0; j < nums.length - i -1; j++) {if(nums[j] > nums[j + 1]) {int tmp = nums[j];nums[j] = nums[j + 1];nums[j + 1] = tmp;flag = true;}}// 小优化,无数据交换证明已排序好if(!flag) {return;}}}}
单指针遍历
解题思路:遍历,先将0移至数组前,然后将1移至数组中0的后面
class Solution {public void sortColors(int[] nums) {// 单指针解法int ptr = 0;// 遍历,将0移至数组前面for(int i = 0; i < nums.length; i++) {if(nums[i] == 0) {int tmp = nums[ptr];nums[ptr] = nums[i];nums[i] = tmp;ptr++;}}// 遍历,将1移至数组0后面for(int i = ptr; i < nums.length; i++) {if(nums[i] == 1) {int tmp = nums[ptr];nums[ptr] = nums[i];nums[i] = tmp;ptr++;}}}}
时间复杂度:O(n)
空间复杂度:O(1)
首尾双指针遍历
解题思路:跟单指针遍历的思路是一样的,通过首尾双指针,双向遍历,先将2交换至数组后面,在所有2都交换完毕后,将首指针重置,重新双向遍历,将1交换至2后面。
class Solution {public void sortColors(int[] nums) {// 首尾双指针int head = 0, tail = nums.length - 1;// 先将2交换至数组尾部while(head <= tail) {if(nums[head] != 2) {head++;continue;}if(nums[tail] == 2) {tail--;continue;}swap(nums, head, tail);}// 重置首指针,尾指针保持不变head = 0;// 重新遍历,交换尾指针while(head <= tail) {if(nums[head] != 1) {head++;continue;}if(nums[tail] == 1) {tail--;continue;}swap(nums, head, tail);}}private void swap(int[] nums, int p, int q) {int tmp = nums[p];nums[p] = nums[q];nums[q] = tmp;}}
时间复杂度:O(n)
空间复杂度:O(1)
首尾双指针解法二
class Solution {public void sortColors(int[] nums) {// 双指针解法int head = 0, tail = nums.length -1, size = nums.length;for(int i = 0; i < size; i++) {// 使用while是为了将数组前的2都交换至数组尾部,处理类似这种的特殊情况:[2,0,2,1,1,2]while(i < tail && nums[i] == 2) {swap(nums, i, tail);tail--;}if(nums[i] == 0) {swap(nums, i, head);head++;}}}private void swap(int[] nums, int p, int q) {int tmp = nums[p];nums[p] = nums[q];nums[q] = tmp;}}
时间复杂度:O(n)
空间复杂度:O(1)
147.对链表进行插入排序(中等)
头插法
解题思路:
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/class Solution {public ListNode insertionSortList(ListNode head) {ListNode newHead = new ListNode(Integer.MIN_VALUE), p = head;while(p != null) {ListNode tmp = p.next;ListNode cur = newHead;while(cur.next != null && cur.next.val <= p.val) {cur = cur.next;}p.next = cur.next;cur.next = p;p = tmp;}return newHead.next;}}
时间复杂度:O()
空间复杂度:O(1)
归并排序
解题思路:归并排序,利用递归来实现
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/class Solution {public ListNode insertionSortList(ListNode head) {// 边界条件if(head == null || head.next == null) {return head;}// 归并排序// 1. 找到中间节点ListNode midNode = findMidNode(head);// 右边分区的头结点ListNode rightHead = midNode.next;// 中断链表,实现真正的分区midNode.next = null;// 2. 然后递归分区ListNode left = insertionSortList(head);ListNode right = insertionSortList(rightHead);// 3. 合并return mergeTwoList(left, right);}// 找到中间节点public ListNode findMidNode(ListNode head) {// 使用快慢指针找到中间节点if(head == null || head.next == null) {return head;}ListNode slow = head, fast = head.next.next;while(fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}return slow;}// 合并两个链表public ListNode mergeTwoList(ListNode left, ListNode right) {// 使用一个虚拟节点 + 头插法,完成两个链表的合并ListNode dumyHead = new ListNode(), p = dumyHead;while(left != null && right != null) {if(left.val <= right.val) {p.next = left;left = left.next;} else {p.next = right;right = right.next;}p = p.next;}p.next = left == null ? right : left;return dumyHead.next;}}
148.排序链表(中等) 链表上的归并排序
/*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* ListNode(int val) { this.val = val; }* ListNode(int val, ListNode next) { this.val = val; this.next = next; }* }*/class Solution {public ListNode sortList(ListNode head) {// 归并排序if(head == null || head.next == null) {return head;}ListNode midNode = findMidNode(head);ListNode rightHead = midNode.next;midNode.next = null;ListNode left = sortList(head);ListNode right = sortList(rightHead);return mergeTwoList(left, right);}public ListNode findMidNode(ListNode head) {if(head == null || head.next == null) {return head;}// 快慢指针ListNode slow = head, fast = head.next.next;while(fast != null && fast.next != null) {slow = slow.next;fast = fast.next.next;}return slow;}public ListNode mergeTwoList(ListNode left, ListNode right) {// 虚拟头结点 + 头插法ListNode dumyNode = new ListNode(), p = dumyNode;while(left != null && right != null) {if(left.val <= right.val) {p.next = left;left = left.next;} else {p.next = right;right = right.next;}p = p.next;}p.next = left == null ? right : left;return dumyNode.next;}}
215.数组中的第K个最大元素(中等)
快速排序
class Solution {private Random random = new Random();public int findKthLargest(int[] nums, int k) {// 快排的递归公式:// quickSort(s, e) = partition(s, e) + quickSort(s, q - 1) + quickSort(q + 1, e)// q 为 分区点的下标return quickSort(nums, 0 , nums.length - 1, k);}private int quickSort(int[] nums, int s, int e, int k) {if(s > e) {return - 1;}// 以[ 大于分区点 | 分区点 | 小于分区点 ] 这种形式进行分区int q = randomPartition(nums, s, e);// 刚好等于k时,即为int size = q-s+1;if(size == k) {return nums[q];} else if (size > k) {return quickSort(nums, s, q - 1, k);} else {return quickSort(nums, q + 1, e, k - size);}}private int randomPartition(int[] nums, int s, int e) {// 优化点:随机选择分区点int i = random.nextInt(e - s + 1) + s;swap(nums, i, e);return partition(nums, s, e);}private int partition(int[] nums, int s, int e) {int i = s - 1;for(int j = s; j < e; j++) {if(nums[j] > nums[e]) {swap(nums, ++i, j);}}swap(nums, ++i, e);return i;}private void swap(int[] nums, int i, int j) {int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}}
面试题17.14.最小K个数(中等)
快速排序
class Solution {private Random random = new Random();public int[] smallestK(int[] arr, int k) {quickSort(arr, 0, arr.length - 1, k);int[] res = new int[k];for(int i = 0; i < res.length; i++) {res[i] = arr[i];}return res;}private void quickSort(int[] arr, int s, int e, int k) {if(s > e) {return;}int p = randomPartition(arr, s, e);int num = p - s + 1;if(num == k) {return;} else if(num > k) {quickSort(arr, s, p - 1, k);} else {quickSort(arr, p + 1, e, k - num);}}private int randomPartition(int[] arr, int s, int e) {// 优化点int i = random.nextInt(e - s + 1) + s;swap(arr, i, e);return partition(arr, s, e);}private int partition(int[] arr, int s, int e) {int i = s - 1;for(int j = s; j < e; j++) {if(arr[j] < arr[e]) {swap(arr, ++i, j);}}swap(arr, ++i, e);return i;}private void swap(int[] arr, int i, int j) {int temp = arr[i];arr[i] = arr[j];arr[j] = temp;}}
