剑指offer二刷(精刷)

时间:2023-03-08 22:50:51
剑指offer二刷(精刷)

剑指 Offer 03. 数组中重复的数字

题目描述

在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。

Input:
{2, 3, 1, 0, 2, 5} Output:
2

解题思路

要求时间复杂度 O(N),空间复杂度 O(1)。因此不能使用排序的方法,也不能使用额外的标记数组。

对于这种数组元素在 [0, n-1] 范围内的问题,可以将值为 i 的元素调整到第 i 个位置上进行求解。在调整过程中,如果第 i 位置上已经有一个值为 i 的元素,就可以知道 i 值重复。

以 (2, 3, 1, 0, 2, 5) 为例,遍历到位置 4 时,该位置上的数为 2,但是第 2 个位置上已经有一个 2 的值了,因此可以知道 2 重复:

class Solution {
public int findRepeatNumber(int[] nums) {
if(nums==null||nums.length==0){
return -1;
}
int temp;
for(int i=0;i<nums.length;i++){
while(nums[i]!=i){
if(nums[i]==nums[nums[i]])return nums[i];
temp=nums[i];
nums[i]=nums[temp];
nums[temp]=temp;
}
}
return -1;
}
}

剑指 Offer 04. 二维数组中的查找

题目描述

给定一个二维数组,其每一行从左到右递增排序,从上到下也是递增排序。给定一个数,判断这个数是否在该二维数组中。

Consider the following 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]
] Given target = 5, return true.
Given target = 20, return false.

解题思路

要求时间复杂度 O(M + N),空间复杂度 O(1)。其中 M 为行数,N 为 列数。

该二维数组中的一个数,小于它的数一定在其左边,大于它的数一定在其下边。因此,从右上角开始查找,就可以根据 target 和当前元素的大小关系来快速地缩小查找区间,每次减少一行或者一列的元素。当前元素的查找区间为左下角的所有元素。

class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix==null||matrix.length==0||matrix[0].length==0)return false;
int m=matrix.length,n=matrix[0].length;
int row=0,col=n-1;
while(row<m&&col>=0){
int temp=matrix[row][col];
if(temp==target)return true;
else if(temp>target)col--;
else row++;
}
return false;
}
}

剑指 Offer 05. 替换空格

题目描述

将一个字符串中的空格替换成 "%20"。

Input:
"A B" Output:
"A%20B"

解题思路

① 在字符串尾部填充任意字符,使得字符串的长度等于替换之后的长度。因为一个空格要替换成三个字符(%20),所以当遍历到一个空格时,需要在尾部填充两个任意字符。

② 令 P1 指向字符串原来的末尾位置,P2 指向字符串现在的末尾位置。P1 和 P2 从后向前遍历,当 P1 遍历到一个空格时,就需要令 P2 指向的位置依次填充 02%(注意是逆序的),否则就填充上 P1 指向字符的值。从后向前遍是为了在改变 P2 所指向的内容时,不会影响到 P1 遍历原来字符串的内容。

③ 当 P2 遇到 P1 时(P2 <= P1),或者遍历结束(P1 < 0),退出。

class Solution {
public String replaceSpace(String s) {
StringBuilder res=new StringBuilder();
for(Character c:s.toCharArray()){
if(c==' ')res.append("%20");
else res.append(c);
}
return res.toString();
}
}

剑指 Offer 06. 从尾到头打印链表

题目描述

从尾到头反过来打印出每个结点的值。

解题思路

1. 使用递归

要逆序打印链表 1->2->3(3,2,1),可以先逆序打印链表 2->3(3,2),最后再打印第一个节点 1。而链表 2->3 可以看成一个新的链表,要逆序打印该链表可以继续使用求解函数,也就是在求解函数中调用自己,这就是递归函数。

class Solution {
ArrayList<Integer> list=new ArrayList<>();
public int[] reversePrint(ListNode head) {
reverse(head);
int[] arr=new int[list.size()];
for(int i=0;i<list.size();i++){
arr[i]=list.get(i);
}
return arr;
}
public void reverse(ListNode head){
if(head==null)return;
reverse(head.next);
list.add(head.val);
}
}

2. 使用栈

栈具有后进先出的特点,在遍历链表时将值按顺序放入栈中,最后出栈的顺序即为逆序。

class Solution {
public int[] reversePrint(ListNode head) {
Stack<Integer> stack=new Stack<>();
while(head!=null){
stack.push(head.val);
head=head.next;
}
int[] res=new int[stack.size()];
int size=stack.size();
for(int i=0;i<size;i++){
res[i]=stack.pop();
}
return res;
}
}

剑指 Offer 07. 重建二叉树

题目描述

根据二叉树的前序遍历和中序遍历的结果,重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。

解题思路

前序遍历的第一个值为根节点的值,使用这个值将中序遍历结果分成两部分,左部分为树的左子树中序遍历结果,右部分为树的右子树中序遍历的结果。然后分别对左右子树递归地求解。

/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
int[] preorder;
HashMap<Integer,Integer>map=new HashMap();
public TreeNode buildTree(int[] preorder, int[] inorder) {
this.preorder=preorder;
for(int i=0;i<inorder.length;i++){
map.put(inorder[i],i);
}
return recur(0,0,inorder.length-1);
} private TreeNode recur(int root,int left,int right){
if(left>right) return null;
TreeNode node=new TreeNode(preorder[root]);
// 根节点在中序序列中的位置,用于划分左右子树的边界
int i=map.get(preorder[root]);
// 左子树在前序中的根节点位于:root+1,左子树在中序中的边界:[left,i-1]
node.left=recur(root+1,left,i-1);
// 右子树在前序中的根节点位于:根节点+左子树长度+1 = root+i-left+1
// 右子树在中序中的边界:[i+1,right]
node.right=recur(root+1+i-left,i+1,right);
return node;
}
}

剑指 Offer 09. 用两个栈实现队列

题目描述

用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。

解题思路

in 栈用来处理入栈(push)操作,out 栈用来处理出栈(pop)操作。一个元素进入 in 栈之后,出栈的顺序被反转。当元素要出栈时,需要先进入 out 栈,此时元素出栈顺序再一次被反转,因此出栈顺序就和最开始入栈顺序是相同的,先进入的元素先退出,这就是队列的顺序。

class CQueue {
Stack<Integer>in;
Stack<Integer>out;
public CQueue() {
in=new Stack();
out=new Stack();
} public void appendTail(int value) {
in.push(value);
} public int deleteHead() {
if(out.isEmpty()){
while(!in.isEmpty()){
out.push(in.pop());
}
} if(out.isEmpty())return -1; return out.pop();
}
} /**
* Your CQueue object will be instantiated and called as such:
* CQueue obj = new CQueue();
* obj.appendTail(value);
* int param_2 = obj.deleteHead();
*/

剑指 Offer 10- I. 斐波那契数列

题目描述

求斐波那契数列的第 n 项,n <= 39。

解题思路

如果使用递归求解,会重复计算一些子问题。例如,计算 f(4) 需要计算 f(3) 和 f(2),计算 f(3) 需要计算 f(2) 和 f(1),可以看到 f(2) 被重复计算了。

class Solution {
public int fib(int n) {
if(n==0)return 0;
int[] dp=new int[n+1];
dp[0]=0;
dp[1]=1;
for(int i=2;i<=n;i++){
dp[i]=dp[i-1]+dp[i-2];
dp[i]%=1000000007;
}
return dp[n];
}
}

剑指 Offer 10- II. 青蛙跳台阶问题

题目描述

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

解题思路

跳 n 阶台阶,可以先跳 1 阶台阶,再跳 n-1 阶台阶;或者先跳 2 阶台阶,再跳 n-2 阶台阶。而 n-1 和 n-2 阶台阶的跳法可以看成子问题,该问题的递推公式为:

剑指offer二刷(精刷)

class Solution {
public int numWays(int n) {
int[] dp=new int[n+1];
if(n==0)return 1;
dp[0]=1;
dp[1]=1;
for(int i=2;i<n+1;i++){
dp[i]=dp[i-1]+dp[i-2];
dp[i]%=1000000007;
}
return dp[n];
}
}

10.2 矩形覆盖

题目链接

NowCoder

题目描述

我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,总共有多少种方法?

public class Solution {
public int RectCover(int target) {
int[] dp=new int[target+1];
if(target<=2)return target;
dp[1]=1;
dp[2]=2;
for(int i=3;i<target+1;i++){
dp[i]=dp[i-1]+dp[i-2];
}
return dp[target];
}
}

10.4 变态跳台阶

题目链接

NowCoder

题目描述

一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

同样,跳上 n 级台阶,可以从 n-1 级跳 1 级上去,也可以从 n-2 级跳 2 级上去... ,那么

f(n) = f(n-1) + f(n-2) + ... + f(0)
public class Solution {
public int JumpFloorII(int target) {
int[] a=new int[target+1];
a[0]=1;
a[1]=1;
for(int i=2;i<=target;i++){
a[i]=0;
for(int j=i-1;j>=0;j--){
a[i]+=a[j];
}
}
return a[target];
}
}

剑指 Offer 11. 旋转数组的最小数字

解题思路

将旋转数组对半分可以得到一个包含最小元素的新旋转数组,以及一个非递减排序的数组。新的旋转数组的长度是原数组的一半,从而将问题规模减少了一半,这种折半性质的算法的时间复杂度为 O(log2N)。

此时问题的关键在于确定对半分得到的两个数组哪一个是旋转数组,哪一个是非递减数组。我们很容易知道非递减数组的第一个元素一定小于等于最后一个元素。

剑指offer二刷(精刷)

这个题目和一般的二分法的解法不太相同

主要的不同点在于比较的对象是有边界j,因此需要注意设置都为闭区间

注意他的结束条件需要的是i==j这个情况,不然会进入i==j的情况判断

class Solution {
public int minArray(int[] numbers) {
int i=0,j=numbers.length-1;
while(i<j){
int m=(i+j)/2;
if(numbers[m]>numbers[j])i=m+1;
else if(numbers[m]<numbers[j])j=m;
else j--;
}
return numbers[i];
}
}

剑指 Offer 12. 矩阵中的路径

解题思路

使用回溯法(backtracking)进行求解,它是一种暴力搜索方法,通过搜索所有可能的结果来求解问题。回溯法在一次搜索结束时需要进行回溯(回退),将这一次搜索过程中设置的状态进行清除,从而开始一次新的搜索过程。例如下图示例中,从 f 开始,下一步有 4 种搜索可能,如果先搜索 b,需要将 b 标记为已经使用,防止重复使用。在这一次搜索结束之后,需要将 b 的已经使用状态清除,并搜索 c。

全排列问题的模板

class Solution {
private int[][] direction={{0,1},{0,-1},{1,0},{-1,0}};
private int m,n;
public boolean exist(char[][] board, String word) {
if(board==null||board.length==0||board[0].length==0){
return false;
}
if(word==null||word.length()==0){
return true;
}
m=board.length;
n=board[0].length;
boolean[][] hasVisited=new boolean[m][n];
char[] arr=word.toCharArray();
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(dfs(board,i,j,hasVisited,0,arr)){
return true;
}
}
}
return false;
} private boolean dfs(char[][] board,int i,int j,boolean[][] hasVisited,int len,char[] arr){
if(len==arr.length){
return true;
}
if(i<0||i>=m||j<0||j>=n||board[i][j]!=arr[len]||hasVisited[i][j]){
return false;
}
hasVisited[i][j]=true;
for(int[] d:direction){
if(dfs(board,i+d[0],j+d[1],hasVisited,len+1,arr)){
return true;
}
}
hasVisited[i][j]=false;
return false;
}
}

剑指 Offer 13. 机器人的运动范围

题目描述

地上有一个 m 行和 n 列的方格。一个机器人从坐标 (0, 0) 的格子开始移动,每一次只能向左右上下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于 k 的格子。

例如,当 k 为 18 时,机器人能够进入方格 (35,37),因为 3+5+3+7=18。但是,它不能进入方格 (35,38),因为 3+5+3+8=19。请问该机器人能够达到多少个格子?

解题思路

使用深度优先搜索(Depth First Search,DFS)方法进行求解。回溯是深度优先搜索的一种特例,它在一次搜索过程中需要设置一些本次搜索过程的局部状态,并在本次搜索结束之后清除状态。而普通的深度优先搜索并不需要使用这些局部状态,虽然还是有可能设置一些全局状态。

class Solution {
public int movingCount(int m, int n, int k) {
boolean[][] visited=new boolean[m][n];
return dfs(0,0,m,n,k,visited);
}
private int dfs(int i,int j,int m,int n,int k,boolean[][] visited){
if(i<0||i>=m||j<0||j>=n||(i/10+i%10+j/10+j%10)>k||visited[i][j]){
return 0;
}
visited[i][j]=true;
return 1+dfs(i + 1, j, m, n, k, visited) + dfs(i - 1, j, m, n, k, visited) + dfs(i, j + 1, m, n, k, visited) + dfs(i, j - 1, m, n, k, visited) ;
}
}

剑指 Offer 14- I. 剪绳子

剑指offer二刷(精刷)

class Solution {
public int cuttingRope(int n) {
int[] dp=new int[n+1];
for(int i=2;i<=n;i++){
for(int j=1;j<i;j++){
dp[i]=Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
}
}
return dp[n];
}
}

数学方法

public int cuttingRope(int n) {
if (n==1 || n==2)
return 1;
if (n==3)
return 2;
int sum=1;
while (n>4){
sum*=3;
n-=3;
} return sum*n;
}

剑指 Offer 14- II. 剪绳子 II

class Solution {
public int cuttingRope(int n) {
if(n<4)return n-1;
else if(n==4){
return n;
}
long res=1;
while(n>4){
res*=3;
res%=1000000007;
n-=3;
}
return (int)(res*n%1000000007);
}
}

剑指 Offer 15. 二进制中1的个数

public class Solution {
// you need to treat n as an unsigned value
public int hammingWeight(int n) {
int cnt=0;
while(n!=0){
if((n&1)==1)cnt++;
n>>>=1;
}
return cnt;
}
}

剑指 Offer 16. 数值的整数次方

剑指offer二刷(精刷)

class Solution {
public double myPow(double x, int n) {
if(n==0)return 1;
if(n==1)return x;
if(n==-1)return 1/x;
double half=myPow(x,n/2);
double mod=myPow(x,n%2);
return half*half*mod;
}
}

剑指 Offer 17. 打印从1到最大的n位数

这个题目难度不小,需要注意的是他的dfs组合很巧妙,多背

class Solution {
int[] res;
char[] num;
int count=0,n;
public int[] printNumbers(int n) {
this.n=n;
num=new char[n];
res=new int[(int)(Math.pow(10,n)-1)];
dfs(0);
return res;
} private void dfs(int n){
if(n==this.n){
String tmp=String.valueOf(num);
int curNum=Integer.parseInt(tmp);
if(curNum!=0)res[count++]=curNum;
return;
}
for(char i='0';i<='9';i++){
num[n]=i;
dfs(n+1);
}
}
}

剑指 Offer 18. 删除链表的节点

class Solution {
public ListNode deleteNode(ListNode head, int val) {
ListNode pre=new ListNode(0);
pre.next=head;
ListNode cur=head;
if(head.val==val)return head.next;
while(cur.val!=val){
pre=cur;
cur=cur.next;
}
pre.next=cur.next;
return head;
}
}

剑指 Offer 19. 正则表达式匹配

这道题目比较难,无情动态规划

考虑的情况太多不容易理解

class Solution {
public boolean isMatch(String s, String p) {
int n=s.length();
int m=p.length();
boolean[][] f = new boolean[n+1][m+1]; for(int i=0; i<=n; i++){
for(int j=0; j<=m;j++){
//分为空正则表达式和非空正则表达式俩种
if(j==0){
f[i][j]=i==0;
}
else {
//非空正则分为两种情况*和非*
if(p.charAt(j-1)!='*'){
if(i > 0 && (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) =='.')){
f[i][j]=f[i-1][j-1];
}
}
else{
//碰到*,分为看和不看两种情况
//不看
if(j>=2){
f[i][j] |= f[i][j-2];
//看
if( i>=1 && (s.charAt(i-1) == p.charAt(j-2) || p.charAt(j-2)=='.')){
f[i][j] |= f[i-1][j];
}
}
}
}
}
}
return f[n][m]; }
}

剑指 Offer 20. 表示数值的字符串

假设字符串为A.BeC或A.BEC, 也就是整数部分为A,小数部分为B,指数部分为C,按顺序判断是否包含这三部分。

  • 在字符串后添加结束标志
  • 使用全局index遍历字符串
  • scanInteger扫描有符号整数,用来匹配A和C部分
  • scanUnsignedInteger扫描无符号整数,用来匹配B部分
class Solution {
private int index=0; //全局索引
private boolean scanUnsignedInteger(String str) {
//是否包含无符号数
int before =index;
while(str.charAt(index)>='0'&&str.charAt(index)<='9')
index++;
return index > before;
} private boolean scanInteger(String str){
//是否包含有符号数
if(str.charAt(index)=='+'||str.charAt(index)=='-'){
index++;
}
return scanUnsignedInteger(str);
} public boolean isNumber(String s) {
//空字符串
if(s==null||s.length()==0){
return false;
}
//添加结束标志
s=s+'|';
//跳过首部的空格
while(s.charAt(index)==' '){
index++;
}
boolean numeric=scanInteger(s); //是否包含整数部分
if(s.charAt(index)=='.'){
index++;
//有小数点,处理小数部分
//小数点两边只要有一边有数字就可以,所以用||,
//注意scanUnsignedInteger要在前面,否则不会进
numeric=scanUnsignedInteger(s)||numeric;
}
if((s.charAt(index)=='E'||s.charAt(index)=='e')){
index++;
//指数部分
//e或E的两边都要有数字,所以用&&
numeric=numeric&&scanInteger(s);
}
//跳过尾部空格
while(s.charAt(index)==' ')
index++;
return numeric&&s.charAt(index)=='|';
}
}

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

class Solution {
public int[] exchange(int[] nums) {
int i=0,j=nums.length-1,tmp;
while(i<j){
while(i<j&&(nums[i]%2==1))i++;
while(i<j&&(nums[j]%2==0))j--;
tmp=nums[i];
nums[i]=nums[j];
nums[j]=tmp;
}
return nums;
}
}

剑指 Offer 22. 链表中倒数第k个节点

class Solution {
public ListNode getKthFromEnd(ListNode head, int k) {
ListNode fast=head;
ListNode slow=head;
while(k-->0){
fast=fast.next;
}
while(fast!=null){
fast=fast.next;
slow=slow.next;
}
return slow;
}
}

剑指offer23142. 环形链表 II

解题思路:分两个步骤,首先通过快慢指针的方法判断链表是否有环;接下来如果有环,则寻找入环的第一个节点。具体的方法为,首先假定链表起点到入环的第一个节点A的长度为a【未知】,到快慢指针相遇的节点B的长度为(a + b)【这个长度是已知的】。现在我们想知道a的值,注意到快指针p2始终是慢指针p走过长度的2倍,所以慢指针p从B继续走(a + b)又能回到B点,如果只走a个长度就能回到节点A。但是a的值是不知道的,解决思路是曲线救国,注意到起点到A的长度是a,那么可以用一个从起点开始的新指针q和从节点B开始的慢指针p同步走,相遇的地方必然是入环的第一个节点A。

public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null){
return null;
}
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
while(head != fast){
fast = fast.next;
head = head.next;
}
return head;
}
}
return null;
}
}

剑指 Offer 24. 反转链表

用的是迭代

class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre=null,next,cur=head;
while(cur!=null){
next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
return pre;
}
}

用的是递归

class Solution {
public ListNode reverseList(ListNode head) {
if(head==null||head.next==null){
return head;
}
ListNode cur=reverseList(head.next);
head.next.next=head;
head.next=null;
return cur; }
}

剑指 Offer 25. 合并两个排序的链表

class Solution {
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head=new ListNode(0),cur=head;
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;
}
cur.next=l1!=null?l1:l2;
return head.next;
}
}

剑指 Offer 26. 树的子结构

双重递归很经典的题好好看看

class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null|| B == null)return false;
return isTree(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
} public boolean isTree(TreeNode A,TreeNode B){
if(B==null)return true;
if(A==null||A.val!=B.val)return false;
return isTree(A.left,B.left)&&isTree(A.right,B.right);
}
}

剑指 Offer 27. 二叉树的镜像

class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root==null)return null;
TreeNode cur=root.left;
root.left=root.right;
root.right=cur;
mirrorTree(root.left);
mirrorTree(root.right);
return root;
}
}

剑指 Offer 28. 对称的二叉树

class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null)return true;
return treeHelper(root.left,root.right);
} public boolean treeHelper(TreeNode left,TreeNode right){
if(left==null&&right==null)return true;
if(left==null||right==null||left.val!=right.val)return false;
return treeHelper(left.left,right.right)&&treeHelper(left.right,right.left);
}
}

剑指 Offer 29. 顺时针打印矩阵

剑指offer二刷(精刷)

剑指offer二刷(精刷)

class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0) return new int[0];
int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;
int[] res = new int[(r + 1) * (b + 1)];
while(true) {
for(int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right.
if(++t > b) break;
for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.
if(--r < l) break;
for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.
if(--b < t) break;
for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.
if(++l > r) break;
}
return res;
}
}

剑指 Offer 30. 包含min函数的栈

有一个很容易错误的点就是==这个符号是会判断地址的,所以要用equals来判断Integer类型的数据是否相同

class MinStack {
Stack<Integer> stack1=new Stack<>(); //min辅助栈
Stack<Integer> stack2=new Stack<>();
/** initialize your data structure here. */
public MinStack() {
stack1=new Stack();
stack2=new Stack();
} public void push(int x) {
stack2.push(x);
if(stack1.isEmpty())stack1.push(x);
else if(stack1.peek()>=x){
stack1.push(x);
}
} public void pop() {
if((stack2.pop()).equals(stack1.peek()))stack1.pop();
} public int top() {
return stack2.peek();
} public int min() {
return stack1.peek();
}
} /**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.min();
*/

剑指 Offer 31. 栈的压入、弹出序列

class Solution {
public boolean validateStackSequences(int[] pushed, int[] popped) {
Stack<Integer>stack=new Stack();
int j=0;
for(int i=0;i<pushed.length;i++){
stack.push(pushed[i]);
while(!stack.isEmpty()&&stack.peek()==popped[j]){
stack.pop();
j++;
}
}
return stack.isEmpty();
}
}

剑指 Offer 32 - I. 从上到下打印二叉树

class Solution {
public int[] levelOrder(TreeNode root) {
if(root==null)return new int[0];
Queue<TreeNode>queue=new LinkedList();
queue.add(root);
ArrayList<Integer>arr=new ArrayList();
while(!queue.isEmpty()){
for(int i=queue.size();i>0;i--){
TreeNode tmp=queue.poll();
arr.add(tmp.val);
if(tmp.left!=null)queue.add(tmp.left);
if(tmp.right!=null)queue.add(tmp.right);
}
}
int[] res=new int[arr.size()];
for(int i=0;i<arr.size();i++){
res[i]=arr.get(i);
}
return res;
}
}

剑指 Offer 32 - II. 从上到下打印二叉树 II

class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> list=new LinkedList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(root!=null)queue.add(root);
while(!queue.isEmpty()){
List<Integer> tmp=new LinkedList();
for(int i=queue.size();i>0;i--){
TreeNode node=queue.poll();
tmp.add(node.val);
if(node.left!=null)queue.add(node.left);
if(node.right!=null)queue.add(node.right);
}
list.add(tmp);
}
return list;
}
}

剑指 Offer 32 - III. 从上到下打印二叉树 III

class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> res=new LinkedList();
LinkedList<TreeNode> queue=new LinkedList();
if(root!=null)queue.add(root);
while(!queue.isEmpty()){
LinkedList<Integer>tmp=new LinkedList();
for(int i=queue.size();i>0;i--){
TreeNode node=queue.poll();
if(res.size()%2==0) tmp.addLast(node.val);
else tmp.addFirst(node.val);
if(node.left != null) queue.add(node.left);
if(node.right != null) queue.add(node.right);
}
res.add(tmp);
}
return res;
}
}

剑指 Offer 33. 二叉搜索树的后序遍历序列

主要是后序遍历的性质,找到一个比根节点大的则为右子树,应该都大于根节点

class Solution {
public boolean verifyPostorder(int[] postorder) {
return recur(postorder,0,postorder.length-1);
}
public boolean recur(int[] postorder,int left,int right){
if(left>=right)return true;
int mid=left;
int root=postorder[right];
while(postorder[mid]<root){
mid++;
}
int temp=mid;
while(temp<right){
if(postorder[temp++]<root){
return false;
}
}
return recur(postorder,left,mid-1)&&recur(postorder,mid,right-1);
}
}
public boolean verifyPostorder(int[] postorder) {
return helper(postorder, 0, postorder.length - 1);
} boolean helper(int[] postorder, int left, int right) {
//如果left==right,就一个节点不需要判断了,如果left>right说明没有节点,
//也不用再看了,否则就要继续往下判断
if (left >= right)
return true;
//因为数组中最后一个值postorder[right]是根节点,这里从左往右找出第一个比
//根节点大的值,他后面的都是根节点的右子节点(包含当前值,不包含最后一个值,
//因为最后一个是根节点),他前面的都是根节点的左子节点
int mid = left;
int root = postorder[right];
while (postorder[mid] < root)
mid++;
int temp = mid;
//因为postorder[mid]前面的值都是比根节点root小的,
//我们还需要确定postorder[mid]后面的值都要比根节点root大,
//如果后面有比根节点小的直接返回false
while (temp < right) {
if (postorder[temp++] < root)
return false;
}
//然后对左右子节点进行递归调用
return helper(postorder, left, mid - 1) && helper(postorder, mid, right - 1);
}

剑指 Offer 34. 二叉树中和为某一值的路径

class Solution {
public List<List<Integer>> pathSum(TreeNode root, int sum) {
List<List<Integer>>res=new ArrayList();
backtracking(res,new ArrayList(),root,sum);
return res;
} public void backtracking(List<List<Integer>> res,ArrayList<Integer> list,TreeNode node,int sum){
if(node==null)return;
list.add(node.val);
if(node.left==null&&node.right==null&&sum==node.val){
res.add(new ArrayList(list));
}
backtracking(res,list,node.left,sum-node.val);
backtracking(res,list,node.right,sum-node.val);
list.remove(list.size()-1);
}
}

剑指 Offer 35. 复杂链表的复制

方法一

class Solution {
public Node copyRandomList(Node head) {
if(head==null)return null;
Node cur=head;
//复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
HashMap<Node,Node>map=new HashMap();
while(cur!=null){
map.put(cur,new Node(cur.val));
cur=cur.next;
}
cur=head;
//构建新链表的 next 和 random 指向
while(cur!=null){
map.get(cur).next=map.get(cur.next);
map.get(cur).random=map.get(cur.random);
cur=cur.next;
}
//返回新链表的头节点
return map.get(head);
}
}

方法二

class Solution {
public Node copyRandomList(Node head) {
if(head == null) return null;
Node cur = head;
// 1. 复制各节点,并构建拼接链表
while(cur != null) {
Node tmp = new Node(cur.val);
tmp.next = cur.next;
cur.next = tmp;
cur = tmp.next;
}
// 2. 构建各新节点的 random 指向
cur = head;
while(cur != null) {
if(cur.random != null)
cur.next.random = cur.random.next;
cur = cur.next.next;
}
// 3. 拆分两链表
cur = head.next;
Node pre = head, res = head.next;
while(cur.next != null) {
pre.next = pre.next.next;
cur.next = cur.next.next;
pre = pre.next;
cur = cur.next;
}
pre.next = null; // 单独处理原链表尾节点
return res; // 返回新链表头节点
}
}

剑指 Offer 36. 二叉搜索树与双向链表

剑指offer二刷(精刷)

剑指offer二刷(精刷)

class Solution {
Node head,pre;
public Node treeToDoublyList(Node root) {
if(root==null)return null;
dfs(root);
pre.right=head;
head.left=pre;
return head;
}
public void dfs(Node cur){
if(cur==null)return;
dfs(cur.left);
if(pre!=null)pre.right=cur;
else head=cur;
cur.left=pre;
pre=cur;
dfs(cur.right);
}
}

剑指 Offer 37. 序列化二叉树

首先,这个序列化就是简单的层序遍历即可。遇到空节点就序列化为特殊符号,例如'$'.
在反序列化的时候,先将字符串解析的过程中将新建的节点存入queue容器。
然后根据节点间的顺序关系,构建连接树结构。

public class Codec {

    // Encodes a tree to a single string.
public String serialize(TreeNode root) {
if(root==null)return "[]";
StringBuilder res=new StringBuilder("[");
Queue<TreeNode>queue=new LinkedList();
queue.add(root);
while(!queue.isEmpty()){
TreeNode node=queue.poll();
if(node!=null){
res.append(node.val).append(",");
queue.add(node.left);
queue.add(node.right);
}
else res.append("null,");
}
res.delete(res.length()-1,res.length());
res.append("]");
return res.toString();
} // Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data=="[]")return null;
String[] vals=data.substring(1,data.length()-1).split(",");
TreeNode root=new TreeNode(Integer.parseInt(vals[0]));
Queue<TreeNode> queue=new LinkedList();
queue.add(root);
int i=1;
while(!queue.isEmpty()){
TreeNode node=queue.poll();
if(!vals[i].equals("null")){
node.left=new TreeNode(Integer.parseInt(vals[i]));
queue.add(node.left);
}
i++;
if(!vals[i].equals("null")){
node.right=new TreeNode(Integer.parseInt(vals[i]));
queue.add(node.right);
}
i++;
}
return root;
}
}

剑指 Offer 38. 字符串的排列

使用set去重,因为set里加元素,如果相同就返回false不加入。
借助了布尔数组来判断每一个字符是使用过

class Solution {
Set<String> res=new HashSet<>();
public String[] permutation(String s) {
if(s==null)return new String[]{};
boolean[] visited=new boolean[s.length()];
dfs(s,"",visited);//看作剩下的字符有多少情况
String[] result=new String[res.size()];
return res.toArray(result);
}
private void dfs(String s,String letter,boolean[] visited){
if(s.length()==letter.length()){
res.add(letter);
return ;//为了退出递归
}
for(int i=0;i<s.length();i++){
char c=s.charAt(i);
if(visited[i])continue;
visited[i]=true;
dfs(s,letter+c,visited);
visited[i]=false;
}
}
}

剑指 Offer 39. 数组中出现次数超过一半的数字

摩尔投票法,非常简单易懂

class Solution {
public int majorityElement(int[] nums) {
int count=0;
int candidate=nums[0];
for(int i=0;i<nums.length;i++){
if(candidate==nums[i]){
count++;
}
else{
count--;
if(count==0){
candidate=nums[i];
count++;
}
}
}
return candidate;
}
}

剑指 Offer 40. 最小的k个数

快速排序

模板记忆

class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k==0||arr.length==0)return new int[0];
return quickSearch(arr,0,arr.length-1,k-1);
} private int[] quickSearch(int[]nums,int left,int right,int k){
int j=partition(nums,left,right);
if(j==k){
return Arrays.copyOf(nums,j+1);
}
return j>k?quickSearch(nums,left,j-1,k):quickSearch(nums,j+1,right,k);
} private int partition(int[] nums,int left,int right){
int i=left,j=right+1;
while(true){
while(++i<=right&&nums[i]<nums[left]);
while(--j>=left&&nums[j]>nums[left]);
if(i>=j){
break;
}
swap(nums,i,j);
}
swap(nums,left,j);
return j;
}
private void swap(int[] a, int i, int j) {
int t = a[i];
a[i] = a[j];
a[j] = t;
}
}

优先队列,堆排序

class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if(k==0||arr.length==0){
return new int[0];
}
Queue<Integer>queue=new PriorityQueue<>((v1,v2)->v2-v1);
for(int i=0;i<arr.length;i++){
if(queue.size()<k){
queue.add(arr[i]);
}else if(arr[i]<queue.peek()){
queue.poll();
queue.add(arr[i]);
}
} int[] res=new int[queue.size()];
int idx=0;
for(int num:queue){
res[idx++]=num;
}
return res; }
}

剑指 Offer 41. 数据流中的中位数

class MedianFinder {
Queue<Integer> A,B; /** initialize your data structure here. */
public MedianFinder() {
A=new PriorityQueue<>(); //小顶堆放较大的数
B=new PriorityQueue<>((x,y)->(y-x)); //大顶堆放较小的数
} public void addNum(int num) {
if(A.size()!=B.size()){
A.add(num);
B.add(A.poll());
}
else{
B.add(num);
A.add(B.poll());
}
} public double findMedian() {
return A.size() != B.size() ? A.peek() : (A.peek() + B.peek()) / 2.0;
}
} /**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder obj = new MedianFinder();
* obj.addNum(num);
* double param_2 = obj.findMedian();
*/

剑指 Offer 42. 连续子数组的最大和

1.状态,即子问题。
dp[i] 代表以元素 nums[i] 为结尾的连续子数组最大和。

2.转移策略,自带剪枝。
若 dp[i−1]≤0 ,说明 dp[i−1] 对 dp[i] 产生负贡献,即 dp[i−1]+nums[i] 还不如 nums[i] 本身大。

3.状态转移方程,根据前两步抽象而来。

  • 当 dp[i−1]>0 时:执行 dp[i] = dp[i-1] + nums[i];
  • 当 dp[i−1]≤0 时:执行 dp[i] = nums[i] ;

4.设计dp数组,保存子问题的解,避免重复计算

5.实现代码

class Solution {
public int maxSubArray(int[] nums) {
int[] dp=new int[nums.length];
dp[0]=nums[0];
int res=dp[0];
for(int i=1;i<nums.length;i++){
dp[i]=Math.max(dp[i-1],0)+nums[i];
res=Math.max(res,dp[i]);
}
return res;
}
}

剑指 Offer 43. 1~n 整数中 1 出现的次数

找规律

class Solution {
public int countDigitOne(int n) {
int digit=1,res=0;
int high=n/10,cur=n%10,low=0;
while(high!=0||cur!=0){
if(cur==0)res+=high*digit;
else if(cur==1) res+=high*digit+low+1;
else res+=(high+1)*digit;
low+=cur*digit;
cur=high%10;
high/=10;
digit*=10;
}
return res;
}
}

递归

解题思路
f(n))函数的意思是1~n这n个整数的十进制表示中1出现的次数,将n拆分为两部分,最高一位的数字high和其他位的数字last,分别判断情况后将结果相加,看例子更加简单。

例子如n=1234,high=1, pow=1000, last=234

可以将数字范围分成两部分1~999和1000~1234

1~999这个范围1的个数是f(pow-1)
1000~1234这个范围1的个数需要分为两部分:
千分位是1的个数:千分位为1的个数刚好就是234+1(last+1),注意,这儿只看千分位,不看其他位
其他位是1的个数:即是234中出现1的个数,为f(last)
所以全部加起来是f(pow-1) + last + 1 + f(last);

例子如3234,high=3, pow=1000, last=234

可以将数字范围分成两部分1~999,1000~1999,2000~2999和3000~3234

1~999这个范围1的个数是f(pow-1)
1000~1999这个范围1的个数需要分为两部分:
千分位是1的个数:千分位为1的个数刚好就是pow,注意,这儿只看千分位,不看其他位
其他位是1的个数:即是999中出现1的个数,为f(pow-1)
2000~2999这个范围1的个数是f(pow-1)
3000~3234这个范围1的个数是f(last)
所以全部加起来是pow + high*f(pow-1) + f(last);

class Solution {
public int countDigitOne(int n) {
return f(n);
}
private int f(int n ) {
if (n <= 0)
return 0;
String s = String.valueOf(n);
int high = s.charAt(0) - '0';
int pow = (int) Math.pow(10, s.length()-1);
int last = n - high*pow;
if (high == 1) {
return f(pow-1) + last + 1 + f(last);
} else {
return pow + high*f(pow-1) + f(last);
}
}
}

剑指 Offer 44. 数字序列中某一位的数字

/* 数字范围    数量  位数    占多少位
1-9 9 1 9
10-99 90 2 180
100-999 900 3 2700
1000-9999 9000 4 36000 ... 例如 2901 = 9 + 180 + 2700 + 12 即一定是4位数,第12位 n = 12;
数据为 = 1000 + (12 - 1)/ 4 = 1000 + 2 = 1002
定位1002中的位置 = (n - 1) % 4 = 3 s.charAt(3) = 2;
*/
class Solution {
public int findNthDigit(int n) {
int digit=1; //位数
long start=1; //初始数字1、10、100
long count=9; //占多少位
while(n>count){
n-=count;
digit++;
start*=10;
count=digit*start*9;
}
long num=start+(n-1)/digit;
return Long.toString(num).charAt((n-1)%digit)-'0';
}
}

剑指 Offer 45. 把数组排成最小的数

class Solution {
public String minNumber(int[] nums) {
List<String> list=new ArrayList();
for(int num:nums){
list.add(String.valueOf(num));
}
StringBuilder sb=new StringBuilder();
list.sort((o1,o2)->(o1+o2).compareTo(o2+o1));
for(String s:list){
sb.append(s);
}
return sb.toString();
}
}

剑指 Offer46. 把数字翻译成字符串

剑指offer二刷(精刷)

剑指offer二刷(精刷)

class Solution {
public int translateNum(int num) {
String str=String.valueOf(num);
int[] dp=new int[str.length()+1];
dp[0]=dp[1]=1;
for(int i=2;i<=str.length();i++){
String tmp=str.substring(i-2,i);
if(tmp.compareTo("10")>=0&&tmp.compareTo("25")<=0){
dp[i]=dp[i-1]+dp[i-2];
}
else dp[i]=dp[i-1];
}
return dp[str.length()];
}
}

剑指 Offer 47. 礼物的最大价值

dfs递归超时

class Solution {
int m,n;
public int maxValue(int[][] grid) {
this.m=grid.length;
this.n=grid[0].length;
if(grid==null)return 0;
return dfs(0,0,grid);
} public int dfs(int i,int j,int[][]grid){
if(i>=m||j>=n)return 0;
return Math.max(dfs(i+1,j,grid),dfs(i,j+1,grid))+grid[i][j];
}
}

来一手动态规划

当然也可以缩减空间

class Solution {
public int maxValue(int[][] grid) {
int m=grid.length;
int n=grid[0].length;
int[][] dp=new int[m+1][n+1];
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-1][j-1];
}
}
return dp[m][n];
}
}
class Solution {
public int maxValue(int[][] grid) {
int m = grid.length, n = grid[0].length;
int[] dp = new int[n + 1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
dp[j] = Math.max(dp[j], dp[j - 1]) + grid[i - 1][j - 1];
}
} return dp[n];
}
}

剑指 Offer 48. 最长不含重复字符的子字符串

class Solution {
public int lengthOfLongestSubstring(String s) {
char[] chars=s.toCharArray();
int start=-1;
int maxLen=0;
HashMap<Character,Integer>map=new HashMap();
for(int i=0;i<chars.length;i++){
if(map.containsKey(chars[i])){
start=Math.max(start,map.get(chars[i]));
}
map.put(chars[i],i);
maxLen=Math.max(maxLen,i-start);
}
return maxLen;
}
}

剑指 Offer 49. 丑数

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

剑指 Offer 50. 第一个只出现一次的字符

class Solution {
public char firstUniqChar(String s) {
int[] target=new int[26];
for(int i=0;i<s.length();i++){
target[s.charAt(i)-'a']++;
}
for(int i=0;i<s.length();i++){
if(target[s.charAt(i)-'a']==1)return s.charAt(i);
}
return ' ';
}
}

剑指 Offer 51. 数组中的逆序对

经典的归并排序

class Solution {
public int reversePairs(int[] nums) {
return mergeSort(nums,0,nums.length-1);
}
private int mergeSort(int[] nums,int left,int right){
if(left>=right)return 0;
int mid=left+(right-left)/2;
return mergeSort(nums,left,mid)+mergeSort(nums,mid+1,right)+merge(nums,left,mid,right);
} private int merge(int[] nums,int left,int mid,int right){
int i=left;
int j=mid+1;
int k=0;
int count=0;
int[] res=new int[right-left+1];
while(i<=mid && j<=right){
if(nums[i]>nums[j])count+=mid-i+1;//如果j位置小于i位置,那么j位置小于i位置后所有的左半边的数
res[k++] = nums[i] <= nums[j] ? nums[i++] : nums[j++];
}
while (i <= mid) res[k++] = nums[i++];
while (j <= right) res[k++] = nums[j++];
for (int m = 0; m < res.length; m++) {
nums[left + m] = res[m];
}
return count;
}
}
class Solution {
public int reversePairs(int[] nums) {
return mergeSortPlus(nums, 0, nums.length - 1);
} int mergeSortPlus(int[] arr, int arrStart, int arrEnd) {
if (arrStart >= arrEnd) return 0;//递归结束条件 int arrMid=arrStart+((arrEnd-arrStart)>>1);//左右数组分裂的中间界点位置 //左右分别进行递归并统计逆序对数
int count = mergeSortPlus(arr, arrStart, arrMid) + mergeSortPlus(arr, arrMid + 1, arrEnd); int[] tempArr = new int[arrEnd - arrStart + 1];//新建一个该层次的临时数组用于左右数组排序后的合并
int i = arrStart;//左边数组的移动指针
int j = arrMid + 1;//右边数组的移动指针
int k = 0;//临时数组tempArr的移动指针 while (i <= arrMid && j <= arrEnd) {//左右两数组都还剩有数字未排序时
if(arr[i]>arr[j]){ //如果左边大于右边,构成逆序对
/*核心代码*/
//左数组i位置及其后的数值均比右数值j位置大,故累加上i位置及其后的数值的长度
count+=arrMid-i+1;
/*核心代码*/
tempArr[k++]=arr[j++]; //右数组移动到tempArr中
}else{ //如果左小于等于右,不构成逆序对
tempArr[k++]=arr[i++]; //左数组移动到tempArr中
}
}
//左右两数组有一边已经移动完毕,剩下另一边可进行快速移动
while (i <= arrMid) //右边数组已全部被对比过且移动完成,将剩下的左边数组快速移入tempArr中
tempArr[k++] = arr[i++]; while (j <= arrEnd) //左边数组已全部被对比过且移动完成,将剩下的右边数组快速移入tempArr中
tempArr[k++] = arr[j++]; //将tempArr中的数还原回原arr中,需加上arrStart确保在原arr上位置正确
for(int a = 0;a < tempArr.length;a++)
arr[a+arrStart]=tempArr[a]; return count;
}
}

剑指 Offer 52. 两个链表的第一个公共节点

public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA=headA;
ListNode curB=headB;
while(curA!=curB){
if(curA==null)curA=headB;
else curA=curA.next;
if(curB==null)curB=headA;
else curB=curB.next;
}
return curA;
}
}

剑指 Offer 53 - I. 在排序数组中查找数字 I

二分法,老题目了不谈了必会哦

全闭区间

class Solution {
public int search(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]<=target)left=mid+1;
else right=mid-1;
}
int r=left;
if(right>0&&nums[right]!=target)return 0;
left=0;right=nums.length-1;
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]>=target)right=mid-1;
else left=mid+1;
}
int l=right;
return r-l-1;
}
}

剑指 Offer 53 - II. 0~n-1中缺失的数字

二分法的变种完全没问题

class Solution {
public int missingNumber(int[] nums) {
int left=0,right=nums.length-1;
while(left<=right){
int mid=(left+right)/2;
if(nums[mid]!=mid)right=mid-1;
else left=mid+1;
}
return left;
}
}

剑指 Offer 54. 二叉搜索树的第k大节点

class Solution {
ArrayList<Integer>arr=new ArrayList();
public int kthLargest(TreeNode root, int k) {
if(root==null)return 0;
help(root);
return arr.get(arr.size()-k);
}
public void help(TreeNode root){
if(root==null)return;
help(root.left);
arr.add(root.val);
help(root.right);
}
}

剑指 Offer 55 - I. 二叉树的深度

class Solution {
public int maxDepth(TreeNode root) {
if(root==null)return 0;
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}
}

剑指 Offer 55 - II. 平衡二叉树

class Solution {
public boolean isBalanced(TreeNode root) {
if(root==null)return true;
int tmp=Math.abs(depth(root.left)-depth(root.right));
return isBalanced(root.left)&&isBalanced(root.right)&&tmp<=1;
} public int depth(TreeNode root){
if(root==null)return 0;
return Math.max(depth(root.left),depth(root.right))+1;
}
}

后序遍历的效果更好可以剪枝

class Solution {
private boolean balance=true;
public boolean isBalanced(TreeNode root) {
helper(root);
return balance;
} public int helper(TreeNode root){
if(root==null||!balance)return 0;
int left=helper(root.left);
int right=helper(root.right);
if(Math.abs(left-right)>1){
balance=false;
}
return Math.max(left,right)+1;
}
}

剑指 Offer 56 - I. 数组中数字出现的次数

俩个注意点

1、以下为运算符优先级,忘了的人赶紧看一下,(div & ret)==0,这里的括号不能少,因为==的优先级大于&。

对C++而言
9 == != 等于/不等于
10 & 按位与
11 ^ 按位异或
12 | 按位或

2、小技巧 在异或结果中找到任意为 11 的位。

可以用diff &= -diff 得到出 diff 最右侧不为 0 的位,也就是不存在重复的两个元素在位级表示上最右侧不同的那一位,利用这一位就可以将两个元素区分开来

class Solution {
public int[] singleNumbers(int[] nums) {
int tmp=0;
for(int i:nums){
tmp^=i;
}
int a=1;
while((tmp&a)==0){
a<<=1;
}
int res1=0,res2=0;
for(int i:nums){
if((i&a)==0){
res1=res1^i;
}
else{
res2=res2^i;
}
}
return new int[]{res1,res2};
}
}

牛逼版本

class Solution {
public int[] singleNumber(int[] nums) {
int diff=0;
for(int num:nums)diff^=num;
diff&=-diff; //得到最右一位
int[] ret=new int[2];
for(int num;nums){
if(num&diff)==0)ret[0]^=num;
else ret[1]^=num;
}
return ret;
}
}

剑指 Offer 56 - II. 数组中数字出现的次数 II

第一个方法不是位运算空间复杂度高

class Solution {
public int singleNumber(int[] nums) {
HashMap<Integer,Integer> map=new HashMap();
for(int i:nums){
map.put(i,map.getOrDefault(i,0)+1);
}
for(int i:nums){
if(map.get(i)==1)return i;
}
return 0;
}
}

第二个是O(1)

class Solution {
public int singleNumber(int[] nums) {
int[] cur=new int[32];
for(int num:nums){
for(int i=0;i<32;i++){
cur[i]+=(num&(1<<i))!=0?1:0;
}
}
int res=0;
for(int i=0;i<32;i++){
res+=(1<<i)*(cur[i]%3);
}
return res;
}
}

剑指 Offer 57. 和为s的两个数字

双指针

class Solution {
public int[] twoSum(int[] nums, int target) {
int left=0,right=nums.length-1;
while(left<right){
int sum=nums[left]+nums[right];
if(sum==target)return new int[]{nums[left],nums[right]};
else if(sum<target)left++;
else right--;
}
return new int[2];
}
}

剑指 Offer 57 - II. 和为s的连续正数序列

高级双指针,注意一下返回的toArray(new int[0][])

class Solution {
public int[][] findContinuousSequence(int target) {
int i=1,j=2,s=3;
List<int[]>res=new ArrayList<>();
while(i<j){
if(s==target){
int[] ans=new int[j-i+1];
for(int k=i;k<=j;k++)
ans[k-i]=k;
res.add(ans);
}
if(s>=target){
s-=i;
i++;
}
else{
j++;
s+=j;
}
}
return res.toArray(new int[0][]);
}
}

不需要二维数组的列长度

剑指offer二刷(精刷)

剑指 Offer 58 - I. 翻转单词顺序

class Solution {
public String reverseWords(String s) {
String[] cur=s.trim().split(" ");
StringBuilder str=new StringBuilder();
for(int i=cur.length-1;i>=0;i--){
if(cur[i].equals(""))continue;
str.append(cur[i]);
str.append(" ");
}
return str.toString().trim();
}
}

剑指 Offer 58 - II. 左旋转字符串

class Solution {
public String reverseLeftWords(String s, int n) {
String cur1=s.substring(0,n);
String cur2=s.substring(n,s.length());
return cur2+cur1;
}
}
class Solution {
public String reverseLeftWords(String s, int n) {
char[] c=s.toCharArray();
reverse(c,0,n-1);
reverse(c,n,c.length-1);
reverse(c,0,c.length-1);
return new String(c);
}
private void reverse(char[] arr,int i,int j){
while(i<j){
char tmp=arr[i];
arr[i]=arr[j];
arr[j]=tmp;
i++;
j--;
}
}
}

剑指 Offer 59 - I. 滑动窗口的最大值

单调队列

算法概要:

  1. 准备一个双端队列
  2. 从数组0位置到末尾遍历
  3. 如果队列中还没有元素#2位置,直接将新的元素加入到队列尾(addLast()
  4. 如果队列中已经有元素,而且当前遍历到的元素>=队列尾对应的元素#2位置 (注意:队列中添加的是数组的下标),弹出队尾元素,直到队尾元素不再小于当前元素==配合视频食用更加==
  5. 此时队列中队尾元素已经不小于当前遍历到的元素,再将当前遍历到的元素下标加入队列,这就保证了队列头一直是最大的(且从队列头到队列尾是递减的)
  6. 如果发现此时队列头已经在窗口之外了#3位置,就弹出队列头
  7. 如果发现此时窗口已经形成(当前遍历到的位置已经>=k-1:也就是初始状态的窗口右边界),就将队列头放入结果数组
  8. 循环结束
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(nums.length==0||nums==null||k<1){
return new int[0];
}
Deque<Integer>queue=new LinkedList<>();
int[] res =new int[nums.length-k+1];
int index=0;
for(int i=0;i<nums.length;i++){
while(!queue.isEmpty()&&nums[i]>=nums[queue.peekLast()]){
queue.pollLast();
}
queue.addLast(i);
if(queue.peekFirst()<=(i-k)){
queue.pollFirst();
}
if(i>=k-1){
res[index++]=nums[queue.peekFirst()];
}
}
return res;
}
}

剑指 Offer 59 - II. 队列的最大值

class MaxQueue {
Queue<Integer> queue;
Deque<Integer> deque; public MaxQueue() {
queue=new LinkedList<>();
deque=new LinkedList<>();
} public int max_value() {
if(deque.isEmpty())return -1;
else return deque.peekFirst();
} public void push_back(int value) {
queue.add(value);
while(!deque.isEmpty()&&deque.peekLast()<value){
deque.pollLast();
}
deque.addLast(value);
} public int pop_front() {
if(queue.isEmpty())return -1;
if(queue.peek().equals(deque.peekFirst())){
deque.pollFirst();
}
return queue.poll();
}
}

剑指 Offer 60. n个骰子的点数

public double[] twoSum(int n) {
int dp[][]=new int[n+1][6*n+1];//表示i个骰子掷出s点的次数
for(int i=1;i<=6;i++) {
dp[1][i]=1;//表示一个骰子掷出i点的次数为1
}
for(int i=2;i<=n;i++) {//表示骰子的个数
for(int s=i;s<=6*i;s++) {//表示可能会出现的点数之和
for(int j=1;j<=6;j++) {//表示当前这个骰子可能掷出的点数
if(s-j<i-1) {//当总数还没有j大时,就不存在这种情况
break;
}
dp[i][s]+=dp[i-1][s-j];//当前n个骰子出现的点数之和等于前一次出现的点数之和加上这一次出现的点数
}
}
}
double total = Math.pow((double)6,(double)n);//掷出n次点数出现的所有情况
double[] ans = new double[5*n+1];//因为最大就只会出现5*n+1中点数
for(int i=n;i<=6*n;i++){
ans[i-n]=((double)dp[n][i])/total;//第i小的点数出现的概率
}
return ans;
}

剑指 Offer 61. 扑克牌中的顺子

class Solution {
public boolean isStraight(int[] nums) {
Set<Integer> repeat = new HashSet<>();
int max = 0, min = 14;
for(int num : nums) {
if(num == 0) continue; // 跳过大小王
max = Math.max(max, num); // 最大牌
min = Math.min(min, num); // 最小牌
if(repeat.contains(num)) return false; // 若有重复,提前返回 false
repeat.add(num); // 添加此牌至 Set
}
return max - min < 5; // 最大牌 - 最小牌 < 5 则可构成顺子
}
}

剑指 Offer 62. 圆圈中最后剩下的数字

剑指offer二刷(精刷)

剑指offer二刷(精刷)

class Solution {
public int lastRemaining(int n, int m) {
int res=0;
for(int i=2;i<=n;i++){
res=(res+m)%i;
}
return res;
}
}

剑指 Offer 63. 股票的最大利润

class Solution {
public int maxProfit(int[] prices) {
int res_max=0;
int minp=Integer.MAX_VALUE;
for(int i=0;i<prices.length;i++){
if(prices[i]<minp)minp=prices[i];
res_max=Math.max(prices[i]-minp,res_max);
}
return res_max;
}
}

剑指 Offer 64. 求1+2+…+n

class Solution {
public int sumNums(int n) {
int res=n;
boolean flag= n>0&&(res+=sumNums(n-1))>0;
return res;
}
}

剑指 Offer 65. 不用加减乘除做加法

记住口诀:异或与和

class Solution {
public int add(int a, int b) {
while(b!=0){
int c=(a&b)<<1;//进一位和
a=(a^b);//非进位和
b=c;
}
return a;
}
}

剑指 Offer 66. 构建乘积数组

分成俩个数组进行求和left,right

class Solution {
public int[] constructArr(int[] a) {
int[] res=new int [a.length];
int left=1;
for(int i=0;i<res.length;i++){
res[i]=left;
left*=a[i];
}
int right=1;
for(int i=res.length-1;i>=0;i--){
res[i]*=right;
right*=a[i];
}
return res; }
}

剑指 Offer 67. 把字符串转换成整数

class Solution {
public int strToInt(String str) {
char[] cs = str.trim().toCharArray();//这个必须在前,否则" "输入在第二个if处会报错
if(cs.length==0) return 0;//字符串为空或字符串仅包含空白字符时,返回0;
int i = 0;
int flag = 1;//默认输出的数为正数
//确定是否存在符号位
if(cs[i]=='+') i++;
else if(cs[i]=='-') {//这里else不能少,否则"+"或者"+-"输入会报错
i++;
flag=-1;
}
long res= 0;//这里必须是long,否则后面的判断临界值时计算会越界,无法判断出正确的结果,如输入"-91283472332"时
while(i<cs.length){//尽量取更多的数字
if(cs[i]>'9'||cs[i]<'0') break;
if(flag==1&&res*10+flag*(cs[i]-'0')>Integer.MAX_VALUE) return Integer.MAX_VALUE;
if(flag==-1&&res*10+flag*(cs[i]-'0')<Integer.MIN_VALUE) return Integer.MIN_VALUE;
res = res*10+flag*(cs[i]-'0');
i++;
}
return (int)res;//这里强转不能省略;
}
}

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==null)return null;
if(root.val>p.val && root.val>q.val){
return lowestCommonAncestor(root.left,p,q);
}
else if(root.val < p.val && root.val < q.val){
return lowestCommonAncestor(root.right,p,q);
}
else return root;
}
}

剑指 Offer 68 - II. 二叉树的最近公共祖先

class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root==p||root==q)return root;
if(root==null)return null;
TreeNode left=lowestCommonAncestor(root.left,p,q);
TreeNode right=lowestCommonAncestor(root.right,p,q);
if(left!=null&&right!=null)return root;
else if(left!=null)return left;
else return right;
}
}