题目
- day1 二叉树和为某一路径
- day2复杂链表的复刻
- day3二叉搜索树与双向链表
- day4数字排列
- day5找出出现次数超过一半的次数
- day6 二进制中1的个数
- day7 二叉树的最近公共祖先
- day8 字符串转换为整数
- day9 构建乘积数组
- day10不用加减乘除的加法
- day11求1+2....+n
- day11 股票的最大价值
- day13扑克牌的顺子
- day14骰子的点数
- day15滑动窗口的最大值
- day16左旋转字符串
- day17反转单词顺序
- day18 和为S的正整数序列
- day19 和为s的两个数字
- day20平衡二叉树
- day21二叉树深度
- day22二叉树的第k个节点、
- day23 数组中数值和下标相等的元素
- day24 0到n-1中缺失的数字
- day25 数字在排序数组中出现的次数
- day 26 链表的第一个公共节点
- day27 逆序对的数量
- day28字符流中第一个出现的字母
- day29丑数
- day30最长不含重复的字符串子串
- day31礼物的最大价值
- day 32 把数字翻译成字符串
- day33把数组排成数字最小的数
- day34数据流中的中位数
- day35数组中最小的k个数
- day36数字的排列
- day 37 二叉树的后序遍历
- day38 之字形打印二叉树
- day39分行从上到下打印二叉树
- day40不分行从上到下打印二叉树
- day41 栈的压入弹出序列
- day42 最小栈
- day43顺时针打印链表
- day45 二叉树的镜像
- day46对称的二叉树
- day47 树的子结构
- day48链表的反转
- day49合并两个有序链表
- day50 链表环的入口
- day51链表中倒数第k个节点
- day52调整顺序使数组的奇数在偶数前
- day53删除链表的重复节点
- day54在O(1)时间删除链表节点
- day55表示数值的字符串
- day56 数值的整数次方
- day57 二进制中1的个数
- day58 机器人的运动范围
- day59 剪绳子
- day60旋转数组的最小值
- day61 矩阵中的路径
- day62 斐波那契数列
- day63用两个栈实现队列
- day64二叉树的下一个节点*
- day65 重建二叉树
- day66从尾到头打印链表
- day67 替换空格
- day68 二维数组中的查找
- day69 不修改找出数组重复的数字
- day70找出数组中重复的数字
day1 二叉树和为某一路径
思路分析
- 初步想法:这个题就是一个典型的爆搜问题,我们最简单的一个想法就是,搜索所有路径,并求和进行判断,所以我们可以使用dfs模板
- 简化:为了判断是否于tar相等,我们需要每次递归时都传入,sum,tar两个参数,我们可以将加法转化为减法,与0进行比较
- 在这里我们需要使用递归函数,所以采用递归三步法进行分析:定义,终止条件,单层逻辑;
class Solution {
static List<List<Integer>> ans;
static List<Integer> res=new ArrayList<>();
public List<List<Integer>> findPath(TreeNode root, int sum) {
ans=new ArrayList<>();
dfs(root,sum);
return ans;
}
//1.定义:对路径进行搜索,无返回值
public static void dfs(TreeNode root,int sum){
//2.递归终止条件,当传入节点为null时,则无需进行下一步递归。
if(root==null)return;//如果为null则直接返回
//3.单层逻辑
res.add(root.val);//3.1将该值加入res
sum=sum-root.val;//3.2并用sum减去该值
//3.3判断此时的节点是否符合要求
//**********重点**********
/*为什么在ans.add(new ArrayList<>(res)) 要重新建立一个list?
*答:在该题中res为成员变量,所有的方法共享一个,指向同一地址;
*如果直接加入,在后续语句中,依旧会修改res,导致其答案不一样
*
*/
if(root.left==null && root.right==null && sum==0)ans.add(new ArrayList<>(res));
//3.4递归处理左右子树
dfs(root.left,sum);
dfs(root.right,sum);
res.remove(res.size()-1);//恢复现状,如果该条路不同则,退出该值,
}
day2复杂链表的复刻
思路分析
- 首先我们可以使用hash表存储每个点对应的来next指针 random指针,而后复现
- 在这里我们还有另一种做法:
1. 在每个点对应的后面复制每一个点
2. 遍历链表处理random指针
3. 将这两条互相交叉的链分开(必须将原有链恢复原样,不然会报错)
class Solution {
public ListNode copyRandomList(ListNode head) {
//1.在原链表的每一个节点后加上节点的复制
if(head==null)return null;
for(ListNode p = head ; p != null;){
ListNode q = new ListNode(p.val);
//防止断链
ListNode next = p.next;
p.next = q;
q.next = next;
p = next;
}
//2.将新加入的节点的random指针指向原链表的random指针
for(ListNode p = head ; p != null ; p = p.next.next){
if(p.random != null){
//新节点的random指针指向原节点random指针指向的下一个节点
p.next.random = p.random.next;
}
}
//3.将两条链分开
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
for(ListNode p = head; p != null; p = p.next){
cur.next = p.next;
cur = cur.next;
// 前面破坏了原head链表,恢复链表
p.next = p.next.next;
}
return dummy.next;
}
}
day3二叉搜索树与双向链表
思路分析
- 我们可以知道对于一个二叉搜索树,中序遍历与有序列表的元素顺序相同
- 所以我们在这里使用中序遍历的模板
- 对于中序遍历会先重最左边的节点(4)开始处理
class Solution {
//指针:用来记录前一个节点·
static TreeNode pre=null;
public TreeNode convert(TreeNode root) {
if(root==null)return null;
dfs(root);
while(root.left!=null)root=root.left;
return root;
}
//我们一第一个节点4[6]来讲解这个单层逻辑
//1.函数定义:将二叉搜索树变为有序的双向链表,无返回值
public static void dfs(TreeNode root){
//2.递归终止条件:若节点为空,则结束
if(root==null)return;
dfs(root.left);
//由于中序遍历,所以root 会依次是 4 6 8 10 .。。。
root.left=pre;// null<—4(<-6)[4<—6]
if(pre!=null)pre.right=root;//pre为空不执行 [4—>(<——)6]
pre=root;//pre=4;[pre=6]
dfs(root.right);
}
}
day4数字排列
思路分析
- 很容易知道这里应该使用回溯算法求解,很经典
class Solution {
static List<Integer> path;
static boolean[] st;//该数是否被使用
static Set<List<Integer>> res;
static int n;
public List<List<Integer>> permutation(int[] nums) {
path=new ArrayList<>();
st=new boolean[nums.length];
res=new HashSet<>();
n=nums.length;
dfs(0,nums);
List<List<Integer>> ans=new ArrayList<>(res);
return ans;
}
public static void dfs(int u,int[] nums){
if(u==n){
res.add(new ArrayList<>(path));
return;
}
for(int i=0;i<n;i++){
if(!st[i]){
path.add(nums[i]);
st[i]=true;
dfs(u+1,nums);
st[i]=false;
path.remove(path.size()-1);
}
}
}
}
day5找出出现次数超过一半的次数
class Solution {
public int moreThanHalfNum_Solution(int[] nums) {
int cnt=1,val=nums[0];
for(int i=0;i<nums.length;i++){
if(nums[i]==val)cnt++;
else cnt--;
//如果出现问题则进行重置
if(cnt==0){
val=nums[i];
cnt=1;
}
}
return val;
}
}
测试例子解析
- 例1:input([1,2,1,1,3])
i=0:nums[i]=1,val=1,cnt=2
i=1:nums[i]=2,val=1,cnt=1
i=2:nums[i]=1,val=1,cnt=2
i=3:nums[i]=1,val=1,cnt=3
i=4:nums[i]=3,val=1,cnt=2
例2:input([2,0,1,1,3])
i=0:nums[i]=2,val=2,cnt=2
i=1:nums[i]=0,val=2,cnt=1
i=2:nums[i]=1,val=2,cnt=0,重置,val=1,cnt=1
i=3:nums[i]=1,val=1,cnt=2
i=4:nums[i]=3,val=1,cnt=1
day6 二进制中1的个数
基本知识点
- 计算机中数的二进制表示
3:00000011
-3:使用补码进行表示
先计算-3绝对值的二进制 00000011
取反:11111100
加1:11111101(这就是计算级的-3表示)
- 无符号整数:计算机里的数是用二进制表示的,最左边的这一位一般用来表示这个数是正数还是负数,这样的话这个数就是有符号整数。无符号整数包括0和正数。
举例:假设有一个 数据类型有8位组成
无符号整数:8位全部表示数值;范围位[0,2^8-1]
有符号整数:最左边表示符号,后7位表示数值;范围[-111 1111,+111 1111]
- 位运算
un&1:将二进制的个位取出
un>>>1:将个位删掉
思路分析
如果n位正数(0),我们直接挨个将每一位数字取出计算即可
负数:我们可以将负数先转换位无符号整数,在进行与正数相同操作
class Solution {
public int NumberOf1(int n) {
int res=0;
int un=n & 0xffffffff;//将数字转换为无符号整数;0xffffffff表示32个1
while(un!=0){
res=res+(un&1);
un=un>>>1;
}
return res;
}
}
day7 二叉树的最近公共祖先
这里推荐一个大佬的题解
大佬题解(非常详细有用)
思路分析
day8 字符串转换为整数
思路分析
按照以下步骤就可以
- 最前面的空格
- 判断符号
- 数位相乘计算整数
- char 转换位数字 :char-‘0’
class Solution {
public int strToInt(String str) {
long res=0;
char[] ch=str.toCharArray();
int k=0;//去除空格
while(k<ch.length && ch[k]==' ')k++;
int flag=1;
if(k<ch.length && ch[k]=='-'){
flag=-1;
k++;
}else if(k<ch.length && ch[k]=='+') k++;
// System.out.println(k<ch.length && ch[k]>='0' && ch[k]<='9');
while(k<ch.length && ch[k]>='0' && ch[k]<='9'){
// System.out.println(ch[k]-'0');
res=res*10+(ch[k]-'0');
if (res > 1e11) break;
k++;
}
// System.out.println(res);
res=(res*flag);
if (res > Integer.MAX_VALUE) res = Integer.MAX_VALUE;
if (res < Integer.MIN_VALUE) res = Integer.MIN_VALUE;
return (int)res;
}
}
day9 构建乘积数组
思路分析
-
B[i]主要由 A[0,i] 与A[i+1,n-1] 两个连续部分组成
-
[0,i-1]的计算
B[i-1]=A[0]…*A[i-2]
B[i] =A[0]… *A[i-2] * A[i-1]
观测可知 B[i]=B[i-1]*A[i-1] -
在后半部分计算有着同样的规律,所以我们可以利用两个循环来实现答案的计算
class Solution {
/*分析可知
*1.B[i]的乘积有两部分组成,[0,i-1]与[i+1,r]
*2.我们可以注意到在计算[0,i-1]部分上的B[i]乘积时,可以利用前一个B[i-1]*a[i-1]得到
*3.第二部分倒序计算时,有着相同的规律
*5.所以我们可以利用两次循环来获得结果
*/
public int[] multiply(int[] A) {
int[] ans=new int[A.length];
if(A.length==0)return ans;
ans[0]=1;
//循环计算第一部分
for(int i=1;i<A.length;i++){
ans[i]=ans[i-1]*A[i-1];
}
//计算第二部分循环
int tmp=1;//tmp记录前一个循环的值
for(int i=A.length-2;i>=0;i--){
tmp=tmp*A[i+1];
ans[i]*=tmp;
}
return ans;
}
}
day10不用加减乘除的加法
思路分析
- 这里我们利用位运算来求解
- 位运算根据值的不同可以分为4种情况
| a(i) | b(i) | c(i) | c(i+1) |
| ---- | ---- | ---- | ------ |
| 0 | 0 | 0 | 0 |
| 0 | 1 | 1 | 0 |
| 1 | 0 | 1| 0 |
| 1 | 1 | 0 | 1 | - 本位:当两个同为1/0时,等于0;否则为1;即 本位 为 a^b
- 进位:两个数同为 1 时 为1 所以 进位 为 a&b
- 我们可以类比10进制,当知道10位数为a,个位数为b—> (a*10+b )
同理,二进制即为:a&b<<1+a^b;
由于不能用加法,我们可以多次循环值进位为0,则此时的 非进位即所求答案
大佬详解
class Solution {
public int add(int num1, int num2) {
if(num2==0)return num1;
return add(num1^num2,(num1&num2)<<1);
}
}
day11求1+2…+n
思路分析
- 根据题意不能使用循环和乘除法,所以很容易想到使用递归
- 最小子问题 n=1 return 1;
- 单层逻辑:n+getSum(n-1)
class Solution {
public int getSum(int n) {
if(n==1)return 1;
return n+getSum(n-1);
}
}
day11 股票的最大价值
思路分析
- 状态表示
- 状态集合:前i个元素中任选两个元素的所有选法
- 属性:序号后-序号前的最大值
- 状态计算
- 不包含 nums[i] 最大值为 dp[i-1]
- 包含nums[i] 最大值 为 nums[i]-min(前i个元素的最小值)
所以状态转移方程为 dp[i]=Math.max(dp[i-1],nums[i]-min)
day13扑克牌的顺子
day14骰子的点数
day15滑动窗口的最大值
day16左旋转字符串
day17反转单词顺序
day18 和为S的正整数序列
day19 和为s的两个数字
day20平衡二叉树
day21二叉树深度
day22二叉树的第k个节点、
day23 数组中数值和下标相等的元素
day24 0到n-1中缺失的数字
day25 数字在排序数组中出现的次数
day 26 链表的第一个公共节点
day27 逆序对的数量
day28字符流中第一个出现的字母
day29丑数
思路分析
- 如果去使用分解质因数的方法进行判断,复杂度过高
- 这里最好的办法就是直接求解丑数序列
- 丑数的质因子只有 2,3,5
- 所以我们这里可以使用一个三路归并,直接将2,3,5的倍数序列及进行排序即可
- 注意可能出现2个数或者三个数共同的公倍数。
class Solution {
public int getUglyNumber(int n) {
int[] tmp=new int[n];
int m2=0,m3=0,m5=0;//三个指针
tmp[0]=1;//1同时属于三个序列,特殊情况
for(int i=1;i<n;i++){
tmp[i]=Math.min(tmp[m2]*2,Math.min(tmp[m3]*3,tmp[m5]*5));
//下面的指针移动,可能同时移动多个指针,比如 10 ,m2,m5同时后移
if(tmp[i]==tmp[m2]*2)m2++;
if(tmp[i]==tmp[m3]*3)m3++;
if(tmp[i]==tmp[m5]*5)m5++;
}
return tmp[n-1];
}
}
day30最长不含重复的字符串子串
思路分析
- 这是滑动窗口最经典的一个应用
- 重要思想,“序列S不重复,加入c后重复,所以重复的一定是c”
class Solution {
public int longestSubstringWithoutDuplication(String s) {
Map<Character,Integer> win=new HashMap<>();//用来记录窗口中各个字母的数量
int res=0;//记录更新答案
char[] ch=s.toCharArray();
int j=0;//指向窗口左边界
//这里 维护一个[j,i]左闭右闭的区间
for(int i=0;i<ch.length;i++){//枚举右边界
char c=ch[i];
win.put(c,win.getOrDefault(c,0)+1);//将下一个字符加入窗口
while(j<ch.length && win.get(c)>=2){//判断是否有重复
char t=ch[j];
j++;
win.put(t,win.get(t)-1);
}
//将所有重复去除后,更新答案
res=Math.max(res,i-j+1);
}
return res;
}
}
day31礼物的最大价值
思路分析
- 这是一个典型的dp问题
- 状态表示:dp[i][j] 从00到 [i,j]可以获得的礼物最大价值(属性)
- 状态计算:每个位置可以向下或者向右移动所以,[i,j] 可以由[i-1,j] [i,j-1]走到
d p [ i ] [ j ] = M a t h . m a x ( d p [ i − 1 ] [ j ] , d p [ i ] [ j − 1 ] ) + g r i d [ i − 1 ] [ j − 1 ] dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1] dp[i][j]=Math.max(dp[i−1][j],dp[i][j−1])+grid[i−1][j−1]
class Solution {
public int getMaxValue(int[][] grid) {
int n=grid.length;
int m=grid[0].length;
int[][] dp=new int[n+1][m+1];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])+grid[i-1][j-1];
}
}
return dp[n][m];
}
}
day 32 把数字翻译成字符串
思路分析
- 这个与爬楼梯问题及为相似
- 我们一个看出每个数字的范围 0~25 所以最多为2位数
- dp[i]:前i个数字的翻译方式
- dp[i]一定包含dp[i-1]个表示方式,只需要第i个数字单独表示一个字符即可
- 如果 i-1,i-2表示的两位数合理则 dp[i]也可由dp[i-2]到达
class Solution {
public int getTranslationCount(String s) {
char[] ch=s.toCharArray();
int n=ch.length;
int[] dp=new int[n+1];
dp[0]=1;
for(int i=1;i<=n;i++){
dp[i]=dp[i-1];//ch[i]作为一个数
//i-1,i作为两位数
int t=0;
if(i>=2)t=(ch[i-2]-'0')*10+(ch[i-1]-'0');
if(t>=10 && t<=25)dp[i]+=dp[i-2];
}
return dp[n];
}
}
day33把数组排成数字最小的数
day34数据流中的中位数
day35数组中最小的k个数
思路分析
这个题最基本的思路,就是堆数组进行排序,排序后取前k个即可
第二个使用堆,把所有数组都输入小顶堆,而后取k次
//使用堆解决
class Solution {
public List<Integer> getLeastNumbers_Solution(int[] input, int k) {
List<Integer> ans=new ArrayList<>();
Queue<Integer> h=new PriorityQueue<>();
for(int num:input)h.add(num);
for(int i=0;i<k;i++)ans.add(h.poll());
return ans;
}
}
day36数字的排列
思路分析
- 这里是一个非常经典的额回溯问题的应用,直接使用回溯算法模板即可解决该问题
- 唯一需要注意的是这里需要去重
- 去重1:我们可以使用HashSet存储答案,将重复答案去除
- 去重2:再搜索的时候就进行 去重,将对应路径进行剪枝
- 由于去重1实现起来较为简单,所以再这里我们使用去重2的方法来写代码
class Solution {
static List<List<Integer>> ans=new ArrayList<>();//存储答案
static List<Integer> path=new ArrayList<>();//存储路径
static boolean[] st=new boolean[10];//标记该数是否被使用过
public List<List<Integer>> permutation(int[] nums) {
Arrays.sort(nums);//必须进行排序
dfs(nums,0);
return ans;
}
//在这里之所以可以使用u,是因为每次只进行一路的递归,而非多路
public static void dfs(int[] nums,int u){
//递归终止条件
if(u==nums.length){
ans.add(new ArrayList<>(path));//注意这里必须使用一个新list将当前的path值存下来,不然答案会改变
return;
}
/*
*判断跳过条件分为两种
*1.nums[i]已经被使用
*2.nums[i]与前一个数(已经被使用)相同
*/
for(int i=0;i<nums.length;i++){
if(st[i] || i!=0 && nums[i]==nums[i-1] && st[i-1] )continue;
path.add(nums[i]);
st[i]=true;
dfs(nums,u+1);
st[i]=false;
path.remove(path.size()-1);
}
}
}
day 37 二叉树的后序遍历
思路分析
- 二叉搜索树的中序遍历是一个有序数组,所以我们可以根据中序遍历结果和后序遍历看是否可以构造出二叉树
- 上述做法比较麻烦,并非最优解,所以我们可以使用判断该序列是否符合后序遍历的特征来判断
class Solution {
/*这里使用了递归,我们使用三部曲
*3.函数定义:判断一个序列是否符合二叉搜索树的后序遍历,是true,否false;
*1.最小子问题:只有一个数,则一定符合返回false
*2.单层逻辑:查询左右子树分界点,判断是否符合条件,递归左右子树的序列
*
*/
//这里我们使用构建二叉搜索树的方法来判断该序列是否合格
static int[] seq;
public boolean verifySequenceOfBST(int [] sequence) {
seq=sequence;
return check(seq,0,seq.length-1);
}
public static boolean check(int[] seq,int l,int r){
if(l>=r)return true;//l>=r:只有一个元素
//单层逻辑
int rootval=seq[r];
//左子树全部小于rootval,右子树全部大于rootval
int k=l;
while(k<r && seq[k]<rootval)k++;//结束后k指向右子树的第一个节点
int index=k;
while(k<r){
if(seq[k]<rootval)return false;
k++;
}
return check(seq,l,index-1) && check(seq,index,r-1);
}
}
day38 之字形打印二叉树
思路分析
- 我们可以发现这里是层序遍历的一个变形,我们只需要使用一个boolean值来记录每层的添加顺序即可
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
static List<List<Integer>> res=new ArrayList<>();
static boolean flag=false;
public List<List<Integer>> printFromTopToBottom(TreeNode root) {
if(root==null)return res;
ArrayDeque<TreeNode> q=new ArrayDeque<>();
q.offer(root);
while(!q.isEmpty()){
int size=q.size();
// TreeNode t=q.peek();
List<Integer> level=new ArrayList<>();
if(!flag){
for(int i=0;i<size;i++){
TreeNode t=q.poll();
if(t.left!=null)q.offer(t.left);
if(t.right!=null)q.offer(t.right);
level.add(t.val);
}
flag=!flag;
}else{
for(int i=0;i<size;i++){
TreeNode t=q.poll();
if(t.left!=null)q.offer(t.left);
if(t.right!=null)q.offer(t.right);
level.add(0,t.val);
}
flag=!flag;
}
res.add(level);
}
return res;
}
}
day39分行从上到下打印二叉树
思路分析
- 这个就是最简单的层序遍历
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
static List<List<Integer>> res=new ArrayList<>();
public List<List<Integer>> printFromTopToBottom(TreeNode root) {
if(root==null)return res;
ArrayDeque<TreeNode> q=new ArrayDeque<>();
q.offer(root);
while(!q.isEmpty()){
int size=q.size();
List<Integer> level=new ArrayList<>();
for(int i=0;i<size;i++){
TreeNode t=q.poll();
if(t.left!=null)q.offer(t.left);
if(t.right!=null)q.offer(t.right);
level.add(t.val);
}
res.add(level);
}
return res;
}
}
day40不分行从上到下打印二叉树
思路分析
- 这是最简单的层序遍历,我们直接适用模板即可
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
static List<Integer> ans=new ArrayList<>();
public List<Integer> printFromTopToBottom(TreeNode root) {
if(root==null)return ans;
ArrayDeque<TreeNode> q=new ArrayDeque<>();
q.offer(root);
while(!q.isEmpty()){
TreeNode t=q.poll();
if(t.left!=null)q.offer(t.left);
if(t.right!=null)q.offer(t.right);
ans.add(t.val);
}
return ans;
}
}
day41 栈的压入弹出序列
思路分析
- 这个题我们可以采用实际模拟的方法来做
- 首先我们遍历pushV将每个元素压入栈,并判断栈顶元素是否与当前应该弹出元素相等,若相当则弹出
- 若最后栈为空,则true
class Solution {
public boolean isPopOrder(int [] pushV,int [] popV) {
//先判断两个数组长度是否相等
if(popV.length!=pushV.length)return false;
ArrayDeque<Integer> s=new ArrayDeque<>();
int k=0;
for(int i=0;i<pushV.length;i++){
s.push(pushV[i]);
while(!s.isEmpty() && s.peek()==popV[k]){
s.pop();
k++;
}
}
return s.isEmpty();
}
}
day42 最小栈
思路分析
- 在这里我们主要使用两个栈来实现该功能
- 主栈(sta)用来实现push(),pop(),top()
- 辅助栈(h)用来实现 getMin()
top()与getMin()并不会对栈中的数造成影响,所以我们的主要设计应该放在pop(),push>>()功能上
- 主栈实现与正常的栈相同,重点时辅助栈的设计
- 栈的特性是先进后出,所以当栈中有一个比cur小的数时,最小值 就不会时cur,cur不必压入h
- 当弹出一个数时,如果s弹出的数与h栈顶数相同,则h弹出
总而言之,s是一个从单调不增栈。
class MinStack {
static ArrayDeque<Integer> sta;
static ArrayDeque<Integer> h;
/** initialize your data structure here. */
public MinStack() {
sta=new ArrayDeque<>();
h=new ArrayDeque<>();
}
public void push(int x) {
sta.push(x);
if(h.isEmpty() || h.peek()>=x)h.push(x);
}
public void pop() {
int val=sta.pop();
if(val==h.peek())h.pop();
}
public int top() {
return sta.peek();
}
public int getMin() {
return h.peek();
}
}
/**
* Your MinStack object will be instantiated and called as such:
* MinStack obj = new MinStack();
* obj.push(x);
* obj.pop();
* int param_3 = obj.top();
* int param_4 = obj.getMin();
*/
day43顺时针打印链表
思路分析
- 当坐标为(x,y)时,我们一般有四种选择,向上,向下,向左,向右。顺时针打印正好需要这四种走法
- 我们可以发现顺时针打印:右—下—左—上,一直在重复这个顺序
- 转向:当下一个坐标发生越界错误,或者遇到已经访问过的元素则转向
class Solution {
public int[] printMatrix(int[][] matrix) {
//特判
if(matrix.length==0 ||matrix[0].length==0)return new int[0];
int r=matrix.length;
int c=matrix[0].length;
int[] ans=new int[c*r];//用来存储答案
//定义四个方向
int[] dx={0,1,0,-1};
int[] dy={1,0,-1,0};
boolean[][] st=new boolean[r][c];//访问过true,否则false
int x=0,y=0,d=0;//横坐标,纵坐标,方向
for(int i=0;i<r*c;i++){
ans[i]=matrix[x][y];
st[x][y]=true;
int a=x+dx[d];
int b=y+dy[d];
//判断是否需要转向
if(a<0 || a>=r || b<0 || b>=c || st[a][b] ){
d=(d+1)%4;
a=x+dx[d];
b=y+dy[d];
}
x=a;
y=b;
}
return ans;
}
}
day45 二叉树的镜像
思路分析
- 所谓的镜像,就是将二叉树的左右节点进行交换
- 这里我们依旧使用递归来完成该操作
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
//交换每一个节点的左右节点
public void mirror(TreeNode root) {
//递归终止条件
if(root==null)return;
// System.out.println(root.left==null);
//单层逻辑
TreeNode t=root.left;
root.left=root.right;
root.right=t;
mirror(root.left);
mirror(root.right);
}
}
day46对称的二叉树
思路分析
- 这里起始就是判断 节点是否相等
- (左,右)—>(左左,右右),(左右,右左)—》。。。。
- 这里我们依旧使用递归进行处理
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null)return true;
return check(root.left,root.right);
}
//检查两个节点是否相等
public static boolean check(TreeNode left,TreeNode right){
/*这里包含了三种情况
*1.left==null ,right==null:不必进行递归,直接返回true
*2.left==null,right!=null:返回false,不必递归
*3.left!=null,right==null:返回false,不必递归
*/
if(left==null || right==null)return left==right;
if(left.val!=right.val){
return false;
}
return check(left.left,right.right)&&check(left.right,right.left);
}
}
day47 树的子结构
思路分析
- 我们只需要遍历A,对比A中节点为根的子树是否与B相等即可
- 所以我们需要一个辅助函数连进行对比两个树是否相同
- 遍历我们同样使用递归来实现
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean hasSubtree(TreeNode pRoot1, TreeNode pRoot2) {
//特判
if(pRoot1==null || pRoot2==null)return false;
//遍历根节点,完全相同的则true
if(check(pRoot1,pRoot2))return true;
//否则遍历左右节点
return hasSubtree(pRoot1.left,pRoot2) || hasSubtree(pRoot1.right,pRoot2);
}
//比较当前子树与对应子树是否相同
public static boolean check(TreeNode t1,TreeNode t2){
/*终止条件分多种情况
*1.t2==null:说明子树已经匹配完,返回true
*2.t1==null&&t2!=null:说明不能完全匹配子树,false
*3.t1.val!=t2.val:不匹配返回false
*返回值:如果满足条件则对比左右子节点
*/
if(t2==null)return true;//把t2的节点遍历至叶子节点时返回true
if(t1==null || t1.val!=t2.val)return false;
return check(t1.left,t2.left)&&check(t1.right,t2.right);
}
}
day48链表的反转
思路分析
- 记录当前节点的前一个节点
- 改变他们之间的指针关系
- 两个指针后移即可
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre=null;
ListNode cur=head;
while(cur!=null){
ListNode next=cur.next;
cur.next=pre;
pre=cur;
cur=next;
}
return pre;
}
}
day49合并两个有序链表
思路分析
- 在这里我们可以直接使用归并排序的相关思路即可
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode merge(ListNode l1, ListNode l2) {
ListNode dummy=new ListNode(-1);
ListNode cur=dummy;
while(l1!=null && l2!=null){
if(l1.val<l2.val){
cur.next=l1;
cur=cur.next;
l1=l1.next;
}else{
cur.next=l2;
cur=cur.next;
l2=l2.next;
}
}
while(l1!=null){
cur.next=l1;
cur=cur.next;
l1=l1.next;
}
while(l2!=null){
cur.next=l2;
cur=cur.next;
l2=l2.next;
}
return dummy.next;
}
}
day50 链表环的入口
思路分析
- 思路非常巧妙,我们使用快慢指针对链表进行扫描,证明如下
- b:链表入口
- c:快慢指针第一次相遇点
- 第 一次相遇
:
后为当前指针路程
fast: x+(y+z)*n+y
slow: x+y
fast速度为slow的两倍所以:2(x+y)=x+(y+z)n+y
化简可得:x=(n-1)(y+z)+z相遇后
slow走 x步: x+n*(y+z),刚好到达b点
new指针:(从head)走 x步也刚好到达b点,二者相遇,可得入口
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
class Solution {
public ListNode entryNodeOfLoop(ListNode head) {
ListNode slow=hasCycle(head);
if(slow==null)return null;
else{
ListNode fast=head;
while(fast!=slow){
fast=fast.next;
slow=slow.next;
}
}
return slow;
}
public static ListNode hasCycle(ListNode head){
ListNode fast=head;
ListNode slow=head;
while(fast!=null && fast.next!=null){
fast=fast.next.next;
slow=slow.next;
if(fast==slow)return slow;
}
return null;
}
}
day51链表中倒数第k个节点
思路分析
- 我们先遍历一次计算出链表长度cnt
- 计算节点正序的位置
- 第二次遍历可得
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public ListNode findKthToTail(ListNode pListHead, int k) {
int cnt=0;
ListNode cur=pListHead;
while(cur!=null){
cnt++;
cur=cur.next;
}
cur=pListHead;
//特判
if(cnt<k)return null;
cnt=cnt-k;
while(cnt-->0){
cur=cur.next;
}
return cur;
}
}
day52调整顺序使数组的奇数在偶数前
思路分析
- 我们只需要使用类似于快排的思想,使用两个指针对数组进行一遍扫描即可
class Solution {
//使用快排的相关思想,双指针进行扫描
public void reOrderArray(int[] array) {
int len=array.length;
if(len==0)return;
int l=-1,r=len;
while(l<r){
do{
l++;
}while(l<len&&array[l]%2!=0);
do{
r--;
}while(r>=0 && array[r]%2==0);
if(l<r){
int t=array[l];
array[l]=array[r];
array[r]=t;
}
}
return;
}
}
day53删除链表的重复节点
思路分析
- 我们依旧使用两个指针进行扫描
- 第一个指针记录可能需要删除节点的前一个节点
- 第二个指针:对重复部分进行扫描,
- 指针移动:只需要判断第一个指针是否需要移动即可
class Solution {
public ListNode deleteDuplication(ListNode head) {
//虚拟节点:不需要对头节点进行特殊处理
ListNode dummy=new ListNode(-1);
dummy.next=head;
ListNode cur=dummy;
while(cur.next!=null){//下面需要用的cur.next
//p指针初始指向cur的下一个节点
ListNode p=cur.next;
//移动条件:只要cur的下一个指针相等便移动
while(p!=null&& cur.next.val==p.val)p=p.next;
//根据指针的位置判断是否需要删除
if(cur.next.next==p){
cur=cur.next;
}else{
cur.next=p;
}
}
return dummy.next;
}
}
day54在O(1)时间删除链表节点
思路分析
- 正常删除节点,我们需要去寻找他的前一个节点,但是这里并未输入头节点,不适用
- 我们可以将后一个值赋给当前节点,而后删除后一个节点
class Solution {
public void deleteNode(ListNode node) {
node.val=node.next.val;
node.next=node.next.next;
}
}
day55表示数值的字符串
思路分析
- 这道题十分繁琐,每一步都不难,难点在不符合要求的数太多,且情况各异,所以我们需要一步一步处理
- 处理步骤
- 去除首尾空格—>判断此时长度 :
" "
- 判断正负号,有则跳过—判断是否为空,和是否只有一个. :
"+" / "+ ."/ "."
(负号同理) - 若是1.2全部符合,我们便可以对其进行扫描,并且记录
.
和e
的个数及相对位置(从前向后遍历,由个数可推断出位置)
对于字符串中每一个字符
1~9
.
e|E
- 其他字符:
return false
- 去除首尾空格—>判断此时长度 :
class Solution {
/*
*1. ""
*2." "
*3."+ " /"+."
*4."123 e 4 . 56" "123 .4 . 5 "
*5. "123e" "exxx" "E xx E xx" ".EXXX"
*6."XXXE+"(负号同理)
*7.
*/
public boolean isNumber(String s) {
//1. ""
if(s.length()==0)return false;
// 2.去除首尾空格 " "
int l=0,r=s.length()-1;
while(s.charAt(l)==' ')l++;
while(s.charAt(r)==' ')r--;
if(l>r)return false;
s=s.substring(l,r+1);//将多余部分去除
// 3.判断符号 是正符号则 删除 符号
if(s.charAt(0)=='-' || s.charAt(0)=='+')s=s.substring(1);
// System.out.println(s);
//4.判断字符串是否只有正负号或者正负号和一个. "+" || "+."
if(s.length()==0 || (s.charAt(0)=='.' && s.length()==1))return false;
int dot=0,e=0;//统计 . 和e的个数
for(int i=0;i<s.length();i++){
if(s.charAt(i)>='0' && s.charAt(i)<='9')continue;
else if(s.charAt(i)=='.'){
dot++;
//5." xxx e xxx ." "xx . xx ."
if(e>0 || dot>1)return false;
}else if(s.charAt(i)=='e' || s.charAt(i)=='E'){
e++;
//6. "123e" "exxx" "E xx E xx" ".EXXX"
if(i+1==s.length() || i==0 || e>1 ||((i==1)&&s.charAt(0)=='.' )){
return false;
}
if(s.charAt(i+1)=='+' || s.charAt(i+1)=='-'){
if(i+2==s.length())return false;
i++;
}
}else return false;
}
return true;
}
}
day56 数值的整数次方
思路分析
- 这里需要使用快速幂
- 我们可以直接使用快速幂相关模板即可
class Solution {
public double Power(double base, int exponent) {
return (qmi(base,exponent));
}
public static double qmi(double base ,int exponent){
double res=1.0;
long k=exponent;
if(k<0){
base=1/base;
k=-k;
}
while(k>0){
if((k&1)==1)res=res*base;
k=k>>1;
base=base*base;
}
return res;
}
}
day57 二进制中1的个数
思路分析
计算机数值的表示,及位运算可查看位运算
- 这里我们需要注意到负数是使用补码表示的,所以我们需要首先将数字转换为无符号数值
- 将无符号数字逐位取出,统计即可
class Solution {
public int NumberOf1(int n) {
int u=n&(0xffffffff);
int res=0;
while(u!=0){
res+=u&1;
u=u>>>1;//注意这里用的无符号右移,如果使用 >> 会使得移动时高位补1,结果出些错误
}
return res;
}
}
day58 机器人的运动范围
思路分析
- 这个问题我们可以使用宽搜的模板来做
- 我们从原点出发逐步向外扩展
- 每个点如果符合要求我们就将其上下四个方向放入队列
class Solution {
//宽搜魔板
static int thre;
static boolean[][] st;
public int movingCount(int threshold, int rows, int cols)
{
int res=0;
thre=threshold;
int[] dx={0,1,0,-1};
int[] dy={1,0,-1,0};
ArrayDeque<int[]> q=new ArrayDeque<>();
q.offer(new int[]{0,0});
st=new boolean[rows][cols];
while(!q.isEmpty()){
int[] tmp=q.poll();
int x=tmp[0],y=tmp[1];
// System.out.println(x+" "+y);
if(x<0 || x>=rows || y<0 || y>=cols || st[x][y] || getSum(x)+getSum(y)>thre)continue;
res++;
st[x][y]=true;
for(int i=0;i<4;i++){
int a=x+dx[i],b=y+dy[i];
q.offer(new int[]{a,b});
}
}
return res;
}
public static int getSum(int x){
int res=0;
while(x!=0){
res+=x%10;
x=x/10;
}
return res;
}
}
day59 剪绳子
思路分析
- 这是一道非常经典的数学规律题
- 首次,显然1不会出现在其中
- n=4,最长可以分为2,2乘积不变
- n=5,3(n-3)=3n-9>n,化简2n>9,所以不等式成立
- n=6:33>22*2
- 综上,我们可以证明我们需要尽可能的将绳子分为长度为3的长度
class Solution {
public int maxProductAfterCutting(int length)
{
int res=1;
//长度小于3,必须分为两段
if(length<=3)return (length-1);
//33余1的情况
if(length%3==1){
res=4;
length=length-4;
}
//33余2的情况
if(length%3==2){
res=2;
length=length-2;
}
//经过预处理后length一定可以整除3
while(length>0){
res*=3;
length=length-3;
}
return res;
}
}
day60旋转数组的最小值
思路分析
- 这里我们需要定位到最小值,我们可以发现,min的最小值左边全部大于该值,min的右边全部小于该值
- 我们根据这个特征可以进行特殊的二分
- 将左右的边界逐步逼近至最小值的位置
class Solution {
public int findMin(int[] nums) {
//特殊情况
if(nums.length==0)return -1;
//左右边界
int l=0,r=nums.length-1;
if(nums[0]<nums[r])return nums[0];
while(r>=0 && nums[r]==nums[0])r--;
if(l>=r)return nums[0];
while(l<r){
int mid=l+r>>1;
if(nums[mid]<nums[0])r=mid;
else l=mid+1;
}
return nums[l];
}
}
day61 矩阵中的路径
思路分析
- 这里我们需要分两步进行,第一步遍历字符串的起点
- 第二步我们从起点进行深度遍历(回溯算法)如果可以遍历到字符串最后一个证明,存在返回true
class Solution {
//深度优先搜索模板
//先遍历符合条件的
//回溯算法
public boolean hasPath(char[][] matrix, String str) {
char[] ch=str.toCharArray();
for(int i=0;i<matrix.length;i++){
for(int j=0;j<matrix[0].length;j++){
if(dfs(matrix,ch,i,j,0))return true;
}
}
return false;
static boolean dfs(char[][] matrix,char[] ch,int x,int y,int u){
//递归终止条件,不符合直接返回false;
if(matrix[x][y]!=ch[u])return false;
//根据上一个判断语句可知最后一个一定相等,所以,如果到达最后一个位置,我们可以返回true;
if(u==ch.length-1)return true;//
//记录当前字符
char t=matrix[x][y];
//改为不可能匹配的字符,直接去除该回头路
matrix[x][y]='*';
//这里定义四个方向
int[] dx={0,1,0,-1},dy={1,0,-1,0};
//进行下一步搜索,因为我们是矩阵,所以需要向4个方向进行扩展
for(int i=0;i<4;i++){
int a=x+dx[i],b=y+dy[i];
if(a>=0 && a<matrix.length && b>=0 && b<matrix[0].length){
if(dfs(matrix,ch,a,b,u+1))return true;
}
}
//回溯算法,恢复原状
matrix[x][y]=t;
return false;
}
}
day62 斐波那契数列
思路分析
这个是个非常简单的循环的使用,我们直接使用该递推公式进行编写即可
class Solution {
public int Fibonacci(int n) {
//特殊情况
if(n==0)return 0;
if(n<=2)return 1;
List<Integer> t=new ArrayList<>();
t.add(1);
t.add(1);
for(int i=2;i<n;i++){
t.add(t.get(i-2)+t.get(i-1));
}
return t.get(n-1);
}
}
day63用两个栈实现队列
思路分析
- 这种题目最重要的就是输入输出的设计
- 输入:直接压入主栈
- 弹出:辅助栈h为空,直接弹出,否则将主栈数据全部压入,而后弹出
- 判空:两个栈都为空则为空
class MyQueue {
ArrayDeque<Integer> sta;//主栈
ArrayDeque<Integer> h;
/** Initialize your data structure here. */
public MyQueue() {
sta=new ArrayDeque<>();
h=new ArrayDeque<>();
}
/** Push element x to the back of queue. */
public void push(int x) {
sta.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int pop() {
if(h.isEmpty()){
while(!sta.isEmpty()){
h.push(sta.pop());
}
}
return h.pop();
}
/** Get the front element. */
public int peek() {
if(h.isEmpty()){
while(!sta.isEmpty()){
h.push(sta.pop());
}
}
return h.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return h.isEmpty()&&sta.isEmpty();
}
}
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue obj = new MyQueue();
* obj.push(x);
* int param_2 = obj.pop();
* int param_3 = obj.peek();
* boolean param_4 = obj.empty();
*/
day64二叉树的下一个节点*
思路分析
- 这里我们根据具体例子以及中序遍历的规律来进行推倒
- 有右孩子的节点F,他的后继节点就是右子树最左边的节点(F)
- 没有右孩子的节点A:他的后继节点- (寻找最近的左子树的祖宗节点)向上找到是其父亲节点左节点的节点tmp(A),tmp的父亲节点
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode father;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode inorderSuccessor(TreeNode p) {
if(p.right!=null){
p=p.right;
while(p.left!=null)p=p.left;
return p;
}
while(p.father!=null && p.father.right==p)p=p.father;//最近的左子树的祖宗节点
return p.father;
}
}
day65 重建二叉树
思路分析
- 这道题我们使用递归来进行建树
- 首先递归终止条件:当传入的前序列表为空时,返回空节点
- 本层操作:建立根节点,对左右子树进行递归
- 返回值:返回根节点
/**
* Definition for a binary tree node.
* class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
TreeNode root=bulit(preorder,0,preorder.length-1,inorder,0,inorder.length-1);
return root;
}
public static TreeNode bulit(int[] pre,int ps,int pe,int[] in,int il,int ir){
//递归终止条件
if(ps>pe)return null;
int rootval=pre[ps];
int index=0;//记录rootval在in中的下标
//寻找根节点在中序遍历中的位置
for(int i=il;i<=ir;i++){
if(in[i]==rootval){
index=i;
break;
}
}
TreeNode root=new TreeNode(rootval);
int leftlen=index-il;//左子树长度
//递归处理左右子树,参数可以根据具体例子推导更容易理解
root.left=bulit(pre,ps+1,ps+leftlen,in,il,index-1);
root.right=bulit(pre,ps+leftlen+1,pe,in,index+1,ir);
return root;
}
}
day66从尾到头打印链表
思路分析
- 这道题是一道比较简单的题,我们可以采用多种做法
- 先反转,在遍历
- 利用栈,ArrayList等数据结构
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
class Solution {
public int[] printListReversingly(ListNode head) {
ArrayList<Integer> tmp=new ArrayList<>();
ListNode cur=head;
while(cur!=null){
tmp.add(cur.val);
cur=cur.next;
}
Collections.reverse(tmp);
int[] ans=new int[tmp.size()];
for(int i=0;i<tmp.size();i++)ans[i]=tmp.get(i);
return ans;
}
}
day67 替换空格
思路分析
- 对数组进行遍历,如果不为空直接加入该字母
- 为空加入 “%20”
class Solution {
public String replaceSpaces(StringBuffer str) {
StringBuilder sb=new StringBuilder();
char[] ch=new String(str).toCharArray();
for(int i=0;i<ch.length;i++){
if(ch[i]!=' '){
sb.append(ch[i]);
}else{
sb.append("%20");
}
}
return new String(sb);
}
}
day68 二维数组中的查找
思路分析
- 注意数组右上角的数字,是改行最大的,该列最小的
- 我们通过tar与右上角的数字进行对比,每次可以排除一行或一列
class Solution {
//注意右上角的数字
public boolean searchArray(int[][] array, int target) {
if(array.length==0 || array[0].length==0)return false;
int rows=array.length,cols=array[0].length;
int i=0,j=cols-1;
while(i<rows && j>=0){
if(array[i][j]>target){
j--;
}else if(array[i][j]<target){
i++;
}else{
return true;
}
}
return false;
}
}
day69 不修改找出数组重复的数字
思路分析
- 这里我们使用二分的思想解决这道题
- 范围在 1~n内,我们每次对范围进行二分,则一定会存在某个范围的数大于该范围的宽度,则对该范围继续搜索
class Solution {
public int duplicateInArray(int[] nums) {
int n=nums.length-1;
int l=1,r=n;//注意是对 1~n进行二分
while(l<r){
int mid=l+r>>1;
int cnt=0;
for(int i=0;i<=n;i++){
if(nums[i]>=l && nums[i]<=mid)cnt++;
}
if(cnt>mid-l+1)r=mid;
else l=mid+1;
}
return l;
}
}
day70找出数组中重复的数字
思路分析
- 我们将所有的数与其下标相同,不同则交换道相同为至
- 如果一个数对应位置已经符合条件,说明该数重复
class Solution {
public int duplicateInArray(int[] nums) {
int n=nums.length;
//查看是否存在不符合条件的数字
for(int num:nums){
if(num<0 || num>n-1)return -1;
}
for(int i=0;i<n;i++){
while(nums[i]!=i){
if(nums[nums[i]]==nums[i])return nums[i];
int tmp=nums[i];
nums[i]=nums[nums[i]];
nums[tmp]=tmp;
}
}
return -1;
}
}