剑指offer
- JZ1 二维数组中的查找
- JZ2 替换空格
- JZ3 从尾到头打印链表
- JZ4 重建二叉树
- JZ5 用两个栈实现队列
- JZ6 旋转数组的最小数字
- JZ7 斐波那契数列
- JZ8 跳台阶
- JZ9 变态跳台阶
- JZ10 矩形覆盖
- JZ11 二进制中1的个数
- JZ12 数值的整数次方
- JZ13 调整数组顺序使奇数位于偶数前面
- JZ14 链表中倒数第k个节点
- JZ15 反转链表
- JZ16 合并两个排序的链表
- JZ17 树的子结构
- JZ18 二叉树的镜像
- JZ20 包含min函数的栈
- JZ21 栈的压入、弹出序列
- JZ22 从上往下打印二叉树
- JZ23 二叉搜索树的后序遍历序列
- JZ24 二叉树中和为某一值的路径
- JZ25 复杂链表的复制
- JZ26 二叉搜索树与双向链表
- JZ27 字符串的排列
- JZ28 数组中出现次数超过一半的数字
- JZ29 最小的K个数
- JZ30 连续子数组的最大和
- JZ31 整数中1出现的次数(从1到n整数中1出现的次数)
- JZ32 把数组排成最小的数
- JZ33 丑数
- JZ34 第一个只出现一次的字符
- JZ35 数组中的逆序对
- JZ36 两个链表的第一个公共结点
- JZ38 二叉树的深度
- JZ39 平衡二叉树
- JZ40 数组中只出现一次的两个数字
- JZ41 和为S的连续正数序列
- JZ42 和为S的两个数字
- JZ43 左旋转字符串
- JZ44 翻转单词序列
- JZ47 求1+2+3+...+n
- JZ50 数组中重复的数字
- JZ51 构建乘积数组
- JZ55 链表中环的入口结点
- JZ56 删除链表中重复的结点
- JZ57 二叉树的下一个结点
- JZ59 按之字形顺序打印二叉树
- JZ60 把二叉树打印成多行
- JZ62 二叉搜索树的第k个结点
迷茫,是青春最真实的状态;但奋斗,才是青春的主基调;努力是打败焦虑的绝好方法!
JZ1 二维数组中的查找
题目:二维数组中的查找
public class Solution {
//双重for循环,遍历查找
public boolean Find(int target, int [][] array) {
//行遍历,array.length为行数
for(int i = 0;i<array.length;i++){
//列遍历,array[0].length为列数
for(int j=0;j<array[0].length;j++){
//存在目标值
if(array[i][j] == target)
return true;
}
}
//默认不存在
return false;
}
}
JZ2 替换空格
题目:替换空格
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param s string字符串
* @return string字符串
*/
public String replaceSpace (String s) {
// write code here
//定义一个String类型保存替换后的字符串
String res= "";
//遍历String里面的每一个字母
for(int i=0;i<s.length();i++){
//为空格,注意s.charAt(i)的类型是字符,单引号
if(s.charAt(i) == ' '){
//为空格,替换
res += "%20";
}else{
//不为空格,不替换
res += s.charAt(i);
}
}
//返回得到的字符串
return res;
}
}
JZ3 从尾到头打印链表
题目:从尾到头打印链表
暂时有点问题代码
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
public class Solution {
//本质为反转链表输出
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
//用于保存数值
ArrayList<Integer> array = new ArrayList<>();
if(listNode == null || listNode.next == null){
return null;
}
//定义三个指针
ListNode beg = null;
ListNode mid = listNode;
ListNode end = listNode.next;
//无限循环反转
while(true){
//反转
mid.next = beg;
//end为空,意味着mid指向最后一个节点,全部反转完成
if(end == null){
//全部反转完成,退出循环
break;
}
//反转之后下一个节点,三个指针同步后移
beg = mid;
mid = end;
end = end.next;
}
//反转完成,反转后的链表是以mid为头节点的链表
//输出的是数值,遍历存储数值
while(mid != null){
array.add(mid.val);
mid = mid.next;
}
//返回存储的反转链表的值
return array;
}
}
JZ4 重建二叉树
题目:重建二叉树
- 先找到根
- 找左右子树
- 递归
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.*;
public class Solution {
//dfs,递归
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
//空树
if(pre.length == 0 || in.length == 0){
return null;
}
//找到根节点,并且创建树的根节点,因为数组中只有值
//前序序列第一个为根节点
TreeNode root = new TreeNode(pre[0]);
//以根结点在中序序列中划分左右子树
for(int i=0;i<in.length;i++){
//找到根节点
//证明左子树i个元素,右子树in.length-i-1个元素
if(in[i] == pre[0]){
//左子树
//先序第一个是根节点,从下标1开始,到包括下标i,因为i个元素
root.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1)
,Arrays.copyOfRange(in,0,i));
//右子树
//除了左子树和根,剩下的都是右子树
root.right = reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length)
,Arrays.copyOfRange(in,i+1,in.length));
}
}
return root;
}
}
JZ5 用两个栈实现队列
题目:用两个栈实现队列
- 队列加入元素方式和栈一样,所以可以
直接push
- 队列
先进先出
,栈后进先出
,所以出栈的时候需要两个栈进行双重逆序转换- 调用出栈函数时,有两种情况:
- 栈2为空,出栈,首先要从栈1放入元素所有元素再出栈
- 栈2不为空,出栈,先直接出栈2的元素,到栈2元素出栈完,再执行1步骤
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
//入栈队列的方式和栈一致
//直接push即可
stack1.push(node);
}
public int pop() {
//队列出栈采用两个栈
//第一个栈入栈,逆序
//出栈放入第二个栈,再出栈,即为双重逆序,即正序,即成队列
//栈2为空,要出栈,首先要从栈1放入元素所有元素再出栈
//因为栈1最底下的元素才是第一个入栈的,所以要全部移到栈2
if(stack2.empty()){
//栈2空,需要从栈1取数据
while(!stack1.empty()){
//栈1栈顶入栈栈2
//pop方法取栈顶并出栈,peek方法仅取栈顶
stack2.push(stack1.pop());
}
}
//栈2不空,直接从栈2出栈即可,所以返回栈2栈顶
return stack2.pop();
}
}
JZ6 旋转数组的最小数字
题目:旋转数组的最小数字
- 暴力求解,循环遍历
import java.util.ArrayList;
public class Solution {
//暴力求解,遍历数组
public int minNumberInRotateArray(int [] array) {
//第一个数值开始
int min = array[0];
//循环遍历
for(int i = 1;i<array.length;i++){
//比较最小值
if(array[i] <= min){
min = array[i];
}
}
//返回最小值
return min;
}
}
- 二分查找
import java.util.ArrayList;
public class Solution {
// 二分查找
public int minNumberInRotateArray(int[] array) {
// 数组大小为0
if (array.length == 0)
return 0;
// 定义数组左边界
int left = 0;
// 定义数组右边界
int right = array.length - 1;
// 定义基准
int target;
// 定义中间值
int mid;
// 循环查找,条件为左界小于右界
while (left < right) {
// 去最右边作为基准,随着边界改变而改变
target = array[right];
// 中值随边界变化而变
mid = (left + right) / 2;
// 最右边的值大于中间的
// 本来递增,旋转后得到的非递减
// 所以最小值一定不在后面,但是mid那个可能是最小的
if (array[mid] < target) {
right = mid;
} else if (array[mid] > target) {
// 中值大于最后的值
// 旋转得到表示中值前面的都大于最后的
// 所以中值后的值中才存在最小的
left = mid + 1;
} else {
// 最后一个值和中值相等
// 无法判断,去掉最后一个值,继续
// 因为中值和最后一个相等,去掉最后无碍
right = right - 1;
}
}
// 最后左右边界相等,即为最小值,返回哪一个边界都可
// 返回左边界198ms,返回右边界188ms
// 不懂为什么,但是返回右边界复杂度小
return array[right];
}
}
JZ7 斐波那契数列
题目:斐波那契数列
public class Solution {
//斐波那契数列,典型递归调用
public int Fibonacci(int n) {
//第0项和第1项,为0和1
if(n==0 || n==1)
return n;
else
//第n项,等于第n-1项和第n-2项的和
//递归调用,自己调用自己
return Fibonacci(n-1) + Fibonacci(n-2);
}
}
JZ8 跳台阶
题目:跳台阶
同斐波那契数列,递归调用即可。
public class Solution {
public int jumpFloor(int target) {
//边界条件
if(target>=0 && target <= 2){
return target;
}
//跳到第n级台阶的跳法等于跳到第n-1级台阶的跳法加上跳到第n-2级台阶的跳法
//到了n-1级,就只有一种跳法了
//n-2级,也就剩一种跳法了
//n-2后,要是一级一级跳,就又是在n-1级里面包含了,可以不考虑
return jumpFloor(target-1) + jumpFloor(target-2);
}
}
JZ9 变态跳台阶
题目:跳台阶扩展问题
public class Solution {
public int jumpFloorII(int target) {
//边界条件
if(target == 0 || target == 1){
return 1;
}
//f(n) = f(n-1) + ......+f(0)
//f(n-1) = f(n-2) + ......+f(0)
//相减,所以f(n) = 2*f(n-1)
//所以是2的n-1次幂
return 2*jumpFloorII(target - 1);
}
}
JZ10 矩形覆盖
题目:矩形覆盖
- 递推(11ms)
public class Solution {
//递推法
public int rectCover(int target) {
//边界条件
if(target == 0 || target == 1 || target == 2){
return target;
}
int res = 0;
int first = 1;
int second = 2;
//循环向后求值
for(int i=3;i<=target;i++){
res = first + second;
first = second;
second = res;
}
return res;
}
}
- 递归(389ms)
public class Solution {
//递归法
public int rectCover(int target) {
//边界条件
if(target == 0)
return 0;
if(target == 1)
return 1;
if(target == 2)
return 2;
//递归
return rectCover(target-2) + rectCover(target-1);
}
}
- 记忆递归(备忘录法)(10ms)
- 核心代码模式
import java.util.*;
public class Solution {
//声明数组,也可以创建数组,但不能初始化在类文件中
int[] bp = new int[200];
//构造函数中创建并且初始化
//利于类的继承
Solution(){
for(int i=0;i<bp.length;i++){
bp[i] = 0;
}
}
//记忆递归,备忘录法
public int rectCover(int target) {
//边界条件
if(target == 1 || target == 2){
//保存数据
bp[target] = target;
}
//当前值没有计算过,考虑bp[0]的值,那个0为边界,要去掉
if(bp[target] == 0 && target != 0){
//递归进行计算
//计算完毕进行保存
bp[target] = rectCover(target-2) + rectCover(target-1);
return bp[target];
}else{
//已经计算过该值,直接返回
return bp[target];
}
}
}
- ACM模式
package com.hnucm;
import java.util.*;
public class Solution {
// 声明数组
static int[] bp;
//记忆递归,备忘录法
public static int rectCover(int target) {
//边界条件
if(target == 1 || target == 2){
//保存数据
bp[target] = target;
}
//当前值没有计算过,考虑bp[0]的值,那个0为边界,要去掉
if(bp[target] == 0 && target != 0){
//递归进行计算
//计算完毕进行保存
bp[target] = rectCover(target-2) + rectCover(target-1);
return bp[target];
}else{
//已经计算过该值,直接返回
return bp[target];
}
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n;
bp = new int[200];
for (int i = 0; i < bp.length; i++) {
bp[i] = 0;
}
while (sc.hasNext()) {
n = sc.nextInt();
System.out.println(rectCover(n));
}
}
}
- 动态规划(11ms)
动态规划有点像递归填表,和记忆递归和递推差不多,更像递推填表,参照上面的即可。
JZ11 二进制中1的个数
题目:二进制中1的个数
public class Solution {
/**
* 求负数的补码的方法。 注意: 负数的补码是在其原码的基础上,符号位不变,其余位取反,然后加1
* @param a
* @author lhever 2017年4月4日 下午8:42:47
* @since v1.0
*/
//负数
public static int back(int a)
{
int s = 0;
for (int i = 0; i < 32; i++)
{
// 0x80000000 是一个首位为1,其余位数为0的整数
int t = (a & 0x80000000 >>> i) >>> (31 - i);
if(t == 1){
s++;
}
}
return s;
}
//正数
int m = 0;
public int normal(int a){
if(a%2 == 1)
m++;
if(a/2 == 0)
return m;
else
return normal(a/2);
}
public int NumberOf1(int n) {
if(n>=0){
return normal(n);
}else{
return back(n);
}
}
}
JZ12 数值的整数次方
题目:数值的整数次方
- 递推(36ms)
public class Solution {
//递推
public double Power(double base, int exponent) {
double s = 1.0;
if(base == 0){
return 0;
}
else if(exponent == 0){
return 1;
}else if(exponent > 0){
for(int i=0;i<exponent;i++){
s *= base;
}
}else{
for(int i=0;i<Math.abs(exponent);i++){
s *= base;
}
s = 1.0/s;
}
return s;
}
}
JZ13 调整数组顺序使奇数位于偶数前面
- 先进先出,采用队列
- 遍历数组,加入队列
- 遍历数组,改变数组值
- 队列(时间:400ms,O(n),空间:30MB,O(n))
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] reOrderArray (int[] array) {
// write code here
//先进先出,采用队列
Queue<Integer> q1 = new LinkedList();
Queue<Integer> q2 = new LinkedList();
//遍历每一个数组元素
for(int i=0;i<array.length;i++){
//偶数
if(array[i] % 2 == 0){
//加入队列2
q2.offer(array[i]);
}
//奇数
else{
//加入队列1
q1.offer(array[i]);
}
}
//遍历每一个元素,更换值
for(int i=0;i<array.length;i++){
//队列1没有出完
if(!q1.isEmpty()){
//存储并出队
array[i] = q1.poll();
}
//队列1结束,队列2开始
else{
array[i] = q2.poll();
}
}
return array;
}
}
- 直接交换法(时间:O(n),空间:O(n))
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] reOrderArray (int[] array) {
// write code here
//定义位置
int i = 0;
//创建新数组存储新的
int[] res = new int[array.length];
//遍历每一个,先加入奇数
//array数组中的每一个元素,定义为j
for(int j : array){
//奇数
if(j%2 != 0){
res[i] = j;
i++;
}
}
//加入偶数
for(int j : array){
//偶数
if(j%2 == 0){
res[i] = j;
i++;
}
}
return res;
}
}
JZ14 链表中倒数第k个节点
题目:链表中倒数第k个节点
import java.util.*;
/*
* public class ListNode {
* int val;
* ListNode next = null;
* public ListNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
//求链表长度
public int length(ListNode pHead){
//定义长度
int length = 0;
//遍历链表
while(pHead != null){
//长度加一
length++;
//向后遍历
pHead = pHead.next;
}
//返回长度
return length;
}
//返回倒数第k个节点,就是返回以倒数第k个节点为头节点的剩下的链表
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
//获得链表长度
int length = length(pHead);
//如果没有第k个节点,链表长度小于k,即k不存在
if(length < k)
return null;
//遍历去掉0 -> (length-k-1)的节点
for(int i=0;i<length-k;i++){
//将指针移到第length-k个节点
//后面还剩下k个,所以这就是倒数第k个节点
pHead = pHead.next;
}
//返回这个节点
return pHead;
}
}
JZ15 反转链表
JZ16 合并两个排序的链表
题目:合并两个排序的链表
有问题
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
//递归、循环,两种解决方式
public ListNode Merge(ListNode list1,ListNode list2) {
ListNode head = null;
ListNode cur = head;
while(list1 != null && list2 != null){
if(list1.val <= list2.val){
cur.next = list1;
list1 = list1.next;
}else{
cur.next = list2;
list2 = list2.next;
}
cur = cur.next;
}
if(cur.next == list1){
cur.next = list1;
}else
cur.next = list2;
return head.next;
}
}
没问题
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
// 递归方式解决
public ListNode Merge(ListNode list1, ListNode list2) {
// 定义头节点
ListNode head = null;
// 有一个链表为空的情况,边界条件,结束条件
// 返回另一个链表
if (list1 == null)
return list2;
else if (list2 == null)
return list1;
//数值小的,加入链表
if (list1.val <= list2.val) {
// 加入
head = list1;
// 递归剩下的链表,继续加入小的
head.next = Merge(list1.next, list2);
} else {
head = list2;
head.next = Merge(list1, list2.next);
}
//返回所有的加入之后的
return head;
}
}
JZ17 树的子结构
题目:树的子结构
- 首先遍历大树
- 找到结点值相同的结点
- 进行判断,以这两个点为根的两个树是否为子结构
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//遍历整个树,大树
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//空树
if(root1 == null || root2 == null)
return false;
//如果当前结点值和子树根节点相同,判断以该结点为根的树是否是子结构
if(root1.val == root2.val){
//判断是否是子结构
if(judge(root1,root2)){
return true;
}
}
//当前结点值不等
//递归遍历左右孩子,有一个是,那就是,所以采用||
return HasSubtree(root1.left,root2) || HasSubtree(root1.right,root2);
}
//判断是否是子结构
public boolean judge(TreeNode node1,TreeNode node2){
//子树已经循环完毕,证明全部匹配,是子结构
if(node2 == null){
return true;
}
//整个树已经循环完毕,未成功匹配完全,不是子结构
if(node1 == null){
return false;
}
//当前结点值相等,那么只需要左右孩子为子结构,那么就为子结构
if(node1.val == node2.val){
//要两个同时为子结构,当前的才是子结构,所以是&&
return judge(node1.left,node2.left) && judge(node1.right,node2.right);
}
return false;
}
}
JZ18 二叉树的镜像
题目:二叉树的镜像
方法一:BFS(广度优先搜索)
方法二:DFS(深度优先搜索)
方法三:中序遍历
方法四:递归
import java.util.*;
/*
* public class TreeNode {
* int val = 0;
* TreeNode left = null;
* TreeNode right = null;
* public TreeNode(int val) {
* this.val = val;
* }
* }
*/
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pRoot TreeNode类
* @return TreeNode类
*/
// 镜像变换
public TreeNode Mirror(TreeNode pRoot) {
// write code here
// 递归遍历
if (pRoot == null) {
return null;
}
// 中序遍历
// 将当前结点的右子树当作根节点,将它的子树镜像变换
Mirror(pRoot.right);
// 将当前结点的左右子树整个交换
TreeNode node = pRoot.right;
pRoot.right = pRoot.left;
pRoot.left = node;
// 左子树变成了右子树,将右子树做根节点,将它的子树再变换
Mirror(pRoot.right);
// 返回根节点
return pRoot;
}
}
JZ20 包含min函数的栈
题目:包含min函数的栈
以空间换时间,很常见的方法。
import java.util.Stack;
public class Solution {
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
if(stack2.isEmpty() || stack2.peek() >= node){
stack2.push(node);
}else{
stack2.push(stack2.peek());
}
}
public void pop() {
stack1.pop();
stack2.pop();
}
public int top() {
return stack1.peek();
}
public int min() {
return stack2.peek();
}
}
JZ21 栈的压入、弹出序列
题目:栈的压入、弹出序列
辅助栈进行判断,后面循环,必加入数据,还需要判断栈不为空,还未理解这一点。
import java.util.*;
public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
if(pushA.length == 0 || popA.length == 0 || popA.length != pushA.length){
return false;
}
//辅助栈
Stack<Integer> stack = new Stack<Integer>();
//定义指向出栈数组的指针
int j = 0;
//入栈
for(int i=0;i<pushA.length;i++){
//入栈
stack.push(pushA[i]);
//判断当前栈顶元素是否等于出栈数组元素
while(!stack.isEmpty() && stack.peek() == popA[j]){
//栈顶元素等于出栈元素
//栈顶出栈,指针后移
stack.pop();
j++;
}
}
//判断是否出栈顺序正确
//辅助栈空,正确,不为空,错误
return stack.isEmpty();
}
}
JZ22 从上往下打印二叉树
题目:从上往下打印二叉树
- 层次遍历
- 广度优先搜索
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//先序遍历,深度优先递归,广度优先队列
//深度优先dfs不对,要广度优先搜索bfs
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
//建立链表存储结点值
ArrayList<Integer> array = new ArrayList<Integer>();
//新建队列进行广度优先搜索,层次遍历
Queue<TreeNode> queue = new LinkedList<TreeNode>();
//空树,返回空表
if(root == null){
return array;
}
//不空,使用队列进行层次遍历
//将根结点加入队列,一层一层结点加入
queue.offer(root);
//队列不为空,即树没有遍历完全,无限循环
while(!queue.isEmpty()){
//取出队列头,将头节点的值加入链表
TreeNode node = queue.poll();
array.add(node.val);
//当前结点的左孩子,右孩子都加入,即当前结点的下一层结点全部加入
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
return array;
}
}
JZ23 二叉搜索树的后序遍历序列
题目:二叉搜索树的后序遍历序列
public class Solution {
//划分出左右子树
public boolean divideToLeft(int[] s,int left,int right){
//左边界大于右边界,证明全部化分,正确,是二叉搜索树
//递归结束条件
if(left >= right){
return true;
}
//找到后序遍历的根
int root = s[right];
//定义左右子树边界
int i;
//从左边边界遍历到根前一个结点
for(i = left;i<right;i++){
//值大于根的值,证明是根的右子树,跳出循环
if(s[i] > root){
break;
}
}
//边界前是左子树,后是右子树
//遍历右子树,确保后面不存在左子树的值,确保为二叉搜索树
for(int j=i;j<right;j++){
if(s[j] < root){
return false;
}
}
//返回左右子树
return divideToLeft(s,left,i-1) && divideToLeft(s,i,right-1);
}
public boolean VerifySquenceOfBST(int [] sequence) {
//空树
if(sequence.length == 0){
return false;
}
//返回整个数组的树
return divideToLeft(sequence,0,sequence.length-1);
}
}
JZ24 二叉树中和为某一值的路径
题目:二叉树中和为某一值的路径
import java.util.ArrayList;
import java.util.Stack;
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
ArrayList<Integer> path = new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
if (root == null) {
return res;
}
Path(root, target);
return res;
}
public void Path(TreeNode root, int target) {
//因为FindPath中和 下面程序中都进行了判null操作,root绝对不可能为 null
path.add(root.val);
//已经到达叶子节点,并且正好加出了target
if (root.val == target && root.left == null && root.right == null) {
//将该路径加入res结果集中
res.add(new ArrayList(path));
}
//如果左子树非空,递归左子树
if (root.left != null) {
Path(root.left, target - root.val);
}
//如果右子树非空,递归右子树
if (root.right != null) {
Path(root.right, target - root.val);
}
//这个路径到达叶子结点,去掉这条路径的最后一个结点
//返回父结点,找另一条路径
path.remove(path.size() - 1);
return;
}
}
JZ25 复杂链表的复制
题目:复杂链表的复制
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
/*
*解题思路:
*1、遍历链表,复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
*2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
*3、拆分链表,将链表拆分为原链表和复制后的链表
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead) {
//如果链表为空,无需复制,返回空
if(pHead == null) {
return null;
}
//定义从头结点开始指向当前节点的指针
RandomListNode currentNode = pHead;
//1、复制每个结点,如复制结点A得到A1,将结点A1插到结点A后面;
//遍历整个链表,进行复制
while(currentNode != null){
//定义一个新的克隆节点,值为当前节点的值
RandomListNode cloneNode = new RandomListNode(currentNode.label);
//定义一个原链表的下一个节点
RandomListNode nextNode = currentNode.next;
//将克隆节点插入到当前节点和下一个节点中间
currentNode.next = cloneNode;
cloneNode.next = nextNode;
//跳过加入的克隆节点,将当前指针指向下一个节点,向后遍历
currentNode = nextNode;
}
//重头开始遍历,以pHead为头节点的链表已经改变
currentNode = pHead;
//2、重新遍历链表,复制老结点的随机指针给新结点,如A1.random = A.random.next;
while(currentNode != null) {
//当前节点的下一个节点即为克隆节点
//克隆节点的随机节点为空则为空,否则为当前节点的随机节点的下一个节点
currentNode.next.random = currentNode.random==null?null:currentNode.random.next;
//当前节点指针要跳过下一个克隆节点,直接跳两个节点,继续指向随机节点
currentNode = currentNode.next.next;
}
//3、拆分链表,将链表拆分为原链表和复制后的链表
//重新重头开始
currentNode = pHead;
//定义拷贝后的头节点从原链表的头节点的下一个节点开始,即第一个克隆节点开始
RandomListNode pCloneHead = pHead.next;
//遍历整个链表
while(currentNode != null) {
//取出克隆节点
RandomListNode cloneNode = currentNode.next;
//将克隆节点取出,将当前节点的指针,指向克隆节点的下一个节点,即下一个原节点
currentNode.next = cloneNode.next;
//将克隆节点的下一个节点指向克隆节点的下下个节点,即下一个克隆节点,为空,则置为空
cloneNode.next = cloneNode.next==null?null:cloneNode.next.next;
//因为当前节点的下一个节点指向下一个原节点了,所以直接下一个节点即可
currentNode = currentNode.next;
}
//就分成了两个链表
//返回克隆链表的头节点
return pCloneHead;
}
}
JZ26 二叉搜索树与双向链表
题目:二叉搜索树与双向链表
思路:将整棵树遍历,中序遍历二叉搜索树,直接得到有序的结点。然后将这些结点的关系重新链接,形成双向链表,后只需要返回第一个结点,即是头结点。
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//总方法
public TreeNode Convert(TreeNode pRootOfTree) {
if(pRootOfTree == null){
return null;
}
ArrayList<TreeNode> list = new ArrayList<>();
//遍历
Order(pRootOfTree,list);
//返回头结点
return ConVert(list);
}
//中序遍历,保存节点
public void Order(TreeNode root,ArrayList<TreeNode> list){
//递归遍历左子树
if(root.left != null){
Order(root.left,list);
}
//加入当前节点
list.add(root);
//遍历右子树
if(root.right != null){
Order(root.right,list);
}
}
//将链表中排好序的节点,重新链接关系
//返回头结点
public TreeNode ConVert(ArrayList<TreeNode> list){
//如果只有一个数,无需链接
if(list.size() == 1){
return list.get(0);
}else{
//第一个只向后链接
list.get(0).right = list.get(1);
//最后一个只向前链接
list.get(list.size()-1).left = list.get(list.size()-2);
//中间的结点双向链接
for(int i=1;i<list.size()-1;i++){
list.get(i).left = list.get(i-1);
list.get(i).right = list.get(i+1);
}
}
//返回头结点
return list.get(0);
}
}
JZ27 字符串的排列
题目:字符串的排列
就是全排列问题。
思路:先固定一个位置的数字,然后对剩下的数字继续全排列,直到只剩下一个位置固定,证明一次全排列结束,输出这个序列。(递归)
- 不考虑
字典序输出
和重复的数值
,只考虑全排列
import java.util.ArrayList;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//全排列
public void Permutation(char c[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
list.add(new String(c));
}else{
for(int i = start;i<=end;i++){
//确定首位的数
swap(c,start,i);
//后面的所有数,全排列
Permutation(c,start+1,end);
//避免重复排列
swap(c,start,i);
}
}
}
//交换
public void swap(char c[],int i,int j){
char temp;
if(i != j){
temp = c[i];
c[i] = c[j];
c[j] = temp;
}
}
public ArrayList<String> Permutation(String str) {
char c[] = str.toCharArray();
Permutation(c,0,str.length()-1);
return list;
}
}
- 不考虑
字典序
,考虑全排列
和重复值
import java.util.ArrayList;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//全排列
public void Permutation(char c[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
list.add(new String(c));
}else{
for(int i = start;i<=end;i++){
//后面存在一个相同值
//进行下一个循环
if(!isSwap(c,i,end)){
continue;
}
//无重复值
//执行下述代码
swap(c,start,i);
//后面的所有数,全排列
Permutation(c,start+1,end);
//避免重复排列
swap(c,start,i);
}
}
}
//交换
public void swap(char c[],int i,int j){
char temp;
if(i != j){
temp = c[i];
c[i] = c[j];
c[j] = temp;
}
}
//去重复
public boolean isSwap(char c[],int i,int end){
for(int j=i+1;j<=end;j++){
//i后面的值存在和i重复的
//跳过此次循环
if(c[j] == c[i])
return false;
}
return true;
}
public ArrayList<String> Permutation(String str) {
char c[] = str.toCharArray();
Permutation(c,0,str.length()-1);
return list;
}
}
- 全部考虑
import java.util.*;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//全排列
public void Permutation(char c[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
list.add(new String(c));
}else{
for(int i = start;i<=end;i++){
//跳出此次循环
if(!isSwap(c,i,end)){
continue;
}
swap(c,start,i);
//后面的所有数,全排列
Permutation(c,start+1,end);
//避免重复排列
swap(c,start,i);
}
}
}
//交换
public void swap(char c[],int i,int j){
char temp;
if(i != j){
temp = c[i];
c[i] = c[j];
c[j] = temp;
}
}
//去重复
public boolean isSwap(char c[],int i,int end){
for(int j=i+1;j<=end;j++){
//i后面的值存在和i重复的
//跳过此次循环
if(c[j] == c[i])
return false;
}
return true;
}
public ArrayList<String> Permutation(String str) {
if(str == ""){
return null;
}
char c[] = str.toCharArray();
Permutation(c,0,str.length()-1);
//内置函数,升序排列,字典序
Collections.sort(list);
return list;
}
}
字典序法
思路:先给定一个排列,然后找下一个排列顺序。下一个排列顺序要刚好比这个排列大,但是它们中间不能有排列,直到找到排列的左邻没有小于右邻的值为止,即是递减排列了,证明全排结束,全部找到了。
步骤:
- 从右至左,找到左邻小于右邻的第一个数,记录位置
i
和值c[i]
- 从右至左,找到第一个比
c[i]
大的数,记录位置j
和值c[j]
- 交换这两个数字
- 然后将
i
后面的值进行从小到大升序排列。- 因为交换的是第一个比
c[i]
大的值和c[i]
,所以交换后,i
后面的值还是从大到小排序的。所以逆序
就可。
JZ28 数组中出现次数超过一半的数字
- 哈希法
思路:新建一个哈希数组,用于在位置为值的地方存放这个值出现的次数,遍历哈希数组,找出大小比原数组的一半大的数,这个数存放的位置,就是众数的值。
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
//只有一个元素,直接返回
if(array.length == 1){
return array[0];
}
//采用hash数组,存放出现次数
int hash[] = new int[10001];
//初始化数组
for(int i=0;i<hash.length;i++){
hash[i] = 0;
}
//遍历原数组,将值的个数存在在a中
//存放的位置为值
for(int i=0;i<array.length;i++){
hash[array[i]]++;
}
//找出a中值大于原数组一半的数,证明是众数
//返回a中的位置,即为众数的值
for(int i=0;i<hash.length;i++){
if(hash[i] > (array.length/2)){
return i;
}
}
//都不符合上述情况,返回0
return 0;
}
}
- 候选法
思路:将两个数对比,如果这两个数不相同,则一起去掉。所以有两种情况:一种是去掉的是一个众数和一个非众数;另一种是两个非众数。无论是哪一种,如果众数存在,最后剩下的,肯定是众数。
JZ29 最小的K个数
题目:最小的K个数
主要思路:先对数组进行排序,然后返回前k个数。
- 内置函数排序(不推荐)
因为肯定是要考我们排序算法,直接使用内置函数了,就没有意义了。
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
ArrayList<Integer> array = new ArrayList<>();
for(int i=0;i<input.length;i++){
array.add(input[i]);
}
Collections.sort(array);
for(int i=0;i<k;i++){
list.add(array.get(i));
}
return list;
}
}
- 快速排序(推荐)
思路:二分法进行分区,递归快排。
单向扫描快排法
思路:从左到右扫描,小于基准,不动,看下一个;大于基准,将当前值和最后一个交换,最后一个值固定,右指针前移;继续对比当前值和基准,循环进行。
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
//对数组排序
quickSort(input,0,input.length-1);
//取前k个加入list
for(int i=0;i<k;i++){
list.add(input[i]);
}
return list;
}
// 单向扫描分区
public static int partition(int s[], int start, int end) {
//确定基准
int base = s[start];
//左指针
int i = start + 1;
//右指针
int j = end;
//从左到右扫描
while (i <= j) {
//这个数小于基准,不变,看下一个
if(s[i]<=base) {
i++;
}else {
//大于基准,放到后面
swap(s,i,j);
//不在考虑放在后面的数字,固定
j--;
}
}
//无论如何,j最后指向的都是小于base的最后一个数
swap(s, start, j);
return j;
}
//快排
public static void quickSort(int[] s,int start,int end) {
//相等代表只有一个,排序无意义
if(start < end) {
int mid = partition(s,start,end);
//递归排序分区好的左右两边
quickSort(s, start, mid-1);
quickSort(s, mid+1, end);
}
}
//交换
public static void swap(int[] s,int i,int j) {
int temp = 0;
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
双向扫描快排法
思路:定义第一个数为基准;从左到右找第一个比基准大的数字,从右到左找第一个比基准小的数字;如果左指针小于右指针,交换他们,循环往复;直到左指针大于等于右指针结束;交换基准和右指针位置,分区完成。然后递归对每一个子块快排即可。
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
//对数组排序
quickSort(input,0,input.length-1);
//取前k个加入list
for(int i=0;i<k;i++){
list.add(input[i]);
}
return list;
}
//双向扫描分区
public static int partition(int[] s,int start,int end) {
//确定基准
int base = s[start];
//i从前开始
int i = start + 1;
//j从后开始
int j = end;
//只要i在j前
while(i<=j) {
//找到第一个比基准大的i
while(i<=j && s[i]<=base) {
i++;
}
//找到第一个比基准小的j
while(i<=j && s[j]>=base) {
j--;
}
if(i<j) {
swap(s,i,j);
}
}
swap(s,start,j);
return j;
}
//快排
public static void quickSort(int[] s,int start,int end) {
//相等代表只有一个,排序无意义
if(start < end) {
int mid = partition(s,start,end);
//递归排序分区好的左右两边
quickSort(s, start, mid-1);
quickSort(s, mid+1, end);
}
}
//交换
public static void swap(int[] s,int i,int j) {
int temp = 0;
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
还有
三分快排法
,感兴趣可以继续探索。
另外两种方法博客链接:快速排序的三种分区方法(整理)
JZ30 连续子数组的最大和
题目:连续子数组的最大和
- 动态规划法(填表法)
思路:定义一个表dp,根据原来数组的位置去填表,dp[i]表示以第i个位置为结尾的子数组的最大和。所以dp[i]等于array[i]和array[i]+dp[i-1]之间的最大值。每一个dp填入后,最大和就是这张表中的最大值。
public class Solution {
//动态规划法,填表法
public int FindGreatestSumOfSubArray(int[] array) {
//dp[i]表示以i结尾的子数组的最大和
int dp[] = new int[array.length];
//第一个值只有一个值,所以就是第一个值
//可以保证无论数是有正负,还是全负全正,都通用
dp[0] = array[0];
//初始化最大值为第一个
int max = dp[0];
//一重循环,时间复杂度O(n)
for(int i=1;i<array.length;i++){
//下一个值为当前的值加上上一个dp和当前值之间的最大值
//如果上一个dp是负数,那么当前值最大,即子数组开头换了位置一样
dp[i] = Math.max(array[i],array[i]+dp[i-1]);
//最大值,就是当前最大值和dp之比,动态变化
max = Math.max(max,dp[i]);
}
//返回子数组最大和
return max;
}
}
- 备忘录法(查表法)
思路:基本使用动态规划法的题目,备忘录法同样可以解决。思路几乎一样,就是一个查表,一个填表。
- 变量判断法
思路:设置一个变量tmp = 0。如果tmp+array[i] < 0, 说明以i结尾的不作贡献,重新赋值tmp = 0,否则更新tmp = tmp + array[i]。最后判断tmp是否等于0, 如果等于0, 说明数组都是负数,选取一个最大值为答案
JZ31 整数中1出现的次数(从1到n整数中1出现的次数)
- 暴力解法,空间复杂度较高
思路:计算出一个数中1的个数,然后分别求每一个数中1的个数,求和即可。
public class Solution {
//返回1-n这n个数中1的个数
public int NumberOf1Between1AndN_Solution(int n) {
int s = 0;
for(int i=1;i<=n;i++){
s += NumofOne(i);
}
return s;
}
//返回这个数中1的个数
public int NumofOne(int n){
int num = 0;
String s = n + "";
char c[] = s.toCharArray();
for(int i=0;i<s.length();i++){
if(c[i] == '1'){
num++;
}
}
return num;
}
}
JZ32 把数组排成最小的数
题目:把数组排成最小的数
- 全排列
思路:把数组里的整数元素,看成一个字符串整体。进行字符串全排列,然后进行升序排序,第一个即是最小值。
import java.util.*;
public class Solution {
//保存每一个排列序列
ArrayList<String> list = new ArrayList<>();
//得到组成的最小值
public String PrintMinNumber(int [] numbers) {
String str = "";
//输入为空,输出为空
if(numbers.length == 0){
return str;
}
//将整数数组变为字符串数组
String s[] = new String[numbers.length];
for(int i=0;i<numbers.length;i++){
s[i] = numbers[i] + "";
}
//进行全排列
Permutation(s,0,s.length-1);
//字典序
Collections.sort(list);
//最小值
str = list.get(0);
//返回最小数
return str;
}
//全排列
public void Permutation(String s[],int start,int end){
//只有最后一个没固定,只有一种情况
//一次全排列结束
if(start == end){
//把结果加入list中保存
String str = "";
for(int i=0;i<s.length;i++){
str += s[i];
}
list.add(str);
}else{
for(int i = start;i<=end;i++){
//有重复,跳出此次循环
if(isEquals(s,i,end)){
continue;
}
//固定开头的值
swap(s,start,i);
//后面的所有数,全排列
Permutation(s,start+1,end);
//避免重复排列
swap(s,start,i);
}
}
}
//是否有重复
//去重复
public boolean isEquals(String s[],int i,int end){
for(int j=i+1;j<=end;j++){
if(s[i].equals(s[j])){
return true;
}
}
return false;
}
//交换
public void swap(String s[],int i,int j){
String temp;
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
JZ33 丑数
题目:丑数
- 穷举法(枚举法)
思路:第一个丑数是1,下一个丑数肯定是前一个丑数乘以2/3/5的最小值。即第n个丑数肯定可以由第i个丑数乘以2/3/5得到。所以使用这个规律进行枚举。因为得到最小的之后,还有两个剩下的值需要继续比,所以需要三个指针,指向不同的第i个丑数。
import java.util.*;
public class Solution {
//控制一个数组列举丑数,然后返回这个丑数
public int GetUglyNumber_Solution(int index) {
//第0个丑数,不合理,返回0
if(index == 0){
return 0;
}
//创建数组保存丑数
//为了不浪费空间,刚好到所求的第n个丑数
int res[] = new int[index];
//第一个是1
res[0] = 1;
//定义三个表示丑数*2*3*5
int p2 = 0;
int p3 = 0;
int p5 = 0;
//循环放入丑数
for(int i=1;i<index;i++){
//下一个丑数是三个里面最小的
res[i] = Math.min(res[p2]*2,Math.min(res[p3]*3,res[p5]*5));
//三个指针向后移动
if(res[i] == res[p2]*2){
p2++;
}
if(res[i] == res[p3]*3){
p3++;
}
if(res[i] == res[p5]*5){
p5++;
}
}
//返回最后一个丑数,即是所求
return res[index-1];
}
}
JZ34 第一个只出现一次的字符
题目:第一个只出现一次的字符
- 数组法
思路:在这个值的位置,放置它出现的次数。但是有点问题,使用map得到值增加有点问题。所以改成了ASC码和数组保存其出现的次数。
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
//asc码的长度多1
int map[] = new int[128];
//遍历得到每个字符出现次数
for(int i=0;i<str.length();i++){
map[str.charAt(i)]++;
}
//遍历,找到第一个出现次数只有一次的字符
for(int i=0;i<str.length();i++){
if(map[str.charAt(i)]==1){
//返回位置
return i;
}
}
//没有只出现一次的字符
return -1;
}
}
- 哈希法
思路:由于在这个位置保存其出现的次数有问题,而题目只需要我们判断第一个出现一次的字符的位置,所以可以把出现一次的看做无重复,把多次出现的,记作重复,采用boolean进行判断。
import java.util.*;
public class Solution {
public int FirstNotRepeatingChar(String str) {
//创建哈希表
HashMap<Character,Boolean> map = new HashMap<>();
//遍历判断其是否重复出现
for(int i=0;i<str.length();i++){
//重复出现
if(map.get(str.charAt(i)) != null){
//设置true,表示重复出现
map.put(str.charAt(i),true);
}else{
//第一次出现
map.put(str.charAt(i),false);
}
}
//重新遍历原字符串
//找出第一个不重复出现的字符的位置
for(int i=0;i<str.length();i++){
//map中key为这个字符的,不重复
if(map.get(str.charAt(i)) == false){
//返回其在原数组中的位置
return i;
}
}
//都不符合,返回-1
return -1;
}
}
JZ35 数组中的逆序对
题目:数组中的逆序对
- 暴力解法
思路:以当前数字固定,找出它后面比它小的数字的个数,这些数字就可以和它组成这么多逆序对。然后遍历数组,使固定的数字不同。得到的所有数之和即为逆序对总数。因为有双重循环,所以对于10^5来说,一定超时了。
public class Solution {
//所有的逆序对
public int InversePairs(int [] array) {
int res = 0;
for(int i=0;i<array.length-1;i++){
res += NumsOfSmall(array,i);
}
return res;
}
//在后面中小于这个数的数字个数
public int NumsOfSmall(int[] array,int i){
int s = 0;
for(int j=i+1;j<array.length;j++){
if(array[i] > array[j]){
s++;
s %= 1000000007;
}
}
return s;
}
}
- 归并排序法
思路:先分解数组,直到不能再分,然后进行对比两个数组,排序,将小的先放入,大的后放入,大的还要计算逆序数。
只要前一个数组的当前值大于后一个数组的值,那么在前一个数组中,位于这个数后面的所有数,都可以和后一个数组的这个数组成逆序对。
最后返回逆序对总数。
public class Solution {
//记录逆序对总数
private int res;
//归并排序
private void MergeSort(int[] array, int start, int end){
if(start>=end)return;
//分界点
int mid = (start+end)/2;
//分成两个数组
MergeSort(array, start, mid);
MergeSort(array, mid+1, end);
//进行合并,排序,并记录逆序数
MergeOne(array, start, mid, end);
}
//合并,排序,记录逆序数
private void MergeOne(int[] array, int start, int mid, int end){
//保存排序后的数组
int[] temp = new int[end-start+1];
//两个指针,指向两个数组的第一个数值
int k=0,i=start,j=mid+1;
while(i<=mid && j<= end){
//如果前面的元素小于后面的不能构成逆序对
if(array[i] <= array[j])
temp[k++] = array[i++];
else{
//如果前面的元素大于后面的
//那么在前面元素之后的元素都能和后面的元素构成逆序对
temp[k++] = array[j++];
//计算逆序对数量
res = (res + (mid-i+1))%1000000007;
}
}
//合并第一个数组
while(i<= mid)
temp[k++] = array[i++];
//合并第二个数组
while(j<=end)
temp[k++] = array[j++];
//改变数组位置,进行排序
for(int l=0; l<temp.length; l++){
array[start+l] = temp[l];
}
}
//调用方法,计算数量
public int InversePairs(int [] array) {
MergeSort(array, 0, array.length-1);
return res;
}
}
JZ36 两个链表的第一个公共结点
题目:两个链表的第一个公共结点
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}*/
public class Solution {
//双重遍历,寻找第一个相同的节点
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
//保存第二个链表
ListNode temp = pHead2;
//两个链表都不为空
if (pHead2 != null && pHead1 != null) {
//pHead1不为空,进入循环
while (pHead1 != null) {
//pHead2不为空,进入循环,双重循环
while (pHead2 != null) {
//两个相等,返回任意一个都行
if (pHead2 == pHead1) {
return pHead2;
}
//不等,第一个链表不变,第二个向右继续
pHead2 = pHead2.next;
}
//一轮过后,第二个链表指针指向了末尾
//需要重新指向开头
pHead2 = temp;
//第一个链表向右,开始下一轮
pHead1 = pHead1.next;
}
}
//没有,返回空
return null;
}
}
JZ38 二叉树的深度
题目:二叉树的深度
方法一:分治法,先求左子树,后求右子树(递归DFS)
分治法简介:求一个规模为n的问题,先求左边规模大约为n/2的问题,再求右边规模大约为n/2的问题,然后合并左边,右边的解,从而求得最终解。具体可参考归并排序。
步骤:
- 求 pro(left, rigth) -> int
- 先求pro(left, (left+right)/2) -> left
- 再求pro((left+right)/2 + 1, right) -> right
- merge(left, right) -> result
求二叉树的最大深度,我们不必管函数具体是怎么实现的。
所以最终结果为 max( 头结点左子树的最大深度, 头结点右子树的最大深度)+1
步骤:
- 求TreeDepth(TreeNode pRoot)->int
- 先求 TreeDepth(pRoot.left) ->left
- 再求TreeDepth(pRoot.right) ->right
- return Math.max(left, right) + 1
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//求树的深度,根结点到叶结点的最长路径
public int TreeDepth(TreeNode root) {
//树不存在,返回0
if(root == null){
return 0;
}
//找出左子树的深度
int left = TreeDepth(root.left);
//找出右子树的深度
int right = TreeDepth(root.right);
//返回左右子树大的深度加一,就是最长的路径,即整个树的深度
return Math.max(left,right) + 1;
}
}
方法二:层次遍历(队列BFS)
非递归:求最大深度,可用队列。因为要满足先进先出的特性。
- 初始化:一个队列queue<TreeNode*> q, 将root节点入队列q
- 如果队列不空,做如下操作:
- 弹出队列头,保存为node,将node的左右非空孩子加入队列
- 做2,3步骤,知道队列为空
/**
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//层次遍历,广度优先搜索BFS
public int TreeDepth(TreeNode root) {
//空树,返回0
if(root == null){
return 0;
}
Queue<TreeNode> queue = new LinkedList();
TreeNode nlast = null;
TreeNode last = root;
int depth = 0;
queue.offer(root);
while(!queue.isEmpty()){
TreeNode cur = queue.poll();
if(cur.left != null){
queue.offer(cur.left);
nlast = cur.left;
}
if(cur.right != null){
queue.offer(cur.right);
nlast = cur.right;
}
if(cur == last){
depth++;
last = nlast;
}
}
return depth;
}
}
JZ39 平衡二叉树
题目:平衡二叉树
在这里,我们只需要考虑其平衡性,不需要考虑其是不是排序二叉树
平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右
两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。
方法一:双重递归(自顶向下遍历)
public class Solution {
//求出树的高度(深度),递归
public int TreeDepth(TreeNode root){
if(root == null){
return 0;
}
//递归
int left = TreeDepth(root.left);
int right = TreeDepth(root.right);
//返回左子树和右子树较大的深度加一即是树的深度
return Math.max(left,right) + 1;
}
//判断是否是平衡二叉树
public boolean IsBalanced_Solution(TreeNode root) {
//空树,是平衡二叉树
if(root == null){
return true;
}
//不空,默认左右都存在
//递归
if(Math.abs(TreeDepth(root.left)-TreeDepth(root.right)) <= 1 &&
IsBalanced_Solution(root.left) && IsBalanced_Solution(root.right)){
//左右子树的高度差绝对值不超过1,左右子树都是平衡二叉树,那么树就是平衡二叉树
return true;
}
//返回除了上述是平衡二叉树的情况之外的其余所有的不是平衡二叉树的情况
return false;
}
}
方法二:回溯加剪枝(自底向上遍历)
剪枝就是将不满足的情况减去。
public class Solution {
//以高度进行剪枝
public int TreeDepth(TreeNode root){
if(root == null)
//空树,高度为0
return 0;
//左子树高度
int left = TreeDepth(root.left);
if(left == -1)
//如果发现左子树不平衡,剪枝
return -1;
//右子树高度
int right = TreeDepth(root.right);
if(right == -1)
//如果发现右子树不平衡,剪枝
return -1;
//如果左右子树高度差超过1,剪枝
if(Math.abs(left - right) > 1)
return -1;
else{
//满足条件,返回树高度
return Math.max(left,right) + 1;
}
}
//判断是否是平衡二叉树
public boolean IsBalanced_Solution(TreeNode root) {
//满足条件,返回的就是树高度,就不为-1,返回true,是平衡二叉树
//不满足条件,高度返回的是-1,所以返回false,不是平衡二叉树
return TreeDepth(root) != -1;
}
}
JZ40 数组中只出现一次的两个数字
- 哈希法
思路:采用键值对存储,在这个值的位置,存储它出现的次数。然后遍历hashmap,将出现次数等于1的键保存到数组中,返回数组即可。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] FindNumsAppearOnce(int[] array) {
//键值对存储
HashMap<Integer,Integer> map = new HashMap<>();
//遍历数组,记录出现次数
for(int i=0;i<array.length;i++){
//表示存在值,已经出现一次了
if(map.get(array[i]) != null){
map.put(array[i],2);
}else{
map.put(array[i],1);
}
}
//只有两个数字出现一次
int[] res = new int[2];
//从数组0开始写入
int count = 0;
//遍历hashmap找到这两个数字
//keySet()方法返回key的集合
for(int i:map.keySet()){
//出现一次
if(map.get(i)==1){
//放入值,值就等于在hashmap中的位置
res[count] = i;
//到下一个位置
count++;
}
}
//返回数组
return res;
}
}
补充:map.keySet()方法是为了得到key的集合。
- ArrayList方法
思路:遍历数组,将所有第一次到来的数据,直接加入list;第二次到来,删除list中和这个数据值相等的数据。最后返回list,里面就只有出现一次的数据。由题意可知,长度必是2个。
注意:用这个方法,有一个坑。就是移除数据的时候,因为数据值就是int类型的,所以它会移除索引为这个值的数据,而不是移除这个值。分析源码得到,将其强转为Object类型,即可删除这个值,而不是删除索引为这个值的数据。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param array int整型一维数组
* @return int整型一维数组
*/
public int[] FindNumsAppearOnce(int[] array) {
//创建ArrayList保存数据
ArrayList<Integer> list = new ArrayList<>();
//遍历原数组
for (int i = 0; i < array.length; i++) {
//里面存在这个数据
if (list.contains(array[i])) {
//删除这个数据
list.remove((Object)array[i]);
} else {
//第一次出现,加入
list.add(array[i]);
}
}
//升序排序
Collections.sort(list);
//用数组保存数据,由题意知长度为2
int[] s = new int[2];
//将list中的数据转移到数组中
s[0] = list.get(0);
s[1] = list.get(1);
//返回数组
return s;
}
}
JZ41 和为S的连续正数序列
题目:和为S的连续正数序列
- 暴力解法(数学法)O(n^3)
思路:确定左右边界,然后加起来区间和,相等则加入这个序列,大于则停止,小于不做判断,因为开始就是必定小于的。
import java.util.ArrayList;
public class Solution {
//保存所有序列
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
//数学暴力解法
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
//定义左边界
for(int i=1;i<=sum/2;i++){
//定义右边界
for(int j=i+1;j<=sum;j++){
//定义区间和
int temp = 0;
//求出区间和
for(int k=i;k<=j;k++){
temp += k;
}
//相等,加入这一个序列
if(temp == sum){
//每次里面的内容不同,所以使用的时候再创建
//保存这个序列
ArrayList<Integer> list = new ArrayList<>();
//形成这个序列
for(int k=i;k<=j;k++){
list.add(k);
}
//序列加入
res.add(list);
//一定要大于,小于就停止的话,会出现问题
//因为小于是一个几乎必然的条件
}else if(temp > sum){
break;
}
}
}
//返回所有序列
return res;
}
}
- 前缀和法 O(n^2)
思路:求前
j
个数的和。又有些不一样,左边界可动,不固定。左边界向后遍历,右边界不知道在哪,相加,若是相等,当前的j
即是右边界,从左边界i
到右边界j
的序列即连续子序列。
import java.util.ArrayList;
public class Solution {
//保存所有序列
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
//前缀和法
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
//定义区间和
int temp = 0;
//定义左边界
for(int i=1;i<=sum/2;i++){
//右边界未定
//从左边界开始,往后相加
//直到相等,就找到了右边界
for(int j=i;j<sum;j++){
//求出区间和
temp += j;
//相等,找到了右边界,即当前的j
//加入这一个序列
if(temp == sum){
//每次里面的内容不同,所以使用的时候再创建
//保存这个序列
ArrayList<Integer> list = new ArrayList<>();
//形成这个序列
for(int k=i;k<=j;k++){
list.add(k);
}
//序列加入
res.add(list);
//一定要大于,小于就停止的话,会出现问题
//因为小于是一个几乎必然的条件
}else if(temp > sum){
//剪枝
temp = 0;
break;
}
}
}
//返回所有序列
return res;
}
}
- 滑动窗口法 O(n)
思路:定义一个窗口,左边界和右边界,
注意窗口是左闭右开的
,并且从头开始,只能向右移动,不可以向左移动。值小了,就要扩大窗口,右边界扩大;值大了,就要缩小窗口,左边界后移。直到左边界到中值停止或者找到值相等的右边界和左边界。加入序列,返回。
import java.util.ArrayList;
public class Solution {
//保存所有序列
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
//滑动窗口法
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
//定义窗口左边界
int left = 1;
//定义窗口右边界
int right = 1;
//定义窗口内值的和
int temp = 0;
//左边界大于一半时停止
while(left <= sum/2){
//和小于所需的值
if(temp < sum){
//加入右边界
temp += right;
//扩大窗口
right++;
//和大于所需的值
}else if(temp > sum){
//左边界后移,去掉左边界值
temp -= left;
//缩小窗口
left++;
//和等于所需值
//即找到序列加入
}else{
//保存这个序列
ArrayList<Integer> list = new ArrayList<>();
//形成这个序列
//注意滑动窗口的左闭右开
for(int k=left;k<right;k++){
list.add(k);
}
//序列加入
res.add(list);
temp -= left;
left++;
}
}
//返回所有序列
return res;
}
}
JZ42 和为S的两个数字
题目:和为S的两个数字
- 双指针法
思路:定义两个指针,左指针指向开头,右指针指向末尾。因为递增,所以和小于所求值时,数字变大,左指针右移;大于的时候,数字变小,右指针左移;等于所求值时,要比较乘积,乘积大于前两个数字的乘积,数字不变,指针移动;乘积小于前两个数字的乘积,更新两个数字和最小乘积。
import java.util.ArrayList;
public class Solution {
//保存这两个数字
ArrayList<Integer> res = new ArrayList<>();
//双指针法
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
//数组不存在,直接返回
if(array.length == 0){
return res;
}
//定义一个标志为最大值,表示两个数的乘积
int flag = java.lang.Integer.MAX_VALUE;
//定义左右指针
int i=0,j=array.length-1;
//结束条件为左指针大于右指针
while(i<j){
//这两个数和为所求值
if(array[i] + array[j] == sum){
//乘积小于flag,则更新这两个数字
if(array[i]*array[j] < flag){
//最小乘积更新
flag = array[i] * array[j];
//更新两个数字
res.clear();
res.add(array[i]);
res.add(array[j]);
}
//左指针右移
i++;
//右指针左移
j--;
//小于的时候,数字变大
}else if(array[i] + array[j] < sum){
//递增,所以左指针右移
i++;
}else{
//大于,右指针左移
j--;
}
}
//返回乘积最小,和为所求值的两个数
return res;
}
}
JZ43 左旋转字符串
题目:左旋转字符串
- 暴力解法
思路:将后面的字符先放入数组,前面旋转到后面的,后面放入数组,将数组变为字符串返回即可。
public class Solution {
//暴力法
public String LeftRotateString(String str,int n) {
//边界
if(str.length() == 0 || n == 0 || n == str.length()){
return str;
}
//存放旋转后的字符
char c[] = new char[str.length()];
//存放位置
int j = 0;
//旋转n位,所以从第n个开始,变成第一位
for(int i=n;i<str.length();i++){
c[j] = str.charAt(i);
j++;
}
//然后把前面的加入后面,旋转成功
for(int i=0;i<n;i++){
c[j] = str.charAt(i);
j++;
}
//组合成字符串并返回
String s = new String(c);
return s;
}
}
JZ44 翻转单词序列
题目:翻转单词序列
- 双指针法
思路:定义两个指针,一个从开头开始,一个从末尾开始,向中间移动。当左指针大于等于右指针时结束,不然就翻转位于这两个位置的字符串。最后拼接字符串,需要注意前面的都要加空格,最后一个不需要加空格,处理一下就好了。
public class Solution {
//双指针法
public String ReverseSentence(String str) {
//定义左指针
int i = 0;
//按空格划分字符串
String[] str1 = str.split(" ");
//定义右指针
int j = str1.length-1;
//左指针大于等于右指针为结束条件
while(i<j){
//翻转
swap(str1,i,j);
//改变指针
i++;
j--;
}
//用来存储翻转后的字符串
String s = new String("");
//注意前几个拼接需要空格
for(int k=0;k<str1.length-1;k++){
s = s + str1[k] + " ";
}
//最后一个拼接不需要空格
s = s + str1[str1.length-1];
//返回翻转的字符串
return s;
}
//交换
public void swap(String s[],int i,int j){
String temp;
temp = s[i];
s[i] = s[j];
s[j] = temp;
}
}
JZ47 求1+2+3+…+n
题目:求1+2+3+…+n
public class Solution {
public int Sum_Solution(int n) {
//递归求和
if(n==1){
//边界结束条件
return 1;
}
//返回递归和,本身加上前n-1项的和
return n + Sum_Solution(n-1);
}
}
JZ50 数组中重复的数字
题目:数组中重复的数字
- 哈希法
思路:在key为这个值的位置,放置其是否是重复数字的flag,以此来判断其是否是重复数字。
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param numbers int整型一维数组
* @return int整型
*/
//哈希法,在每个位置放入其是否是重复的数字
public int duplicate (int[] numbers) {
// write code here
//创建哈希表
HashMap<Integer,Boolean> map = new HashMap<>();
//遍历判断其是否重复
for(int i=0;i<numbers.length;i++){
//第二次出现
if(map.get(numbers[i]) != null){
//设置true,表示是重复的
map.put(numbers[i],true);
}else{
//第一次出现,不是重复的
map.put(numbers[i],false);
}
}
//遍历map中的每一个key
for(int i:map.keySet()){
//重复的,返回key即可
if(map.get(i) == true){
return i;
}
}
//都不符合,返回-1
return -1;
}
}
JZ51 构建乘积数组
题目:构建乘积数组
- 暴力解法
import java.util.ArrayList;
public class Solution {
public int[] multiply(int[] A) {
int res[] = new int[A.length];
for(int i=0;i<A.length;i++){
res[i] = 1;
}
for(int i=0;i<A.length;i++){
for(int j=0;j<A.length;j++){
if(j==i){
continue;
}
res[i] *= A[j];
}
}
return res;
}
}
JZ55 链表中环的入口结点
题目:链表中环的入口结点
方法一:哈希存储法
- 遍历单链表的每个结点
- 如果当前结点地址没有出现在set中,则存入set中
- 否则,出现在set中,则当前结点就是环的入口结点
- 整个单链表遍历完,若没出现在set中,则不存在环
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
//哈希存储法
public class Solution {
//运用HashSet存储节点,HashSet实现了Set接口
Set<ListNode> s = new HashSet<>();
public ListNode EntryNodeOfLoop(ListNode pHead) {
//遍历整个单链表
while(pHead != null){
//已经在HashSet中,证明形成了环,pHead就是入口节点
if(s.contains(pHead)){
return pHead;
}else{
//不在HashSet中,节点加入,遍历下一个
s.add(pHead);
pHead = pHead.next;
}
}
//无环,返回空
return null;
}
}
方法二:快慢指针法(双指针法)
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
//快慢指针法
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead) {
//定义快指针
ListNode fast = pHead;
//定义慢指针
ListNode slow = pHead;
//存在环,必相遇,快的追上慢的
while(fast != null && fast.next != null){
//快指针走两步
fast = fast.next.next;
//慢指针走一步
slow = slow.next;
//第一次相遇,停止
if(fast == slow)
break;
}
//没有环,返回空
if(fast == null || fast.next == null)
return null;
//快指针指向开头
fast = pHead;
//未相遇
while(fast != slow){
//快慢指针都走一步
fast = fast.next;
slow = slow.next;
}
//相遇于入口结点,返回入口结点
return fast;
}
}
JZ56 删除链表中重复的结点
题目:删除链表中重复的结点
方法一:遍历链表,Set存储,暴力求解法
遍历链表,存储结点值,然后再遍历一次单链表,删除重复值即可。
找重复值的具体步骤:
- 初始化:set< int > st
- 遍历单链表相邻两个元素,如果相等,就加入到set当中
- 直到单链表遍历完< /int>
删除重复值的具体步骤:
- 初始化:pre指针指向重复值的前一个节点,cur指向当前遍历的节点值
- 遍历单链表当前元素,然后再set中检查,如果是重复值,就删除,pre->next = cur->next
- 否则,不是重复值,pre = pre->next, cur = cur->next
- 知道单链表遍历完
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//Set存储,暴力求解
public ListNode deleteDuplication(ListNode pHead) {
//链表为空
if(pHead == null || pHead.next == null){
return pHead;
}
//定义Set存储相同结点值
HashSet<Integer> st = new HashSet<>();
//定义指向当前结点的指针和指向前一个结点的指针
ListNode pre = pHead;
ListNode cur = pHead.next;
//遍历链表,保存重复值
while(cur != null){
if(pre.val == cur.val){
//值相同,保存
st.add(pre.val);
}
//向右遍历
pre = pre.next;
cur = cur.next;
}
ListNode head = new ListNode(0);
pre = head;
cur = pHead;
while(cur != null){
if(!st.contains(cur.val)){
pre.next = cur;
pre = pre.next;
}
cur = cur.next;
}
return head.next;
}
}
改了好几遍,才AC九个例子,最后那个我总觉得对的,没错逻辑,就是出不来,有会的人请评论区留言,感谢。
方法二:边遍历边删除,直接删除法
根据方法一,可以优化,在遍历单链表的时候,检查当前节点与下一点是否为相同值,如果相同,继续查找祥同值的最大长度,然后指针改变指向。
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
//链表为空
if(pHead == null){
return null;
}
//存储重复的结点值
HashSet<Integer> set = new HashSet<>();
//定义前一个指针
ListNode pre = pHead;
//定义当前指针
ListNode cur = pHead.next;
//遍历存储重复值
while(cur != null){
if(pre.val == cur.val){
set.add(pre.val);
}
pre = pre.next;
cur = cur.next;
}
//直接删除法
//定义新链表头结点
ListNode head = new ListNode(0);
//将新的链表加上原来的,处理原来的链表
head.next = pHead;
//从新链表的头节点开始
pre = head;
//当前结点指针指向原来链表头节点开始
cur = pHead;
//遍历原来链表
while(cur != null){
//包含这个结点值,证明这个结点是个重复结点
if(set.contains(cur.val)){
//指向下一个结点
cur = cur.next;
//屏蔽当前结点,直接跳到下一个结点,就去掉了
pre.next = cur;
}else{
//不包含,证明这个结点无重复,留下
pre = pre.next;
cur = cur.next;
}
}
//返回新的链表
return head.next;
}
}
方法三:递归
/*
public class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
*/
public class Solution {
public ListNode deleteDuplication(ListNode pHead) {
//链表为空或者只有一个结点,返回即可,边界条件
if(pHead == null || pHead.next == null){
return pHead;
}
//定义下一个结点
ListNode next = pHead.next;
//当前结点的值和下一个结点的值相等
if(pHead.val == next.val){
//下一个结点存在,并且一直相等
while(next != null && pHead.val == next.val){
//向后移一直到不等的下一个结点
next = next.next;
}
//从下一个结点开始,前面相等的结点全部去掉
return deleteDuplication(next);
}else{
//两个结点不等
//当前结点不变,即存在,然后下一个结点继续
pHead.next = deleteDuplication(pHead.next);
//返回去掉重复结点的链表头结点
return pHead;
}
}
}
JZ57 二叉树的下一个结点
题目:二叉树的下一个结点
直接查找下一个结点,分为三种情况。
- 右子树存在
- 右子树不存在,当前结点是父结点的左子树
- 右子树不存在,当前结点是父结点的右子树
这题还有些不懂,但是暂时还想不通
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null;
TreeLinkNode(int val) {
this.val = val;
}
}
*/
public class Solution {
// 直接找下一个结点
public TreeLinkNode GetNext(TreeLinkNode pNode) {
// 当前结点右子树存在,中序下来就是右子树的左子树结点
if (pNode.right != null) {
// 保存右子树
TreeLinkNode pRight = pNode.right;
// 遍历整个右子树,找到其最下面的左子树
while (pRight.left != null) {
pRight = pRight.left;
}
return pRight;
}
// 没有右子树且该节点是父节点的左子树
if (pNode.next != null && pNode.next.left == pNode) {
return pNode.next;
}
// 没有右子树且该节点是父节点的右子树
if(pNode.next != null){
//没有右子树的根节点,没有父节点
TreeLinkNode pNext = pNode.next;
while(pNext.next != null && pNext.next.right == pNext){
pNext = pNext.next;
}
return pNext.next;
}
return null;
}
}
JZ59 按之字形顺序打印二叉树
题目:按之字形顺序打印二叉树
import java.util.*;
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
public class Solution {
//存储返回值
ArrayList<ArrayList<Integer>> res = new ArrayList<>();
public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
//标志位,判断从左到右还是从右到左
boolean flag = true;
//队列,遍历存储二叉树
Queue<TreeNode> queue = new LinkedList<>();
//队列中加入根结点
queue.offer(pRoot);
//队列不为空
while(!queue.isEmpty()){
//每一行的结点数量
int size = queue.size();
//存储每一行的结点值
ArrayList<Integer> list = new ArrayList<>();
//循环每一个结点
for(int i=0;i<size;i++){
//取出结点
TreeNode node = queue.poll();
//队列空了,跳出当前循环
if(node == null){
continue;
}
//true代表从左到右
//false代表从右到左
if(flag){
list.add(node.val);
}else{
list.add(0,node.val);
}
//队列中加入左右子树
queue.offer(node.left);
queue.offer(node.right);
}
//这一行不为空,加入
if(list.size() != 0){
res.add(list);
}
//下一行,方向改变,需要改变标志位
flag = !flag;
}
//返回结果
return res;
}
}
JZ60 把二叉树打印成多行
题目:把二叉树打印成多行
- 一定要考虑边界值,空树的情况。
- 将每一层结点加入队列
- 根据每一层结点数量,出队列,加入到list中
- 将该结点的左右结点加入队列,即加入下一层结点
- 将该层结点的值加入array中
- 循环进行下一层,到最后一层为止
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
//创建返回值
ArrayList<ArrayList<Integer>> array = new ArrayList<ArrayList<Integer>>();
//边界值
if(pRoot == null){
return array;
}
//创建队列,bfs,广度优先搜索,层次遍历
Queue<TreeNode> queue = new LinkedList();
//加入根节点
queue.offer(pRoot);
//队列不为空,遍历
while(!queue.isEmpty()){
//得到队列中结点个数
int size = queue.size();
//创建对象保存每一层的结点
ArrayList<Integer> list = new ArrayList<>();
//队列中有结点,出队列,每一层
while(size-- != 0){
//得到队列头元素,相当于指向队头的指针
TreeNode node = queue.peek();
//头元素出队列
queue.poll();
//将头元素加入那一层的arraylist中
list.add(node.val);
//下一层,左右子树结点加入
if(node.left != null){
queue.offer(node.left);
}
if(node.right != null){
queue.offer(node.right);
}
}
//将得到的每一层结点值加入
array.add(list);
}
//返回所有层的结点
return array;
}
}
JZ62 二叉搜索树的第k个结点
题目:二叉搜索树的第k个结点
二叉搜索树,左子树的值小于根节点小于右子树。
/*
public class TreeNode {
int val = 0;
TreeNode left = null;
TreeNode right = null;
public TreeNode(int val) {
this.val = val;
}
}
*/
import java.util.*;
public class Solution {
//非递归,中序遍历
TreeNode KthNode(TreeNode pRoot, int k) {
//如果是空树,或者k值不符合常理
if(pRoot == null || k <= 0){
return null;
}
Stack<TreeNode> stack = new Stack<>(); //建立栈
//从当前结点,即头节点开始查找
TreeNode cur = pRoot;
//while 部分为中序遍历
//栈内有结点或者当前结点存在
while(!stack.isEmpty() || cur != null){
if(cur != null){
//当前节点不为null
//入栈
stack.push(cur);
//寻找左儿子
cur = cur.left;
}else{
//二叉搜索树的性质
//当前节点null则弹出栈内元素,相当于按顺序输出最小值。
cur = stack.pop();
if(--k == 0){ //计数器功能
return cur;
}
//寻找右儿子
cur = cur.right;
}
}
return null;
}
}