目录
剑指 Offer 04. 二维数组中的查找
剑指 Offer 29. 顺时针打印矩阵
剑指 Offer 09. 用两个栈实现队列
剑指 Offer 30. 包含min函数的栈
剑指 Offer 10- I. 斐波那契数列 [类型:记忆优化 + 递归 / 动态规划]
剑指 Offer 10- II. 青蛙跳台阶问题 [类型:记忆优化 + 递归 / 动态规划]
剑指 Offer 12. 矩阵中的路径 [类型:岛屿问题 + 回溯]
剑指 Offer 13. 机器人的运动范围 [类型:岛屿问题 + 记忆优化]
剑指 Offer 14- I. 剪绳子 [类型:贪心]
剑指 Offer 15. 二进制中1的个数 [类型:&运算 n & n-1]
剑指 Offer 16. 数值的整数次方 [类型:快速幂 右移>>]
剑指 Offer 06. 从尾到头打印链表 [类型:链表]
剑指 Offer 22. 链表中倒数第k个节点 [类型:链表 --双指针]
剑指 Offer 24. 反转链表 [类型:链表 -- 翻转模板]
剑指 Offer 25. 合并两个排序的链表 [类型:链表 -- 合并模板]
剑指 Offer 35. 复杂链表的复制 [类型:链表-复制]
剑指 Offer 52. 两个链表的第一个公共节点 [类型:链表 -- 相交]
剑指 Offer 55 - I. 二叉树的深度 [类型:二叉树--最大深度模板]
剑指 Offer 55 - II. 平衡二叉树 [类型:二叉树 -- 深度差<=1]
剑指 Offer 27. 二叉树的镜像 [类型:二叉树--先序遍历]
剑指 Offer 28. 对称的二叉树 [类型:二叉树]
剑指 Offer 26. 树的子结构 [类型:二叉树]
112. 路径总和 [类型:二叉树 -- 路径和 -- 递归]
剑指 Offer 34. 二叉树中和为某一值的路径 [类型:二叉树 -- 路径集合 -- 回溯法模板]
剑指 Offer 07. 重建二叉树 [类型:二叉树 -- dfs(start,end,start,end)]
剑指 Offer 33. 二叉搜索树的后序遍历序列 [类型:二叉树 -- dfs(start,end)]
剑指 Offer 37. 序列化二叉树 [类型:二叉树 -- dfs(Deque)]
剑指 Offer 32 - I. 从上到下打印二叉树 [类型:二叉树 -- BFS模板1]
剑指 Offer 32 - III. 从上到下打印二叉树 III [类型:二叉树 -- BFS模板2]
剑指 Offer 36. 二叉搜索树与双向链表 [类型:二叉树 -- 转链表 -- 双节点 中序遍历]
96. 不同的二叉搜索树个数 [类型:二叉树 -- 递归(i-1) (n-i)]
95. 不同的二叉搜索树 II [类型:二叉树 -- 构建二叉树集合dfs(start, end)]
108. 将有序数组转换为二叉搜索树 [类型:二叉树-- dfs(起始点,结束点)]
109. 有序链表转换二叉搜索树 [类型:二叉树 -- dfs(起始点,结束点)]
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 [类型:二叉树 -- 公共祖先]
剑指 Offer 68 - II. 二叉树的最近公共祖先 [类型:二叉树-- 公共祖先]
剑指 Offer 41. 数据流中的中位数 [类型:优先队列]
剑指 Offer 42. 连续子数组的最大和 [类型:前缀和Math函数 / 动态规划]
剑指 Offer 63. 股票的最大利润 [类型:Math函数 / 动态规划]
剑指 Offer 45. 把数组排成最小的数 [类型:字符串排序/快速排序]
剑指 Offer 46. 把数字翻译成字符串 [类型:动态规划 -- fn = fn-1 + fn-2]
剑指 Offer 47. 礼物的最大价值 [类型:二维动态规划 -- 走方格]
剑指 Offer 48. 最长不含重复字符的子字符串 [类型:滑动窗口 + set]
剑指 Offer 57 - II. 和为s的连续子序列 [类型:滑动窗口]
剑指 Offer 57. 和为s的两个数字 [类型:双指针]
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 [类型:双指针 - 交换位置]
剑指 Offer 49. 丑数 [类型:三指针 + 动态规划]
剑指 Offer 61. 扑克牌中的顺子 [类型:哈希set]
剑指 Offer 64. 求1+2+…+n *
剑指 Offer 65. 不用加减乘除做加法 *
剑指 Offer 66. 构建乘积数组 *
剑指 Offer 04. 二维数组中的查找
在一个 n * m 的二维数组中,每一行都按照从左到右 非递减 的顺序排序,每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 matrix 如下:
[ [1, 4, 7, 11, 15], [2, 5, 8, 12, 19], [3, 6, 9, 16, 22], [10, 13, 14, 17, 24], [18, 21, 23, 26, 30] ]
class Solution {
//左下角开始 先缩小行 再增加列
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int row = matrix.length - 1;
int col = 0;
while(row >= 0 && col < matrix[0].length){
if(matrix[row][col] > target){
row--;
}else if(matrix[row][col] < target){
col++;
}else{
return true;
}
}
return false;
}
}
剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]] 输出:[1,2,3,6,9,8,7,4,5]
class Solution {
//while + for 循环 处理四个坐标
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0) return new int[0];
int left = 0;
int right = matrix[0].length - 1;
int top = 0;
int bom = matrix.length - 1;
int x = 0;
//设置长宽
int[] res = new int[matrix.length * matrix[0].length];
while(true){
//右
for(int i = left; i <= right; i++){
res[x++] = matrix[top][i];
}
//关键:跳出循环依据--向右则top+1,向下则right-1....
if(++top > bom) break;
//下
for(int i = top; i <= bom; i++){
res[x++] = matrix[i][right];
}
if(--right < left) break;
//左
for(int i = right; i >= left; i--){
res[x++] = matrix[bom][i];
}
if(--bom < top) break;
//上
for(int i = bom; i >= top; i--){
res[x++] = matrix[i][left];
}
if(++left > right) break;
}
return res;
}
}
剑指 Offer 09. 用两个栈实现队列
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail
和 deleteHead
,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead
操作返回 -1 )
示例 1:
输入: ["CQueue","appendTail","deleteHead","deleteHead","deleteHead"] [[],[3],[],[],[]] 输出:[null,null,3,-1,-1]
笔记:Deque<Integer>stack1 = new LinkedList<>();
offer() = push(), removeLase() = pop(), getFirst() = peek()
class CQueue {
/**
倒序栈
A一直push
删B B每次把A都填充进来
*/
Deque<Integer> stack1;
Deque<Integer> stack2;
public CQueue() {
stack1 = new LinkedList<>();
stack2 = new LinkedList<>();
}
public void appendTail(int value) {
stack1.push(value);
}
public int deleteHead() {
if(!stack2.isEmpty()){
// 2不为空,将2的值弹出
return stack2.pop();
}else if(!stack1.isEmpty()){
while(!stack1.isEmpty()){
//否则将1倒序压入2
stack2.push(stack1.pop());
}
return stack2.pop();
}else{
return -1;
}
}
}
剑指 Offer 30. 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的 min 函数在该栈中,调用 min、push 及 pop 的时间复杂度都是 O(1)。
示例:
MinStack minStack = new MinStack(); minStack.push(-2); minStack.push(0); minStack.push(-3); minStack.min(); --> 返回 -3. minStack.pop(); minStack.top(); --> 返回 0. minStack.min(); --> 返回 -2.
class MinStack {
/**
1. 链表模拟 + min属性
*/
private Node node;
public MinStack() {
}
public void push(int x) {
if (node == null)
node = new Node(x, x, null);
else
//关键:x和val比较最小值
node = new Node(x, Math.min(node.min, x), node);
}
public void pop() {
node = node.next;
}
public int top() {
return node.val;
}
public int min() {
return node.min;
}
private class Node {
int val;
int min;
Node next;
public Node(int val, int min, Node next) {
this.val = val;
this.min = min;
this.next = next;
}
}
}
class MinStack {
/**
2 双stack处理, A存所有数据,B存当前最小数据
*/
Stack<Integer> A;
Stack<Integer> B;
public MinStack(){
A = new Stack<Integer>();
B = new Stack<Integer>();
}
public void push(int x){
A.add(x);
//这个必须<= 因为pop的时候跟A一样B就会pop,如果只有小于,B可能会少
if(B.empty() || x <= B.peek()){
B.add(x);
}
}
public void pop(){
if(A.pop().equals(B.peek())){
B.pop();
}
}
public int top(){
return A.peek();
}
public int min(){
return B.peek();
}
}
剑指 Offer 10- I. 斐波那契数列 [类型:记忆优化 + 递归 / 动态规划]
写一个函数,输入 n
,求斐波那契(Fibonacci)数列的第 n
项(即 F(N)
)。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1 F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2 输出:1 输入:n = 5 输出:5
提示:
0 <= n <= 100
class Solution {
//1. 记忆优化 + 递归
int[] memo = new int[101];
public int fib(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
if(memo[n] != 0) return memo[n];
return memo[n] = (fib(n - 1) + fib(n - 2)) % 1000000007;
}
}
class Solution {
//2. 动态规划
public int fib(int n) {
if(n == 0) return 0;
if(n == 1) return 1;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i < n + 1; i++){
dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;
}
return dp[n];
}
}
class Solution {
//3. 常规累加方法
public int fib(int n) {
if(n <= 1) return n;
int a = 0, b = 1, sum = 0;
for(int i = 2;i <= n; i++){
sum = (a + b) % 1000000007;
//a,b向后移动
a = b;
b = sum;
}
return b;
}
}
剑指 Offer 10- II. 青蛙跳台阶问题 [类型:记忆优化 + 递归 / 动态规划]
一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n
级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
示例 1:
输入:n = 2 输出:2 输入:n = 7 输出:21
提示:
0 <= n <= 100
class Solution {
/**
1. 递归
台阶为0的时候不用跳,就是一种方法
斐波那契数列 求第n个数f(0) = 1 f(1) = 1
*/
private int[] memo = new int[101];
public int numWays(int n) {
if(n <=1) return 1;
if(memo[n] != 0) return memo[n];
return memo[n] = (numWays(n - 1) + numWays(n - 2)) % 1000000007;
}
}
class Solution {
//2. 动态规划
public int numWays(int n) {
//必须提前处理,当n=0,dp[1]越界
if(n <=1) return 1;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < n + 1; i++){
dp[i] = (dp[i - 1] + dp[i - 2]) % 1000000007;
}
return dp[n];
}
}
剑指 Offer 12. 矩阵中的路径 [类型:岛屿问题 + 回溯]
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
例如,在下面的 3×4 的矩阵中包含单词 "ABCCED"(单词中的字母已标出)。
class Solution {
char[] words;
public boolean exist(char[][] board, String word) {
words = word.toCharArray();
for(int i = 0; i < board.length; i++){
for(int j = 0; j < board[0].length; j++){
if(dfs(board, i, j, 0)){
return true;
}
}
}
return false;
}
private boolean dfs(char[][] board, int i, int j, int k){
if(i < 0 || j < 0 || i >= board.length || j >= board[0].length ||
board[i][j] != words[k]){
return false;
}
//全部匹配成功
if(k == words.length - 1) return true;
//防止重复使用
board[i][j] = ' ';
boolean res = dfs(board, i + 1, j, k + 1) ||
dfs(board, i, j + 1, k + 1) ||
dfs(board, i - 1, j, k + 1) ||
dfs(board, i, j - 1, k + 1);
//回溯
board[i][j] = words[k];
return res;
}
}
剑指 Offer 13. 机器人的运动范围 [类型:岛屿问题 + 记忆优化]
地上有一个m行n列的方格,从坐标 [0,0]
到坐标 [m-1,n-1]
。一个机器人从坐标 [0, 0]
的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?
示例 1:
输入:m = 2, n = 3, k = 1 输出:3
笔记:岛屿问题,如果固定从[0, 0]开始,就不用双层for循环
class Solution {
//记忆优化
boolean[][] visited;
int res = 0;
public int movingCount(int m, int n, int k) {
visited = new boolean[m][n];
dfs(m, n, k, 0, 0);
return res;
}
private void dfs(int m, int n, int k, int i, int j){
//回溯数组判断 || 条件判断
if(i < 0 || j < 0 || i >= m || j >= n || visited[i][j] || bitsum(i) + bitsum(j) > k){
return;
}
//剪枝
visited[i][j] = true;
res++;
dfs(m, n, k, i + 1, j);
dfs(m, n, k, i, j + 1);
dfs(m, n, k, i - 1, j);
dfs(m, n, k, i - 1, j);
}
int bitsum(int i){
int sum = 0;
while(i != 0){
sum += i % 10;
i /= 10;
}
return sum;
}
}
剑指 Offer 14- I. 剪绳子 [类型:贪心]
给你一根长度为 n
的绳子,请把绳子剪成整数长度的 m
段(m、n都是整数,n>1并且m>1),每段绳子的长度记为 k[0],k[1]...k[m-1]
。请问 k[0]*k[1]*...*k[m-1]
可能的最大乘积是多少?
例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
class Solution {
/**
任意一个数字拆分成只含2和3的组合时能达到最大
*/
public int cuttingRope(int n) {
if(n < 4) return n - 1;
int res = 1;
while(n > 4){
res *= 3;
n -= 3;
}
return res * n;
}
}
剑指 Offer 15. 二进制中1的个数 [类型:&运算 n & n-1]
编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 '1' 的个数(也被称为 汉明重量).)
示例 1:
输入:n = 11 (控制台输入 00000000000000000000000000001011) 输出:3
public class Solution {
//&运算 n & n-1
public int hammingWeight(int n) {
int count = 0 ;
while(n != 0){
count++;
n = n & (n - 1);
}
return count;
}
}
剑指 Offer 16. 数值的整数次方 [类型:快速幂 右移>>]
实现 pow(x, n) ,即计算 x 的 n 次幂函数。
笔记:奇数侯树判断,奇数:x & 1 == 1, 偶数:x & 1 == 0
class Solution {
//快速幂 右移>>
public double myPow(double x, int n) {
if(x == 0) return 0;
//防止-n越界int
long b = n;
if( b < 0){
x = 1/x;
b = -b;
}
double res = 1.0;
while( b > 0){
// 为奇数,需要乘上该位上的权重
if((b & 1) == 1) res *= x;
x *= x;
b >>= 1;
}
return res;
}
}
剑指 Offer 06. 从尾到头打印链表 [类型:链表]
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
示例 1:
输入:head = [1,3,2] 输出:[2,3,1]
class Solution {
//1 使用栈
public int[] reversePrint(ListNode head) {
Deque<Integer> stack = new LinkedList<>();
while(head != null){
stack.offer(head.val);
head = head.next;
}
int[] res = new int[stack.size()];
for(int i = 0; i < res.length; i++){
res[i] = stack.removeLast();
}
return res;
}
//2 数组倒序赋值
public int[] reversePrint(ListNode head) {
return res;
int len = 0;
ListNode tmp = head;
while (tmp != null){
tmp = tmp.next;
len ++;
}
int[] res = new int[len];
for(int i = len - 1; i >= 0; i--){
res[i] = head.val;
head = head.next;
}
return res;
}
}
剑指 Offer 22. 链表中倒数第k个节点 [类型:链表 --双指针]
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
示例:
给定一个链表: 1->2->3->4->5, 和 k = 2. 返回链表 4->5.
class Solution {
/**
双指针
first先移动K,first和second同时移动,first==null,second就在倒数第K个位置上
*/
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode first = head;
ListNode second = head;
//k-- 先比较再减一
while(k-- > 0){
first = first.next;
}
while(first != null){
first = first.next;
second = second.next;
}
return second;
}
}
剑指 Offer 24. 反转链表 [类型:链表 -- 翻转模板]
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL
class Solution {
//翻转链表模板
public ListNode reverseList(ListNode head) {
ListNode cur = head;
ListNode pre = null;
while(cur != null){
ListNode tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
}
剑指 Offer 25. 合并两个排序的链表 [类型:链表 -- 合并模板]
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
示例1:
输入:1->2->4, 1->3->4 输出:1->1->2->3->4->4
class Solution {
/**
* 合并链表模板:过度链表
*/
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
//创建过度链表进行拼接, 虚拟头结点0,结果是0.next 的节点开始
ListNode dum = new ListNode(0);
ListNode cur = dum;
while(l1 != null && l2 != null){
if(l1.val < l2.val){
cur.next = l1;
l1 = l1.next;
}else{
cur.next = l2;
l2 = l2.next;
}
cur = cur.next;
}
//拼接剩余l1或l2
cur.next = l1 == null ? l2 : l1;
// 虚拟头结点0,结果是0.next 的节点开始
return dum.next;
}
}
剑指 Offer 35. 复杂链表的复制 [类型:链表-复制]
请实现 copyRandomList
函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next
指针指向下一个节点,还有一个 random
指针指向链表中的任意节点或者 null
。
/*
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
//链表复制,map key=旧node,value=新node
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
Map<Node, Node> map = new HashMap<>();
// 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != null) {
map.put(cur, new Node(cur.val));
cur = cur.next;
}
cur = head;
// 4. 构建新链表的 next 和 random 指向
while(cur != null) {
map.get(cur).next = map.get(cur.next);
map.get(cur).random = map.get(cur.random);
cur = cur.next;
}
// 5. 返回新链表的头节点
return map.get(head);
}
}
剑指 Offer 52. 两个链表的第一个公共节点 [类型:链表 -- 相交]
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在节点 c1 开始相交。
class Solution {
ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode A = headA;
ListNode B = headB;
while(A != B){
//关键: 交换头结点
A = A == null ? headB : A.next;
B = B == null ? headA : B.next;
}
return A;
}
}
剑指 Offer 55 - I. 二叉树的深度 [类型:二叉树--最大深度模板]
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
例如:
给定二叉树 [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7 返回它的最大深度 3 。
class Solution {
//二叉树深度模板
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}else{
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
}
剑指 Offer 55 - II. 平衡二叉树 [类型:二叉树 -- 深度差<=1]
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
示例 1:
给定二叉树 [3,9,20,null,null,15,7]
3 / \ 9 20 / \ 15 7 返回 true
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
/**
平衡二叉树 = 左右子树 最大深度差不超过1
*/
public boolean isBalanced(TreeNode root) {
if(root == null) return true;
return Math.abs(maxDepth(root.left) - maxDepth(root.right)) <= 1 &&
isBalanced(root.left) && isBalanced(root.right);
}
//计算最大深度
int maxDepth(TreeNode root){
if(root == null){
return 0;
}else{
return Math.max(maxDepth(root.left), maxDepth(root.right)) + 1;
}
}
}
剑指 Offer 27. 二叉树的镜像 [类型:二叉树--先序遍历]
请完成一个函数,输入一个二叉树,该函数输出它的镜像。
例如输入:
4
/ \
2 7
/ \ / \
1 3 6 9
镜像输出:
4
/ \
7 2
/ \ / \
9 6 3 1
class Solution {
//先序遍历
public TreeNode mirrorTree(TreeNode root) {
dfs(root);
return root;
}
private void dfs(TreeNode root){
if(root == null) return;
TreeNode node = root.left;
root.left = root.right;
root.right = node;
dfs(root.left);
dfs(root.right);
}
}
剑指 Offer 28. 对称的二叉树 [类型:二叉树]
请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
例如,二叉树 [1,2,2,3,4,4,3] 是对称的。
1
/ \
2 2
/ \ / \
3 4 4 3
但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称的:
1
/ \
2 2
\ \
3 3
class Solution {
public boolean isSymmetric(TreeNode root) {
return isCheck(root, root);
}
/**
对称二叉树终止条件:
1. A.val = B.val
2. A.left = B.right
3. A.right = B.left
*/
boolean isCheck(TreeNode root, TreeNode mirror){
//必须先判断两个都为空
if(root == null && mirror == null){
return true;
}
if(root == null || mirror == null){
return false;
}
return root.val == mirror.val && isCheck(root.left, mirror.right) && isCheck(root.right, mirror.left);
}
}
剑指 Offer 26. 树的子结构 [类型:二叉树]
输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
B是A的子结构, 即 A中有出现和B相同的结构和节点值。
例如:
给定的树 A:
3
/ \
4 5
/ \
1 2
给定的树 B:
4
/
1
返回 true,因为 B 与 A 的一个子树拥有相同的结构和节点值。
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null || B == null) return false;
//全匹配 | 左子树匹配 | 右子树匹配
return dfs(A, B) || isSubStructure(A.left, B) || isSubStructure(A.right, B);
}
//子结构 不必要带着这后代相等
boolean dfs(TreeNode A, TreeNode B){
if(B == null) return true; //B已经匹配完了
if(A == null) return false; //B还没匹配完,A已经没有了
return dfs(A.left, B.left) && dfs(A.right, B.right) && A.val == B.val;
}
}
112. 路径总和 [类型:二叉树 -- 路径和 -- 递归]
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
class Solution {
public boolean hasPathSum(TreeNode root, int targetSum) {
if(root == null) return false;
targetSum -= root.val;
//关键判断
if(targetSum == 0 && root.left == null && root.right == null){
return true;
}
return hasPathSum(root.left, targetSum) || hasPathSum(root.right, targetSum);
}
}
剑指 Offer 34. 二叉树中和为某一值的路径 [类型:二叉树 -- 路径集合 -- 回溯法模板]
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22 输出:[[5,4,11,2],[5,8,4,5]]
class Solution {
List<List<Integer>> res = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> pathSum(TreeNode root, int target) {
dfs(root, target);
return res;
}
public void dfs(TreeNode root, int target){
if(root == null) return;
path.add(root.val);
target -= root.val;
//关键:叶子节点+tar=0
if(target == 0 && root.left == null && root.right == null){
res.add(new ArrayList<>(path));
}
dfs(root.left, target);
dfs(root.right, target);
//回溯
//还可以用LinkedList.removeLast()
path.remove(path.size() - 1);
}
}
剑指 Offer 07. 重建二叉树 [类型:二叉树 -- dfs(start,end,start,end)]
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
Input: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7] Output: [3,9,20,null,null,15,7]
思路:
- 通过【前序遍历列表】确定【根节点 (root)】
- 将【中序遍历列表】的节点分割成【左分支节点】和【右分支节点】
- 递归寻找【左分支节点】中的【根节点 (left child)】和 【右分支节点】中的【根节点 (right child)】
class Solution {
//确认根节点,递归左右子树
int[] pre;
Map<Integer, Integer> map = new HashMap<>();
public TreeNode buildTree(int[] preorder, int[] inorder) {
pre = preorder;
for(int i = 0; i < inorder.length; i++){
map.put(inorder[i], i);
}
return dfs(0, preorder.length - 1, 0, inorder.length - 1);
}
TreeNode dfs(int preL, int preR, int inL, int inR){
if(preL > preR || inL > inR){
return null;
}
TreeNode root = new TreeNode(pre[preL]); //注意,不是pre[0]
//根下标
int idx = map.get(pre[preL]);
int len = idx - inL;
//递归左子树
root.left = dfs(preL + 1, preL + len, inL, idx - 1);
//递归右子树
root.right = dfs(preL + len + 1, preR, idx + 1, inR);
return root;
}
}
剑指 Offer 33. 二叉搜索树的后序遍历序列 [类型:二叉树 -- dfs(start,end)]
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回 true
,否则返回 false
。假设输入的数组的任意两个数字都互不相同。
参考以下这颗二叉搜索树:
5 / \ 2 6 / \ 1 3
输入: [1,3,2,6,5] 输出: true
class Solution {
//二叉搜索树 根据根 区分左右子树 递归
int[] post;
public boolean verifyPostorder(int[] postorder) {
post = postorder;
return dfs(0, postorder.length - 1);
}
boolean dfs(int start, int end){
if(start >= end) return true;
//根
int root = post[end];
int index = 0;
//找左子树+1点
while(post[index] < root){
index++;
}
int mid = index;
//找右子树+1点 =根
while(post[index] > root){
index++;
}
return index == end && dfs(start, mid - 1) && dfs(mid, end - 1);
}
}
剑指 Offer 37. 序列化二叉树 [类型:二叉树 -- dfs(Deque)]
你需要设计一个算法来实现二叉树的 序列化serialize(TreeNode root) 与 反序列化deserialize(String data) 。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
public class Codec {
public String serialize(TreeNode root) {
if(root == null) return "null";
return root.val + "," + serialize(root.left) + "," + serialize(root.right);
}
public TreeNode deserialize(String data) {
//构建队列
Deque<String> queue = new LinkedList<>(Arrays.asList(data.split(",")));
return dfs(queue);
}
TreeNode dfs(Deque<String> queue){
if(queue.isEmpty()) return null;
String val = queue.poll();
if("null".equals(val)){
return null;
}
//根 + 左 + 右 顺序反序列化
TreeNode root = new TreeNode(Integer.parseInt(val));
root.left = dfs(queue);
root.right = dfs(queue);
return root;
}
}
剑指 Offer 32 - I. 从上到下打印二叉树 [类型:二叉树 -- BFS模板1]
从上到下打印出二叉树的每个节点,同一层的节点按照从左到右的顺序打印。
例如:
给定二叉树: [3,9,20,null,null,15,7]
,
3 / \ 9 20 / \ 15 7
返回:
[3,9,20,15,7]
笔记:遍历二叉树 -- BFS模板通用模板
class Solution {
//bfs模板
public int[] levelOrder(TreeNode root) {
if(root == null) return new int[0];
List<Integer> res = new ArrayList<>();
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
res.add(node.val);
//先处理左子树
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
}
//toArray 方法只能转成Integer[] 类型
int[] res1 = new int[res.size()];
for(int i = 0; i < res.size(); i++){
res1[i] = res.get(i);
}
return res1;
}
}
剑指 Offer 32 - III. 从上到下打印二叉树 III [类型:二叉树 -- BFS模板2]
请实现一个函数按照之字形顺序打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右到左的顺序打印,第三行再按照从左到右的顺序打印,其他行以此类推。
例如:
3 / \ 9 20 / \ 15 7
返回其层次遍历结果:[[3],[20,9],[15,7]]
笔记:逐遍历二叉树 -- BFS模板通用模板2
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null) return new ArrayList<>();
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
List<List<Integer>> res = new ArrayList<>();
boolean flag = true;
while(!queue.isEmpty()){
int len = queue.size();
LinkedList<Integer> list = new LinkedList<>();
//打印每一行
for(int i = 0; i < len; i++){
TreeNode node = queue.poll();
if(flag){
list.addLast(node.val);
}else{
//偶数行 倒序
list.addFirst(node.val);
}
if(node.left != null) queue.offer(node.left);
if(node.right != null) queue.offer(node.right);
}
res.add(list);
//翻转
flag = !flag;
}
return res;
}
}
剑指 Offer 36. 二叉搜索树与双向链表 [类型:二叉树 -- 转链表 -- 双节点 中序遍历]
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
class Solution {
//双节点 中序遍历 头尾处理
Node pre;
Node head;
public Node treeToDoublyList(Node root) {
if(root == null) return null;
dfs(root);
//此时pre是尾节点,跟head相连
pre.right = head;
head.left = pre;
return head;
}
public void dfs(Node cur){
if(cur == null) return;
//中序遍历
dfs(cur.left);
if(pre == null){
//第一次处理
head = cur;
}else{
//将pre和cur相连
cur.left = pre;
pre.right = cur;
}
//cur不用处理,因为cur已经是left或者right了
pre = cur;
dfs(cur.right);
}
}
96. 不同的二叉搜索树个数 [类型:二叉树 -- 递归(i-1) (n-i)]
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
class Solution {
//递归
Map<Integer,Integer> map = new HashMap<>();
public int numTrees(int n) {
if(n == 0 || n == 1) return 1;
int count = 0;
for(int i = 1; i <= n; i++){
int left = numTrees(i - 1);
int right = numTrees(n - i);
count += left * right;
}
return count;
}
}
class Solution {
//递归 + 记忆优化
Map<Integer,Integer> map = new HashMap<>();
public int numTrees(int n) {
if(n == 0 || n == 1){
return 1;
}
if(map.containsKey(n)){
return map.get(n);
}
int count = 0;
for(int i = 1; i <= n; i++){
//左边有多少种子树
int left = numTrees(i - 1);
//右边有多少种子树
int right = numTrees(n - i);
//乘起来就是当前节点的子树个数
count += left * right;
}
map.put(n, count);
return count;
}
}
95. 不同的二叉搜索树 II [类型:二叉树 -- 构建二叉树集合dfs(start, end)]
给你一个整数 n
,请你生成并返回所有由 n
个节点组成且节点值从 1
到 n
互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。
思路:将 连续的数,一个个遍历,作为根节点, 每次遍历中,将 左边的数组 和 右边的数组 分别进行构建子树,并接到 当前根节点上
笔记:构建二叉树类型题: -- dfs(起始点,结束点)
class Solution {
//三层for
public List<TreeNode> generateTrees(int n) {
if(n == 0) return new ArrayList<TreeNode>();
return dfs(1, n);
}
private List<TreeNode> dfs(int start, int end){
List<TreeNode> list = new ArrayList<>();
if(start > end){
list.add(null);
return list;
}
for(int i = start; i <= end; i++){
//注意:返回的list
List<TreeNode> left = dfs(start, i - 1);
List<TreeNode> right = dfs(i + 1, end);
for(TreeNode l : left){
for(TreeNode r : right){
TreeNode root = new TreeNode(i);
root.left = l;
root.right = r;
list.add(root);
}
}
}
return list;
}
}
108. 将有序数组转换为二叉搜索树 [类型:二叉树-- dfs(起始点,结束点)]
给你一个整数数组 nums
,其中元素已经按 升序 排列,请你将其转换为一棵 高度平衡 二叉搜索树。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
输入:nums = [-10,-3,0,5,9] 输出:[0,-3,9,-10,null,5]
笔记:构建二叉树类型题: -- dfs(起始点,结束点)
class Solution {
//构建二叉树dfs(start, end)
int[] num;
public TreeNode sortedArrayToBST(int[] nums) {
if(nums == null) return null;
num = nums;
return dfs(0, nums.length - 1);
}
private TreeNode dfs(int start, int end){
if(start > end) return null;
//取中点
int mid = (start + end) >> 1;
TreeNode root = new TreeNode(num[mid]); //注意:mid是root下标
root.left = dfs(start, mid - 1);
root.right = dfs(mid + 1, end);
return root;
}
}
109. 有序链表转换二叉搜索树 [类型:二叉树 -- dfs(起始点,结束点)]
输入: head = [-10,-3,0,5,9] 输出: [0,-3,9,-10,null,5]
class Solution {
//链表转list + dfs(start, end)
List<Integer> list = new ArrayList<>();
public TreeNode sortedListToBST(ListNode head) {
if(head == null) return null;
ListNode cur = head;
while(cur != null){
list.add(cur.val);
cur = cur.next;
}
return dfs(0, list.size() - 1);
}
private TreeNode dfs(int start, int end){
if(start > end) return null;
int mid = (start + end) >> 1;
TreeNode root = new TreeNode(list.get(mid));
root.left = dfs(start, mid - 1);
root.right = dfs(mid + 1, end);
return root;
}
}
剑指 Offer 68 - I. 二叉搜索树的最近公共祖先 [类型:二叉树 -- 公共祖先]
给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。
输入: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 输出: 6
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root.val > p.val && root.val > q.val){
return lowestCommonAncestor(root.left, p, q); p q 都比root大 说明再右子树
}else if(root.val < p.val && root.val < q.val){
return lowestCommonAncestor(root.right, p, q);
}else{
return root;
}
}
剑指 Offer 68 - II. 二叉树的最近公共祖先 [类型:二叉树-- 公共祖先]
给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
笔记:二叉树公共祖先--标准模板
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//递归
if(root == null || root == p || root == q) return root;
TreeNode left = lowestCommonAncestor(root.left, p, q);
TreeNode right = lowestCommonAncestor(root.right, p, q);
if(left == null) return right;
if(right == null) return left;
return root;
}
剑指 Offer 41. 数据流中的中位数 [类型:优先队列]
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。
例如,
[2,3,4] 的中位数是 3
[2,3] 的中位数是 (2 + 3) / 2 = 2.5
关键:maxHeap.size() == minHeap.size() PriorityQueue<Integer> maxHeap; 优先队列默认 升序
class MedianFinder {
//优先队列--大顶堆 小顶堆
PriorityQueue<Integer> maxHeap;
PriorityQueue<Integer> minHeap;
public MedianFinder() {
maxHeap = new PriorityQueue<>((x, y) -> (y - x));
minHeap = new PriorityQueue<>();
}
public void addNum(int num) {
if(maxHeap.size() == minHeap.size()){
maxHeap.add(num);
//minHeap多放一个
minHeap.add(maxHeap.poll());
}else{
minHeap.add(num);
maxHeap.add(minHeap.poll());
}
}
public double findMedian() {
if(maxHeap.size() != minHeap.size()){
return minHeap.peek(); //要用peek不用poll
}else{
return (maxHeap.peek() + minHeap.peek()) / 2.0;
}
}
}
剑指 Offer 42. 连续子数组的最大和 [类型:前缀和Math函数 / 动态规划]
输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。
思路:还可以用动态规划
class Solution {
public int maxSubArray(int[] nums) {
int preSum = 0;
int max = nums[0];
for(int x : nums){
preSum = Math.max(preSum + x , x); //保证前缀和是正数
max = Math.max(max, preSum); //保证过程中最大
}
return max;
}
}
剑指 Offer 63. 股票的最大利润 [类型:Math函数 / 动态规划]
假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
class Solution {
//Math函数
public int maxProfit(int[] prices) {
if(prices == null || prices.length == 0) return 0;
int min = prices[0];
int max = 0;
for(int price : prices){
min = Math.min(min, price);
max = Math.max(max, price - min);
}
return max;
}
}
class Solution {
/**
动态规划
持有不一定是当天买的,可能是昨天买的
未持有也不一定是当天卖的,可能昨天已经就卖了
dp[i][0] 表示第i天未持有股票时的最大收益
dp[i][1] 表示第i天持有股票时的最大收益
*/
public int maxProfit(int[] prices) {
if(prices.length == 0) return 0;
int[][] dp = new int[prices.length][2];
dp[0][0] = 0;
dp[0][1] = -prices[0];
for (int i = 1; i < prices.length; i++) {
//第i天未持有,1.前一天买了,今天卖了, 2.前一天也未持有
dp[i][0] = Math.max(dp[i - 1][1] + prices[i], dp[i - 1][0]);
// 如果max前一项是dp[i - 1][0] - prices[i]意味着可以多次买卖
//第i天持有, 1前一天没买,今天买,2 前一天买了,今天不用买
dp[i][1] = Math.max(0-prices[i], dp[i - 1]+0);
}
return dp[prices.length - 1][0];
}
}
剑指 Offer 45. 把数组排成最小的数 [类型:字符串排序/快速排序]
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
输入: [3,30,34,5,9] 输出: "3033459"
笔记:快速排序模板
class Solution {
//1. java内置排序compareTo()
public String minNumber(int[] nums) {
String[] str = new String[nums.length];
for(int i = 0; i < nums.length; i++){
str[i] = String.valueOf(nums[i]);
}
Arrays.sort(str, (x, y) -> (x + y).compareTo(y + x));
StringBuilder sb = new StringBuilder();
for(String s : str){
sb.append(s);
}
return sb.toString();
}
}
class Solution {
//2. 快速排序
public String minNumber(int[] nums) {
String[] strs = new String[nums.length];
for(int i = 0; i < nums.length; i++){
strs[i] = String.valueOf(nums[i]);
}
quickSort(strs, 0, strs.length - 1);
StringBuilder ans = new StringBuilder();
for(String s : strs){
ans.append(s);
}
return ans.toString();
}
void quickSort(String[] strs, int l, int r) {
if(l >= r) return;
int i = l, j = r;
String tmp = strs[i];
while(i < j) {
while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) j--;
while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) i++;
tmp = strs[i];
strs[i] = strs[j];
strs[j] = tmp;
}
strs[i] = strs[l];
strs[l] = tmp;
quickSort(strs, l, i - 1);
quickSort(strs, i + 1, r);
}
}
剑指 Offer 46. 把数字翻译成字符串 [类型:动态规划 -- fn = fn-1 + fn-2]
给定一个数字,我们按照如下规则把它翻译为字符串:0 翻译成 “a” ,1 翻译成 “b”,……,11 翻译成 “l”,……,25 翻译成 “z”。一个数字可能有多个翻译。请编程实现一个函数,用来计算一个数字有多少种不同的翻译方法。
输入: 12258 输出: 5 解释: 12258有5种不同的翻译,分别是"bccfi", "bwfi", "bczi", "mcfi"和"mzi"
class Solution {
public int translateNum(int num) {
String str = String.valueOf(num);
int[] dp = new int[str.length() + 1];
//无数字和只有1位数字
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i < str.length() + 1; i++){
//关键:截取后两位转化成数字
int x = Integer.valueOf(str.substring(i - 2, i));
if(x >= 10 && x <= 25){
//0,01,02 无法被翻译,因此区间为 [10,25]
dp[i] = dp[i - 1] + dp[i - 2];
}else{
dp[i] = dp[i - 1];
}
}
return dp[str.length()];
}
}
剑指 Offer 47. 礼物的最大价值 [类型:二维动态规划 -- 走方格]
在一个 m*n 的棋盘的每一格都放有一个礼物,每个礼物都有一定的价值(价值大于 0)。你可以从棋盘的左上角开始拿格子里的礼物,并每次向右或者向下移动一格、直到到达棋盘的右下角。给定一个棋盘及其上面的礼物的价值,请计算你最多能拿到多少价值的礼物?
输入:
[
[1,3,1],
[1,5,1],
[4,2,1]
]
输出: 12
解释: 路径 1→3→5→2→1 可以拿到最多价值的礼物
笔记: 机器人走格子问题不同于岛屿问题,路径只能向右向下
class Solution {
//二维动态规划
public int maxValue(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[][] dp = new int[m][n];
//1. 行列初始化
dp[0][0] = grid[0][0];
for(int i = 1; i < m; i++){
dp[i][0] = dp[i - 1][0] + grid[i][0];
}
for(int i = 1; i < n; i++){
dp[0][i] = dp[0][i - 1] + grid[0][i];
}
//方程:之前走过的点最大值 + 当前值
for(int i = 1; i < m; i++){
for(int j = 1; j < n; j++){
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
}
}
return dp[m - 1][n - 1];
}
}
剑指 Offer 48. 最长不含重复字符的子字符串 [类型:滑动窗口 + set]
请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
输入: "abcabcbb" 输出: 3 解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
笔记:滑动窗口:
1.滑动窗口一般表示成一个左闭右开区间
2.窗口的左边界和右边界永远只能向右移动
3.滑动窗口只有 右边界向右移动(扩大窗口) 和 左边界向右移动(缩小窗口) 两个操作
class Solution {
滑动窗口,用set维护一个不重复的窗口
public int lengthOfLongestSubstring(String s) {
int res = 0;
Set<Character> set = new HashSet<>();
for(int l = 0, r = 0; r < s.length(); r++){
char c = s.charAt(r);
//关键:pwwkew,重复字母可能在中间
while(set.contains(c)){
set.remove(s.charAt(l++));
}
set.add(s.charAt(r));
res = Math.max(res, r - l + 1);
}
return res;
}
}
剑指 Offer 57 - II. 和为s的连续子序列 [类型:滑动窗口]
输入一个正整数 target
,输出所有和为 target
的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
输入:target = 9 输出:[[2,3,4],[4,5]]
思路:left <= target / 2 数组组成[1, tar] ,所以数组是无重复的升序数组,等差数列,首项 = 1,公差 = 1
笔记:
1. list转二维数组 List<int[]> list.toArrayList();
2. 滑动窗口不一定就是for循环(left,right)
class Solution {
public int[][] findContinuousSequence(int target) {
int left = 1, right = 1, sum = 0;
List<int[]> list = new ArrayList<>();
/**
关键 N=target;连续整数序列至少有两个数字,
假如恰好全部是两个数,{1,2,3,4}tar=5,因此至多循环 target/2次
*/
while(left <= target / 2){
if(sum < target){
sum += right++;
}else if(sum > target){
sum -= left++;
}else{
//相等
int[] tmp = new int[right - left];
for(int i = 0; i < right - left; i++){
tmp[i] = left + i;
}
list.add(tmp);
// sum -= left++;
sum += right++;
}
}
//toArray(new T[0])能够运行是因为java做了优化,能动态生成对应大小的数组,是官方建议的书写方式,
//能避免某些并发问题并且效率更高,也可以写成list.toArray(new int[list.size()][]);
return list.toArray(new int[0][]);
}
}
剑指 Offer 57. 和为s的两个数字 [类型:双指针]
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
输入:nums = [2,7,11,15], target = 9 输出:[2,7] 或者 [7,2]
笔记: 双指针类型题一般数组有序,left和right在两端
class Solution {
//双指针
//最大的加最小的都比target大,所以最大的数舍弃;最小的加最大的都比target小,所以最小的舍弃
public int[] twoSum(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
while(left < right){
int sum = nums[left] + nums[right];
if(sum > target){
right--;
}else if(sum < target){
left++;
}else{
return new int[]{nums[left], nums[right]};
}
}
return new int[0];
}
}
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面 [类型:双指针 - 交换位置]
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数在数组的前半部分,所有偶数在数组的后半部分。
示例:
输入:nums = [1,2,3,4] 输出:[1,3,2,4] 注:[3,1,2,4] 也是正确的答案之一。
class Solution {
public int[] exchange(int[] nums) {
int left = 0, right = nums.length - 1, tmp;
while(left < right){
//从左往右找第一个偶数 所以遇到奇数 往后走,left < right防止出现全是奇数
while(left < right && (nums[left] & 1) == 1){
left++;
}
//从右往左找第一个奇数 所以遇到偶数 往前走
while(left < right && (nums[right] & 1) == 0){
right--;
}
tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
}
return nums;
}
}
剑指 Offer 49. 丑数 [类型:三指针 + 动态规划]
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
输入: n = 10 输出: 12 解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
思路: 这是个十分巧(操)妙(蛋)的动态规划问题,还得用三指针,第一个丑数是1,以后每次乘以2,3,5 后取最小的数,以此类推。
class Solution {
// 1. 便于理解版
public int nthUglyNumber(int n) {
// 设动态规划列表 dp[i] 代表第 i+1 个丑数;
int[] dp = new int[n];
dp[0] = 1;
int p2 =0, p3 = 0, p5 = 0;
for(int i = 1; i < n; i++){
int n2 = dp[p2] * 2;
int n3 = dp[p3] * 3;
int n5 = dp[p5] * 5;
dp[i] = Math.min(n2, Math.min(n3, n5));
if(dp[i] == n2) p2++;
if(dp[i] == n3) p3++;
if(dp[i] == n5) p5++;
}
return dp[n - 1];
}
}
class Solution {
// 2. 简化版
public int nthUglyNumber(int n) {
int[] dp = new int[n];
dp[0] = 1;
int i2 = 0, i3 = 0, i5 = 0;
int p2 =2, p3 = 3, p5 = 5;
for(int i = 1; i < n; i++){
dp[i] = Math.min(p2, Math.min(p3, p5));
if(dp[i] == p2) p2 = dp[++i2] * 2;
if(dp[i] == p3) p3 = dp[++i3] * 3;
if(dp[i] == p5) p5 = dp[++i5] * 5;
}
return dp[n - 1];
}
}
剑指 Offer 61. 扑克牌中的顺子 [类型:哈希set]
从若干副扑克牌中随机抽 5
张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
思路: 你看又是巧(操)妙(蛋)的思路呢
1. 数组中 max - min < 5 ;
2. 不能有重复的牌
class Solution {
public boolean isStraight(int[] nums) {
//注意,max和min初始化是反的
int min = 14;
int max = 0;
Set<Integer> set = new HashSet<>();
for(int n : nums){
if(n == 0) continue;
min = Math.min(min, n);
max = Math.max(max, n);
if(set.contains(n)) return false;
set.add(n);
}
return (max - min) < 5;
}
}
剑指 Offer 64. 求1+2+…+n *
求 1+2+...+n
,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
class Solution {
//递归实现,用&& 代替if判断
int res = 0;
public int sumNums(int n) {
boolean x = n > 1 && sumNums(n - 1) > 0;
res += n;
return res;
}
}
剑指 Offer 65. 不用加减乘除做加法 *
写一个函数,求两个整数之和,要求在函数体内不得使用 “+”、“-”、“*”、“/” 四则运算符号。
class Solution {
public int add(int a, int b) {
//形参b接收的是与运算进位,终会等于零
if(b == 0) return a;
// 转换成非进位和 + 进位
return add(a^b, (a&b)<<1);
}
}
剑指 Offer 66. 构建乘积数组 *
给定一个数组 A[0,1,…,n-1]
,请构建一个数组 B[0,1,…,n-1]
,其中 B[i]
的值是数组 A
中除了下标 i
以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]
。不能使用除法。
输入: [1,2,3,4,5] 输出: [120,60,40,30,24]
思路: 假如数组[a,b,c,d,e],构建错位相乘的两数组
第一次 = [ 1, a, ab, abc, abcd]
第二次 = [bcde , cde, de, e, 1]
class Solution {
//错位相乘
public int[] constructArr(int[] a) {
int n = a.length;
int[] res = new int[n];
int tmp = 1;
//第一次构建
for(int i = 0; i < n; i++){
res[i] = tmp;
tmp *= a[i];
}
tmp = 1;
//第二次构建
for(int i = n - 1; i >= 0; i--){
//错位相乘
res[i] *= tmp;
tmp *= a[i];
}
return res;
}
}