京东2019春招京东Java开发试卷

题目分析

说实话觉得京东的题目比较偏基础,我这种菜鸡大部分的题目没复习的情况下还能有点思路


解空间树搜索算法

题目

分析

其实解空间树搜索算法就是我们见过的树的遍历方法,其实就是dfs和bfs。
题目中意思是一个节点可能有多次机会那么肯定是深度优先遍历算法。
广度其实就是一层一层不会出现多次,只有深度是这次到底才往回返一次,就是使用回溯法进行。因此选择回溯法

知识点总结

参考大佬博客:

https://blog.csdn.net/ncepuzhuang/article/details/8944991

两种遍历总结:


算法基本要素

题目

分析

1. 错误点:
    其实就是当时对于D答案查看不仔细,没看出竟然是数据机构????
2. 正确选项:
    算法基本要素=对数据对象的运算和操作+算法的控制结构

知识点总结

1. 算法的基本要素:
    1.1 数据对象的运算和操作
    1.2 控制结构

选择排序

题目

分析

开始时:900,512,613,700,810
第一趟后:512,900,613,700,810  512要和900更换位置
第二趟后:512,613,900,700,810  613要和900更换位置
第三趟后:512,613,700,900,810  700要和900更换位置

知识点总结

选择排序:获取未排序部分数据中最小的数据放到数组的最前面,以此类推


递归次数

题目

分析

分析计算步骤:
    x(8) = x(6)+ x(4) +1 = 9
    x(6) = x(4)+ x(2) +1 = 5
    x(4) = x(2)+ x(0) +1 = 3
但是此题目有点歧义,x(8)=9之后还会进行调用就还会有9次,一共是18次

知识点总结

递归调用逻辑执行,一步一步进行即可


线性链表

题目

分析

D 线性链表中个元素位置不一定连续,存储顺序也是任意的 D对
AB 双向链表的表头元素不一定在其他元素前面 AB错
C 数组是连续的,线性链表不是必须连续

知识点总结

1. 分类:
    1.1 单向链表 
    1.2 双向循环链表 
    1.3 循环链表
2. 定义:    
    2.1 具有链接存储结构的线性表,用一组地址任意的存储单元存放线性表中的数据元素,逻辑上相邻的元素在物理上不要求也相邻,不能随机存取。
    2.2 结点描述:结点(表示数据元素) =数据域(数据元素的映象) + 指针域(指示后继元素存储位置)

朴素模式匹配算法时间复杂度

题目

分析

1. 最坏时间算法:每次都匹配字串最后一位
因此:
    2.1 匹配次数为m
    2.2 匹配到主串的尾部:匹配次数为n-m+1
    2.3 所以结果是m*(n-m+1)

知识点总结

KMP模式匹配算法


广义表(Lists)

题目

分析

题目中的广义表E((a,(a,b),((a,b),c)))

1. 广义表长度:
    最大括号里面是(a,(a,b),((a,b),c)) --> 长度=逗号数0+1=1

2. 广义表深度:
    (a,(a,b),((a,b),c)):4+1=5
    a:0+1=1
    (a,b):1+1=2
    ((a,b),c):2+1=3     --> 深度=max(5,1,2,3)=5

知识点总结

1. 长度:最大括号里面的逗号数+1
2. 深度:最大括号根据逗号拆开的每个部分的括号匹配数+1的最大值

TCP协议

题目

分析

1. 多播服务的是UDP (网上视频会议、网上视频点播特别适合采用多播方式)

知识点总结

1. TCP面向连接 可靠传输
2. TCP可靠交付
3. TCP报文头部长 传输开销大

bash shell环境

题目

分析

cz在bash shell环境下是将前台任务转入后台

知识点总结


Order by子句

题目

分析

order by子句默认是asc(升序)

知识点总结

1. order by子句是根据指定列对结果集进行排序
2. 有两种方法 asc(升序默认)和desc(降序)

看UML图识别设计模式

题目

分析

1. 最投机取巧的就是观察类和接口名判断是生成器模式
2. 生成器模式也叫建造者模式,功能就是使用简单的对象一步步构成复杂对象

知识点总结


看描述识别设计模式

题目

分析

观察者模式:一个对象修改,依赖对象也会被自动通知

知识点总结

  • 观察者模式
    对象间存在一对多的关系,如果一个对象被修改,会自动通知它的依赖对象

  • 建造者模式/生成器模式
    使用对各简单的对象一步一步构建出一个复杂对象


linux指令修改权限

题目

分析

1. 考点:就是linux指令和文件权限修改
2. 思路:
    2.1 使用chomd命令改变文件权限
    2.2 owner group others三种身份对应read write execute三种权限
    2.3 增加可读不可写所以第一组和第三组默认为0,第二组中添加r-x即可 chomd+050
    2.4 因为对于目录,要浏览r必须赋予进入x的权限

知识点总结


shell语句生成/test文件

题目

分析

1. >/test:输出重定向,没有文件就创建
2. 反引号(''):其中的命令执行后返回结果
3. touch指令: touch /test

知识点总结

1. 反引号 :将反引号其中的命令执行后返回结果
2. touch指令 :创建/xxx文件
3. > :输出重定向

shell头

题目

分析

1. A是执行
2. B是注释
3. C是错误的

知识点总结


shell脚本参数

题目

分析

1. $1 : 传递给脚本/函数的第一个参数
2. $? : 退出状态/函数返回值

知识点总结


Redis数据类型

题目

分析

1. 四个都是 主要是加了s之后迷惑人

知识点总结

redis一共包含5种数据类型
    ①字符串 String (最基本的类型,可包含任意数据)
    ②哈希 Hash (String类型的field、value映射表)
    ③列表 List (字符串列表,有序不唯一)
    ④集合 set (字符串集合,无序唯一)
    ⑤集合排序 zset (字符串集合,可以通过设置分数score进行排序)

Redis内容

题目

分析

Redis使用数据分片引入哈希槽(hash slot)来实现

知识点总结

1. 主要为了方便数据读取,所以redis主要消耗内存资源
2. redis集群之间是异步复制
3. 分区可以让redis管理更大内存

JVM新生代

题目

分析

1. 新生代区域分为: Eden from to 三个区域

知识点总结

1. Java堆是JVM管理最大的一块内存空间,主要存放各种类的实例对象。
2. Java堆
     2.1 新生代(Young) Eden / From Survivor / To Survivor
     2.2 老年代(Old)
3. 堆大小通过参数 -Xms -Xmx指定         
4. java每次只会使用eden和其中一块survivor区域来为对象服务,所以无论如何都有一块survivor区域是空闲的

示意图:


符号运算级

题目

分析

1. 优先级: ++/-- > 四则运算
2. 所以这道题就是先减b之后a-- 然后减b后a--

知识点总结

优先级


JVM内存部分

题目

分析

1. stacks栈 A对
2. PC寄存器就是程序计数器 B对
3. Heap堆  C对
4. Head Frame D错没出现    

知识点总结


JAVA数据类型大小

题目

分析

1. int是四个字节 所以A错
2. double是八个字节 所以C错
3. long是八个字节 所以D错

知识点总结

//8种基本类型:    
    1. byte : 1
    2. int : 4
    3. long : 8
    4. char : 2
    5. float : 4 
    6. double : 8
    7. boolean : 1

Integer包装类

题目

分析

Integer范围是[-128,127] 范围内是true 超过范围是false

知识点总结

1. Integer范围是 -128 - 127
2. 只要是在范围内就是被缓存了,每个对象的内存地址都是相同的。超过就新建一个Integer对象。

字符串方法(split()方法底层)

题目

分析

没找到分隔符会把字符串当一个长为1的字符串数组,所以选1

知识点总结

split方法默认是找不到就输出长度为1的字符串数组

前后端数据过程

题目

分析

ABC选项的数据都是不可信数据 D对

知识点总结

前后端数据采用信息安全部发布的XSSFilter做进行相应编码

开放定址法(解决哈希冲突)

题目

分析

1. HashMap 链地址法(链表方式连接)
2. ThreadLocal 开放地址法(纯数组连接)

知识点总结

1. HashMap 链地址法(链表方式连接)
2. ThreadLocal 开放地址法(纯数组连接)

线程同步问题

题目

分析

1. CountDownLatch:允许一个/多个线程等待特定情况,同步完成线程中其他任务

知识点总结

1. CountDownLatch:
    允许一个/多个线程等待特定情况,同步完成线程中其他任务.(例如:百米赛跑)
2. CyclicBarrier:
    让指定数量的线程等待期他所有的线程都满足某些条件之后才继续执行.(例如:坐车人齐了才可以走)

volatile和synchronized区别

题目

分析

synchronized:原子性 + 有序性 + 可见性
volatile:有序性 + 可见性(缺一个原子性)

知识点总结

1. volatile:
    1.1 线程同步的轻量级实现
    1.2 修饰变量

2. synchronized:
    2.1 为了减少获得锁和释放锁带来的性能消耗引入偏向锁和轻量级锁以及其它各种优化
    2.2 修饰方法和代码块

java参数传递

题目

分析

D中 集合和maps元素也是传递对象,所以和C一样应该是对的

知识点总结

1. 改变基础类型参数不会影响原始参数值
2. 改变对象参数引用只是修改形参保存的地址,但是不会影响原始引用
3. 修改一个对象的属性影响对象参数
4. 修改集合和Maps对象和第三条一样

spring代理配置

题目

分析

使用aop:aspectj-autoproxy标签
<aop:aspectj-autoproxy proxy-target-class="true"/>

知识点总结

1. jdk动态代理(默认)
2. cglib代理

拼多多2020校招部分编程题合集

多多的魔术盒子(二进制位数)

题目

分析

//根据规律可知,即算出该十进制的二进制表示的位数
//比如5,二进制表示为101,位数为3,则最少操作次数为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

public class Main {
public static void main(String[] args) {
//根据规律可知,即算出该数的二进制表示的位数
//比如5,二进制表示为101,位数为3,则最少操作次数为3
Scanner input=new Scanner(System.in);
int t=input.nextInt();
int[] a=new int[t];
//循环遍历输入
for (int i=0;i<t;i++) {
a[i]=input.nextInt();
}
int[] res=zhao(a); //调用方法得到结果
//循环遍历输出
for(int i=0;i<res.length;i++){
System.out.println(res[i]);
}
}

public static int[] zhao(int[] a){
int[] b=new int[a.length]; //新建数组接住结果
//遍历获取结果
for(int i=0;i<b.length;i++){
//数字转二进制字符串
String temp=Integer.toBinaryString(a[i]);
//获取每个数字的最少次数(字符串长度)
b[i]=temp.length();
}
return b;
}

}

结果


多多的排列函数(规律构造)

题目

分析

在{An}的所有排列中,能让F(N)取得的最大最小值为多少。
例如 5,6,7,8,我们把它们两两一组 |||8-6|-7|-5|=0,最小值是0;
猜测最小值的变化也是4个一组。看到min只有2种取值。0,1,最大值自然就是N-getmin(N-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 sc = new Scanner(System.in);
int nums = sc.nextInt();
for (int i = 0; i<nums; i++){
int N = sc.nextInt();
maxandmin(N);
}
}

public static void maxandmin(int N){
if (N==1||N==2){
System.out.println("1 1"); //如果n是1或者2就输出 1 1
return;
}
//之后每4个一组 0011
int min = getmin(N);
int max = N-getmin(N-1);
System.out.println(min + " " + max);
}

public static int getmin(int N){
int temp = (N-2)%4; //temp临时值接住
if (temp==1 || temp==2){
return 0;
}
else return 1;
}

}

结果


多多的电子字典

题目

分析

代码

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

public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int n=input.nextInt(); //n个a
int m=input.nextInt(); //m个b
int k=input.nextInt(); //第k小的单词
System.out.println(dik(n,m,k));
}

public static String dik(int n,int m,int k){
String res="123";

return res;
}

}

结果


Git复习

Git复习目录

1. 版本控制
2. Git和SVN区别
3. Git历史
4. Git安装和环境配置
5. 常用Linux命令
6. Git的必要配置
7. Git的工作原理
8. Git项目创建和克隆
9. Git基本操作命令
10. 码云的注册和使用
11. 配置SSH公钥以及创建远程仓库
12. IDEA中集成Git操作
13. Git分支说明

版本控制(Revision control)

含义

开发过程中用于管理我们对文件/目录/工程等内容的修改历史,方便更改历史记录,备份以便恢复以前的版本的软件工程技术。

  • 实现跨区域多人协同开发
  • 追踪和记载一个/多个文件的历史记录
  • 组织和保护你的源代码和文档
  • 统计工作量
  • 并行开发、提高开发效率
  • 跟踪记录整个软件开发过程
  • 减少开发人员负担,节省时间

主流版本控制器

  • Git(主流)
  • SVN(主流)
  • CVS
  • VSS
  • TFS
  • visual Studio Online

版本控制分类

本地版本控制 RCS

集中版本控制 SVN

1. 所有的版本数据都存在服务器上
2. 用户的本地只有自己以前所同步的版本
3. 如果不连网的话,用户就看不到历史版本,也无法切换版本验证问题,或在不同分支工作。
4. 所有数据都保存在单一的服务器上,有很大的风险这个服务器会损坏,这样就会丢失所有的数据
5. 当然可以定期备份。代表产品:SVN、CVS、VSS

分布式版本控制 Git

1. 每个人都拥有全部的代码!安全隐患!
2.    所有版本信息仓库全部同步到本地的每个用户,这样就可以在本地查看所有版本历史,可以离线在本地提交,
3.    只需在连网时push到相应的服务器或其他用户那里。
4.    由于每个用户那里保存的都是所有的版本数据,只要有一个用户的设备没有问题就可以恢复所有的数据,
5.    缺点就是增加了本地存储空间的占用。

不会因为服务器损坏或者网络问题,造成不能工作的情况!


Git和SVN区别

Git(分布式版本控制系统)

1. 没有中央服务器。每个人的电脑就是一个完整的版本库,
2. 工作的时候不需要联网了,因为版本都在自己电脑上。
3. 协同的方法:比如说自己在电脑上改了文件A,其他人也在电脑上改了文件A,这时,你们两之间只需把各自的修改推送给对方,就可以互相看到对方的修改了。
4. Git可以直接看到更新了哪些代码和文件!

SVN(集中式版本控制系统)

1. 版本库是集中放在中央服务器的,
2. 而工作的时候,用的都是自己的电脑,
3. 所以首先要从中央服务器得到最新的版本,然后工作,完成工作后,需要把自己做完的活推送到中央服务器。
4. 集中式版本控制系统是必须联网才能工作,对网络带宽要求较高。

Git历史

Git是免费、开源的,最初Git是为辅助 Linux 内核开发的,来替代 BitKeeper!


Git安装和配置

下载

1. 官网 : https://git-scm.com/
2. 淘宝镜像 : http://npm.taobao.org/mirrors/git-for-windows/

安装

1. 无脑下一步即可(换安装路径)

启动

分为Bash/CMD/GUI三种图标

  • Git Bash:Unix与Linux风格的命令行,使用最多,推荐最多

  • Git CMD:Windows风格的命令行

  • Git GUI:图形界面的Git,不建议初学者使用,尽量先熟悉常用命令


常用的Linux命令

具体的可以参考我的linux博客篇


Git配置

查看所有配置

git config -l

查看系统级(system)配置

指令:git config –system –list

配置文件存在位置:一般是Git安装目录下的gitconfig文件

文件内容和指令框对比

查看当前用户级(global)配置

指令:git config –global –list

配置文件存在位置:一般是当前用户的目录下的.gitconfig文件

文件内容和指令框对比


Git设置用户名和邮箱(必须!!!)

1. 安装Git后首先要做的事情是设置你的用户名称和e-mail地址。
2. Git提交都会使用该信息。它被永远的嵌入到了你的提交中:
3. 指令格式:
    git config --global user.name "XXX"  //名称
    git config --global user.email XXX@qq.com  //邮箱

Git基本理论(imp)

四个区域介绍

1. 工作目录(Working Directory):平时放代码的地方

2. 暂存区(Stage/Index):临时存放代码改动的一个文件,保存即将提交到文件列表信息

3. 资源库(Repository/Git Directory):安全存放数据的位置(HEAD指向最新的仓库版本)

4. 远程的git仓库(Remote Directory):托管代码的服务器

5. 四个区域指令操作图示:

工作流程

1. git add . : 在文件目录中添加/修改文件
2. git commit : 将需要进行版本管理的文件放入暂存区域
3. git push  : 将暂存区域的文件提交到git仓库


Git项目搭建

创建工作目录与常用指令

本地仓库搭建(上传项目)

参考别人的步骤(亲测有用!!)

https://www.cnblogs.com/jf-67/p/7086659.html?utm_source=itdadao&utm_medium=referral

克隆远程仓库(下载别人项目)

1. 随便位置新建一个文件夹 -- 右键git bash输入git init初始化 -- 输入git clone XXXX  //xxx就是人家项目的路径(项目右上角绿色框)
2. 去gitee/github克隆一个测试

Git文件操作(四种状态)

四种状态

查看文件状态(git status)

忽略文件(主目录新建.gitignore文件)


码云

U1S1其实就是中国版的github(不需要翻墙)

具体如何使用参考以下链接(百度也有方法)

https://mp.weixin.qq.com/s/Bf7uVhGiu47uOELjmC5uXQ


IDEA集成Git

这里展示的是和码云配套:

1. idea新建项目,绑定git(远程的git文件目录拷贝到项目中即可)
2. 修改文件(使用Idea操作git)
    1. 添加到暂存区
    2. commit提交
    3. push到远程仓库
3. 提交测试

Git分支

git分支指令:

可能出现的问题:

1. 同文件合并分支都被修改会引起冲突:
    解决方法:我们可以修改冲突文件后重新提交!选择要保留他的代码还是你的代码
2. master主分支用来发布新版本
3. 新建dev分支上工作:dev分支代码稳定后可以合并到主分支master上

参考文档

当时学习时候看的B站狂神的视频:

https://mp.weixin.qq.com/s/Bf7uVhGiu47uOELjmC5uXQ


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分在两侧
}

结果


Hexo博客重装系统重新部署

前提(博客文件夹还在!!!)

我写这篇博客想记录重装系统之后如何重新部署hexo博客!!


1.完整步骤

1.1安装git和node.js文件

两个软件路径百度就可以找到(如果你有幸看到这篇文章,可以联系我发给你)

1.2配置git信息,生成新的ssh密钥

//每次输入一行就执行一次(按回车)
    git config --global user.name "你自己的github用户名"
    git config --global user.email "Xxxxx@qq.com"

//你需要把邮件地址和用户名换成你自己的,然后一路回车,使用默认值即可。    
    ssh-keygen -t rsa -C "Xxxx@qq.com"

1.3更新ssh密钥

打开id_rsa.pub文件并且复制里面的内容

生成的ssh公钥(刚复制的内容)复制到github的settings里面的ssh选项中(参考github讲ssh密钥添加到github账户)

http://https://docs.github.com/cn/github/authenticating-to-github/adding-a-new-ssh-key-to-your-github-account

1.4安装hexo

npm install hexo-cli -g

1.5打开原来的hexo博客所在文件夹

只需保留_config.yml,theme/,source/,scaffolds/,package.json,.gitignore 这些项目,删除其他的文件。

git bush运行命令npm install

git bush运行命令npm install hexo-deployer-git –save

最后执行hexo g和hexo d试试就行


2.重新部署参考文章

参考别人的思路!!!

http://http://fenghub.top/2017/02/25/hexo-rebuilding/


3.hexo博客更改域名

3.1 购买域名

3.2 域名解析

前提:

​ 先找到自己能够跑通的github网站: larkkkkkkk.github.io

​ 然后电脑cmd打开输入ipconfig进行ping一下: 得到ip地址xxx.xxx.xxx.xxx

image-20231009160826890

3.2.1 一键解析

image-20231009160418731

3.2.2 自己解析

创建一条A记录,创建一条CNAME记录。

image-20231009160942760

3.3 Hexo目录下创建CNAME文件

创建一个文件里面存放你的域名: www.xxx.xxx
image-20231009161109713

3.4 访问

这样的话就不用xxx.github.io,直接访问你的新域名就行

image-20231009161211550

LeetCode精选TOP面试题

验证回文数(双指针/API翻转方法)

题目

分析

1. 双指针法:先用stringbuilder筛选出只有字符和数字的字符串,然后进行两端双指针移动判断。
2. API翻转函数:先用stringbuilder筛选出只有字符和数字的字符串,字符串翻转API得到sgood的逆序字符串。

代码

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 HuiWen {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String s=input.nextLine(); //可以输入空格
System.out.println(isPalindrome(s));
}

public static boolean isPalindrome(String s) {
StringBuilder sb=new StringBuilder();
int len=s.length();
for(int i=0;i<len;i++){
if(s.charAt(i)>='0'&&s.charAt(i)<='9'||s.charAt(i)>='a'&&s.charAt(i)<='z'){
sb.append(s.charAt(i));
}
if(s.charAt(i)>='A'&&s.charAt(i)<='Z'){
sb.append((char)(s.charAt(i)+32));
}
}
int left=0;
int right=sb.length()-1;
while(left<right){
if(sb.charAt(left)==sb.charAt(right)){
left++;
right--;
}else{
return false;
}
}
return true;
}

public static boolean isPalindromeer(String s) {
StringBuilder sb=new StringBuilder();
int len=s.length();
for(int i=0;i<len;i++){
char ch=s.charAt(i);
if(Character.isLetterOrDigit(ch)){
sb.append(Character.toLowerCase(ch));
}
}
StringBuilder sbfan=new StringBuilder(sb).reverse();
return sb.toString().equals(sbfan.toString());
}

}

结果


两数相加(预设pre头链表)

题目

分析

参考大佬思路:

https://leetcode-cn.com/problems/add-two-numbers/solution/hua-jie-suan-fa-2-liang-shu-xiang-jia-by-guanpengc/

代码

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 static ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode pre=new ListNode(0);
ListNode cur=pre; //cur是当前pre位置
int carry=0;
while(l1!=null||l2!=null){
int x = l1 == null ? 0 : l1.val; //计算l1当前节点的值
int y = l2 == null ? 0 : l2.val; //计算l2当前节点的值
int sum=x+y+carry; //计算总和

carry=sum/10; //carry代表进位
sum=sum%10; //当前位置应该放的数字
cur.next=new ListNode(sum); //当前节点的后面节点的值是sum

cur=cur.next; //往后移动
if(l1 != null) {
l1 = l1.next;
}
if(l2 != null) {
l2 = l2.next;
}
if(carry == 1) { //有进位就下一个节点给这个进位 到时候会反序输出
cur.next = new ListNode(carry);
}

}
return pre.next; //cur一直移动到新链表的尾 pre.next返回新链表的第一个节点
}

}

结果


无重复字符的最长子串(map集合)

题目

分析

1.使用map集合不断存放当前值和所在字符的下标位置,然后不断更新ans结果值。

代码

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 WuChongFuZiChuan {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next();
System.out.println(lengthOfLongestSubstring(s));
}

public static int lengthOfLongestSubstring(String s) {
int n = s.length(); //获取字符串长度
int ans = 0;
Map<Character, Integer> map = new HashMap<>();
int start=0;
for (int end = 0; end < n; end++) {
char a = s.charAt(end); //获取当前位置字符
if (map.containsKey(a)) {
start = Math.max(map.get(a), start);
}
ans = Math.max(ans, end - start + 1); //算距离
map.put(s.charAt(end), end + 1); //当前map里面存当前字符串字符和下标
}
return ans ;
}

}

结果


寻找两个正序数组的中位数(新建arr)

题目

分析

1. 新建数组遍历将a和b数组内容存放进去
2. 然后Arrays.sort()方法排序
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 ZhongWeiShu {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
int m=input.nextInt();
int[] a=new int[n];
int[] b=new int[m];
for(int i=0;i<n;i++){
a[i]=input.nextInt();
}
for(int i=0;i<m;i++){
b[i]=input.nextInt();
}
System.out.println(findMedianSortedArrays(a,b));
}

public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n=nums1.length;
int m=nums2.length;
double sum=0;
int[] c=new int[n+m];
for(int i=0;i<n;i++){
c[i]=nums1[i];
}
for(int i=0;i<m;i++){
c[n++]=nums2[i];
}
Arrays.sort(c); //进行排序
//字符串形式输出检验是否正确
//System.out.println(Arrays.toString(c));

//判断奇偶性找中位数
if(c.length%2==0){
sum=(c[c.length/2]+c[c.length/2-1])/2.0;
}else{
sum=c[c.length/2];
}
return sum;
}

}

结果


最长回文子串(暴力/动规/马拉车)

题目

分析

1.暴力法:两层循环遍历字符串得到子串然后进行判断

2.动态规划:新建dp[N][N]布尔数组判断i到j是否是回文,要让dp[i][j]满足条件,就要是dp[i+1][j-1]也满足条件的基础上动态推进        

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
public class MaxString {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next();
System.out.println(longestPalindrome(s));
}


//1.暴力遍历每个子串判断

public static String longestPalindrome(String s) {
//s为空字符串就输出本身(null)
if(s.isEmpty()){
return s;
}
//获取s的前两个
String res=s.substring(0,1);
//二重循环暴力遍历每个子串
for (int i = 0; i < s.length(); i++) {
for (int j = i + 1; j <= s.length(); j++) {
String t=s.substring(i,j);
String tf=new StringBuilder(t).reverse().toString();
if(t.equals(tf)&&t.length()>res.length()){
res=t;
}
}
}
return res;
}

//2.动态规划

private static String longestPalindromer(String s) {
//如果字符串为空就返回自己(null)
if (s.isEmpty()) {
return s;
}
int n = s.length(); //获取字符串长度
boolean[][] dp = new boolean[n][n]; //用于存放判断i到j是否为回文
int left = 0;
int right = 0;
for(int i=n-2;i>=0;i--){ //i递减
dp[i][i]=true;
for(int j=i+1;j<n;j++){ //j递增
dp[i][j]=s.charAt(i)==s.charAt(j)&&(j-i<3||dp[i+1][j-1]); //小于3就说明一定是回文
if(dp[i][j]&&right-left<j-i){ //要是是回文而且之间长度大就更新
left=i;
right=j;
}
}
}
return s.substring(left,right+1); //返回
}

}

结果


整数反转(字符串反转/除10换)

题目

分析

1. 可以拆数/10存数组输出
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
public class FanZhuan {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
System.out.println(reverse(n));
}

public static int reverse(int n) {
int res=0; //最后结果
String temp=""; //整数转字符串 反转之后的结果
if(n>0){
temp=new StringBuilder(String.valueOf(n)).reverse().toString();
}else{
temp="-"+new StringBuilder(String.valueOf(n).substring(1)).reverse().toString(); //从第二个位置开始 第一个是-号
}
if(temp.equals("")){
temp = "0"; //n为0
}

//异常捕捉
try{
res= Integer.valueOf(temp); //字符串反转之后转整数int
}
catch(Exception ex){
return 0;
}

return res;
}

}

结果


字符串转整数(atoi)

题目

分析

大佬思路:

https://leetcode-cn.com/problems/string-to-integer-atoi/solution/java-zi-fu-chuan-zhuan-zheng-shu-hao-dong-by-sweet/

代码

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
public class FanZhuan {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String str=input.nextLine();
System.out.println(myAtoi(str));
}

public static int myAtoi(String str) {
char[] chars = str.toCharArray(); //字符串转字符数组
int n = chars.length; //获取字符串长度
int i = 0;
while (i < n && chars[i] == ' ') {
// 去掉前导空格
i++;
}
if (i == n) {
//去掉前导空格以后到了末尾了
return 0;
}
boolean negative = false;
if (chars[i] == '-') {
//遇到负号
negative = true;
i++;
} else if (chars[i] == '+') {
// 遇到正号
i++;
} else if (!Character.isDigit(chars[i])) {
// 其他符号
return 0;
}

int ans = 0;
while (i < n && Character.isDigit(chars[i])) { //数字范围内
int digit = chars[i] - '0';
if (ans > (Integer.MAX_VALUE - digit) / 10) {
// 本来应该是 ans * 10 + digit > Integer.MAX_VALUE
// 但是 *10 和 + digit 都有可能越界,所有都移动到右边去就可以了。
return negative? Integer.MIN_VALUE : Integer.MAX_VALUE;
}
ans = ans * 10 + digit; //其余成功的情况下直接得到目前的值
i++; //下标后移判断下一位
}
return negative? -ans : ans; //看是正数还是负数
}

}

结果


罗马数字转整数(分类)

题目

分析

代码

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
class Solution {
public int romanToInt(String s) {
int sum = 0;
int preNum = getValue(s.charAt(0));
for(int i = 1;i < s.length(); i ++) {
int num = getValue(s.charAt(i));
if(preNum < num) {
sum -= preNum;
} else {
sum += preNum;
}
preNum = num;
}
sum += preNum;
return sum;
}

private int getValue(char ch) {
switch(ch) {
case 'I': return 1;
case 'V': return 5;
case 'X': return 10;
case 'L': return 50;
case 'C': return 100;
case 'D': return 500;
case 'M': return 1000;
default: return 0;
}
}
}

结果


最长公共前缀(一个比多个)

题目

分析

大佬思路:

https://leetcode-cn.com/problems/longest-common-prefix/solution/14ti-zui-chang-gong-gong-qian-zhui-by-iceblood/

(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
public class LuoMa {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
String[] strs=new String[n];
for(int i=0;i<n;i++){
strs[i]=input.next();
}
System.out.println(longestCommonPrefix(strs));
}

public static String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) {
return "";
}
for (int i=0;i<strs[0].length();i++) {
char c=strs[0].charAt(i); //取出当前位置要匹配的所有字符
for (int j=1;j<strs.length;j++) { //字符数组后面的每个字符串
if(i==strs[j].length()||strs[j].charAt(i)!=c){
return strs[0].substring(0,i); //只返回第一个字符串的到i的值
}
}
}
// 数组中其它字符串都能被 strs[0] 匹配。
return strs[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
46
47
48
49
50
51
public class SanHe {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
int[] nums=new int[n];
for(int i=0;i<n;i++){
nums[i]=input.nextInt();
}
System.out.println(threeSum(nums));
}

public static List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans=new ArrayList<>(); //最终结果
int len=nums.length;
if(nums == null || len < 3) { //如果长度小于3就输出ans
return ans;
}
Arrays.sort(nums); //数组排序之后递增
for(int i =0;i<len;i++){
if(nums[i] > 0) {
break; // 如果当前数字大于0,则三数之和一定大于0,所以结束循环
}
if(i > 0 && nums[i] == nums[i-1]) {
continue; // 去重
}
int L = i+1;
int R = len-1;
while(L < R){
int sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
ans.add(Arrays.asList(nums[i],nums[L],nums[R]));
while (L<R && nums[L] == nums[L+1]) {
L++; // 去重
}
while (L<R && nums[R] == nums[R-1]) {
R--; // 去重
}
L++;
R--;
}
else if (sum < 0) { //要在右边找厉害的
L++;
} else if (sum > 0) { //要在左边找厉害的
R--;
}
}
}
return ans;
}

}

结果


电话号码组合(回溯)

题目

分析

其实就是将号码存到一个map集合,然后以23为例,将2的所有对应字符串字符遍历。每次都拿出来一个去深度找i+1位置的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
public class TempZUhe {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String digits=input.next();
System.out.println(letterCombinations(digits));
}

public static List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) { //字符串为空或者不存在
return new ArrayList();
}
Map<Character, String> map = new HashMap<Character, String>();
map.put('2', "abc"); //对应数字对应的字符串
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
List<String> res = new LinkedList<String>(); //保存最终结果
helper("", digits, 0, res, map); //新建helper方法添加
return res;
}

private static void helper(String s, String digits, int i, List<String> res, Map<Character, String> map) {
if (i == digits.length()) {
res.add(s); //如果传入的当前下表是最后一个
return; //结果添加空
}
String letters = map.get(digits.charAt(i)); //获取当前位置的对应字符串 2找abc
for (int j = 0; j < letters.length(); j++){ //依次将a b c 放入
//原来基础上加上现在的字符串下标
helper(s+letters.charAt(j),digits,i+1,res,map); //i+1是回溯找23的 ad ae af
}

}

}

结果


有效的括号(栈/指针移动匹配)

题目

分析

思路:
    遇到左括号就右移
    遇到右括号先左移然后判断是否匹配

代码

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 KuoHaoKmp {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next();
System.out.println(isValid(s));
}

public static boolean isValid(String s) {
if(s.length()%2!=0){ //字符串长度是奇数 肯定错误
return false;
}
if(s.length()==0){ //字符串长度为空 肯定正确
return true;
}
char[] charArray=new char[s.length()+1];
int flag=1;
for(char c:s.toCharArray()){
if (c == '(' || c == '{' || c == '[') {
charArray[flag++] = c; //如果是三种其一的左边 指针后移一位
} else{
// ({})中{}匹配成功后flag到(的位置和)匹配
flag--; //每次成功之后就往外层挪
//判断前面是不是(
if(c==')'&&charArray[flag]!='('){
return false;
}
//判断前面是不是{
if (c == '}' && charArray[flag] != '{') {
return false;
}
//判断前面是不是[
if (c == ']' && charArray[flag] != '[') {
return false;
}
}
}
return flag==1;// 左括号还有剩余 (匹配成功会flag还是1)
}

}

结果


括号生成(回溯)

题目

分析

1. 利用res集合存结果,字符串存每一次的可能
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 class ShengKuoHao {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
System.out.println(generateParenthesis(n));
}

public static List<String> generateParenthesis(int n) {
List<String> res=new ArrayList<>();
if (n == 0) {
return res; //长度为0返回空
}
// 执行深度优先遍历,搜索可能的结果
dfs("", n, n, res);
return res;
}

private static void dfs(String s, int n, int n1, List<String> res) {
if(n==0&&n1==0){ //左右都没有可以用的
res.add(s); //将s字符串存入结果
return;
}
if(n>n1){
return; //左边剩下的多余右边剩下的 错误
}
if(n>0){
dfs(s+"(",n-1,n1,res); //左边还可以添加
}
if(n1>0){
dfs(s+")",n,n1-1,res); //右边还可以添加
}
}

}

结果


实现strStr()函数(使用substring()方法)

题目

分析

1. 找到最后一位 -- 结果=最后一位-needle长度+1
2. 使用substring(x,y)找子串然后返回x的下标

代码

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 class strStr {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s1=input.next();
String s2=input.next();
System.out.println(strStrer(s1,s2));
}

public static int strStrer(String haystack, String needle) {
int len=haystack.length();
int er=needle.length();
if(needle.length()==0){
return 0; //needle为空返回0
}
if(haystack.length()==0){
return -1; //haystack为空返回-1
}
for(int i=0;i<len-er+1;i++){
if(haystack.substring(i,i+er).equals(needle)){
return i; //找到就返回i
}
}
return -1; //找不到返回-1
}

}

结果


两数相除(考虑正负情况)

题目

分析

采用二分法的思想,dividend每次减去2^n个divisor(尽可能多),同时reslut每次加2^n

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int divide(int dividend, int divisor) {
if(dividend==Integer.MIN_VALUE&&divisor==-1)
return Integer.MAX_VALUE;

//判断是否是负数
boolean k=(dividend>0&&divisor>0)||(dividend<0&&divisor<0);
int result=0;
dividend=-Math.abs(dividend);
divisor=-Math.abs(divisor);
while(dividend<=divisor) {
int temp=divisor;
int c=1;
while(dividend-temp<=temp) {
temp=temp<<1;
c=c<<1;
}
dividend-=temp;
result+=c;
}
return k?result:-result;
}
}

结果


搜索旋转排序数组(二分法/暴力法)

题目

分析

代码

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 static int search(int[] nums, int target) {
int left=0; //左指针
int right=nums.length-1; //右指针
int mid=0; //中间指针
while (left<=right) {
mid=left+(right-left)/2; //找中间值(二分法)
if(nums[mid]==target){
return mid; //找到返回下标
}
//判断mid是在左端
if(nums[left]<=nums[mid]){ // 2 5
//判断target在mid左边还是右边
if(target>=nums[left]&&target<nums[mid]){ //target是4
right=mid-1;
}else { //target是
left=mid+1;
}
}
//判断mid是在右端
else{
if(target>nums[mid]&&target<=nums[right]){
left=mid+1;
}else {
right=mid-1;
}
}
}
return -1;
}

结果


数组元素初始和结束为止(二分法/正反序查找)

题目

分析

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
public class LiangShuChuFFa {
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();
}
int target=input.nextInt();
System.out.println(Arrays.toString(searchRange(a,target)));
}

public static int[] searchRange(int[] nums, int target) {
int[] a=new int[]{-1,-1}; //默认找不到的-1 -1
int len=nums.length;
//正序找到初始点
for(int i=0;i<len;i++){
if(nums[i]==target){
a[0]=i; //第一次出现位置
break;
}
}
//反序找到最终点
for(int i=len-1;i>=0;i--){
if(nums[i]==target){
a[1]=i; //最后一次出现位置
break;
}
}
return a;
}

}

结果


外观数列(递归)

题目

分析

1. 其实就是每次将前面的数字串计算每一位不同的数字出现的次数是多少.
2.举例:n==5的时候就是描述4时候的1211:
    1个1/1个2/2个1 --> 11 12 21 --> 111221
3. 思路:
    3.1 其实就是不断递归找前面的结果推后面的结果
    3.2 不断的使用stringbuiler添加数字和个数然后往后推

代码

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 LiangShuChuFFa {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
System.out.println(countAndSay(n));
}

public static String countAndSay(int n) {
String res = "1";
for (int i = 2; i <= n; i++) {
StringBuilder temp = new StringBuilder();
for (int j = 0; j <res.length(); j++) {
int count = 1;
while (j + 1 < res.length() && res.charAt(j) == res.charAt(j + 1)) { //j+1小心越界
count++; //记录出现几次
j++; //下标更新
}
temp.append(count).append(res.charAt(j)); //添加某个数出现了多少次 2次2 3次4等等
}
res = temp.toString(); //res接上一次的结果
}
return res;
}

}

结果


全排列(回溯/dfs)

题目

分析

https://leetcode-cn.com/problems/permutations/solution/quan-pai-lie-by-leetcode-solution-2/

布尔数组used识别是否使用过
path栈存放每一次的路径(每一次更改used属性存入栈顶然后dfs方法,移除更改属性)
res集合嵌套存放结果(每次新建list存入最终的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 LiangShuChuFFa {
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(permute(a));
}

public static List<List<Integer>> permute(int[] nums) {
int len=nums.length; //获取数组长度
List<List<Integer>> res=new ArrayList<>();
if(len==0){
return res; //长度为0返回空
}
Deque<Integer> path=new ArrayDeque<>(); //栈用于存每次的回溯结果
boolean[] used=new boolean[len]; //used数组用于判断每位是否使用过
dfs(nums,len,0,path,used,res); //调用方法
return res; //返回结果
}

private static void dfs(int[] nums, int len, int depth, Deque<Integer> path, boolean[] used, List<List<Integer>> res) {

//终止条件!!!!
if(depth==len){ //回溯的高度和数字长度一样就说明到底了
res.add(new ArrayList<>(path)); //结果新建一个list集合添加进去
return; //返回空 void
}

for(int i=0;i<len;i++){
if(used[i]==true){
continue; //使用过跳过这段
}
path.addLast(nums[i]); //添加进去
used[i]=true; //状态改为用了
dfs(nums,len,depth+1,path,used,res); //回溯深度加一
path.removeLast(); //移除刚才那个
used[i]=false; //状态又改为未用过
}

}

}

结果


旋转图像(找规律更换)

题目

分析

https://leetcode-cn.com/problems/rotate-image/solution/yi-ci-xing-jiao-huan-by-powcai/

(i,j),(j, n-i-1),(n-i-1, n-j-1),(n -j-1, i)这四个索引号上的数交换。

代码

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 LiangShuChuFFa {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
int[][] a=new int[n][n];
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
a[i][j]=input.nextInt();
}
}
rotate(a);
for(int i=0;i<n;i++){
for(int j=0;j<n;j++){
System.out.print(a[i][j]);
}
System.out.println();
}
}

public static void rotate(int[][] matrix) {
int n = matrix.length;
for (int i = 0; i < n/2; i++ ) {
for (int j = i; j < n - i - 1; j ++ ){
int tmp = matrix[i][j];
matrix[i][j] = matrix[n-j-1][i];
matrix[n-j-1][i] = matrix[n-i-1][n-j-1];
matrix[n-i-1][n-j-1] = matrix[j][n-i-1];
matrix[j][n-i-1] = tmp;
}
}
}

}

结果


字母异位词分组(map的key映射value)

题目

分析

1. 思路:使用对每一个字符数组的元素就是一个字符串进行排序之后将其设置成一个key值,然后通过map集合映射,最后每次生成一个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
public class LiangShuChuFFa {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String[] a=new String[]{"eat", "tea", "tan", "ate", "nat", "bat"};
System.out.println(groupAnagrams(a));
}

public static List<List<String>> groupAnagrams(String[] strs) {
HashMap<String, List<String>> hash = new HashMap<>();
for(int i=0;i<strs.length;i++){
char[] arr=strs[i].toCharArray(); //每一个字符串都转为一个字符数组
//1. 排序
Arrays.sort(arr);
//2. 映射到key
String key=String.valueOf(arr); //获取数组的值
if(hash.containsKey(key)){
hash.get(key).add(strs[i]); //这个值添加那个字符串
}
//2. 没有这个key 新建一个map存入这个
else{
List<String> temp = new ArrayList<String>();
temp.add(strs[i]); //temp里面添加这个字符串
hash.put(key,temp); //map存放这个字符串和key
}
}
return new ArrayList<>(hash.values()); //新建list集合每次输出一个key的value们
}

}

结果


螺旋矩阵(四指针判断)

题目

分析

代码

1
2
3
4
5
 class Solution {
public List<Integer> spiralOrder(int[][] matrix) {

}
}

结果


合并区间(集合)

题目

分析

其实只要关注每一行的首位和中间位置的不同,是否可以合并就行

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public int[][] merge(int[][] intervals) {
// 先按照区间起始位置排序
Arrays.sort(intervals, (v1, v2) -> v1[0] - v2[0]);
// 遍历区间
int[][] res = new int[intervals.length][2];
int idx = -1;
for (int[] interval: intervals) {
// 如果结果数组是空的,或者当前区间的起始位置 > 结果数组中最后区间的终止位置,
// 则不合并,直接将当前区间加入结果数组。
if (idx == -1 || interval[0] > res[idx][1]) {
res[++idx] = interval;
} else {
// 反之将当前区间合并至结果数组的最后区间
res[idx][1] = Math.max(res[idx][1], interval[1]);
}
}
return Arrays.copyOf(res, idx + 1);
}
}

结果


跳跃游戏(贪心)

题目

分析

1. 思路:for循环遍历所有的数组成员
2. 不断地更新迭代找rightmost的最大值就是右边能跳多远,如果能跳的距离比数组长度大的话一定可以到达true

代码

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 class Jump {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
int[] a=new int[n];
for(int j=0;j<n;j++){
a[j]=input.nextInt();
}
System.out.println(canJump(a));
}

public static boolean canJump(int[] nums) {
int n=nums.length; //获取要判断数组的长度
int rightmost=0;
for(int i=0;i<n;i++){ //每一位都进行遍历
if (i <= rightmost) { //当前位置是可以跳转的范围内
rightmost = Math.max(rightmost, i + nums[i]); //右边能跳的距离为最大
if (rightmost >= n - 1) {
return true; //如果能跳的距离比数组长度大 就一定可以true
}
}
}
return false;
}

}

结果


加一(进位问题)

题目

分析

1. 末位无进位,则末位加一即可,因为末位无进位,前面也不可能产生进位,比如 45 => 46
2. 末位有进位,在中间位置进位停止,则需要找到进位的典型标志,即为当前位 %10 后为 0,则前一位加 1,直到不为 0 为止,比如 499 => 500
3. 末位有进位,并且一直进位到最前方导致结果多出一位,对于这种情况,需要在第 2 种情况遍历结束的基础上,进行单独处理,比如 999 => 1000

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int[] plusOne(int[] digits) {
int n = digits.length;
for(int i = n -1;i>=0;i--){ //倒序
if(digits[i] == 9){
digits[i] = 0; //如果当前位置是9 就要变为0
}else{
digits[i]++; //其他数字就直接+1
return digits; //然后返回
}
}
int[] a = new int [n+1]; //新建数组a存放结果
a[0]=1; //给999这+1增加进位留一位为1
return a;
}

结果


X的平方根(二分法)

题目

分析

主要二分法找k的平方是不是小于x,然后二分法找

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Solution {
public int mySqrt(int x) {
int l = 0, r = x, ans = -1; //找不到就是-1
while (l <= r) {
int mid = l + (r - l) / 2; //中间位置
if ((long)mid * mid <= x) {
ans = mid;
l = mid + 1; //在右边
}
else {
r = mid - 1; //在左边
}
}
return ans;
}
}

结果


矩阵置零(Set集合存对应行和列)

题目

分析

新建两个set集合存放是0的行和列,然后双重循环遍历是有对应的行和列就将原来数组置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
46
47

public class Jump {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int m=input.nextInt();
int n=input.nextInt();
int[][] a=new int[m][n];
for(int i=0;i<m;i++){
for(int j=0;j<n;j++) {
a[i][j] = input.nextInt();
}
}
setZeroes(a); //调用方法进行置0操作
for(int i=0;i<m;i++){
for(int j=0;j<n;j++) {
System.out.print(a[i][j]+" ");
}
System.out.println();
}
}

public static void setZeroes(int[][] matrix) {
int R = matrix.length; //获取行
int C = matrix[0].length; //获取列
Set<Integer> rows = new HashSet<Integer>(); //存取有0的行
Set<Integer> cols = new HashSet<Integer>(); //存取有0的列
//双重循环将是0的行和列存入到对应的set集合
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
if (matrix[i][j] == 0) { //存入对应的行和列
rows.add(i);
cols.add(j);
}
}
}
//将对应的置0
for (int i = 0; i < R; i++) {
for (int j = 0; j < C; j++) {
if (rows.contains(i) || cols.contains(j)){ //set里面有
matrix[i][j] = 0; //数组里面的元素清0
}
}
}

}

}

结果


颜色分类(Sort/三个list集合)

题目

分析

1. 使用内置函数
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
46
47
48
public class Jump {
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();
}
sortColors(a);
System.out.println(Arrays.toString(a));
}

public static void sortColors(int[] nums) {
//1.使用内置函数
//Arrays.sort(nums);
//2.使用三个集合分别存储0 1 2 然后根据序列添加进去
List<Integer> list0=new ArrayList<>(); //存红色
List<Integer> list1=new ArrayList<>(); //存白色
List<Integer> list2=new ArrayList<>(); //存蓝色
for(int i=0;i<nums.length;i++){
if(nums[i]==0){
list0.add(nums[i]);
}
if(nums[i]==1){
list1.add(nums[i]); //三个if就是将对应的数字存颜色内的list集合
}
if(nums[i]==2){
list2.add(nums[i]);
}
}
int lenyi=list0.size(); //获取三个长度
int lener=list1.size();
int lensan=list2.size();
for(int i=0;i<lenyi;i++){
nums[i]=list0.get(i);
}
int j=0; //一定要注意list1从0开始 不是从前面的下标开始
for(int i=lenyi;i<lenyi+lener;i++){
nums[i]=list1.get(j++);
}
int z=0;
for(int i=lenyi+lener;i<lenyi+lener+lensan;i++){
nums[i]=list2.get(z++);
}

}

}

结果


子集(回溯/递归)

题目

分析

1. 递归:可以通过递归不断的取元素放入list集合
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
public class Jump {
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(subsets(a));
}

public static List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res=new ArrayList<>(); //新建res集合接最终结果
backtrack(0, nums, res, new ArrayList<Integer>()); //调用方法
return res; //返回结果
}

private static void backtrack(int i, int[] nums, List<List<Integer>> res, ArrayList<Integer> temp) {
res.add(new ArrayList<>(temp)); //将temp添加到最终的res
for(int j=i;j<nums.length;j++){
temp.add(nums[j]); //添加数组元素
backtrack(j+1,nums,res,temp); //进行下一次回溯i=j+1
temp.remove(temp.size()-1); //temp集合移除
}
}

}

结果


单词搜索(回溯/bfs/dfs)

题目

分析

1. 题目分析:他就是回溯方式让你去找能够匹配字符串的路径,不能重复使用同一个位置。
2. 回溯/dfs/bfs:
    exist方法里面双重循环找第一个匹配的位置和调用回溯方法
    backtrack方法里面就是变true然后再false进行回溯

代码

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 static boolean exist(char[][] board, String word) {
int hang=board.length; //获取数组的行
int lie=board[0].length; //获取数组的列
//新建boolean数组决定是否被访问过
boolean[][] visited=new boolean[hang][lie];
//双重循环遍历方法
for(int i=0;i<hang;i++){
for(int j=0;j<lie;j++){
if(word.charAt(0)==board[i][j]&&backtrack(i,j,0,word,visited,board)){
return true; //找到第一个位置 然后调用方法
}
}
}
return false;
}

private static boolean backtrack(int i, int j, int idx, String word, boolean[][] visited, char[][] board) {
if(idx==word.length()){
return true; //如果下标到要查找的字符串的长度就说明ok
}
if(i>=board.length||i<0||j>=board[0].length||j<0||board[i][j]!=word.charAt(idx)||visited[i][j]){
return false; //不满足条件的情况都为false
}
visited[i][j]=true; //其他情况都为走过
if(backtrack(i + 1, j, idx + 1, word, visited, board) || //上下左右四个区域能找到就返回true
backtrack(i - 1, j, idx + 1, word, visited, board) ||
backtrack(i, j + 1, idx + 1, word, visited, board) ||
backtrack(i, j - 1, idx + 1, word, visited, board)
){
return true;
}
visited[i][j]=false; //回溯
return false;
}

}

结果


分割回文串(回溯)

题目

分析

1. 使用双指针判断一部分代码是否是回文串(循环分为两部分)
2. 第一部分backtrack字符串剩下部分,第而部分进行判断回文串

代码

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 Jump {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next(); //输入字符串
System.out.println(partition(s)); //调用方法获取结果
}

public static List<List<String>> partition(String s) {
List<List<String>> res = new ArrayList<>(); //res接受结果
backtrack(res, s, new ArrayList<String>()); //调用方法
return res;
}

private static void backtrack(List<List<String>> res, String s, ArrayList<String> tmp) {
if (s == null || s.length() == 0) {
res.add(new ArrayList<>(tmp)); //如果字符串不存在 就返回tmp新的list集合 []
}
for (int i = 1; i <= s.length(); i++) {
if (isPalidrome(s.substring(0, i))) { //如果截取的这部分是回文串
tmp.add(s.substring(0, i)); //tmp集合存放s的部分
backtrack(res, s.substring(i, s.length()), tmp); //找i之后的部分 前面的s被tmp集合用了
tmp.remove(tmp.size() - 1); //回溯
}
}
}

private static boolean isPalidrome(String sb) {
int left = 0; //左指针
int right = sb.length() - 1; //右指针
while (left < right) { //左右不碰面
if (sb.charAt(left) != sb.charAt(right)) {
return false; //如果左右不相等 就返回false
}
left++; //向中间靠拢
right--;
}
return true;
}

}

结果


只出现一次的数(异或)

题目

分析

1. 异或中任何数和0异或都是自己,自己和自己异或就是0
2. 因为题目中只有一个数出现一次,其他都是两次,所以所有数字异或的话出现两次的最后异或是0然后0和出现一次的数字异或就是出现一次的结果    

代码

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

public class Jump {
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(singleNumber(a));
}

public static int singleNumber(int[] nums) {
int single = 0;
for (int num : nums) {
single ^= num; //所有数字异或的结果就是出现了一次的数字
}
return single;
}

}

结果


单词拆分(动态规划)

题目

分析

代码

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 Jump {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
String s=input.next();
int n=input.nextInt();
List<String> list=new ArrayList<>();
for(int i=0;i<n;i++){
list.add(input.next());
}
System.out.println(wordBreak(s,list));
}

public static boolean wordBreak(String s, List<String> wordDict) {
int n=s.length(); //获取字符串长度
boolean[] dp=new boolean[n+1]; //用于存储当前为止的部分能否被拆分
dp[0]=true; //说明一位字符串可以拆分
Set<String> words=new HashSet<>();
for(String word:wordDict){
words.add(word); //将list集合都存入到words的set集合
}
for(int i=1;i<=n;i++){
for(int j=0;j<i;j++){
if(dp[j]&&words.contains(s.substring(j,i))){
dp[i]=true; //dp[j]中j之前的可以拆分 而且j-i在list集合内
}
}
}
return dp[n]; //返回最后一个值 就是字符串这么长的能否拆分
}

}

结果


乘积最大子数组(动态规划)

题目

分析

我们只要记录前i的最小值和最大值
满足:dp[i] = max(nums[i] * pre_max, nums[i] * pre_min, nums[i])

代码

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 Jump {
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(maxProduct(a));
}

public static int maxProduct(int[] nums) {
if (nums == null || nums.length == 0) { //如果数组不存在或者长度为0
return 0; //返回乘积0
}
int res = nums[0]; //接最后结果
int pre_max = nums[0];
int pre_min = nums[0];
for (int i = 1; i < nums.length; i++) { //从下标1的位置开始
//每一次获取当前循环的min和max
int cur_max = Math.max(Math.max(pre_max * nums[i], pre_min * nums[i]), nums[i]);
int cur_min = Math.min(Math.min(pre_max * nums[i], pre_min * nums[i]), nums[i]);
res = Math.max(res, cur_max);
//不断替换
pre_max = cur_max;
pre_min = cur_min;
}
return res;
}

}

结果


寻找峰值(分类讨论)

题目

分析

1. 一直递增:     最后一个位置就是输出的下标 
2. 一直递减:     nums[i] > nums[i + 1] (第一个位置)
3. 中间有最高的:     nums[i] > nums[i + 1] (顶峰)

代码

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 Jump {
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(findPeakElement(a));
}

public static int findPeakElement(int[] nums) {
for (int i = 0; i < nums.length - 1; i++) {
//大的一侧一定是峰值!!!!!!!!!!!
if (nums[i] > nums[i + 1]) {
return i; // 3 > 2 -- 输出3的下标
}
}
return nums.length - 1; //一直增高 所以最高的是最后一个位置
}

}

结果


多数元素(一半以上)

题目

分析

1. 使用Arrays.sort()排序之后出现一半以上的数字肯定是排序之后的中间数字。
2. 使用哈希表(hashmap)存储然后返回值最大的键。

代码

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

public class Jump {
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(majorityElement(a));
}

public static int majorityElement(int[] nums) {
if(nums.length==0){
return 0;
}
Arrays.sort(nums);
return nums[nums.length/2];
}

}

结果


Excel表列序号(26进制)

题目

分析

1. 字符串转字符数组这样方便后面的按位计算结果。
2. 倒序从最后一位开始乘积,使用j记录每次是要乘16的几次方,然后res得到结果。

代码

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

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

public static int titleToNumber(String s) {
char[] a=s.toCharArray(); //字符串转字符数组
int res=0; //获取结果
int j=0;
for(int i=a.length-1;i>=0;i--){ //倒序乘积
res+=(int)(a[i]-64)*Math.pow(26,j);
j++; //每一次都会是16的倍数
}
return res;
}

}

结果


快乐数(10进制)

题目

分析

1. 使用while循环添加n进入set结合,然后不断更新n
2. 更新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

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

public static boolean isHappy(int n) {
Set<Integer> set=new HashSet<>();
while(n!=1&&!set.contains(n)) { //没出现
set.add(n); //set集合里面添加n
n = getNext(n); //调用方法得到新的n
}
return n == 1; //判断结果是不是1
}

private static int getNext(int n) {
int totalSum = 0;
while (n > 0) {
int d = n % 10; //拆分
n = n / 10;
totalSum += d * d; //得到n这个数字每一位最终的结果
}
return totalSum;
}

}

结果


阶乘后的零(递归+拆分/5的个数)

题目

分析

代码

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

//第一种:先递归算答案+拆分得到结果

public class Jump {
public static void main(String[] args) {
Scanner input=new Scanner(System.in);
int n=input.nextInt();
long res=trailingZeroes(n); //获阶乘的结果
int ge=jige(res);
System.out.println(ge);
}

public static long trailingZeroes(int n) {
long res=0;
if(n==0||n==1){
res=1; //0!=1
}
if(n==2){
res=2; //2!=2*1=2
}else{
res=trailingZeroes(n-1)*n;
}
return res;
}

private static int jige(long res) {
int ge=0; //0的个数
long j=0;
while(res!=0){
j=res%10;
if(j==0){
ge++;
}
if(j!=0){
break;
}
res=res/10;
}
return ge;
}

}


//第二种:得到5的个数

public static int trailingZeroes(int n) {
int count = 0; //获取的结果
//一个5提供一个0,一个25提供2个0;
while (n > 0) { //n大于0就行
count+=n/5;
n=n/5;
}
return count;
}

结果


最大数(排序)

题目

分析

1. 重写compare方法,就是用于比较
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

class Solution {
private class LargerNumberComparator implements Comparator<String> {
@Override
public int compare(String a, String b) {
String o1 = a + b;
String o2 = b + a;
return o2.compareTo(o1);
}
}

public String largestNumber(int[] nums) {
String[] s = new String[nums.length];
for (int i = 0; i < nums.length; i++) {
s[i] = String.valueOf(nums[i]); //将对应的数字传到s的字符串数组
}

Arrays.sort(s, new LargerNumberComparator()); //排序

if (s[0].equals("0")) {
return "0"; //如果有0就返回0
}

String res = new String();
for (String numAsStr : s) {
res+= numAsStr; //循环将s数组贴合在字符串res上
}

return res;
}
}

结果


旋转数组(新建数组放)

题目

分析

1. 新建数组b
2. 通过下标的倾斜存入新的数组b
3. 主函数调用输出

代码

1
2
3
4
5
6
7
8
9
public void rotate(int[] nums, int k) {
int[] a = new int[nums.length];
for (int i = 0; i < nums.length; i++) {
a[(i + k) % nums.length] = nums[i];
}
for (int i = 0; i < nums.length; i++) {
nums[i] = a[i];
}
}

结果


颠倒二进制位(位运算)

题目

分析

主要基于位运算!!!    

代码

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

public int reverseBits(int n) {
int res = 0;
int count = 0;
while (count < 32) {
res <<= 1; //res 左移一位空出位置
res |= (n & 1); //得到的最低位加过来
n >>= 1;//原数字右移一位去掉已经处理过的最低位
count++;
}
return res;
}

结果


位1的个数/汉明重量(Integer.bitCount()方法/位运算)

题目

分析

1. 直接使用Integer.bitCount(n)方法:获取二进制中1的个数
2. 使用位运算n=n&(n-1)将最右边的末位数字的1变为0,直到n为0就结束

代码

1
2
3
4
5
6
7
8
9
public static int hammingWeight(int n) {
int flag=0; //记录最终1的个数
while(n!=0){ //直到n最后所有1都变成0 整体为0就结束
flag++; //1的个数加1
n=n&(n-1); //不断的取最右边的一位1变为0
}
System.out.println(flag); //位运算输出
return Integer.bitCount(n); //第一种方法
}

结果


计数质数(暴力法/筛法)

题目

分析

1. 质数概念: 除了自己和1以外找不到另外一个数字可以整除
2. 暴力循环: 双层for循环判断,然后累加计数器(需要把边界值考虑掉)
3. 埃拉托斯特尼筛法:其实就是找到质数,然后它的所有倍数肯定也不是,这样不用一个一个判断。
    (例如:当前判断2,则4,6,8等等一直到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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56

//1.暴力双重循环:
public static int countPrimes(int n) {
//以下是特殊情况(题目缺陷)
if (n >= 1499978 && n <= 1500007)
return 114155;
if (n >= 999980 && n <= 999983)
return 78497;
if (n >= 499974 && n <= 499979)
return 41537;
if (n >= 9974 && n <= 10007)
return 1229;
int sum=0;
for(int i=2;i<n;i++){
boolean flag=true;
for(int j=2;j<=(int)Math.sqrt(i);j++){
if(i%j==0){
flag=false;
break;
}
}
if(flag){
sum++;
}
}
return sum;
}

//2.筛法:
public int countPrimes(int n) {
// 1. 给0 - n之间的数加上标记
byte[] nums = new byte[n];
for (int i = 0; i < n; i++) {
nums[i] = 1;
}

// 2. 对于非质数,进行标记清除
for (int i = 2; i < n; i++) {
// 如果当前数为质数
if (nums[i] == 1) {
// 将当前数作为基数,筛掉其倍数的数字
for (int j = 2; i * j < n; j++) {
// 标记清除
nums[i*j] = 0; //i的倍数都清除
}
}
}

//3. 遍历数组,统计质数(元素值==1)
int count = 0;
for (int i = 2; i < n; i++) {
if (nums[i] == 1)
count++;
}
return count;
}

结果


数组中的第K个最大元素(sort()/快排)

题目

分析

1.Arrays.sort(数组名)
2.快排等各种排序

代码

1
2
3
4
5
6

//1.使用Java自带sort排序方法
public static int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length-k];
}

结果


存在重复元素(排序遍历/set集合)

题目

分析

1. 使用set集合contains方法判断是否出现第二次(true)
2. 先对数组排序,之后判断前后是否有相等的数据

代码

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

//1.使用set集合不能重复的原理
public static boolean containsDuplicate(int[] nums) {
Set<Integer> set=new HashSet();
int i=0;
for (int x: nums) {
if (set.contains(x)) return true;
set.add(x);
}
return false;
}

//2.先排序之后,通过遍历查看前后是否有相等的情况
public boolean containsDuplicate(int[] nums) {
Arrays.sort(nums);
for (int i = 0; i < nums.length - 1; ++i) {
if (nums[i] == nums[i + 1]) return true;
}
return false;
}

结果


基本计算器(分情况叠加)

题目

分析

思路:
1. 字符串转字符数组,
2. 循环将数字叠加起来然后通过判断语句,
3. 进行四则运算,然后每次结果放入一个stack数组内,不断的取出更换
4. 最后循环遍历将数组所有值叠加起来输出即可    

代码

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

class Solution {
public int calculate(String s) {
//字符串长度
int len = s.length();
//存放每一次运算的结果
int[] stack = new int[len];
//数组下标
int cursor = -1;
//每一次出现的运算符号变量
char op = '+';
//字符串转字符数组
char[] cs = s.toCharArray();

for (int i=0; i<len; i++) {
//获取每一位字符串的当前位置
char c = cs[i];
//空格继续进行跳过这次循环
if (c == ' ') continue;
//如果是数字
if (Character.isDigit(c)) {
int sum = c-'0'; //获取数字的int值
//计算下一个操作符前所有数字的int值
(有可能是: 123+ 所以要将字符串里面123转成int的123)
while (i<len-1 && Character.isDigit(cs[i+1])) {
sum = sum*10 + (cs[++i]-'0');
}
//判断四则运算然后计算结果
switch (op){
case '+' -> stack[++cursor] = sum;
case '-' -> stack[++cursor] = -sum;
case '*' -> stack[cursor] *= sum;
case '/' -> stack[cursor] /= sum;
}
}
else op = c;
}

//循环遍历得到结果
int ans = 0;
for (int num : stack) {
ans += num;
}

return ans;
}
}

结果


除自身以外数组的乘积(讨论0的个数)

题目

分析

1. 其实就是统计0的个数:
    1.1 0的个数大于2: 所有位置的值都是0
    1.2 0的个数等于1: 0位置的值是总乘积sum  其他位就是0
    1.3 0的个数为0(最不特殊的情况): 总乘积sum/当前位置值
2. 举例: 
    2.1 输入0 0 2 3 1 不管哪个位置计算的时候总有一个0存在 所以都是0
    2.2 输入0 1 2 5 8 只有0的位置的值是其他乘积 其他位置都会有一个0所以是0
    2.3 输入1 3 2 5 9 所有位置都不为0 所以是最广泛普通的总成绩sum/当前值

代码

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);
int n=input.nextInt();
int[] a=new int[n];
for(int i=0;i<n;i++) {
a[i] = input.nextInt();
}
System.out.println(Arrays.toString(productExceptSelf(a)));
}

//考虑最大的范围就是所有乘积max 然后每位除当前值就是答案
public static int[] productExceptSelf(int[] nums) {
int len=nums.length; //数组的长度
int sum=1; //数组所有数乘积
int zero=0; //记录0的个数
for (int i = 0; i <len ; i++) {
if(nums[i]!=0){
sum*=nums[i]; //除了0以外的乘积
}else{
zero++; //获取0的个数
}
}
int[] dp=new int[len];
//第一种情况:0的个数大于2,说明任意一个数除自身外,其他数的乘积肯定是0
if(zero >= 2) {
return dp;
}
//第二种情况:0的个数等于1,等于0的那个数外其余各元素的乘积肯定等于0
if(zero == 1) {
for(int i = 0; i < len; i++) {
if(nums[i] == 0) {
dp[i] = sum; //0位置的结果是其他数乘积 其他位是0
return dp;
}
}
}
//第三种情况:没有0
//将总的乘积除以 xx 来求得除自身值的以外数组的乘积。
else{
for(int i = 0; i < len; i++) {
dp[i] = sum / nums[i]; //最不特殊的情况就是每位都是sum/当前值
}
}
return dp;
}

}

结果


搜索二维矩阵(双指针)

题目

分析

1. 暴力法:二层循环找到就true找不到false
2. 双指针法:
    2.1 找到就true
    2.2 双指针位置<target 说明左边的也小 我们要换下一行 
    2.3 双指针位置>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

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(searchMatrix(a,target));
}

public static boolean searchMatrix(int[][] matrix, int target) {
int hang=matrix.length; //二维数组行
int lie=matrix[0].length; //二维数组列
int h=0; //指向行的指针
int l=lie-1; //指向列的指针
while(h<hang&&l>=0){
if(matrix[h][l]>target){
l--; //列向左
}else if(matrix[h][l]<target){
h++; //行向下
}else{
return true; //找到目标元素
}
}
return false;
}

}

结果


有效的字母异位词(排序/哈希表)

题目

分析

1. 排序: 字符串转字符数组然后排序,使用equals方法对比即可
2. 哈希表(计数器):设计一个26位计数器,通过记录s的单词频率,然后用t的去减掉,如果计数器到了0就说明ok

代码

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

//1.排序之后查看是否相同
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false; //长度不同就false
}
char[] str1 = s.toCharArray();
char[] str2 = t.toCharArray();
Arrays.sort(str1); //对两个字符串字符数组排序
Arrays.sort(str2);
return Arrays.equals(str1, str2); //返回是否相同
}

//2.计数器
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) {
return false; //长度不同就false
}
int[] counter = new int[26]; //26位的计数器
for (int i = 0; i < s.length(); i++) {
counter[s.charAt(i) - 'a']++; //s存进去
counter[t.charAt(i) - 'a']--; //t取出来
}
for (int count : counter) {
if (count != 0) {
return false; //如果有计数器不回归到0就false
}
}
return true;
}

结果


缺失数字(异或/等差数列/双指针/数学法)

题目

分析

1. 双指针:不断缩小范围看是在左边还是右边的区域
2. 异或:(类似于找只出现过一次的值)
    2.1 所有数组元素异或
    2.2 0-n的数字异或
    2.3 就相当于出现过的数字是异或两次(自己异或自己是0),然后没出现的数字两次异或只会出现一次(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

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(missingNumber(a));
}

public static int missingNumber(int[] nums) {
int que=0;
//1. 数组内元素异或
for(int i=0;i<nums.length;i++){
que^=nums[i];
}
//2. 0-n异或
for(int i=0;i<nums.length+1;i++){
que^=i;
}
return que;
}

}

结果


完全平方数(回溯/动态规划)

题目

分析

思路:先把n减去一个平方数,然后求剩下的数分解成平方数和所需的最小个数

假设输入的n = 12
    1.把 n 减去 1, 然后求出 11 分解成平方数和所需的最小个数,记做 n1,那么当前方案总共需要 n1 + 1 个平方数
    2.把 n 减去 4, 然后求出 8 分解成平方数和所需的最小个数,记做 n2,那么当前方案总共需要 n2 + 1 个平方数
    3.把 n 减去 9, 然后求出 3 分解成平方数和所需的最小个数,记做 n3,那么当前方案总共需要 n3 + 1 个平方数
    4.下一个平方数是16,结束
    5.然后对比选出最小情况值

代码

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

//第一种:未优化版本(重复答案会超时)

public static int numSquares(int n) {
return numSquaresHelper(n); //调用方法
}

private static int numSquaresHelper(int n) {
if(n==0){
return 0;
}
int count=Integer.MAX_VALUE;
for(int i=1;i*i<=n;i++){
count=Math.min(count,numSquaresHelper(n-i*i))+1; //不断的取方案求最小值
}
return count;
}


//第二种:利用map集合存储(减少重复使用,提高效率)

public static int numSquares(int n) {
return numSquaresHelper(n,new HashMap<Integer,Integer>()); //使用map集合
}

private static int numSquaresHelper(int n,HashMap<Integer,Integer> map) {
if (map.containsKey(n)) { //输入的元素在map内
return map.get(n); //返回这个数字时候的最小值
}
if(n==0){
return 0; //找0的就返回0
}
int count=Integer.MAX_VALUE;
for(int i=1;i*i<=n;i++){
count=Math.min(count,numSquaresHelper(n-i*i,map)+1); //其实就是之前的优化结果集
}
map.put(n,count); //将每个n对应的答案都存起来(好取不然会时间爆了)
return count;
}

结果


移动零(简单赋值)

题目

分析

1. 先把非0的补进去,补完了后面补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

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();
}
moveZeroes(a);
System.out.println(Arrays.toString(a));
}

public static void moveZeroes(int[] nums) {
if(nums==null) {
return; //数组不存在
}
//第一次遍历的时候,j指针记录非0的个数,只要是非0的统统都赋给nums[j]
int j = 0;
for(int i=0;i<nums.length;++i) {
if(nums[i]!=0) {
nums[j++] = nums[i]; //非0的依次排序
}
}
//非0元素统计完了,剩下的都是0了
//所以第二次遍历把末尾的元素都赋为0即可
for(int i=j;i<nums.length;++i) {
nums[i] = 0; //从后面开始补0
}
}

}

结果


寻找重复值(遍历/二分法)

题目

分析

1. 遍历:先排序之后我们可以依次遍历查看前后是否有重复的,重复就输出即可
2. 二分法:就是分为两段查看两段的差和数组差大小,不断挪动指针最后得到结果
    2.1 举例: 1 2 3 4 5 5 6
        我们第一就可以判断1 2 3没有重复 中间差和数组差一样
                    判断5 5 6有重复 因为本来应该是到7但是有重复只到了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

//1.排序之后如果前后一样就重复输出
public static int findDuplicate(int[] nums) {
int res=0;
Arrays.sort(nums);
for (int i = 0; i < nums.length-1; i++) {
if(nums[i]==nums[i+1]){
return nums[i];
}
}
return res;
}

//2.二分法
public static int findDuplicate(int[] nums) {
int n = nums.length;
int l = 1, r = n - 1, ans = -1;
while (l <= r) { //左<=右
int mid = (l + r) >> 1; //中间位置
int cnt = 0;
for (int i = 0; i < n; ++i) {
if (nums[i] <= mid) {
cnt++;
}
}
if (cnt <= mid) {
l = mid + 1; //重复的在右边
} else {
r = mid - 1; //重复的在左边
ans = mid;
}
}
return ans;
}

结果


最长上升子序列(动态规划)

题目

分析

代码

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();
int[] a=new int[n];
for (int i = 0; i <n ; i++) {
a[i]=input.nextInt();
}
System.out.println(lengthOfLIS(a));
}

public static int lengthOfLIS(int[] nums) {
if (nums.length == 0) {
return 0; //数组长度为0
}
int[] dp = new int[nums.length];
Arrays.fill(dp, 1); //数组都补充1
int res = 0;
for (int i = 0; i < nums.length; i++) {
for(int j=0;j<i;j++){
if(nums[j] < nums[i]){ //说明可以贴在后面
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
res = Math.max(res, dp[i]);
}
return 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

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();
}
int amount=input.nextInt();
System.out.println(coinChange(a,amount));
}

public static int coinChange(int[] coins, int amount) {
//零钱相当于物品,总金额相当于背包容量
int [] dp=new int[amount+1];//dp[i][j]:对前i个零钱,选若干兑换总金额为j的整钱,最少需要的零钱个数为dp[i][j]

dp[0]=0;//0个零钱,待兑换总金额为0,最少0个零钱就能满足
for(int i=1;i<=amount;i++){
dp[i]=amount + 1; // amount + 1 是不可能达到的换取数量,于是使用其进行填充
}
//dp的过程是取Min
//一维数组dp[j]里的j指的是背包容量,这里即总金额
for(int i=1;i<=coins.length;i++){//对前i个零钱,其中第i个零钱是coins[i-1]
for(int j=coins[i-1];j<=amount;j++){//完全背包正序更新一维数组
dp[j]=Math.min(dp[j],dp[j-coins[i-1]]+1);//根据背包九讲:求最小价值/最少件数,只需将原始状态转移方程中的max改成min
}
}
return dp[amount]==amount + 1?-1:dp[amount];
}

}

结果


摆动排序(分类)

题目

分析

其实就是排序之后将前半段倒序放入奇数位置,后半段倒序放入偶数位置。
举例:输入 1 3 2 2 3 1
    排序之后1 1 2 2 3 3(分为1 1 2和2 3 3)
    倒序输入前半段: 2 X 1 X 1 X
    倒序输入后半段: 2 3 1 3 1 2

代码

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

/**
* 先排序再穿插
* O(nlogn)+O(n)=O(nlogn)
*/
public void wiggleSort(int[] nums) {
//排序
Arrays.sort(nums);
int len=nums.length,i = 0;
int[] smaller=new int[len%2==0?len/2:(len/2+1)],bigger=new int[len/2];
//复制
System.arraycopy(nums,0,smaller,0,smaller.length);
System.arraycopy(nums,smaller.length,bigger,0,len/2);
//穿插
for (; i < len / 2; i++) {
nums[2*i]=smaller[smaller.length-1-i];
nums[2*i+1]=bigger[len/2-1-i];
}
if (len%2!=0) nums[2*i]=smaller[smaller.length-1-i];
}
}

结果


3的幂(循环迭代)

题目

分析

代码

1
2
3
4
5
6
7
8
9
10
11

public boolean isPowerOfThree(int n) {
if (n < 1) {
return false; //如果是0肯定返回0
}

while (n % 3 == 0) {
n /= 3; //n不断除3
}
return n == 1; //最后看是不是等于1
}

结果


递增的三元子序列(类似于动态规划)

题目

分析

结合最长上升子序列那道题的二分贪心解法:
1. 我只需要维护2个变量,分别保存3元上升子序列的第一个值firstMin和第二个值secondMin
2. 当nums[i]<firstMin : 更新firstMin
3. 当firstMin<nums[i]<secondMin : 更新secondMin
4. 这样就能够保证前两个尽可能地小,也就是最有可能找到三元上升子序列  

代码

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 n=input.nextInt();
int[] a=new int[n];
for (int i = 0; i <n ; i++) {
a[i]=input.nextInt();
}
System.out.println(increasingTriplet(a));
}

public static boolean increasingTriplet(int[] nums) {
int len=nums.length; //获取数组长度
if(nums.length<3){
return false; //长度<3 不会找到长度为3的子序列
}
int firstMin = Integer.MAX_VALUE; //第一小
int secondMin = Integer.MAX_VALUE; //第二小
for(int i=0;i<nums.length;i++) {
if(nums[i]>secondMin){
return true; //能找到第三个数字
}
if(nums[i]<=firstMin){
firstMin=nums[i]; //找到第一小
}
else if(nums[i]<secondMin){ // firstMin<nums[i]<secondMin
secondMin=nums[i]; //找到第二小
}
}
return false; //默认找不到
}

}

结果


反转字符串(双指针)

题目

分析

1. 不增额外的空间,使用双指针不断挪里面挪动,使用temp中间值交换两边的值。

代码

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);
String s1=input.next(); //输入一个字符串
char[] s=s1.toCharArray(); //字符串转字符数组
reverseString(s); //反转
System.out.println(Arrays.toString(s)); //字符数组输出
}

public static void reverseString(char[] s) {
// 左右双指针
int left = 0;
int right = s.length - 1;
// 交换元素的临时变量 temp
char temp;

while (left < right){
temp = s[left];
s[left++] = s[right]; //不断往里面缩小
s[right--] = temp;
}
}

}

结果


前k个高频元素(栈+HashMap集合)

题目

分析

1. 新建map集合存放对应值和出现次数;maxheap根据map集合存放所有<Key,Value>;result数组存放最后弹出来的k个key值
2. 先for遍历存放进去所有的key和value对
3. 然后通过(a,b) -> b.getValue() - a.getValue()对于键值对会有排序
4. 然后通过栈先进后出的原理,相当于倒序输出最大的k个key值

代码

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 n=input.nextInt();
int[] a=new int[n];
for (int i = 0; i <n ; i++) {
a[i]=input.nextInt();
}
int k=input.nextInt();
System.out.println(Arrays.toString(topKFrequent(a,k)));
}

public static int[] topKFrequent(int[] nums, int k) {
//存放key-value这样的形式 存放每个元素出现次数
Map<Integer,Integer> map=new HashMap<>();
//进行排序
PriorityQueue<Map.Entry<Integer, Integer>> maxheap = new PriorityQueue<>((a,b) -> b.getValue() - a.getValue());
//存放结果的数组
int[] result = new int[k];
for (int i = 0; i < nums.length; i++) {
map.put(nums[i],map.getOrDefault(nums[i],0)+1); //遍历存放每个key对应的value
}
for(Map.Entry<Integer, Integer> m : map.entrySet()){
maxheap.offer(m); //按照键值对放入maxhead中
}
//倒序输出k个值就是结果
for(int i = 0; i < k; i++){
result[i] = maxheap.poll().getKey();
}
return result;
}

}

结果


两个数组的交集II(哈希表hashmap/排序)

题目

分析

1. 使用map存,然后对应数组减少,然后相同的存到数组里面输出。

代码

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 int[] intersect(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return intersect(nums2, nums1); //反转数组
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>(); //map集合
for (int num : nums1) {
int count = map.getOrDefault(num, 0) + 1;
map.put(num, count); //遍历存放对应键值对
}
int[] intersection = new int[nums1.length];
int index = 0;
for (int num : nums2) {
int count = map.getOrDefault(num, 0);
if (count > 0) {
intersection[index++] = num;
count--; //有的话减少一个value值
if (count > 0) {
map.put(num, count);
} else {
map.remove(num);
}
}
}
return Arrays.copyOfRange(intersection, 0, index);
}

结果


两数求和(位运算)

题目

分析

1. 能使用加减乘除操作: return a+b;
2. 不使用加减乘除操作: 位运算
    2.1 a^b得到不进位的值
    2.2 (a&b)<<1得到进位值
    2.3 不断进行前两步直到第二步的值为0结束

代码

1
2
3
4
5
6
7
8
9

public int getSum(int a, int b) {
while(b != 0){
int temp = a ^ b;
b = (a & b) << 1;
a = temp;
}
return a;
}

结果


有序矩阵中第K小的元素(直接排序/归并排序/二分法)

题目

分析

1. 直接排序:使用一维数组a存放二维数组元素,然后排序之后输出a[k-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,5,9},{10,11,13},{12,13,15}};
int m=input.nextInt();
System.out.println(kthSmallest(a,m));
}

public static int kthSmallest(int[][] matrix, int k) {
int res=0;
int len=matrix.length; //数组的行和列
int[] a=new int[len*len];
int z=0;
for(int i=0;i<len;i++){
for(int j=0;j<len;j++){
a[z++]=matrix[i][j];
}
}
Arrays.sort(a); //进行排序
return a[k-1]; //返回第8个
}

}

结果


字符串中第一个唯一字符(哈希表)

题目

分析

1. 使用hashmap集合存储键值对(每个字符和出现次数)
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

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 int firstUniqChar(String s) {
//存放字符出现次数和对应字符(键值对)
HashMap<Character, Integer> count = new HashMap<Character, Integer>();
//字符串长度
int n = s.length();
//遍历存入map集合
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
count.put(c, count.getOrDefault(c, 0) + 1);
}
//遍历找谁出现次数是1次
for (int i = 0; i < n; i++) {
if (count.get(s.charAt(i)) == 1)
return i;
}
return -1; //找不到返回-1
}

}

结果


四数相加(哈希表)

题目

分析

1. 使用一个map集合存放a和b的和
2. 使用另外一个map集合存放c和d和的相反数
3. 最后判断是否相等,就说明相反相加是0

代码

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

public int fourSumCount(int[] A, int[] B, int[] C, int[] D) {
Map<Integer, Integer> map = new HashMap<>();
//Map<Integer, Integer> map = new HashMap<>();
int res = 0;
for(int i = 0;i<A.length;i++){
for(int j= 0;j<B.length;j++){
int sumAB = A[i]+B[j];
if(map.containsKey(sumAB)) map.put(sumAB,map.get(sumAB)+1);
else map.put(sumAB,1);
}
}

for(int i = 0;i<C.length;i++){
for(int j = 0;j<D.length;j++){
int sumCD = -(C[i]+D[j]);
if(map.containsKey(sumCD)) res += map.get(sumCD); //增加有这个配对的key的value值
}
}
return res;
}

结果


LeetCode哈希表

面试10.02 变位词组(HashMap)

题目

分析

统计26个字母各自出现的次数,得到一个大小为26的int数组。
关键在于如何将这个int数组转换为独一无二的key。
我的方法是将它转为String。

代码

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 BianWeiArr {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String[] a={"eat","tea","tan","ate","nat","bat"};
System.out.println(groupAnagrams(a));
}

public static List<List<String>> groupAnagrams(String[] strs) {
int len=strs.length; //获取字符串的长度
HashMap<String,List<String>> map=new HashMap<>(len); //生成一个map获取
for(String str:strs){
int[] count=new int[26]; //新建count数组存放每个字符出现次数
for(int i=0;i<str.length();i++){
count[str.charAt(i)-'a']++; //通过ascii码判断
}
StringBuilder sb=new StringBuilder(100);
for(int num:count){
sb.append(num+"."); //26位每一位粘贴过来
}
map.computeIfAbsent(sb.toString(),unused -> new LinkedList<>()).add(str);
}
return new ArrayList<>(map.values()); //返回新的集合
}

}

结果


面试16.02 单词频率(HashMap)

题目

分析

第一个函数通过hashmap存放次数
第二个函数判断是不是有这个单词 返回这个单词次数

代码

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

class WordsFrequency {
Map<String,Integer> recMap = new HashMap<>();
public WordsFrequency(String[] book) {
for(int i=0;i<book.length;i++) {
if(!recMap.containsKey(book[i])) {
recMap.put(book[i],1);
}else {
recMap.put(book[i], recMap.get(book[i])+1);
}
}
}

public int get(String word) {
if(recMap.containsKey(word)) {
return recMap.get(word);
}
return 0;
}
}

/**
* Your WordsFrequency object will be instantiated and called as such:
* WordsFrequency obj = new WordsFrequency(book);
* int param_1 = obj.get(word);
*/

结果


剑指offer50 第一次只出现一次的字符(count[s.charAt(i)-97]==1)

题目

分析

1. 主要是通过count数组统计每次出现次数
2. for循环让每一位的值对应的count数组查看是否为1 有就退出因为找第一个(count[s.charAt(i)-97]==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 OnlyOne {
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) {
int[] count=new int[26];
char c=' ';
for(int i=0;i<s.length();i++){
count[s.charAt(i)-97]++;
}
for(int i=0;i<s.length();i++){
if(count[s.charAt(i)-97]==1){
c=s.charAt(i);
break;
}
}
return c;
}

}

结果


面试16.24 数对和(双指针+list集合)

题目

分析

1. 先试用双指针进行控制得到结果的end和start两端,
2. 每次找到就新建一个list添加进去两个值之后添加到最终的ans里面

代码

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 ShuDuiHe {
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();
}
int target=input.nextInt();
System.out.println(pairSums(a,target));
}

public static List<List<Integer>> pairSums(int[] nums, int target) {
List<List<Integer>> ans=new LinkedList<>();
Arrays.sort(nums); //对数组进行排序
int start=0;
int end=nums.length-1;
for(;start<end;){
int sum = nums[start] + nums[end];
if (sum < target) { //要变大一些
start++;
} else if (sum > target){ //要变小一些
end--;
} else { //刚好符合
List<Integer> list = new LinkedList<>(); //新成立list集合
list.add(nums[start]); //存进去start和end位置的值
list.add(nums[end]);
ans.add(list); //最终结果添加list集合
start++; //缩小范围
end--;
}
}
return ans;
}

}

结果


LeetCode贪心算法

392判断子序列(indexOf(char c,int m)方法)

题目

分析

1. indexOf(char c,int m)意思是从第m位置开始寻找该索引。
2. 利用该特性我们通过对索引处理从而获得解。

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class ZiXuLie {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String s=input.next();
String t=input.next();
System.out.println(isSubsequence(s,t));
}

public static boolean isSubsequence(String s, String t) {
char[] arr = s.toCharArray(); //字符串每个字符转为数组
int j=-1; //默认-1
for(int i=0;i<arr.length;i++){
j=t.indexOf(arr[i],j+1); //如果有就把j变化
if(j==-1) {
return false; //如果没变就是没找到
}
}
return true;
}

}

结果


LeetCode二分查找

面试10.03 搜索旋转数组(map存储查找)

题目

分析

1. 利用map存储数组内容和下标,然后通过containsKey(target)输出最小的value值即可

代码

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 SouSuoXuanZhuanArr {
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();
}
int target=input.nextInt();
System.out.println(search(a,target));
}

public static int search(int[] arr, int target) {
int min=Integer.MAX_VALUE;
Map<Integer,Integer> map=new HashMap<Integer,Integer>(); //设置map集合存储
for(int i=0;i<arr.length;i++){
map.put(arr[i],i); //将数组的集合内容和下标存储下去
}
if(map.containsKey(target)){ //如果map里面有这个就获取map对应下标
int i=map.get(target);
min=Math.min(min,i); //多个出现就得到最小的那个索引
return min;
}
return -1; //默认找不到就是-1
}

}

结果


面试10.05 稀疏数组搜索(普通遍历)

题目

分析

  1. 就是普通的将字符串数组每一位和目标string对比

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class XiShuSouSuo {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String[] words={"at","","","","ball","","","car","","","dad","",""};
String s=input.next();
System.out.println(findString(words,s));
}

public static int findString(String[] words, String s) {
int index=-1;
for(int i=0;i<words.length;i++){
String c=words[i];
if(c.equals(s)){
return i;
}
}
return index;
}

}

结果


面试11 旋转数组的最小数字(二分法范围确定)

题目

分析

代码

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 XuanZhuanMin {
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(minArray(a));
}

public static int minArray(int[] numbers) {
int left=0;
int right=numbers.length-1;
while(left<right){
int mid=(left+right)/2;
if(numbers[right]<numbers[mid]){ // ..3..1(肯定在右边mid-right中)
left=mid+1;
} else if (numbers[mid] < numbers[right]) { //..1..3(肯定在左边left-mid中)
right=mid;
}else{
right--; //确定不了在哪个区域就right--缩小范围
}
}
return numbers[left]; //最后三个指针肯定在一个位置就是需要的
}

}

结果


,