leetcode剑指offer

数组中重复的数字(哈希表/排序遍历/二分)

题目

分析

1. 哈希表:使用hashset.contains方法选重复的弹出
2. 排序遍历:数组排序之后遍历如果前后有相同的就弹出

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14

public static int findRepeatNumber(int[] nums) {
int res=0;
Set<Integer> set=new HashSet<>();
Arrays.sort(nums); //排序
for(int i=0;i<nums.length;i++){
if(set.contains(nums[i])){
return nums[i]; /如果出现过就弹出这个值
}else{
set.add(nums[i]); //没出现过就添加到set里面
}
}
return 0;
}

结果


二维数组中的查找(双指针/暴力法)

题目

分析

1. 暴力法: 二重循环遍历查找
2. 双指针:定义hang和列两个指针,然后依次判断和target大小(因为行和列都是递增的)
    2.1 如果matrix[hang][lie]==target 输出true
        如果matrix[hang][lie]<target 这列上面都小 所以换下一行hang++
        如果matrix[hang][lie]>target 这行后面的都大 所以换上一列   

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int[][] a=new int[][]{
{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}
};
int target=input.nextInt();
System.out.println(findNumberIn2DArray(a,target));
}

public static boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
int hang=0; //从第一行开始
int lie=matrix[0].length-1; //从最后一列开始
while(hang<matrix.length&&lie>=0){
if(matrix[hang][lie]==target){
return true; //找到了就返回true
}else if(matrix[hang][lie]>target){
lie--; //这行后面都大 所以换前一列lie--
}else if(matrix[hang][lie]<target){
hang++; //这列上面都小 所以换下一行hang++
}
}
return false; //默认找不到false
}

}

结果


替换空格(StringBuilder类)

题目

分析

1. StringBuilder类 : sb.append()方法添加,然后遇到空格就分别添加% 2 0
2. 新建char数组: 直接定义下标,然后遇到空格就按顺序填充% 2 0
3. 字符串替换 : s.replace(" ","%20"); 

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.nextLine(); //一定要用nextLine() 因为字符串有空格!!!!!!
System.out.println(replaceSpace(s)); //调用方法返回结果
}

public static String replaceSpace(String s) {
StringBuilder sb=new StringBuilder(); //新建sb对象
for(int i=0;i<s.length();i++){
char c=s.charAt(i); //获取字符串每个位置字符
if(c==' '){
sb.append('%'); //遇到空格就分别添加 % 2 0
sb.append('2');
sb.append('0');
}else{
sb.append(c); //不是空格就按序添加进去
}
}
return sb.toString(); //sb对象要转成字符串输出
}

}

结果


从尾到头打印链表(stack栈)

题目

分析

1. 新建栈存入所有链表元素
2. 然后依次出栈存入数组
3. 返回数组

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

//定义好的链表类
public class ListNode {
int val;
ListNode next;
ListNode(int x) {
val = x;
}
}


//实现类
public class LianBiao {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
ListNode head=new ListNode(1);
ListNode l2=new ListNode(3);
head.next=l2; // 1 -> 3
ListNode l3=new ListNode(2);
l2.next=l3; // 1 -> 3 -> 2
System.out.println(Arrays.toString(reversePrint(head)));
}

public static int[] reversePrint(ListNode head) {
//栈存储链表所有元素
Stack<ListNode> stack=new Stack<ListNode>();
//按需存储压栈
while(head!=null){
stack.push(head); //不断地压入栈
head=head.next;
}
//结果返回到int数组
int size=stack.size(); //获取stack栈的长度
int[] a=new int[size]; //后面才能确定长度
for (int i = 0; i < size ; i++) {
a[i]=stack.pop().val; //依次弹出
}
return a; //返回数组元素
}

}

结果


重建二叉树

题目

分析

代码

1
2


结果


实现队列(双栈)

题目

分析

1. 思路:使用双栈(stack1添加元素/stack2删除元素)
2. 具体实现步骤:
    2.1 生成两个栈stack(stack1用来实现添加元素/stack2用来实现删除元素)
    2.2 stack1只需要push
    2.3 stack2先将stack1压栈 然后stack2再出栈

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CQueue {
Deque<Integer> stack1; //创建两个stack栈
Deque<Integer> stack2;

public CQueue() {
stack1 = new LinkedList<Integer>();
stack2 = new LinkedList<Integer>();
}

public void appendTail(int value) {
stack1.push(value); //压栈
}

public int deleteHead() {
// 如果第二个栈为空
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop()); //把stack1压入stack2
}
}
if (stack2.isEmpty()) {
return -1; //stack2为空 返回-1
} else {
int deleteItem = stack2.pop();
return deleteItem; //stack2不为空 出栈
}
}
}

结果


斐波那契数列(动态规划/递归)

题目

分析

1. 递归: 从第二位开始都是fib(n)=fib(n-1)+fib(n-2)
2. 动态规划:
    2.1 每次都是abc三个数字不断替换迭代
    2.2 每次通过数组元素不断替换迭代 

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
System.out.println("递归:"+fib(n));
System.out.println("动态规划:"+fiber(n));
}

//递归
public static int fib(int n) {
if(n<=1){
return n; // 0 1
}
if(n==2){
return 1; // 0 1 1
}
return (fib(n-1)+fib(n-2))%1000000007;
}

//动态规划
public static int fiber(int n) {
int a=0; //第一个值
int b=1; //第二个值
int c=0; //第三个值
//不断迭代
for(int i=0;i<n;i++){
c=(a+b)%1000000007; //每次计算都要取余
a=b;
b=c;
}
return a;
}

}

结果


青蛙跳台阶(递归/动态规划)

题目

分析

1. 递归: 从第二位开始都是fib(n)=fib(n-1)+fib(n-2)
2. 动态规划:
    2.1 每次都是abc三个数字不断替换迭代
    2.2 每次通过数组元素不断替换迭代 

思路:

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
/*for (int i = 0; i < n; i++) {
a[i]=input.nextInt();
}*/
System.out.println("递归:"+numWays(n));
System.out.println("动态规划:"+numWayser(n));
}

public static int numWays(int n) {
if(n<=1){
return 1; // 1 1
}
if(n==2){
return 2; // 1 1 2
}
return (numWays(n-1)+numWays(n-2))%1000000007;
}

public static int numWayser(int n) {
int a=1; //跳1个台阶 1
int b=1; //跳1个台阶 1
int c=0; //跳2个台阶 2
//不断迭代
for(int i=0;i<n;i++){
c=(a+b)%1000000007; //每次计算都要取余
a=b;
b=c;
}
return a;
}

}

结果


旋转数组的最小数字(排序/二分法)

题目

分析

1. 排序:排序之后输出数组a[0]即可
2. 二分法:就是不断的缩小范围(通过确定缩小条件)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
int[] a=new int[n];
for (int i = 0; i < n; i++) {
a[i]=input.nextInt();
}
System.out.println(minArray(a));
}

//排序输出
public static int minArray(int[] numbers) {
Arrays.sort(numbers); //排序
return numbers[0]; //输出最小的第一位
}

//二分法
public static int minArray(int[] numbers) {
int i=0; //左指针
int j=numbers.length-1; //右指针
while(i<j){
int z=(i+j)/2; //找中间位置
if(numbers[z]>numbers[j]){
i=z+1; //min值在右边出现
}else if(numbers[z]<numbers[j]){
j=z; //min值在左边出现
}else {
j--; //无法确定范围 缩小范围继续找
}
}
return numbers[i];
}

}

结果


矩阵中的路径(DFS+剪枝)

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
char[][] a= new char[][]{ //定义好要找的路径集
{'A','B','C','E'},
{'S','F','C','S'},
{'A','D','E','E'}};
String word=input.next(); //输入要找的路径
System.out.println(exist(a,word)); //调用方法
}

public static boolean exist(char[][] board, String word) {
char[] words=word.toCharArray(); //字符串转字符数组
for(int i = 0; i < board.length; i++) {
for(int j = 0; j < board[0].length; j++) {
if(dfs(board, words, i, j, 0)) {
return true; //二重循环调用方法
}
}
}
return false; //默认失败
}

//board是要找的路径集合 words字符串转的字符数组 i和j是board下标 k是找到了几个
private static boolean dfs(char[][] board, char[] words, int i, int j, int k) {
if(i >= board.length || i < 0 || j >= board[0].length || j < 0 || board[i][j] != words[k]){
return false; // i和j越界 / 找不到符合
}
if(k == words.length - 1) { //已经全部找到了!!!
return true;
}
char temp=board[i][j]; //中间值
board[i][j]='/'; //用过之后改成 ‘/’
//上下左右都找找
boolean res=dfs(board, words, i + 1, j, k + 1) || dfs(board, words, i - 1, j, k + 1) ||
dfs(board, words, i, j + 1, k + 1) || dfs(board, words, i , j - 1, k + 1);
board[i][j]=temp; //用完之后再回溯返回原值
return res;
}

}

结果


机器人的运动范围

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int m=input.nextInt();
int n=input.nextInt();
int k=input.nextInt();
System.out.println(movingCount(m,n,k));
}

public static int movingCount(int m, int n, int k) {
int res=123;
return res;
}

}

结果


剪绳子(动态规划/归类)

题目

分析

尽量都凑出来3份

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int m=input.nextInt();
System.out.println(cuttingRope(m));
}

public static int cuttingRope(int n) {
if(n<=3){
return n-1;
}
int a=n/3;
int b=n%3;
if(b==0){
return (int)Math.pow(3,a);
}
if(b==1){
return (int)Math.pow(3,a-1)*4;
}
return (int)Math.pow(3,a)*2;
}

}

结果


剪绳子II(贪心/快速幂)

题目

分析

1. 与上面的剪绳子1.0相比需要取余,我们选择贪心算法
2. 具体实现:
    2.1 摆出n<4的每一个可能结果
    2.2 摆出n>4的while循环
    2.3 相比于1.0的题目我们其实还是不断的乘3,然后取余即可(n<=4跳出来)
    2.4 因为跳出来的n可能是4 3 2三种可能(不可能n最后为1),但是这三种情况其实也是直接乘n之后取余即可

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int m=input.nextInt();
System.out.println(cuttingRope(m));
}

public static int cuttingRope(int n) {
//把其他特定条件列出
if(n==2){
return 1; //分为1 1
}
if(n==3){
return 2; //分为1 2
}
if(n==4){
return 4; //分为2 2
}
if(n<4){
return n-1; //如果长度<4 返回 1 3
}
long res=1; //最终结果
int mod=1000000007;
while(n>4){
res=res*3; //不停地乘3
res=res%mod; //然后取余 (防止溢出)
n=n-3; //每次使用完减去3
}
res=res*n; //乘上剩下的n (最后剩下的肯定是4 3 2 这三种情况就直接乘n即可)
res=res%mod; //不要忘记取余
return (int)res; //返回最后res
}

}

结果


二进制中1的个数(位运算/按位判断)

题目

分析

1. 按位判断:就相当于十进制按位拆开,判断每一位是1就计数器+1
2. 位运算: n&(n−1) 二进制最右边的1变为0,其余不变。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int m=input.nextInt();
System.out.println(hammingWeight(m));
}

public static int hammingWeight(int n) {
int res=0; //接最后结果
while(n!=0){
res++;
n=n&(n-1); //消除数字n最右边的1
}
return res;
}

}

结果


删除链表的节点(跳过要删除的节点连接)

题目

分析

1. 其实就是考虑两件事情:定位和删除
2. 定位:设置一个临时temp=head,然后去判断head.val和val的大小直到找到。
3. 删除:我们考虑提前找head.next.val和val去判断,这样的话我找到就可以head.next=head.next.next,不然一般的删除,我都遍历到要删除的节点了,但是不知道怎将前面的节点找到!!!

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public class DelJie {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
ListNode head=new ListNode(4);
ListNode l2=new ListNode(5);
head.next=l2; // 4 -> 5
ListNode l3=new ListNode(1);
l2.next=l3; // 4 -> 5 -> 1
ListNode l4=new ListNode(2);
l3.next=l4; // 4 -> 5 -> 1 -> 9
int val=input.nextInt(); //输入要删除的节点
}

public static ListNode deleteNode(ListNode head, int val) {
if(head==null){ //如果链表为空返回null
return null;
}
if(head.val==val){ //如果当前节点就是要删除的
return head.next; //返回head.next就行
}
ListNode temp=head; //head节点给一个临时temp节点
while(temp.next!=null){
if(temp.next.val==val){ //如果下一个节点是要删除的节点
temp.next=temp.next.next; //直接这个节点的下一个节点连接下下一个节点(跳过要删除的节点)
}else{
temp=temp.next; //不是的话就继续下一个节点
}
}
return head; //返回原节点
}

}

结果


正则表达式匹配(动态规划)

题目

分析

主要是各种情况:

https://leetcode-cn.com/problems/zheng-ze-biao-da-shi-pi-pei-lcof/solution/zhu-xing-xiang-xi-jiang-jie-you-qian-ru-shen-by-je/

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next(); //输入s
String p=input.next(); //输入p
System.out.println(isMatch(s,p)); //调用方法
}

public static boolean isMatch(String s, String p) {
int n=s.length(); //获取s的长度
int m=p.length(); //获取p的长度
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){ //要判断的p字符串为空
f[i][j]=i==0;
}
//非空正则
else{
//分为 * 和 非*
if(p.charAt(j-1)!='*'){
//两个倒数第二位一样 p的倒数第二位还是.
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]; //直接砍掉p的后面两个
}
//看*之前的内容
if (i >= 1 && j >= 2 && (s.charAt(i - 1) == p.charAt(j - 2) || p.charAt(j - 2) == '.')) {
f[i][j] |= f[i - 1][j]; //s串前移一个
}
}
}

}
}
return f[n][m]; //返回当前位置结果
}

}

结果


表示数值的字符串/有效数字(分情况讨论)

题目

分析

1. 主要分为数字/./指数形式/+-开头/其他不符合条件情况
2. 具体实现:
    先设定numSeen,dotSeen和eSeen三种boolean变量,分别代表是否出现数字、点和E.然后遍历目标字符串
    1.判断是否属于数字的0~9区间
    2.遇到点的时候,判断前面是否有点或者E,都需要return false
    3.遇到E的时候,判断前面数字是否合理,是否有E,并把numSeen置为false,防止E后无数字
    4.遇到-+的时候,判断是否是第一个,如果不是第一个判断是否在E后面,都不满足则return false
    5.其他情况都为false
    6.最后返回numSeen的结果即可

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next();
System.out.println(isNumber(s));
}

public static boolean isNumber(String s) {
if(s==null||s.length()==0) {
return false; //字符串为空格
}
boolean numSeen=false; //出现数字
boolean dotSeen=false; //出现 .
boolean eSeen=false; //出现 e/E
char arr[]=s.trim().toCharArray(); //转字符数组(去除空格
for(int i=0; i<arr.length; i++){
if(arr[i]>='0'&&arr[i]<='9'){ //数字
numSeen=true; //更改为true
}
else
if(arr[i]=='.'){ // .
if(dotSeen||eSeen){ //在这个.之前有 . / e/E就false (举例: . . / e .)
return false; //在这个.之前有数字没啥影响( 举例:123. )
}
dotSeen=true; //更改为true
}
else
if(arr[i]=='E'||arr[i]=='e'){ // e/E
if(eSeen||!numSeen){
return false; //在这个之前没有数字 / . 就false
}
eSeen=true; //更改为true
numSeen=false; //设置成false防止e/E后面没有数字
}
else
if(arr[i]=='+'||arr[i]=='-'){ // + / -
if(i!=0&&arr[i-1]!='e'&&arr[i-1]!='E'){ // 不是第一位出现 / 前一个不是e/E
return false;
}
}
else{ //其他情况
return false; //其他情况都是false
}
}
return numSeen; //返回数字属性
}

}

结果


调整数组顺序奇数在偶数前面(二分法/分情况添加)

题目

分析

1. 二分法:定义两个指针,就是左边是偶数右边是奇数才可以交换,否则就左右指针靠近。
2. 分情况添加:新建数组或者stringbuilder然后奇数添加左边开始,偶数从最右边往左边添加。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
int[] a=new int[n];
for (int i = 0; i < a.length; i++) {
a[i]=input.nextInt();
}
System.out.println(Arrays.toString(exchange(a)));
}

public static int[] exchange(int[] nums) {
int i=0; //左指针
int j=nums.length-1; //右指针
int temp=0; //临时交换用
while(i<j) {
while (i < j && ((nums[i] % 2) != 0)) {
i++; //奇数往后移动
}
while (i < j && ((nums[j] % 2) == 0)) {
j--; //偶数往前移动
}
//左边是偶数 右边是奇数的话就交换
temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
return nums;
}

}

结果


链表中倒数第K个节点(两个链表)

题目

分析

1. 初始化两个指针former和latter指向头节点head
2. 然后先让前指针former前进k步(former在第k个位置,latter还在0的位置)
3. 两个指针然后继续走,直到former走结束,然后latter到了的位置就是我们要的位置,然后输出latter指针

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public class DaoShuK {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
ListNode head=new ListNode(4);
ListNode l2=new ListNode(5);
head.next=l2; // 4 -> 5
ListNode l3=new ListNode(1);
l2.next=l3; // 4 -> 5 -> 1
ListNode l4=new ListNode(2);
l3.next=l4; // 4 -> 5 -> 1 -> 9
int k=input.nextInt(); //输入要查看的倒数第k个节点
System.out.println(getKthFromEnd(head,k));
}

public static ListNode getKthFromEnd(ListNode head, int k) {
ListNode former = head, latter = head; //初始化两个指向头节点head的链表
// 假设5个数字 求倒数第二个(正数第四个) 5-2+1=4
for(int i = 0; i < k; i++) {
if(former == null) return null;
former = former.next; //先让former提前走k步
}
// former走了2步 现在在第二个数位置
while(former != null) {
former = former.next; //一起走了3步之后
latter = latter.next; //latter到了倒数第k个值得位置
}
return latter; //返回latter链表
}

}

结果


反转链表(栈/双指针)

题目

分析

1. 因为要反转我们可以使用先进后出的进行存储
2. 新建链表然后依次出栈即可

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

public class DaoShuK {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
ListNode head=new ListNode(4);
ListNode l2=new ListNode(5);
head.next=l2; // 4 -> 5
ListNode l3=new ListNode(1);
l2.next=l3; // 4 -> 5 -> 1
ListNode l4=new ListNode(2);
l3.next=l4; // 4 -> 5 -> 1 -> 9
System.out.println(reverseList(head));
}

public static ListNode reverseList(ListNode head) {
//新建栈存链表元素
Stack<Integer> stack = new Stack<Integer>();
//新建tmp链表用来将head存入栈
ListNode tmp = head;
while(tmp!=null){
stack.push(tmp.val); //压栈
tmp = tmp.next; //链表往后移
}
//因为已经用完了所以重新指向head
tmp = head;
while(tmp!=null){
tmp.val = stack.pop(); //出栈
tmp = tmp.next; //链表往后移动
}
return head; //因为之前把head位置的值改了就从head输出即可
}

}

结果


合并两个排序的链表(新建链表)

题目

分析

1. 新建链表l3存放结果,一开始要自定义一个值当做假的开始,等我们算出答案后return l3.next即可
2. 新建cur链表指向l3,然后通过判断l1和l2的val值确定插入到cur链表的顺序。
3. 肯定会有l1和l2长度不相等的情况,while之后要判断是否还有后续的要添加

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

public class DaoShuK {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
//第一个链表l1
ListNode l1=new ListNode(1);
ListNode l12=new ListNode(2);
l1.next=l12; // 1 -> 2
ListNode l13=new ListNode(4);
l12.next=l13; // 1 -> 2 -> 4
//第二个链表l2
ListNode l2=new ListNode(1);
ListNode l22=new ListNode(3);
l2.next=l22; // 1 -> 3
ListNode l23=new ListNode(4);
l22.next=l23; // 1 -> 3 -> 4
//调用方法返回结果
System.out.println(mergeTwoLists(l1,l2));
}

public static ListNode mergeTwoLists(ListNode l1, ListNode l2) {
if(l1==null){ //l1链表为空
return l2; //直接返回l2链表
}
if(l2==null){ //l2链表为空
return l1; //直接返回l1链表
}
ListNode l3=new ListNode(0); //前置伪头节点(只是为了连起来)
ListNode cur=l3;
while(l1.next!=null&&l2.next!=null){
if(l1.val>l2.val){
cur.next=l2;
l2=l2.next;
}else{
cur.next=l1;
l1=l1.next;
}
cur=cur.next;
}
//跳出while肯定是有一个为空
if(l1.next!=null){
cur.next=l1.next;
}else{
cur.next=l2.next;
}
return l3.next; //第一个位置是伪头节点
}

}

结果


树的子结构(递归求子结构)

题目

分析

参考思路:

https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/solution/tao-wa-ta-lai-liao-jing-pin-tu-jie-by-orangex/

具体实现总结:
1. isSubStructure(TreeNode A,B)用来判断是不是这个节点,找的话找isPartSame方法判断是不是这个部分,如果找不到就找当前节点的左节点和右节点是否符合
2. isPartSame方法如果找到了这个值,判断它的左节点和右节点的是不是符合(递归)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

/**
* 定义树类:
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public static boolean isSubStructure(TreeNode A, TreeNode B) {
// A/B结束了还没找到
if(A == null || B == null){
return false;
}
//是一部分
if(isPartSame(A,B)){
return true;
}else{
//不是一部分 去找左节点或者右节点
return isSubStructure(A.left,B)||isSubStructure(A.right,B);
}
}

//判断是不是存在的一部分!
public static boolean isPartSame(TreeNode A, TreeNode B) {
if(B==null){
return true; //已经找完了
}
if(A==null){
return false; //还没找完A没了
}
if(A.val==B.val){ //当前节点找到了 找下面的节点(递归)
return isPartSame(A.left,B.left) && isPartSame(A.right,B.right);
}else{
return false; //节点都不相同
}
}
}

结果


二叉树的镜像(递归/辅助栈)

题目

分析

1. 思路:就是递归不断地往下找,然后交换左右节点值

代码

1
2
3
4
5
6
7
8
9
10
11
12

//1.递归
public static TreeNode mirrorTree(TreeNode root) {
if(root==null){
return null; //如果root为空 返回空
}
TreeNode leftRoot = mirrorTree(root.right); //递归交换当前节点的右节点
TreeNode rightRoot = mirrorTree(root.left); //递归交换当前节点的左节点
root.left = leftRoot; //两个交换
root.right = rightRoot;
return root;
}

结果


对称的二叉树(递归)

题目

分析

1. 通过递归判断左右子树的左左/左右/右左/右右的对比
2. 特殊情况:
    2.1 没有两个子节点 -- true
    2.2 只有一个子节点 -- 肯定false(不对称)
    2.3 拥有两个子节点 --值不同肯定false
    2.4 拥有两个子节点 --就要递归判断左右子树的节点是否满足(递归)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public static boolean isSymmetric(TreeNode root) {
//方法调用
return isSymmetric(root,root);
}

public static boolean isSymmetric(TreeNode root1,TreeNode root2){
if(root1 == null && root2 == null){
return true; //空树
}
//只有一个子结点
if(root1 == null || root2 == null){
return false; //肯定不对称
}
//有两个子节点 但是值不相同
if(root1.val != root2.val){
return false;
}
//递归调用
return isSymmetric(root1.left, root2.right) && isSymmetric(root1.right,root2.left);
}

结果


顺时针打印矩阵(四指针)

题目

分析

1. 如果数组为空 --返回为null
2. 如果数组不为空
     2.1 建立ArrayList集合
     2.2 建立四个坐标用于标识数组变化
     2.3 从左往右(行:top    列:left到right)
     2.4 从上到下(行:top+1到bottom  列:right) 
     2.5 从右到左(行:bottom  列:right-1到left)  --但要注意可能最后是一行就不需要进行这一步
     2.6 从下到上(行:bottom-1到top  列:left)    --但要注意可能最后是一列就不需要进行这一步

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int[][] a=new int[][]{{1,2,3},{4,5,6},{7,8,9}};
System.out.println(Arrays.toString(spiralOrder(a)));
}

public static int[] spiralOrder(int [][] matrix) {
if(matrix.length == 0) {
return new int[0]; //数组空就弹出空数组
}
int row=matrix.length; //行
int col=matrix[0].length; //列
int left=0; //二维数组上面行的最左边
int right=col-1; //二维数组上面行的最右边
int top=0; //二维数组左边的最上边
int bottom=row-1; //二维数组左边的最下边
int[] a=new int[row*col];
int q=0; //控制结果数组下标
while(left<=right&&top<=bottom){
//从左到右
for(int i=left;i<=right;i++){
a[q++]=matrix[top][i]; //top行
}
//从上到下(从下一行开始向下走)
for(int i=top+1;i<=bottom;i++){
a[q++]=matrix[i][right]; //right列
}
//从右到左(从左边一列开始向左走)
if(top!=bottom) { //如果是一行的话就不需要返回去(已经左到右)
for (int i=right-1;i>=left;i--) {
a[q++]=matrix[bottom][i]; //bottom行
}
}
//从下到上(从上一行开始向上走)
if(left!=right) { //如果是一列的话就不需要返回去(已经上到下)
for (int i=bottom-1;i>top;i--) {
a[q++]=matrix[i][left]; //left列
}
}
//下一个正方形矩阵(往里缩小 -- 变换四个下标)
top++;
right--;
left++;
bottom--;
}
return a; //返回a数组
}

}

结果


包含main函数的栈(辅助栈)

题目

分析

1. 使用辅助栈然后调用方法进行解决

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

class MinStack {
Stack<Integer> A, B;
public MinStack() {
A = new Stack<>();
B = new Stack<>();
}
public void push(int x) {
A.add(x);
if(B.empty() || B.peek() >= x)
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();
}
}

结果


栈的压入、弹出序列(辅助栈)

题目

分析

1. 新建辅助栈:用来一边入栈一边出栈,然后查看最终栈是否为空

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int[] a=new int[]{1,2,3,4,5}; //压栈
int[] b=new int[]{4,5,3,2,1}; //压栈的一种出栈可能
System.out.println(validateStackSequences(a,b));
}

public static boolean validateStackSequences(int[] pushed, int[] popped) {
//生成新的栈(尝试模拟)
Stack<Integer> stack=new Stack<Integer>();
int count=0;
for(int i=0;i<pushed.length;i++){
stack.push(pushed[i]); //按照pushed数组入栈
while(!stack.isEmpty()&&stack.peek()==popped[count]){ //栈顶值==出栈的当前位置
stack.pop(); //出栈
count++; //后移(判断下一位出栈)
}
}
return stack.empty(); //是否所有都出栈(归0)
}

}

结果


从上到下打印二叉树(递归/bfs/辅助队列)

题目

分析

1. 队列queue: 每次用来存储树的节点(按照层次bfs的遍历方式)
2. 集合list: 每次存储当前节点的值
3. 数组res: 将list集合内容依次遍历

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

public class ErChaShu {
public static void main(String[] args) {
TreeNode root=new TreeNode(3); //根节点3
TreeNode r1=new TreeNode(9);
root.left=r1;
TreeNode r2=new TreeNode(20);
root.right=r2;
TreeNode r21=new TreeNode(15);
r2.left=r21;
TreeNode r22=new TreeNode(7);
r2.right=r22;
System.out.println(Arrays.toString(levelOrder(root)));
}

public static int[] levelOrder(TreeNode root) {
//空树
if(root==null){
return new int[0]; //返回空
}
//新建队列
Queue<TreeNode> queue = new LinkedList<>();
queue.add(root); //先将root放入
//新建list集合(存放读取顺序)
List<Integer> list=new ArrayList<>();
while(!queue.isEmpty()){ //当queue为空就弹出 (所有节点都取出)
TreeNode temp=queue.poll(); //弹出当前节点
list.add(temp.val); //list集合接住当前节点的值
if(temp.left != null)
queue.add(temp.left); //队列添加左节点
if(temp.right != null)
queue.add(temp.right); //队列添加右节点
}
//新建res数组用于存储结果
int[] res=new int[list.size()] ;
for (int i = 0; i < res.length; i++) {
res[i]=list.get(i); //遍历放入数组
}
return res;
}

}

结果


从上到下打印二叉树II(递归/队列)

题目

分析

1. 与I对比:只是输出时候每次一行行输出
2. 队列queue: 每次用来存储树的节点(按照层次bfs的遍历方式)
3. 集合res: 每层结束之后把集合temp存入res
4. 集合temp: 每次存储当前节点的值

5. 难点(问题)
    5.1 怎么判断是一层: 从root之后都每次后面会添加两个节点,所以用for循环判断temp集合存储几个(for循环结束之后将temp存入res)
    5.2 输出的问题:list集合先转数组然后数组转字符串输出

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

public class ErChaShu {
public static void main(String[] args) {
TreeNode root=new TreeNode(3); //根节点3
TreeNode r1=new TreeNode(9);
root.left=r1;
TreeNode r2=new TreeNode(20);
root.right=r2;
TreeNode r21=new TreeNode(15);
r2.left=r21;
TreeNode r22=new TreeNode(7);
r2.right=r22;
//返回list集合先转数组toArray() 然后Arrays.toString()转字符串输出
System.out.println(Arrays.toString(levelOrder(root).toArray()));
}

public static List<List<Integer>> levelOrder(TreeNode root) {
//空树
if(root==null){
return new ArrayList<>();
}
//新建队列存储每个节点(<>里面是treenode)
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root); //先存入root根节点
//存储最终结果res集合
List<List<Integer>> res=new ArrayList<>();
while(!queue.isEmpty()) { //最后队列是否为空(用完了)
List<Integer> temp = new ArrayList<>();
for (int i=queue.size();i>0;i--) {
TreeNode t = queue.poll(); //出队
temp.add(t.val); //将弹出的节点的值放入temp队列
if (t.left != null) {
queue.add(t.left); //刚才要弹出节点的左节点入队
}
if (t.right != null) {
queue.add(t.right); //刚才要弹出节点的左节点入队
}
}
res.add(temp); //每一层的temp加入到list最终集合
}

return res;
}

}

结果


从上到下打印二叉树III(递归/队列)

题目

分析

1. 与II的对比:奇数行从左到右输出,偶数行从右到左输出
2. 解法和II的对比不同只是在于最后temp存入到res时需要判断奇偶数
    //奇数行(第一行flag=1 第三行flag=3)
        if(flag%2==1){
           res.add(temp); //每行结果添加到res集合
        }
    //偶数行(第二行flag=2 第四行flag=4)
         if(flag%2==0){
           List<Integer> ou = new ArrayList<>(); //新建集合存储
           for(int i=temp.size()-1;i>=0;i--){
              ou.add(temp.get(i));  //倒序放入新集合
            }
              res.add(ou); //每行结果添加到res集合
         }

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

public class ErChaShu {
public static void main(String[] args) {
TreeNode root=new TreeNode(3); //根节点3
TreeNode r1=new TreeNode(9);
root.left=r1;
TreeNode r2=new TreeNode(20);
root.right=r2;
TreeNode r21=new TreeNode(15);
r2.left=r21;
TreeNode r22=new TreeNode(7);
r2.right=r22;
//返回list集合先转数组toArray() 然后Arrays.toString()转字符串输出
System.out.println(Arrays.toString(levelOrder(root).toArray()));
}

public static List<List<Integer>> levelOrder(TreeNode root) {
//空树
if(root==null){
return new ArrayList<>();
}
//新建队列(存储节点)
Queue<TreeNode> queue=new LinkedList<>();
queue.add(root); //先存入root根节点
//新建嵌套list集合存储最终结果
List<List<Integer>> res=new ArrayList<>();
//新建计数器(奇偶数)
int flag=0;
//直到队列queue为空结束
while(!queue.isEmpty()) {
List<Integer> temp = new ArrayList<>(); //存储每行的结果
//每次准备出队的时候+1(每次开始肯定是一行的内容进行循环)
flag++;
// 每次出队时候里面存的都是一行的节点
for (int i=queue.size();i>0;i--) {
TreeNode t = queue.poll(); //出队
temp.add(t.val); //存储出队结果值
if (t.left != null) {
queue.add(t.left); //队列添加左节点
}
if (t.right != null) {
queue.add(t.right); //队列添加右节点
}
}
//奇数行(第一行flag=1 第三行flag=3)
if(flag%2==1){
res.add(temp); //每行结果添加到res集合
}
//偶数行(第二行flag=2 第四行flag=4)
if(flag%2==0){
List<Integer> ou = new ArrayList<>();
for(int i=temp.size()-1;i>=0;i--){
ou.add(temp.get(i));
}
res.add(ou); //每行结果添加到res集合
}

}
return res;
}

}

结果


二叉搜索树的后序遍历序列(递归分治)

题目

分析

1. 二叉搜索树: 
    1.1 左子树中所有节点的值<根节点的值<右子树中所有节点的值
    1.2 左、右子树也分别为二叉搜索树

具体实现:

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

public class ErChaShu {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
int[] a=new int[n];
for (int i = 0; i < n; i++) {
a[i]=input.nextInt();
}
System.out.println(verifyPostorder(a));
}

public static boolean verifyPostorder(int[] postorder) {
return recur(postorder, 0, postorder.length - 1); // 调用方法( 数组postorder i j )
}

public static boolean recur(int[] postorder, int i, int j) {
if(i >= j){
return true; //子树节点<=1 肯定true
}

//找左子树
int chang = i;
while(postorder[chang] < postorder[j]){ //左边肯定比根节点小(第一个大的数字就是右节点的开始位置)
chang++;
}

//找右子树
int zhong = chang; //找到第一个 > postorder[j]的值就是右子树的开始位置
while(postorder[chang] > postorder[j]) { //右边肯定都比根节点大
chang++;
}

//判断长度是否相等 而且左右子树也是二叉搜索树(递归)
return chang==j&&recur(postorder,i,zhong-1)&&recur(postorder,zhong,j-1); //j位置是左右根的根
}

}

结果


二叉树中和为某一值的路径(回溯/dfs深度优先遍历)

题目

分析

1. 定义path集合用来走路径,合适的话就加入到最终的res集合
2. 思路:
    2.1 path添加进入当前节点的值
    2.2 然后更新target目标值
    2.3 判断如果遍历到没有左右子节点而且target成功了就弹出
    2.4 左节点递归
    2.5 右节点递归
    2.6 回溯(最后一位移除)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

public class LuJing {
private ArrayList<ArrayList<Integer>> paths = new ArrayList<ArrayList<Integer>>();
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
TreeNode root=new TreeNode(5); //根节点3
TreeNode r1=new TreeNode(4);
root.left=r1;
TreeNode r2=new TreeNode(8);
root.right=r2;
TreeNode r21=new TreeNode(11);
r2.left=r21;
TreeNode r22=new TreeNode(13);
r2.right=r22;
int sum=input.nextInt();
//返回list集合先转数组toArray() 然后Arrays.toString()转字符串输出
System.out.println(pathSum(root,sum));
}

private Stack<Integer> path = new Stack<Integer>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root == null){
return paths; //如果空树 返回默认空paths集合
}
path.push(root.val); //存入当前节点值
target = target -root.val; //更新剩下的target
if(target == 0 && root.left == null && root.right == null){ //如果target找到了 而且到最底层(叶子节点)
paths.add(new ArrayList<Integer>(path)); //path放入paths集合
}
FindPath(root.left, target); //左递归
FindPath(root.right, target); //右递归
path.pop(); //回溯 弹出最后一个点往上返
return paths; //返回paths集合
}

}

结果


复杂链表的复制(HashMap集合)

题目

分析

1. 首先,构造一个Map<Node,Node>,前一个是原链表的指针,后一个是新链表的指针
2. 将他们用HashMap关联起来
3. 接着遍历原链表,查找到关联的新链表节点,在对其赋值
4. 用原链表的next节点找到新链表的next节点
5. 用原链表的random找到新链表的random

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

// 定义节点类
class Node {
int val;
Node next;
Node random;

public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}

//具体实现方法
public static Node copyRandomList(Node head) {
//创建HashMap集合
HashMap<Node,Node> map = new HashMap<>();
Node cur=head; //新建节点指向head
//复制节点值
while(cur!=null){
//存储put:<key,value1>
map.put(cur,new Node(cur.val)); //顺序遍历
cur=cur.next;
}
//复制节点指向
cur=head; //上面已经动过了重新指向head
while(cur!=null){
//得到get:<key>.value2,3
map.get(cur).next=map.get(cur.next);
map.get(cur).random=map.get(cur.random);
cur=cur.next;
}
//返回复制的链表
return map.get(head);
}

结果


二叉搜索树与双向链表(中序遍历)

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

class Solution {
Node pre, head;
public Node treeToDoublyList(Node root) {
//判断空树
if(root == null) return null;
//中序遍历 左根右(递增)
dfs(root);
//处理头尾节点(相互指向)
head.left = pre;
pre.right = head;
return head;
}
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);
}
}

结果


序列化二叉树

题目

分析

代码

1
2


结果


字符串的排列(回溯)

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

public ArrayList<String> Permutation(String str) {
ArrayList<String> ans=new ArrayList<>();//所有排列的可能都在这
if(str!=null||str.length()>0){ //str真实存在
help(0,str.toCharArray(),ans);
Collections.sort(ans); //调用方法排序
}
return ans;
}

public static void help(int i,char[] cha,ArrayList<String> ans){
if(i==cha.length-1){ //找到最后位置
String val=String.valueOf(cha); //转字符串
if(!ans.contains(val)){
ans.add(val); //答案里面没有就添加到ans
}
}else{
for(int j=i;j<cha.length;j++){
swap(i,j,cha);//依次选一个数固定住
help(i+1,cha,ans);//让后面的进行全排列
swap(i,j,cha);//恢复原来的模样,回溯关键

}
}
}

//两者交换
public static void swap(int i,int j,char[] cha){
char temp=cha[i];
cha[i]=cha[j];
cha[j]=temp;
}

}

结果


数组中出现次数超过一半的数字(摩尔投票法/数组排序/map集合)

题目

分析

摩尔投票法分析:

核心:一定会有sum>0成立!!!

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

//1.摩尔投票法
public static int majorityElement(int[] nums) {
int votes=0; //投票和
int x=0; //众数
for(int i=0;i<nums.length;i++){
if(votes==0){
x=nums[i]; //如果当前位置votes=0 众数换当前值
}
//判断当前值是不是众数
if(nums[i]==x){
votes++; //是众数,投票数加一
}else{
votes--; //不是众数,投票数减一
}
}
return x; //返回众数
}


//2.排序之后取中位数
public int majorityElement(int[] nums) {
Arrays.sort(nums); //对数组排序
return nums[nums.length/2]; //其他数字只出现一次 所以超过一半的数字肯定是中间位置开始
}

//3.map集合存储次数 然后找长度超过一半的数组值
public int MoreThanHalfNum_Solution(int [] array) {
if(array.length==0){
return 0;
}
int flag=0;
Map<Integer,Integer> map=new HashMap<>();
for(int i=0;i<array.length;i++){
int c=array[i];
map.put(c,map.containsKey(c)?map.get(c)+1:1);
}
for(int i=0;i<array.length;i++){
int c=array[i];
if(map.get(c).intValue()>=array.length/2+1){
flag=c;
}
}
return flag;
}

结果


最小的k个数(排序法)

题目

分析

1. 使用排序之后将前k个值赋给新的数组即可

代码

1
2
3
4
5
6
7
8
9
10

//1.排序之后将前k个值赋给新的数组
public int[] getLeastNumbers(int[] arr, int k) {
Arrays.sort(arr);
int[] a=new int[k];
for(int i=0;i<k;i++){
a[i]=arr[i];
}
return a;
}

结果


数据流中的中位数

题目

分析

代码

1
2


结果


连续子数组的最大和(动态规划)

题目

分析

1. 新建dp数组存放每个nums[i]值时候的最大和
2. 新建res每次dp[i]确定值之后进行比较,不断取出dp里面最大值(或者可以dp最后排序取出最后一位即可)
3. 思路:
    3.1 res和dp[0]都必须先默认是第一位(不然报错)
    3.2从第二位开始遍历
    3.3 如果dp[i-1]<0说明对于后面的和没有作用,不如dp[i]=nums[i];
    3.4 反之有加强作用,因此dp[i]=dp[i-1]+nums[i];
    3.5 每次循环结束前进行res和dp[i]取最大

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//动态规划
public static int maxSubArray(int[] nums) {
//接答案(默认第一个值就是最大的和)
int res=nums[0];
//记录每个位置能取得最大值
int[] dp=new int[nums.length];
dp[0]=nums[0]; //初始最大值是第一个值
//依次遍历
for (int i=1;i<nums.length;i++) { //从第二个位置开始
if(dp[i-1]>0){ //如果前面的和大于0
dp[i]=dp[i-1]+nums[i]; //就加上当前值更大
}else{ //前面的和小于0 那我还不如当前值为和
dp[i]=nums[i]; //更换为当前值
}
res=Math.max(res,dp[i]); //每次把当前值的dp[i]和最大值比较更新res
}
return res; //res最后就是dp数组最大值
}

结果


1-n中整数中1出现的次数(拆除法)

题目

分析

1. 按位拆除判断(超时):for循环让每个数组判断,进入方法拆除每一位判断是否为1计数,最终求和
2. 数学归纳法:
    1. 对于百位数>=2和百位数字==0这两种情况即(a+8)/10*100
    2. 对于百位数字==1情况可以用(a+8)10*100+(b+1)

https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/xiang-jie-gui-lu-yong-shi-0ms-by-sircarol/

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

//1. 按位拆除判断(超时)
public static int countDigitOne(int n) {
int res=1;
if(n==0){ //0的话出现0次
res=0;
}
if(n==1){ //1的话出现1次
res=1;
}
for(int i=2;i<=n;i++){ //从2开始
res+=jige(i);
}
return res;
}
//统计1的次数
public static int jige(int i){
int sum=0;
while(i!=0){ //缩小范围到0
if(i%10==1){
sum++; //出现1的时候计数器+1
}
i=i/10; //不断缩小
}
return sum;
}


//2. 数学归纳法
public int countDigitOne(int n) {
int res = 0;
for (long m = 1; m <= n; m *= 10) {
long a = n / m;
long b = n % m;
res += (a + 8) / 10 * m + (a % 10 == 1 ? b + 1 : 0);
}
return res;
}

结果


数字序列中某一位的数字(数学归纳)

题目

分析

1. 第一个while用来判断多少个
2. 用char数组存放结果,然后不断地取出到哪一位

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public static int findNthDigit(int n) {
int res=0;
if(n<10){
return n; //前10位是按照顺序0123456789
}
int i=1;
while(n>i*(Math.pow(10,i-1)*9)){ // 9(10个) 99(90个) 999(900个)
n-=i*Math.pow(10,i-1)*9;
i++;
}
char[] result=String.valueOf((int)Math.pow(10,i-1)+(n-1)/i).toCharArray();//我们用字符串来接收值,方便找位数 result结果为我们要的那个数的
int value=result[(n-1)%i]-'0'; //(n-1)%位数 得出我们要的第x位的数
return value;
}

结果


把数字翻译成字符串(动态规划)

题目

分析

代码

1
2


结果


礼物的最大价值(回溯)

题目

分析

1.思路:其实就是典型的回溯算法解决。    

代码

1
2


结果


最长不含重复字符的子字符串(动态规划)

题目

分析

1. 动态规划法:循环遍历确定start和end两个位置,就可以确定最终的长度由ans接住。
2. 具体实现:
    2.1 新建map集合存放字符和数字出现几率
    2.2 如果map里面有当前位置的字符就让start取当前位置字符出现次数+1的最大值,然后更新ans
    2.3 循环结束前更新当前位置字符和出现位置end

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

class Solution {
public int lengthOfLongestSubstring(String s) {
int n = s.length();
int ans = 0;
Map<Character, Integer> map = new HashMap<>();//key出现的字符,value对应的最新的位置
// try to extend the range [i, j]
int start=0;
for (int end = 0; end < n; end++) {
if (map.containsKey(s.charAt(end))) {
start = Math.max(map.get(s.charAt(end)) + 1, start);//由于重复的坐标不知道在start的前方还是后方,所以要取个最大值
}
ans = Math.max(ans, end - start + 1); //更新最大长度
map.put(s.charAt(end), end); //存放的字符对应位置更新
}
return ans;
}
}

结果


丑数(三指针)

题目

分析

1. 分析丑数:从第一位1之后都是2 4 6 8 10加上3 6 9 12以及5 10 15 20等等
2. 解决方案:通过dp数组存放结果,然后不断求2和3和5之间倍数和交叉和的结果。
3. 举例:
    求第二位的丑数: p2 p3 p5都为0   dp[1]=min(2*dp[0] 3**dp[0] 5*dp[0])=2 p2=1(递增)
    求第三位的丑数: p2=1 p3=0 p5=0  dp[2]=min(2*dp[1] 3**dp[0] 5*dp[0])=3 p3=1(递增)
    求第四位的丑数: p2=1 p3=1 p5=0  dp[3]=min(2*dp[1] 3**dp[1] 5*dp[0])=5 p5=1(递增)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
System.out.println(nthUglyNumber(n)); //调用方法
}

public static int nthUglyNumber(int n) {
int p2=0; //只乘2
int p3=0; //只乘3
int p5=0; //只乘5
int[] dp=new int[n]; //存放所有的丑数
dp[0]=1; //第一个丑数是1
for(int i=1;i<n;i++){
dp[i]=Math.min(dp[p2]*2,Math.min(dp[p3]*3,dp[p5]*5)); //从第二位开始求当前位置丑数
//看是哪个指针走了就跳后面
if(dp[i]==dp[p2]*2){
p2++;
}
if(dp[i]==dp[p3]*3){
p3++;
}
if(dp[i]==dp[p5]*5){
p5++;
}
}
return dp[n-1]; //最后返回n-1结果
}

}

结果


第一个只出现一次的字符(计数遍历/hashmap存储出现次数)

题目

分析

//第一种计数遍历
    1. 第一次遍历将所有字符的出现次数记录
    2. 第二次遍历找到只出现1次的字符然后就弹出即可(找第一个只出现一次的)

//第二种hashmap存储
    1. 遍历存储每个字符出现次数
    2. 第二次遍历找到次数res.get(str.charAt(i)).intvalue()是1的就返回然后break停止遍历

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

public class Main {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next();
System.out.println(firstUniqChar(s));
}

//第一种:计数遍历
public static char firstUniqChar(String s) {
//定义计数器 记录26个字母出现次数
int[] count=new int[26];
//存取最终结果
char c=' '; //默认找不到返回空字符

//遍历数组 计数
for(int i=0;i<s.length();i++){
count[s.charAt(i)-97]++; //字符数组存的ascii码
}

//遍历数组找到出现了一次的字符
for(int i=0;i<s.length();i++){
if(count[s.charAt(i)-97]==1){ //出现了一次
c=s.charAt(i); //结果赋给c
break; //弹出(第一次出现)
}
}
return c;
}


//第二种:hashmap集合存储
public int FirstNotRepeatingChar(String str) {
if(str.length()==0){
return -1; //如果字符串为0 返回-1找不到
}
int flag=-1; //接住最后结果
Map<Character,Integer> res=new HashMap<>(); //记录每个字符出现次数
for(int i=0;i<str.length();i++){
char c=str.charAt(i); //获取每个位置的字符
res.put(c,res.containsKey(c)?res.get(c)+1:1); //map集合存储次数
}
for(int i=0;i<str.length();i++){
char c=str.charAt(i);
if(res.get(c).intValue()==1){ //第一个出现次数是1的字符就输出 break结束
flag=i;
break ;
}
}
return flag; //返回flag结果
}

}

结果


数组中的逆序列

题目

分析

代码

1
2


结果


两个链表的第一个公共节点(双指针)

题目

分析

参考图即可:

https://leetcode-cn.com/problems/liang-ge-lian-biao-de-di-yi-ge-gong-gong-jie-dian-lcof/solution/shuang-zhi-zhen-fa-lang-man-xiang-yu-by-ml-zimingm/

1. 其实就是两个节点不断往后推,然后两个到最后的时候交换然后遍历,直到两个节点到同一个节点上输出即可(pA==pB)。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null; //两个不存在
}
ListNode pA = headA; //pA指向headA节点
ListNode pB = headB; //pB指向headB节点
//循环判断
while(pA != pB){
if (pA == null) {
pA=headB;
}else{
pA=pA.next; //没空就一直往后推
}
if (pB == null) {
pB=headA;
}else{
pB=pB.next; //没空就一直往后推
}
}
return pA;
}

结果


二叉搜索树的第k大节点(后序遍历递减)

题目

分析

1. 二叉搜索树中序遍历递增 所以选择递减的后序遍历
2. 然后递归一次计数器加一次当等于k时候就是到了,然后返回当前节点的值

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

int count=0,num=0; //全局变量

public int kthLargest(TreeNode root, int k) {
helper(root,k); //调用方法
return num;
}

public void helper(TreeNode root,int k){
if(root==null){ //如果为空返回空
return;
}
helper(root.right,k); //右
count++; //计数器增加
if(count==k){ //如果计数器的和k位置一样了赋值节点值给num
num=root.val; //根
return;
}
helper(root.left,k); //左
}

结果


二叉搜索树的深度(递归DFS)

题目

分析

1. 树深度=Math.max(左子树深度,右子树深)+1
2. 所以我们使用递归不断求每个子树的深度,然后取最大值+1

代码

1
2
3
4
5
6
7
8

public int maxDepth(TreeNode root) {
if(root==null){
return 0; //左右节点为空 所以长度是0
}
//返回当前节点的深度就是 max(左深度,右深度)+1
return Math.max(maxDepth(root.left),maxDepth(root.right))+1;
}

结果


平衡二叉树(求深度题目基础上增加判断)

题目

分析

1. 树深度=Math.max(左子树深度,右子树深)+1
2. 所以我们使用递归不断求每个子树的深度,然后取最大值+1
3. 在第二步基础上如果不符合<=1的话就方法赋-1 然后主方法输出false

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

public boolean isBalanced(TreeNode root) {
return recur(root) != -1; //判断是不是-1
}

//求二叉搜索树的深度
private int recur(TreeNode root) {
if (root == null) { // 左/右子树为空
return 0;
}
int left = recur(root.left); //获取左子树长度
if(left == -1) return -1;
int right = recur(root.right); //获取右子树长度
if(right == -1) return -1;
return Math.abs(left - right) < 2 ? Math.max(left, right) + 1 : -1; //如果异常就-1
}

结果


数组中数字出现的次数I(哈希表)

题目

分析

1. 使用hashset不能存放重复的机制
2. 如果第二次出现了就让set移除出去,然后
3. 剩下的就是只出现一次

代码

1
2
3
4
5
6
7
8
9
10
11

public static Object[] singleNumberser(int[] nums) {
int[] a=new int[nums.length];
Set<Integer> set=new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if(!set.add(nums[i])){
set.remove(nums[i]); //出现第二次的就挪出去
}
}
return set.toArray(); //转数组
}

结果


数组中数字出现的次数II(哈希表)

题目

分析

1. 使用hashmap存放每个数字出现次数,然后遍历找value==1的key即可

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

public static int singleNumber(int[] nums) {
int res=0; //最终结果
//记录每个数字出现次数
HashMap<Integer,Integer> map=new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if(map.containsKey(nums[i])){
map.put(nums[i], map.get(nums[i]) + 1); //递增value
}else{
map.put(nums[i],1); //没出现过value为1
}
}
for(int i=0;i<nums.length;i++){
if(map.get(nums[i]).intValue()==1){ //找value是1的key
res=nums[i]; //res接住
}
}
return res; //接住结果
}

结果


和为s的两个数字(双指针)

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] a = new int[]{10,26,30,31,47,60};
int target=input.nextInt();
System.out.println(Arrays.toString(twoSum(a,target)));
}

public static int[] twoSum(int[] nums, int target) {
int i = 0, j = nums.length - 1; //定义两个指针
while(i < j) {
int s = nums[i] + nums[j]; //求和
if(s < target){
i++; //太小了往后挪
}
else if(s > target){
j--; //太大了往前挪
}
else return new int[]{nums[i], nums[j]}; //找到了就返回这两个值
}
return new int[0]; //默认返回空
}

}

结果


和为s的连续正数序列(滑动窗口)

题目

分析

不断地使用滑动窗口取结果集

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int target=input.nextInt();
System.out.println(findContinuousSequence(target));
}

public static int[][] findContinuousSequence(int target) {
int i = 1; // 滑动窗口的左边界
int j = 1; // 滑动窗口的右边界
int sum = 0; // 滑动窗口中数字的和
//res集合接收结果
List<int[]> res = new ArrayList<>();
while(i<=target/2){ //只能到一半的位置
if(sum<target){
sum+=j; //太小了 往右移动找新的
j++;
}else if(sum>target){
sum-=i; //太大了 往右移动把最左边的删除
i++;
}else{ //找到了结果集
int[] arr=new int[j-i]; //长度是j-i
for(int k=i;k<j;k++){
arr[k-i]=k; //要把i和j开始遍历放入数组
}
//将每一个结果数组放入res集合
res.add(arr);
//左边界限向右移动(找到了i就要往后挪动)
sum-=i; //sum要减去i
i++;
}
}
return res.toArray(new int[res.size()][]); //集合转数组
}

}

结果


翻转单词顺序(空格分开)

题目

分析

1. 将s去掉两边空格然后以空格隔开得到单词集合strs数组[s.trim().split(" ")]
2. 新建res对象接住结果
3. 从后面遍历每一个单词添加进来然后转数组

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String s=input.nextLine(); //nextLine()输入格式
System.out.println(reverseWords(s));
}

public static String reverseWords(String s) {
if(s.length()==0){
return ""; //输入空字符串
}
String[] strs = s.trim().split(" "); // 删除首尾空格,分割字符串
if(strs.length==1){ //输入两个空格 数组长度就是1
return str;
}
StringBuilder res = new StringBuilder();
for(int i = strs.length - 1; i >= 0; i--) { // 倒序遍历单词列表
if(strs[i].equals("")){
continue; // 遇到空单词则跳过
}
res.append(strs[i]+" "); // 将单词拼接至 StringBuilder
}
return res.toString().trim(); // 转化为字符串,删除尾部空格,并返回
}

}

结果


左旋转字符串(遍历)

题目

分析

1. 新建stringbuilder对象然后将n之后的添加然后添加n之前的
2. 可以考虑substring()方法添加

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String s=input.next();
int n=input.nextInt();
System.out.println(reverseLeftWords(s,n));
}

public static String reverseLeftWords(String s, int n) {
StringBuilder res=new StringBuilder();
//将n后面的遍历加入
for(int i=n;i<s.length();i++){
res.append(s.charAt(i));
}
//将n之前的遍历加入
for(int i=0;i<n;i++){
res.append(s.charAt(i));
}
return res.toString(); //转字符串
}

}

结果


滑动窗口的最大值(双指针移动)

题目

分析

1. 其实就是不断的挪动窗口取每次的最大值
2. 实现步骤:
    2.1 判断特殊情况:窗口为0就返回0  窗口为1就返回原数组(每一位就是最大值)
    2.2 判断一般情况:
        2.2.1 定义数组a接住结果
        2.2.2 定义每次滑动窗口的left和right指针
        2.2.3 每次都调用maxer方法去找到最大值(maxer方法要有左右指针位置和nums数组)
        2.2.4 每次结束之后将left和right往后移动一位
        2.2.5 最后输出结果        

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

public ArrayList<Integer> maxInWindows(int [] num, int size)
{
int len = num.length - size + 1; //假如数组长度6 要找框为3 只需要4次
ArrayList<Integer> res = new ArrayList<>();
if(size == 0){
return res; //框为0就返回res集合
}
for(int i = 0; i < len; i++)
{
int max = num[i];
for(int j=i; j<i+size;j++)
{
if(num[j]>max)
max = num[j];
}
res.add(max);
}
return res;
}

结果


队列的最大值(链表)

题目

分析

https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof/solution/mian-shi-ti-59-ii-javashi-xian-yuan-li-he-mian-shi/

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

public class MaxQueue {

Queue<Integer> queue;
LinkedList<Integer> max;
public MaxQueue() {
queue = new LinkedList<>();
max = new LinkedList<>();//LinkedList是双端链表
}

public int max_value() {
return max.size()==0?-1:max.getFirst();
}

public void push_back(int value) {
queue.add(value);
while(max.size()!=0&&max.getLast()<value){//注意:这里第二个判断条件不能带等号,即max中对于当前queue中的具有相同值的元素会全部存储,而不是存储最近的那个。
max.removeLast();
}
max.add(value);
}

public int pop_front() {
if(max.size()!=0&&queue.peek().equals(max.getFirst()))//Integer类型的值的比较不能直接使用==
max.removeFirst();
return queue.size()==0?-1:queue.poll();
}

}

结果


n个骰子的点数(动态规划)

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

public static double[] twoSum(int n) {
if(n==0) {
return new double[0]; //n个筛子
}
double[] dp=new double[6*n+1];
double[] ans=new double[5*n+1];
double all=Math.pow(6,n);
for(int i=1;i<=6;i++){
dp[i]=1;
ans[i-1]=1.0/6;
}
for(int i=2;i<=n;i++){
for(int j=6*n;j>=1;j--){
int temp=0;
for(int k=1;k<=6;k++){
temp+=(j>=k)?dp[j-k]:0;
}
dp[j]=temp;
if(i==n && j>=n){
ans[j-i]=dp[j]/all;
}
}
}
return ans;
}

结果


扑克牌中的顺子(Set集合)

题目

分析

1. 除大小王外,所有牌没有重复
2. 最大和最小的牌:max-min<5

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

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 则可构成顺子
}

结果


圆圈中最后剩下的数字(约瑟夫杀人环list集合)

题目

分析

https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/javajie-jue-yue-se-fu-huan-wen-ti-gao-su-ni-wei-sh/

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n=input.nextInt();
int m=input.nextInt();
System.out.println(lastRemaining(n,m));
}

public static int lastRemaining(int n, int m) {
//新建list集合
ArrayList<Integer> list = new ArrayList<>();
//将所有元素都加入list集合
for (int i=0;i<n;i++){
list.add(i);
}
//定义下标--确定每次删除的位置
int i=0;
//循环删除
while(n>1){
i=(i+m-1)%n;
list.remove(i); //list删除
n--; //总数减少1个
}
return list.get(0); //剩下的唯一一个数字
}

}

结果


股票的最大利润(动态规划)

题目

分析

1. 新建dp数组存放每个位置的利润
2. 不断的找min最小值,然后取当前位置的利润

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] a=new int[]{7,1,5,3,6,4};
System.out.println(maxProfit(a));
}

public static int maxProfit(int[] prices) {
if(prices.length==0){
return 0; //数组长为0
}
int min=prices[0]; //购买的日子
int[] dp=new int[prices.length]; //存每个位置的利润
for (int i=1;i<prices.length;i++) {
min=Math.min(min,prices[i-1]); //找最小值
dp[i]=Math.max(dp[i-1],prices[i]-min); //每个位置的利润
}
return dp[prices.length-1];
}

}

结果


求1+2+..+n的和(归纳)

题目

分析

1. 递归
2. 迭代
3. 数学归纳法

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n=input.nextInt();
System.out.println(sumNums(n));
}

public static int sumNums(int n) {
int res=0;
res=((n+1)*n)/2;
return res;
}

}

结果


不用加减乘除做加法(位运算)

题目

分析

https://leetcode-cn.com/problems/bu-yong-jia-jian-cheng-chu-zuo-jia-fa-lcof/solution/jin-zhi-tao-wa-ru-he-yong-wei-yun-suan-wan-cheng-j/

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n=input.nextInt();
int m=input.nextInt();
System.out.println(add(n,m));
}

public static int add(int a, int b) {
while(b!=0){
int tempSum=a^b; //求不考虑进位的相加和
int carrySum=(a&b)<<1; //进位的和
//不断循环迭代 直到没有进位(b==0)
a=tempSum;
b=carrySum;
}
return a;
}

}

结果


构建乘积数组(不用除法就分区)

题目

分析

1. 因为不能用除法,所以分左下角和右上角计算(对角线是那个位置)    

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int[] a=new int[]{1,2,3,4,5};
System.out.println(Arrays.toString(constructArr(a)));
}

public static int[] constructArr(int[] a) {
if(a.length==0){
return new int[0]; //返回0
}
int[] b=new int[a.length];
b[0]=1;
int temp=1;
//计算左下角
for(int i=1;i<a.length;i++){ //从上到下
b[i]=b[i-1]*a[i-1];
}
//计算右上角
for(int i=a.length-2;i>=0;i--){ //从下往上
temp=temp*a[i+1];
b[i]=b[i]*temp;
}

return b;
}

}

结果


字符串转整数(考虑成分)

题目

分析

1. 组成四个部分:  空格+正负号+数字+非数字部分
2. 空格: 直接跳过(i下标++)
3. 正负号:使用布尔变量标识(i下标++)
4. 数字部分:进行10进制的位数增加(注意有越界情况) 
5. 非数字部分:跳过不管
6. 最后记得是负数要添加负号(*-1)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String s=input.next();
System.out.println(strToInt(s));
}

public static int strToInt(String str) {
if(str==null) {
return 0; //字符串为空 返回0
}
int n=str.length(); //获取字符串长度
int i=0;
int res=0; //接最后结果
boolean is_negative=false; //判断正负数
// 第一步,跳过前面若干个空格
while(i<n&&str.charAt(i)==' '){
++i; //i往后调
}
//如果字符串全是空格直接返回
if(i==n) {
return 0;
}
//第二步,判断正负号
if(str.charAt(i)=='-') {
is_negative = true;
}
//如果是正负号,还需要将指针i,跳过一位
if(str.charAt(i)=='-' || str.charAt(i)=='+') {
++i;
}
//第三步,循环判断字符是否在 0~9之间
while(i<n && Character.isDigit(str.charAt(i))) {
//'0'的ASCII码是48,'1'的是49,这么一减就从就可以得到真正的整数值
int tmp = str.charAt(i)-48;
//判断是否大于 最大32位整数
if(!is_negative &&(res>214748364 ||(res==214748364 && tmp>=7))) {
return 2147483647;
}
//判断是否小于 最小32位整数
if(is_negative &&(-res<-214748364 || (-res==-214748364 && tmp>=8))) {
return -2147483648;
}
//将字符串转为整数(10进制)
res = res*10 + tmp;
++i;
}
//如果有负号标记则返回负数
if(is_negative) {
return -res;
}
return res;
}

}

结果


二叉搜索树的最近公共祖先(迭代/递归)

题目

分析

1. 由于是二叉搜索树所以有以下三种情况:
    1.1 root.val < p.val 则p在root右子树中
    1.2 root.val > p.val 则p在root左子树中
    1.3 root.val = p.val 则p和root指向同一节点        
2. 递归法思路:
    当p和q都在root右子树中,递归root.right并返回
    当p和q都在root左子树中,递归root.left并返回

代码

1
2
3
4
5
6
7
8
9
10
11

public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//p q都在右子树 右子树里面找
if(root.val < p.val && root.val < q.val)
return lowestCommonAncestor(root.right, p, q);
//p q都在左子树 左子树里面找
if(root.val > p.val && root.val > q.val)
return lowestCommonAncestor(root.left, p, q);
//公共节点既不在左子树也不再右子树上 只能是顶端的公共节点
return root;
}

结果


二叉树的最近公共祖先(后序遍历DFS)

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

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 && right == null) {
return null; // 1. 左右子树都不包含p和q
}
if(left == null) {
return right; // 3.不在左子树 直接返回right
}
if(right == null) {
return left; // 4.不在右子树 直接返回left
}
return root; // 2.不同时为空 p和q分在两侧
}

结果


×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 数组中重复的数字(哈希表/排序遍历/二分)
    1. 1.1. 题目
    2. 1.2. 分析
    3. 1.3. 代码
    4. 1.4. 结果
  2. 2. 二维数组中的查找(双指针/暴力法)
    1. 2.1. 题目
    2. 2.2. 分析
    3. 2.3. 代码
    4. 2.4. 结果
  3. 3. 替换空格(StringBuilder类)
    1. 3.1. 题目
    2. 3.2. 分析
    3. 3.3. 代码
    4. 3.4. 结果
  4. 4. 从尾到头打印链表(stack栈)
    1. 4.1. 题目
    2. 4.2. 分析
    3. 4.3. 代码
    4. 4.4. 结果
  5. 5. 重建二叉树
    1. 5.1. 题目
    2. 5.2. 分析
    3. 5.3. 代码
    4. 5.4. 结果
  6. 6. 实现队列(双栈)
    1. 6.1. 题目
    2. 6.2. 分析
    3. 6.3. 代码
    4. 6.4. 结果
  7. 7. 斐波那契数列(动态规划/递归)
    1. 7.1. 题目
    2. 7.2. 分析
    3. 7.3. 代码
    4. 7.4. 结果
  8. 8. 青蛙跳台阶(递归/动态规划)
    1. 8.1. 题目
    2. 8.2. 分析
    3. 8.3. 代码
    4. 8.4. 结果
  9. 9. 旋转数组的最小数字(排序/二分法)
    1. 9.1. 题目
    2. 9.2. 分析
    3. 9.3. 代码
    4. 9.4. 结果
  10. 10. 矩阵中的路径(DFS+剪枝)
    1. 10.1. 题目
    2. 10.2. 分析
    3. 10.3. 代码
    4. 10.4. 结果
  11. 11. 机器人的运动范围
    1. 11.1. 题目
    2. 11.2. 分析
    3. 11.3. 代码
    4. 11.4. 结果
  12. 12. 剪绳子(动态规划/归类)
    1. 12.1. 题目
    2. 12.2. 分析
    3. 12.3. 代码
    4. 12.4. 结果
  13. 13. 剪绳子II(贪心/快速幂)
    1. 13.1. 题目
    2. 13.2. 分析
    3. 13.3. 代码
    4. 13.4. 结果
  14. 14. 二进制中1的个数(位运算/按位判断)
    1. 14.1. 题目
    2. 14.2. 分析
    3. 14.3. 代码
    4. 14.4. 结果
  15. 15. 删除链表的节点(跳过要删除的节点连接)
    1. 15.1. 题目
    2. 15.2. 分析
    3. 15.3. 代码
    4. 15.4. 结果
  16. 16. 正则表达式匹配(动态规划)
    1. 16.1. 题目
    2. 16.2. 分析
    3. 16.3. 代码
    4. 16.4. 结果
  17. 17. 表示数值的字符串/有效数字(分情况讨论)
    1. 17.1. 题目
    2. 17.2. 分析
    3. 17.3. 代码
    4. 17.4. 结果
  18. 18. 调整数组顺序奇数在偶数前面(二分法/分情况添加)
    1. 18.1. 题目
    2. 18.2. 分析
    3. 18.3. 代码
    4. 18.4. 结果
  19. 19. 链表中倒数第K个节点(两个链表)
    1. 19.1. 题目
    2. 19.2. 分析
    3. 19.3. 代码
    4. 19.4. 结果
  20. 20. 反转链表(栈/双指针)
    1. 20.1. 题目
    2. 20.2. 分析
    3. 20.3. 代码
    4. 20.4. 结果
  21. 21. 合并两个排序的链表(新建链表)
    1. 21.1. 题目
    2. 21.2. 分析
    3. 21.3. 代码
    4. 21.4. 结果
  22. 22. 树的子结构(递归求子结构)
    1. 22.1. 题目
    2. 22.2. 分析
    3. 22.3. 代码
    4. 22.4. 结果
  23. 23. 二叉树的镜像(递归/辅助栈)
    1. 23.1. 题目
    2. 23.2. 分析
    3. 23.3. 代码
    4. 23.4. 结果
  24. 24. 对称的二叉树(递归)
    1. 24.1. 题目
    2. 24.2. 分析
    3. 24.3. 代码
    4. 24.4. 结果
  25. 25. 顺时针打印矩阵(四指针)
    1. 25.1. 题目
    2. 25.2. 分析
    3. 25.3. 代码
    4. 25.4. 结果
  26. 26. 包含main函数的栈(辅助栈)
    1. 26.1. 题目
    2. 26.2. 分析
    3. 26.3. 代码
    4. 26.4. 结果
  27. 27. 栈的压入、弹出序列(辅助栈)
    1. 27.1. 题目
    2. 27.2. 分析
    3. 27.3. 代码
    4. 27.4. 结果
  28. 28. 从上到下打印二叉树(递归/bfs/辅助队列)
    1. 28.1. 题目
    2. 28.2. 分析
    3. 28.3. 代码
    4. 28.4. 结果
  29. 29. 从上到下打印二叉树II(递归/队列)
    1. 29.1. 题目
    2. 29.2. 分析
    3. 29.3. 代码
    4. 29.4. 结果
  30. 30. 从上到下打印二叉树III(递归/队列)
    1. 30.1. 题目
    2. 30.2. 分析
    3. 30.3. 代码
    4. 30.4. 结果
  31. 31. 二叉搜索树的后序遍历序列(递归分治)
    1. 31.1. 题目
    2. 31.2. 分析
    3. 31.3. 代码
    4. 31.4. 结果
  32. 32. 二叉树中和为某一值的路径(回溯/dfs深度优先遍历)
    1. 32.1. 题目
    2. 32.2. 分析
    3. 32.3. 代码
    4. 32.4. 结果
  33. 33. 复杂链表的复制(HashMap集合)
    1. 33.1. 题目
    2. 33.2. 分析
    3. 33.3. 代码
    4. 33.4. 结果
  34. 34. 二叉搜索树与双向链表(中序遍历)
    1. 34.1. 题目
    2. 34.2. 分析
    3. 34.3. 代码
    4. 34.4. 结果
  35. 35. 序列化二叉树
    1. 35.1. 题目
    2. 35.2. 分析
    3. 35.3. 代码
    4. 35.4. 结果
  36. 36. 字符串的排列(回溯)
    1. 36.1. 题目
    2. 36.2. 分析
    3. 36.3. 代码
    4. 36.4. 结果
  37. 37. 数组中出现次数超过一半的数字(摩尔投票法/数组排序/map集合)
    1. 37.1. 题目
    2. 37.2. 分析
    3. 37.3. 代码
    4. 37.4. 结果
  38. 38. 最小的k个数(排序法)
    1. 38.1. 题目
    2. 38.2. 分析
    3. 38.3. 代码
    4. 38.4. 结果
  39. 39. 数据流中的中位数
    1. 39.1. 题目
    2. 39.2. 分析
    3. 39.3. 代码
    4. 39.4. 结果
  40. 40. 连续子数组的最大和(动态规划)
    1. 40.1. 题目
    2. 40.2. 分析
    3. 40.3. 代码
    4. 40.4. 结果
  41. 41. 1-n中整数中1出现的次数(拆除法)
    1. 41.1. 题目
    2. 41.2. 分析
    3. 41.3. 代码
    4. 41.4. 结果
  42. 42. 数字序列中某一位的数字(数学归纳)
    1. 42.1. 题目
    2. 42.2. 分析
    3. 42.3. 代码
    4. 42.4. 结果
  43. 43. 把数字翻译成字符串(动态规划)
    1. 43.1. 题目
    2. 43.2. 分析
    3. 43.3. 代码
    4. 43.4. 结果
  44. 44. 礼物的最大价值(回溯)
    1. 44.1. 题目
    2. 44.2. 分析
    3. 44.3. 代码
    4. 44.4. 结果
  45. 45. 最长不含重复字符的子字符串(动态规划)
    1. 45.1. 题目
    2. 45.2. 分析
    3. 45.3. 代码
    4. 45.4. 结果
  46. 46. 丑数(三指针)
    1. 46.1. 题目
    2. 46.2. 分析
    3. 46.3. 代码
    4. 46.4. 结果
  47. 47. 第一个只出现一次的字符(计数遍历/hashmap存储出现次数)
    1. 47.1. 题目
    2. 47.2. 分析
    3. 47.3. 代码
    4. 47.4. 结果
  48. 48. 数组中的逆序列
    1. 48.1. 题目
    2. 48.2. 分析
    3. 48.3. 代码
    4. 48.4. 结果
  49. 49. 两个链表的第一个公共节点(双指针)
    1. 49.1. 题目
    2. 49.2. 分析
    3. 49.3. 代码
    4. 49.4. 结果
  50. 50. 二叉搜索树的第k大节点(后序遍历递减)
    1. 50.1. 题目
    2. 50.2. 分析
    3. 50.3. 代码
    4. 50.4. 结果
  51. 51. 二叉搜索树的深度(递归DFS)
    1. 51.1. 题目
    2. 51.2. 分析
    3. 51.3. 代码
    4. 51.4. 结果
  52. 52. 平衡二叉树(求深度题目基础上增加判断)
    1. 52.1. 题目
    2. 52.2. 分析
    3. 52.3. 代码
    4. 52.4. 结果
  53. 53. 数组中数字出现的次数I(哈希表)
    1. 53.1. 题目
    2. 53.2. 分析
    3. 53.3. 代码
    4. 53.4. 结果
  54. 54. 数组中数字出现的次数II(哈希表)
    1. 54.1. 题目
    2. 54.2. 分析
    3. 54.3. 代码
    4. 54.4. 结果
  55. 55. 和为s的两个数字(双指针)
    1. 55.1. 题目
    2. 55.2. 分析
    3. 55.3. 代码
    4. 55.4. 结果
  56. 56. 和为s的连续正数序列(滑动窗口)
    1. 56.1. 题目
    2. 56.2. 分析
    3. 56.3. 代码
    4. 56.4. 结果
  57. 57. 翻转单词顺序(空格分开)
    1. 57.1. 题目
    2. 57.2. 分析
    3. 57.3. 代码
    4. 57.4. 结果
  58. 58. 左旋转字符串(遍历)
    1. 58.1. 题目
    2. 58.2. 分析
    3. 58.3. 代码
    4. 58.4. 结果
  59. 59. 滑动窗口的最大值(双指针移动)
    1. 59.1. 题目
    2. 59.2. 分析
    3. 59.3. 代码
    4. 59.4. 结果
  60. 60. 队列的最大值(链表)
    1. 60.1. 题目
    2. 60.2. 分析
    3. 60.3. 代码
    4. 60.4. 结果
  61. 61. n个骰子的点数(动态规划)
    1. 61.1. 题目
    2. 61.2. 分析
    3. 61.3. 代码
    4. 61.4. 结果
  62. 62. 扑克牌中的顺子(Set集合)
    1. 62.1. 题目
    2. 62.2. 分析
    3. 62.3. 代码
    4. 62.4. 结果
  63. 63. 圆圈中最后剩下的数字(约瑟夫杀人环list集合)
    1. 63.1. 题目
    2. 63.2. 分析
    3. 63.3. 代码
    4. 63.4. 结果
  64. 64. 股票的最大利润(动态规划)
    1. 64.1. 题目
    2. 64.2. 分析
    3. 64.3. 代码
    4. 64.4. 结果
  65. 65. 求1+2+..+n的和(归纳)
    1. 65.1. 题目
    2. 65.2. 分析
    3. 65.3. 代码
    4. 65.4. 结果
  66. 66. 不用加减乘除做加法(位运算)
    1. 66.1. 题目
    2. 66.2. 分析
    3. 66.3. 代码
    4. 66.4. 结果
  67. 67. 构建乘积数组(不用除法就分区)
    1. 67.1. 题目
    2. 67.2. 分析
    3. 67.3. 代码
    4. 67.4. 结果
  68. 68. 字符串转整数(考虑成分)
    1. 68.1. 题目
    2. 68.2. 分析
    3. 68.3. 代码
    4. 68.4. 结果
  69. 69. 二叉搜索树的最近公共祖先(迭代/递归)
    1. 69.1. 题目
    2. 69.2. 分析
    3. 69.3. 代码
    4. 69.4. 结果
  70. 70. 二叉树的最近公共祖先(后序遍历DFS)
    1. 70.1. 题目
    2. 70.2. 分析
    3. 70.3. 代码
    4. 70.4. 结果
,