1. 二维数组中的查找 【数组】
- 思路:
1 2 3
4 5 6
7 8 9
10 11 12
target=11
右上角的元素大于这一行的所有元素,小于这一列的所有元素。所以从右上角开始遍历,第一个右上角元素则是array[0][列数-1]=array[0][2]
,target大于右上角,则行数加1,target小于右上角,则列数减1。直到row<rows && column>=0
,说明遍历了所有元素,遍历结束,没有找到目标,返回false。
- 代码:
public class Solution {
public boolean Find(int target, int [][] array) {
//右上角元素array[0][columns-1]
int rows = array.length; //数组的长度是属性,不是方法
int columns = array[0].length;
int row=0;
int column=columns-1;
while(row<rows && column>=0){
if(target == array[row][column]){
return true;
}else if(target>array[row][column]){
row++;
}else if(target <array[row][column]){
column--;
}
}
return false;
}
}
- 知识点:
数组的length是属性,不是方法,二维数组的长度表示方法为:
int rows = array.length;
int columns = array[0].length;
2. 替换空格 【字符串】
- 思路:
直接用一个新的字符串,拆分原字符串,一个一个将字符传给新的字符串,遇到空格就传入%20
,不是空格就正常传递字符就行了。 - 代码:
public class Solution {
public String replaceSpace(StringBuffer str) {
//StringBuffer没有split这个方法
StringBuffer newstr=new StringBuffer(); //不能够像String str="ab"这么简单的写法
for(int i=0;i<str.length();i++){ //记住字符串有长度这个属性啊
if(str.charAt(i)==' '){
newstr.append("%20");
}else{
newstr.append(str.charAt(i));
}
}
return newstr.toString();
}
}
- 知识点:
StringBuffer没有split这个方法;
StringBuffer newstr=new StringBuffer();
不能够像String str="ab"这么简单的写法;
字符串有length()
方法
可以通过方法charAt()
将每个字符变为char类型
3. 从尾到头打印链表 【链表】【栈】
- 思路:
只是需要将链表的值打印出来,所以没那么复杂,涉及到反转,只需要利用Stack就可以了。 - 代码:
/**
* public class ListNode {
* int val;
* ListNode next = null;
*
* ListNode(int val) {
* this.val = val;
* }
* }
*
*/
import java.util.ArrayList;
import java.util.Stack;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
//返回链表中的值就可以了,考虑用栈
ArrayList<Integer> result = new ArrayList<>();
Stack<Integer> stack=new Stack<>();
while( listNode != null){
stack.push(listNode.val);
listNode=listNode.next; //记得传完值以后,要让节点指向下一个节点啊
}
while(!stack.isEmpty()){
result.add(stack.pop());
}
return result;
}
}
- 知识点:
导包:import java.util.ArrayList;
以及import java.util.Stack;
注意,使用一个节点以后,要将下一个节点指向给当前的节点啊
4. 重建二叉树【二叉树】【递归】
-
思路:
首先了解前序,中序,后序遍历,参考博客。
根据前序和中序重建二叉树的思路:前序的第一个元素是根节点,根据这个根节点,在中序中找到这个根节点的索引,索引前面是这个根的左子树,索引后面是这个根的右子树。同理递归,左子树,右子树。直到pre.length=1
找到了递归的出口,递归结束。 -
代码:
/**
* Definition for binary tree
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
import java.util.Arrays;
public class Solution {
public TreeNode reConstructBinaryTree(int [] pre,int [] in) {
//肯定是得用到递归了
if(pre.length==0){
return null;
}
int rootVal=pre[0]; //先找到根节点
if(pre.length==1){
return new TreeNode(rootVal);
} //递归的出口,只有一个元素,这个元素作为根节点直接返回即可。
TreeNode root = new TreeNode(rootVal);
//在中序中找到根节点那个元素的位置,位置的左面元素则是左子树,右面元素则是右子树
int index=0;
for (int i=0;i<in.length;i++){
if(in[i] == rootVal){
index=i; //在中序中找到了根节点在中序的所在位置
break;
}
}
//同理在继续划分,左子树和右子树,可以使用递归的方式继续划分
root.left = reConstructBinaryTree(Arrays.copyOfRange(pre,1,index+1),Arrays.copyOfRange(in,0,index));
root.right= reConstructBinaryTree(Arrays.copyOfRange(pre,index+1,pre.length),Arrays.copyOfRange(in,index+1,in.length));
//别忘了,我们这是再求左右子树,怎么能不写子树right和left呢
return root;
}
}
- 知识点:
注意刚开始的一些判定条件,长度为0的二叉树这些特殊的例子。
这道题注意递归的出口,是pre的长度为1的时候,就应该是递归的出口了,因为没有元素了,所以直接返回这个根节点就可以了。
5. 用两个栈实现队列【队列和栈】
-
思路:
栈是先进后出,队列是先进先出。用两个栈实现队列的,入队和出队函数。
入队,没什么好说的,就直接放入元素就可以了,也不需要返回值,所以直接存到stack1中记录即可。
主要是出队的时候,注意是要先入队的先出,所以需要将元素从stack1中导入到stack2中,然后再tsack2出队,出队元素就是stack2中的顶级元素,这个元素是最先进入栈中的那个元素。
还有一点需要注意的是,出队以后,方便其他元素下一次入队,是进入到stack1中,所以出队以后,需要将stack2中的元素再放回stack1中。 -
代码:
//第5题:用两个栈实现队列
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node); //入队,没什么好说的,就直接放入元素就可以了,也不需要返回值,所以直接存到stack1中即可
}
public int pop() {
//主要是出队的时候,注意是要先入队的先出,所以需要将元素从stack1中导入到stack2中,然后再出队,出队元素就是stack2中的顶级元素
//还有一点需要注意的是,出队以后,方便下一次入队,是进入到stack1中,所以出队以后,需要将stack2中的元素再放回stack1中
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
int result= stack2.pop();
while(!stack2.isEmpty()){
stack1.push(stack2.pop());
}
return result;
}
- 知识点:
补充用两个队列实现栈:
压入元素,没什么好说的和栈的操作一样,先随便用一个队列存放压入的元素。
出栈的时候,需要注意q1中入队1 2 3 出栈应该出3,所以我们一个一个出队,不是顶层的元素就出队之后存放到q2中。
出掉3以后,再将q2中的元素存回q1中,方便下一次其他元素入队
//第5题补充,两个队列实现栈
Queue<Integer> queue1=new LinkedList<>();
Queue<Integer> queue2=new LinkedList<>();
public void push(int node) {//压入元素,没什么好说的和栈的操作一样,先随便用一个队列存放压入的元素
queue1.offer(node);
}
public int pop(){
//出栈的时候,需要注意q1中入队1 2 3 出栈应该出3,所以我们一个一个出队,不是顶层的元素就出队之后存放到q2中,
// 出掉3以后,再将q2中的元素存回q1中,方便下一次其他元素入队
while (queue1.size()!=1){
queue2.offer(queue1.poll());
}
int result=queue1.poll();
while(!queue2.isEmpty()){
queue1.offer(queue2.poll());
}
return result;
}
也不确定代码错还是队,哪天遇到题验证一下吧。
6. 旋转数组的最小数字【数组】【二分法】
- 思路:
如果使用遍历的思路很简单,但是这没有使用到旋转数组的性质,所以不推荐使用遍历的方法,可以使用二分法的变形。参考博客。 - 代码:
public int minNumberInRotateArray(int [] array) {
//题目解析参考:https://blog.nowcoder.net/n/dcb0f2e6ffd44e1895b7a5297e362778?f=comment
//两组例子:4 5 6 7 8 1 2 3以及 1 0 1 1 1
int low=0;
int high=array.length-1;
int mid=0; //随便赋值一个初值,一会还会重新赋值
while(low<high){ //low的索引小于high的索引,就会一直执行
if(array[low]<array[high]){
return array[low];//如果low小于high,那么说明元素本身就是顺序的,那么直接返回最前面的最小的元素就可以了
}
mid = low+(high-low)/2;
if(array[low]<array[mid]){
low=mid+1;
}else if(array[mid]<array[high]){
high=mid;
}else
low++;
}
return array[low];
}
7. 斐波那契数列【递归】【递归的改进算法】
- 思路:
F(n)=F(n-1)+F(n-2)可以采用递归算法,但是递归算法浪费很多计算量,比较麻烦,所以可以参考博客进行改进。 - 代码:
//递归的方法:但是好多数需要重复计算,浪费时间。
public int Fibonacci(int n) {
if(n==0) return 0;
if(n==1) return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
//递归算法的改进算法
public int Fibonacci(int n){
if(n==0) return 0;
if(n==1) return 1;
//sum存储第n项
//pre1存储第n-1项
//pre2存储第n-2项
// 0 1 2 3 4 5 6
// 0 1 1 2 3 5 8
int sum=1;
int pre1=1;
int pre2=0;
for(int i=2;i<=n;i++){
sum=pre1+pre2;
pre2=pre1;
pre1=sum;
}
return sum;
}
8.跳台阶【递归】【递归的改进算法】
- 思路:
target=1,只有一种可能。
target=2,有两种跳法:一次跳一次,跳两次。一次直接跳两个。
target=3,第一次跳1阶,还剩2阶,2阶有2种跳法。或者第一次跳2阶,还剩1阶,1阶有1种跳法。所以一共有2+1=3种跳法。
target=4,第一次跳1阶,还剩3阶,有3种跳法。第一次跳2阶,还剩2阶,有2种跳法。所以一共3+2=5种跳法。 - 代码:
// 1 2 3 4 5
// 1 2 3 5 8
//递归算法
public int JumpFloor(int target) {
if(target==1) return 1;
if(target==2) return 2;
return JumpFloor(target-1)+JumpFloor(target-2);
}
//递归算法改进算法
public int JumpFloor(int target){
if(target<=2) return target;
int sum=0;
int pre1=2;
int pre2=1;
for(int i=3;i<=target;i++){
sum=pre1+pre2;
pre2=pre1;
pre1=sum;
}
return sum;
}
9. 变态跳台阶【递归】【递归推导】
-
思路:
解题思路参考
通过查找关系得到表达式,写出代码。
n=1 f(1)=f(1-1)=1
n=2 f(2)=f(2-1)+f(2-2)=1+1=2
n=3 f(3)=f(3-1)+f(3-2)+f(3-3)=2+1+1=4
n=n-1 f(n-1)=f(n-2)+f(n-3)+…+f(0)
n=n f(n)=f(n-1)+f(n-2)+f(n-3)+…f(0)f(n)-f(n-1)=f(n-1)
f(n)=2*f(n-1) -
代码:
public int JumpFloorII(int target){
if(target<=1) return 1;
else
return JumpFloorII(target-1)*2;
}
10. 矩形覆盖【递归】【递归推导】
- 思路:
参考思路博客
2 ∗ 1 2*1 2∗1 : 1种
2 ∗ 2 2*2 2∗2 : 2种
3 ∗ 2 3*2 3∗2 : 最后一级竖着放 1 ∗ 2 1*2 1∗2,还剩 2 ∗ 2 2*2 2∗2,有2种。最后一级横着放2个 1 ∗ 2 1*2 1∗2即为 2 ∗ 2 2*2 2∗2,还剩 1 ∗ 2 1*2 1∗2,有1种。所以一共3种。
4 ∗ 2 4*2 4∗2 : 最后一级竖着放 1 ∗ 2 1*2 1∗2,还剩 3 ∗ 2 3*2 3∗2,有3种。最后一级横着放2个 1 ∗ 2 1*2 1∗2即为 2 ∗ 2 2*2 2∗2,还剩 2 ∗ 2 2*2 2∗2,有2种。所以一共5种。 - 代码:
//10.矩形覆盖
//参考:https://blog.nowcoder.net/n/cd6309f586174fda98f8c9bdf2e2df07?f=comment
public int RectCover(int target) {
if(target<=2) return target;
else{
return RectCover(target-1)+RectCover(target-2);
}
}
*待完成11. 二进制中1的个数
*待完成12. 数值的整数次方
13. 调整数组顺序使奇数位于偶数前面【数组】【待改进】
- 思路:
思路比较简单,就是先建立了2个list,一个存奇数,一个存偶数,最终将他们合并到一起。这个方法太笨了,肯定还有更好得办法。 - 代码:
public void reOrderArray(int [] array) {
//开辟数组的方法
ArrayList<Integer> list_ji = new ArrayList<Integer>();
ArrayList<Integer> list_ou = new ArrayList<Integer>();
for(int i=0 ; i<array.length; i++){
if(array[i]% 2 == 0){//偶数
list_ou.add(array[i]);
}
else{
list_ji.add(array[i]);
}
}
int m = list_ji.size();
int n = list_ou.size();
for(int i =0 ; i<array.length; i++){
if(i < m){array[i]=list_ji.get(i);}
else{array[i] = list_ou.get(i-m);} //这种方式赋值更好,相比于下面那种
}
/*for(int i=0;i<list_ji.size();i++){
array[i]=list_ji.get(i);
}
for(int j=list_ji.size();j<array.length;j++){
array[j]=list_ou.get(j-list_ji.size());
}*/ //2个循环,麻烦
}
- 知识:
学习一种将2个list组合为一个数组得方法。一个循环就可以,没必要使用下面那种2个循环。
14. 链表中倒数第k个结点【链表】
- 思路:
假设6个节点 1 2 3 4 5 6,那么倒数第2个节点其实就是正数第5个节点,k=2,5=6+1-2。
所以代码就先,求出链表得长度,然后加1减去k就是正数第几个节点,我们在遍历链表,把这个节点作为返回值即可。 - 代码:
public ListNode FindKthToTail(ListNode head,int k){
//假设6个节点,1 2 3 4 5 6 倒数第2个,就是正数第5个,k=2,求正数第5个节点
//先求链表长度
int size=0;
ListNode pHead=head;
while(pHead!=null){
size++;
pHead=pHead.next;
}
if(k>size) return null; //注意判断k是否超出范围啊
else{
int head_num=size+1-k; //6+1-2=5
pHead=head;
for(int i=1;i<head_num;i++){
pHead=pHead.next;
}
return pHead;
}
}
- 知识点:
注意求链表长度得代码块。
15. 反转链表【链表】
- 思路:
反转链表需要三个节点,当前节点head,当前节点得前一个节点pre以及当前节点得后一个节点next。循环:只要当前节点head不为空,就一直交换。 - 代码:
public ListNode ReverseList(ListNode head){
//需要三个节点之间得交换来反转链表
ListNode pre=null;
ListNode next=null;
while(head!=null){//head没到null就说明,head指向得不是最后一个节点,就要一直交换
next=head.next;//首先head指向得是当前节点,那么在最开始head是头节点得时候,pre=null没问题,但是next不应该是null,所以先给next赋值
head.next=pre;//head是当前节点,因为要反转链表,所以当前节点得next应该是前一个节点,也就是pre
pre=head; //前两行代码就算转换完毕了,接下来就是移动节点,所有得节点后移,pre就应该是刚才得当前节点
head=next; //next应该给当前节点赋值
}
return pre;
//最终返回得应该是pre,因为当head指向到最后一个节点的时候,head并不是null,循环执行以后,head指向了null,
// 在判断发现循环条件不满足,
// 但是此时的head指向的是null,pre才是最后一个节点,也就是现在的头节点,所以返回pre
}
- 知识点:
注意循环里面交换的过程需要三个节点进行交换;
注意最后的返回值;
16.合并两个排序的链表【链表】
- 思路:
先比较两个链表的头节点,list1和list2,假设list1的头节点小,那么将list1赋值给list,注意因为最开始假设list是null,所以要将整个list1赋给list,而不单单是val,这样无效。然后修改list的next的值,利用递归比较list1剩余的节点以及list2的节点。 - 代码:
public ListNode Merge(ListNode list1,ListNode list2){
ListNode list=null;
if(list1==null){
return list2;
}else if(list2==null){
return list1;
}
if(list1.val<=list2.val){
// list.val=list1.val;
list=list1; // 上面那样写法不对,因为list最开始是null,所以不存在val,
//你要直接将list1给list,然后再修改next得值
list.next=Merge(list1.next,list2);
}
if(list1.val>list2.val){
// list.val=list2.val;
list=list2; // 上面那样写法不对,因为list最开始是null,所以不存在val,
//你要直接将list2给list,然后再修改next得值
list.next=Merge(list1,list2.next);
}
return list;
}
17. 树的子结构【树】【递归】
//参考:https://blog.csdn.net/ouyangyanlan/article/details/73042736
public boolean HasSubtree(TreeNode root1,TreeNode root2){
//首先总是要先注意各种空树
if(root1==null) return false;
if(root2==null) return false; //题中说空树不是任何树的子结构
//然后考虑两个都不空的时候
boolean result =false;
//首先在tree1中找到和root2值相同的节点,然后比较tree1中的左右子树和root2的左右子树 是否也相同
if(root1!=null && root2!=null){
if(root1.val==root2.val){ //注意是比较值是否相同
//根节点就相同,比较左右子树,需要另外一个函数
result=isSubTree(root1,root2);
//找到根节点相同的节点了,开始判断左右子树是否相同
}
//左右子树不同的话,还要继续遍历tree1的左右子树,继续找和root2相同的根节点
if(!result){
result = HasSubtree(root1.right,root2);
}
if(!result){
result=HasSubtree(root1.left,root2);
}
}
return result;
}
public boolean isSubTree(TreeNode root1,TreeNode root2){
//用来判断tree2是否是tree1的子结构
if(root1 == null && root2 != null) return false;
if(root2 == null) return true;
if(root1.val != root2.val) return false;
return isSubTree(root1.left, root2.left) && isSubTree(root1.right, root2.right);
//根节点相同了,就继续比较左右子树是否相同
}
18.二叉树的镜像【二叉树】【递归】
- 思路:
交换root根节点的左右节点,然后再以同样的方法继续交换左右节点,所以用到递归,那么什么时候递归结束呢?就是root.left==null && root.right==null
作为递归的出口。 - 代码:
public void Mirror(TreeNode root) {
if(root==null) return; //虽然函数本身是void函数,但是仍然可以存在return,
//只不过不返回值即可,遇到return就结束这个函数
if(root.left==null && root.right==null) return;
//左子树 和 右子树 都为空的时候 直接结束函数
//其实这句就是递归的出口
TreeNode val=root.left;
root.left=root.right;
root.right=val;//交换根节点的左右节点
//然后递归 以同样的方式继续交换左右节点下面的节点
Mirror(root.left);
Mirror(root.right);
}
19. 包含min函数的栈【栈】【迭代器】
- 思路:
有两种方法:方法1:剑指offer上的方法,利用一个辅助栈,基础栈中每存入一个元素,辅助栈中就存入最小的元素。详细可以参考书上的解析,代码可以参考链接中的武汉孙一峰的解析
方法2:就是利用迭代器找到最小值返回最小值。 - 代码:
//包含min函数的栈
//有两种方法:一种是借助辅助栈使用剑指offer书上的方法
//一种是使用迭代器的方法
//使用迭代器的方法比较简单,先使用迭代器的方法:
Stack<Integer> stack=new Stack<>();
public void push(int node) {
stack.push(node);
}
public void pop() {
stack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
//迭代器的使用,需要导包
Iterator<Integer> it =stack.iterator();
int min=stack.peek();
int tmp=0;
while (it.hasNext()){
tmp=it.next();//只是用一次
if(min>tmp) min=tmp;
}
// Iterator<Integer> it =stack.iterator();
// int min=stack.peek();
// while (it.hasNext()){
// if(min>it.next()) min=it.next();
// } // 错误示范。错误原因:每次使用it.next都会是下一个元素了,所以循环中多次使用next肯定是有问题的
return min;
}
- 知识:
最主要的就是这个迭代器的错误使用,迭代器再使用的时候,我犯了一个错误就是在循环中多次使用了next,但是实际上,每次使用next都是使用的下一个元素,所以以后在使用next的时候,要先将其赋值给一个tmp变量,然后使用tmp去计算。
20.栈的压入、弹出序列【栈】
-
思路:
栈中每次存入一个元素,就和popA去比较,相同就弹出,且popA进入下一个元素
如果栈顶元素和popA的首个元素相等,那么需要出栈,
出栈以后是要继续判断栈顶元素和popA的下一个元素是否相等的,
如果相等要继续出栈,不相等才进行下一轮的进栈, -
代码:
public boolean IsPopOrder(int [] pushA,int [] popA){
// 对于栈来说,长度是属性
if(pushA.length == 0 || popA.length== 0 || pushA.length != popA.length){
return false;
}
int j=0;
Stack<Integer> stack=new Stack<>();
for(int i=0;i<pushA.length;i++){
stack.push(pushA[i]); //先把元素放入栈中,看看能不能把栈中的元素清空,能清空返回true
//栈中每次存入一个元素,就和popA去比较,相同就弹出,且popA进入下一个元素
//如果栈顶元素和popA的首个元素相等,那么需要出栈,
//出栈以后是要继续判断栈顶元素和popA的下一个元素是否相等的,
//如果相等要继续出栈,不相等才进行下一轮的进栈,
//所以这里需要的应该是一个循环来进行 多次的判断
while(!stack.isEmpty() && stack.peek()==popA[j]){
stack.pop();
j++;
}
}
return stack.isEmpty();
}
21.从上往下打印二叉树【二叉树】【打印】【队列】
- 思路:
打印二叉树需要借助队列,将根节点先存入队列,然后只要队列不为空,就要一直循环,然后将队列中的节点逐个冒出,将值存入list中,然后把左右子节点再依次存放到队列中。 - 代码:
//从上往下打印二叉树
//打印链表需要借助队列
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> list=new ArrayList<>();
if(root==null) return list;
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
while(!queue.isEmpty()){
TreeNode temp=queue.poll();
list.add(temp.val);
// queue.offer(temp.left);
// queue.offer(temp.right);
// 这样写是错的,一定要先判断是否为空再offer值,不然offer,提供的是null
if(temp.left!=null) queue.offer(temp.left);
if(temp.right!=null) queue.offer(temp.right);
}
return list;
}
22.二叉搜索树的后序遍历序列【二叉搜索树】【递归】
- 思路:
搜索二叉树,根节点的值大于其左子树中任意一个节点的值,小于其右节点中任意一节点的值。 - 代码:
//这是对的代码
public boolean VerifySquenceOfBST(int [] sequence) {
//截取数列考虑Arrays.copyOfRange
if(sequence.length==0 || sequence==null) return false;
//1. 找到根节点
int root=sequence[sequence.length-1];
//2. 找到第一个大于根节点的索引
//如果后面的值有小于根节点的,那么就返回false
//如果后面的值都大于根节点,那么就递归左右子树,最后返回left&&right
//这种递归不可能存在于else中,递归不可能存在循环中啊!!!
// 找到第一个大于或者等于root的索引i
//如果只有两个数得时候比如6,7那么是找到等于root=7得那个值得索引
int i=0; //把i定义在外面,可以省一个索引的变量
for(;i<sequence.length-1;i++){
if(sequence[i]<root) continue;
else break; //一旦发现大于root的值,记录索引i 退出循环
}
for(int j=i ; j<sequence.length-1;j++){
if(sequence[j]<root) return false;
//else
//如果有值小于root,那么就直接返回false了。
//如果没有返回false,就直接说明了,所有值都大于root了,可以去完成递归了。
//但是递归一定不要写在循环里面!!!写在外面。
}
//没有返回false,就说明所有值都大于root了,可以去进行递归了
boolean left = true ;
if(i>0){ //说明还有元素
left = VerifySquenceOfBST(Arrays.copyOfRange(sequence,0,i)); //不包括下标i的数
}
boolean right = true;
if(sequence.length-i-1>0){
right = VerifySquenceOfBST(Arrays.copyOfRange(sequence,i,sequence.length-1));
}
return left&&right;
}
对于上面得代码,可以完全通过,下面这段代码对于例子:[4,6,7,5]不能通过,找到错误得原因就是:当执行到 6,7这个数组得时候,会返回false,理由是index这里出现问题,对于上面那段代码:i是等于1的,i=0的时候满足循环条件执行循环体,然后i++之后的i变为1了。而对于下面那段错误的代码index=0,i=0执行循环条件,但是if不能执行,然后i=1了,但是index却还是0。所以导致后面判断的时候发现6<7直接返回false了。
所以再找 第一个大于root的索引i的值的时候,要注意只剩下两个元素的时候,最后一个为根,你找不到大于根的值了,此时要让这个索引等于这个根的索引。
//这是不能通过得代码,除了找序号那里有点不同,其他地方完全相同,但是就是不通过,搞不懂哪里有问题。
//搜索二叉树;根节点的值大于其左子树中任意一个节点的值,小于其右节点中任意一节点的值.
// 0 2 1 5 3 7 9 8 6 length=9
public boolean VerifySquenceOfBST ( int[] sequence){
if(sequence.length==0 || sequence==null) return false;
int val = sequence[sequence.length - 1];
int index = 0;
for (int i = 0; i < sequence.length-1; i++) {
if (sequence[i] > val) {
index = i;
break; // index=5
}
}//错误原因是索引找错了。
/*
for (int i = 0; i < sequence.length; i++) {
if (sequence[i] >= val) {
index = i;
//System.out.println(index);
break; // index=5
}
}*/ //改成这样索引就不会出错了,找到第一个大于或者等于根的索引
// System.out.println(index);
for (int j = index; j < sequence.length - 1; j++) {
if (sequence[j] < val) return false;
}
//同理遍历根节点的左子树和右子树
boolean left = true;
if (index > 0) { //说明左子树还有元素
left = VerifySquenceOfBST(Arrays.copyOfRange(sequence, 0, index));
}
boolean right = true;
if (sequence.length - index - 1 > 0) {//9-5-1=3 还有3个元素说明,还有右子树
right = VerifySquenceOfBST(Arrays.copyOfRange(sequence, index, sequence.length - 1));//索引是length-1
}
return left && right; //两个都为true才返回true
}
23.复杂链表的复制【链表】【HashMap】
- 思路:
首先需要建立一个新的链表,这个新的链表要和原链表建立起来联系,选择使用hashmap映射将新旧链表建立起来联系,新链表存入label值。然后再将新链表连接起来,即存入next和random的值。 - 代码:
//要想复制得到新的链表,首先你得创建一个新的链表,而且新的链表要和原链表建立起来联系
public RandomListNode Clone(RandomListNode pHead){
//先利用hashmap建立起来原链表和新链表的关系
HashMap<RandomListNode,RandomListNode> map=new HashMap<>();//建立原链表与新链表的关系
RandomListNode p1=pHead;用来创建新旧链表的对应节点关系
while(p1!=null){
map.put(p1,new RandomListNode(p1.label)); //把label逐个存入到新的链表中
p1=p1.next;
}//至此,新链表和旧链表关系创建成功,但是新链表只有label值,next和random都还为null
RandomListNode p2=pHead;// 用来存储next和random指针
while(p2!=null){
//由于最后一个节点的next可能为null,所以需要判断一下,是否是最后一个节点
//但是random就没有为null的节点,所以不需要判断
if(p2.next!=null){
map.get(p2).next=map.get(p2.next); //map.get(p2)表示得到新链表的节点,这个节点的next值应当存储下一个节点,
// 下一个节点就应该是刚才建立的新节点new RandomListNode的第二个节点
}else{
map.get(p2).next=null;
}
map.get(p2).random=map.get(p2.random);
p2=p2.next;
}
return map.get(pHead);
}
24.二叉搜索树与双向链表【二叉树】【链表】【ArrayList】
- 思路:
二叉搜索树,左子树的所有节点的值小于根节点的值,右子树所有节点的值大于根节点的值,左<根<右。
将二叉搜索树变为排序好的链表实际上就是中序遍历,然后将中序的结果串起来。
中序遍历参考博客 - 代码:
ArrayList<TreeNode> list=new ArrayList<>();
public TreeNode Convert(TreeNode pRootOfTree){
if(pRootOfTree == null) return null;
//二叉搜索树,左子树的所有节点的值小于根节点的值,右子树所有节点的值大于根节点的值,左<根<右
//将二叉搜索树变为排序好的链表实际上就是中序,然后将中序的结果串起来
Trans(pRootOfTree); //得到排序好的list
//2 5 9 串联起来
for(int i=0;i<list.size()-1;i++){
list.get(i).right=list.get(i+1);//当前值的右节点 存的应该是list中的下一个值
list.get(i+1).left=list.get(i); //i+1 的left存的是前一个节点信息
//这样写的话,就不涉及到特殊的第一个节点和最后一个节点了
}
return list.get(0);
}
public void Trans(TreeNode pRootOfTree){
if(pRootOfTree==null) return ;
else {
Trans(pRootOfTree.left);
list.add(pRootOfTree);
Trans(pRootOfTree.right);
}
}
- 知识点:
将list串联起来为双线链表的时候,注意i的取值,从0开始到<size-1即可,这种方法设计的好厉害啊。
25. 【未解决】二叉树中和为某一值的路径 【未解决】
26. 字符串的排列【未解决】
27.最小的K个数【数组】【排序】
- 思路:
思路很简单,就是先使用快排进行排序,然后取前k个元素即可。 - 代码:
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k){
ArrayList<Integer> list=new ArrayList<>();
if(k>input.length) return list; //注意判断k是否小于input的长度,如果大于是不符合条件的
Arrays.sort(input);
for(int i=0;i<k;i++){
list.add(input[i]);
}
return list;
}
28.【未解决】顺时针打印矩阵
29. 【待优化】数组中出现次数超过一半的数字【数组】
30. 【待解决】连续子数组的最大和
31.整数中1出现的次数(从1到n整数中1出现的次数)【数字变字符串】【数组】【字符串】
- 思路:
就是把数字变成字符串,然后字符串变为数组,检查数组的每个值是否是1。 - 代码:
public int NumberOf1Between1AndN_Solution(int n){
int count=0;
for(int i=0;i<=n;i++){
String str=String.valueOf(i);
char[] arr=str.toCharArray();
for(int j=0;j<arr.length;j++){
if(arr[j] == '1') count++;
}
}
return count;
}
- 知识点:
主要要记住数字怎么变为字符串,String str=String.valueOf(i);
然后补充一下字符串变为数字:int i=Integer.parseInt(str)
‘
32. 把数组排成最小的数【数字变字符串】【数组】【字符串】
- 思路:
比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。所以其实就是通过循环,每次循环得到一个最合适数。 - 代码:
public String PrintMinNumber(int [] numbers){
for(int i=0;i<numbers.length;i++){ //确保每个位置得到最小的数字,
// 比如第一轮循环确保第一个位置是最小的,第二轮循环确保第二个位置是最小的
for(int j=i+1;j<numbers.length;j++){ //和上面那个数字进行比较
Long val1=Long.valueOf(numbers[i]+""+numbers[j]); //数字变字符串,以及字符串变为数字
Long val2=Long.valueOf(numbers[j]+""+numbers[i]);
if(val2<val1){
int tmp=numbers[i];
numbers[i]=numbers[j];
numbers[j]=tmp;
}
}
}
String reslut=new String("");
for(int i=0;i<numbers.length;i++){
reslut=reslut+""+numbers[i];
}
return reslut;
}
- 知识点:
主要要记住数字怎么变为字符串,String str=String.valueOf(i);
String str=i+""
然后补充一下字符串变为数字:int i=Integer.parseInt(str)
‘
【未解决】 33. 丑数
34. 第一个只出现一次的字符【hashmap】【字符串】【出现次数】
- 思路:
凡是出现统计次数的问题都要考虑hashmap。 - 代码:
public int FirstNotRepeatingChar(String str) {
HashMap<Character,Integer> map = new HashMap<>();
for(int i=0;i<str.length();i++){ //字符串的length是方法,不是属性
char c=str.charAt(i);
//取出字符,然后判断这个字符在 hashmap的键中是否存在,存在则值加1 不存在,则添加并将值设为1
if(map.containsKey(c)){
int time = map.get(c); //取出字符所对应的值
time++;
map.put(c,time);
}else{
map.put(c,1); //如果不存在这个键,就存放进去,让次数为1
}
}
//遍历取出第一个出现一次的字符
for(int i=0;i<str.length();i++){ //遍历字符串,而不是遍历hashmap
char c=str.charAt(i);
int time = map.get(c);
if(time==1) return i;
}
return -1;
}
- 知识点:
字符串在拆分的时候,不是只有变为数组这一种方法啊,还可以调用charAt()
方法来得到某一个字符。
另外hashmap在存值的时候,只能通过一种put
函数存,不能通过对get
某一个元素进行赋值的方法存值。
【未解决】35.数组中的逆序对
36.两个链表的第一个公共结点【链表】
- 思路:
求2个链表的长度,然后让长的链表先走差个长度。在同时遍历两个链表,遇到的第一个相同的节点作为返回值即可。 - 代码:
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2){
//求2个链表的长度
int count1=0;
ListNode p1=pHead1;
while (p1!=null){
count1++;
p1=p1.next;
}
int count2=0;
ListNode p2=pHead2;
while (p2!=null){
count2++;
p2=p2.next;
}
//让长的链表先走 两个长度之差的 步数
int sub=count1-count2;
p1=pHead1;
p2=pHead2;
if(sub>0){ //说明链表1长
for(int i=0;i<sub;i++){
p1=p1.next;
}
}else {
sub=-sub;
for (int i=0;i<sub;i++){
p2=p2.next;
}
}
//2个链表一起走,相同的那个节点就作为返回值
while (p1!=null && p2!=null){
if(p1==p2) return p1;
p1=p1.next;
p2=p2.next;
}
return null;
37. 数字在排序数组中出现的次数【二分法】
- 思路:
自己的方法太low了,就是遍历。有机会看看二分法把。 - 代码:
public int GetNumberOfK(int [] array , int k){
int num=0;
for(int i=0;i<array.length;i++){
if(array[i] == k) num++;
}
return num;
}
38.二叉树的深度【二叉树】【队列】
- 思路:
和打印二叉树差不多,借助对列,只不过这里需要一个额外的size变量来记录每层节点的个数,这一层的所有节点都出队以后,结果才递增一个。 - 代码:
public int TreeDepth(TreeNode root){
if(root==null) return 0;
//打印二叉树的时候用到了对列,现在这个和打印二叉树差不多,也考虑队列
Queue<TreeNode> queue=new LinkedList<>();
queue.offer(root);
int result=0;
while (!queue.isEmpty()){
int size=queue.size();
for(int i=0;i<size;i++){
TreeNode temp = queue.poll();
if(temp.left!=null) queue.offer(temp.left);
if(temp.right!=null) queue.offer(temp.right);
}
result++;
}
return result;
}
39.平衡二叉树【二叉树深度】【递归】【Math】
- 思路:
平衡二叉树的定义就是:
条件一:它必须是二叉查找树。
条件二:每个节点的左子树和右子树的高度差至多为1。
所以先设定一个代码用来求二叉树的深度。
然后判断是否是平衡二叉树的时候,要保证左右子树都是平衡二叉树,同时左右子树的高度最多为1,即左右子树的深度小于2. - 代码:
//平衡二叉树
public boolean IsBalanced_Solution(TreeNode root){
if(root==null) return true;
return IsBalanced_Solution(root.right) && IsBalanced_Solution(root.left) &&
Math.abs(TreeDeepth(root.left)-TreeDeepth(root.right))<2;
}
//求二叉树高度的代码
public int TreeDeepth(TreeNode root){
if(root==null) return 0;
int left=TreeDeepth(root.left);
int right=TreeDeepth(root.right);
return Math.max(left,right)+1;
}
- 知识点:
二叉树深度的代码。
Math工具的使用。Math.max(a,b)
以及Math.abs(a-b)
注意这里面是减法。
40.数组中只出现一次的数字【hashmap】【次数】
- 思路:
统计次数,用hashmap。
新建的map中的值是null,所以不能使用map.get(元素) ==0
这种没办法确定次数,要用map.containsKey
- 代码:
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]){
//统计次数用hashmap
HashMap<Integer,Integer> map=new HashMap<>();
for(int i=0;i<array.length;i++){
//新建的map中的值是null,所以不能使用map.get(元素) ==0这种没办法确定次数
if(map.containsKey(array[i])){
int time=map.get(array[i])+1;
map.put(array[i],time);
}else{
map.put(array[i],1);
}
}
//存入到两个数组中,先借助list倒一下
ArrayList<Integer> list=new ArrayList<>();
for (int i=0;i<array.length;i++){
if(map.get(array[i])==1) list.add(array[i]);
}
num1[0]=list.get(0);
num2[0]=list.get(1);
}
41. 和为S的连续正数序列【Arraylist】【数组】
- 思路:
for循环遍历到sum的所有值,然后再求和的过程中需要引入变量j,来作为逐渐递增的元素,借助while循环判断是否继续递增。 - 代码:
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum){
ArrayList<ArrayList<Integer>> result=new ArrayList<>();
for(int i=1;i<sum;i++){ //连续正序列,不包括本身
int temp=0; //用来记录和
int j=i;//递增相加的时候,不能用i,所以引入另一个变量j来做递增相加
while (temp<sum){
temp=temp+j;
j++;
}
if(sum==temp){
ArrayList<Integer> list_temp=new ArrayList<>(); //if里面的变量是局部变量
for(int m=i;m<j;m++){
list_temp.add(m);
}
result.add(list_temp);
}
}
return result;
}
42.和为S的两个数字【数组】【双指针】
- 思路:
首先是找和为sum的组合,找的方法可以使用两个指针,一个头,一个尾,然后求和,移动指针。将乘积可以存在链表的第三个位置。求乘积最小的时候,选择遍历每个第三位的倍数。 - 代码:
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum){
//首先数组是递增的所以可以使用两个指针同时移动的方法
//先将所有和为sum的结果存放在一个list中
ArrayList<Integer> list=new ArrayList<>();
int i=0;
int j=array.length-1;
while(i<j){
if(array[i]+array[j]>sum){
j--;
}else if(array[i]+array[j]<sum){
i++;
}else{
list.add(array[i]);
list.add(array[j]);
//把乘也存入
list.add(array[i]*array[j]);
i++; //和为sum以后,2个指针都要移动
j--;
}
}
// System.out.println(list);
ArrayList<Integer> result=new ArrayList<>();
if(list.size()==0) return result; //如果没有和为sum的就返回空的就可以了
int temp=list.get(2);
for(int m=2;m<list.size();m=m+3){
if(list.get(m)<=temp){
result.add(list.get(m-2));
result.add(list.get(m-1));
}
}
// System.out.println(result);
return result;
}
43.左旋转字符串【字符串】
- 思路:
移动的时候注意元素变化。 - 代码:
public String LeftRotateString(String str,int n) {
char[] arr=str.toCharArray();
char[] result=str.toCharArray();
for(int i=0;i<arr.length;i++){
int index=i-n%arr.length;
if(index<0) index=index+arr.length;
result[index]=arr[i];
}
return new String(result);
}
44.翻转单词顺序列【字符串】
- 思路:
先交换数组,记得只交换一半。然后将新的数组拼接成新的字符串。 - 代码:
public String ReverseSentence(String str){
String[] strs=str.split(" ");
for(int i=0;i<strs.length/2;i++){ //记得除以2啊,只交换一次即可
String temp=strs[i];
strs[i]=strs[strs.length-1-i];
strs[strs.length-1-i]=temp;
}
//将字符串数组,拼成字符串
StringBuffer result=new StringBuffer();
for(int i=0;i<strs.length;i++){
result.append(strs[i]);
result.append(" ");
}
return result.toString().trim(); //去掉空格不是很容易吗。。。
/*String result="";
for(int i=0;i<array.length;i++){
result = result+" "+array[i];
}
return result.trim();*/ //也可以使用这种方式对字符串进行拼接
}
45.扑克牌顺子【数组】
- 思路:
先排序,然后计算0的个数,后面的差值,差一个2,0的个数减少一个,差一个3,0的个数减少2个。最后统计0个个数的正负。
注意两种特殊情况:首先数组为空,直接返回false。
其次,一旦有两个相同的元素,直接返回false - 代码:
public boolean isContinuous(int [] numbers){
// 1 3 5 0 0 其中0可以当作任何数 1 2 3 4 5可以构成顺子
//先排序 0 0 1 3 5 然后求除了0以外的差值,2补一个0 3补2个0
if(numbers.length==0) return false; //注意几种特殊情况,首先数组为空,直接返回false
Arrays.sort(numbers);
int count_zero=0;
for(int i=0;i<numbers.length;i++){
if(numbers[i]==0) count_zero++;
else break;
} //0的个数
for(int i=count_zero+1;i<numbers.length;i++){
if(numbers[i] ==numbers[i-1]) return false; //其次,一旦有两个相同的元素,直接返回false
int sub=numbers[i]-numbers[i-1];
if(sub==1) continue;
else count_zero=count_zero-(sub-1);
}
if (count_zero>=0) return true;
else return false;
}
46.孩子们的游戏(圆圈中最后剩下的数)【数组】【循环】【需要仔细研读】
- 思路:
先将元素存入到list中。list中只要元素数量大于1,就要一直删除元素。删除元素靠循环m,没循环一次删除一个元素,所以需要一个额外的元素cur来记录当前位置,从当前位置往后不断循环。 - 代码:
public int LastRemaining_Solution(int n, int m){
if(n<1||m<1) return -1; //注意特殊情况
ArrayList<Integer> list=new ArrayList<>();
for(int i=0;i<n;i++){
list.add(i);
} //将孩子序号存入到一个list中 [0 1 2 3 4 5 6] m=3
int cur=-1; //指向当前指针
while(list.size()>1){
for(int i=0;i<m;i++){//控制循环次数,每到这个循环执行一次删除一个元素
cur++;
if(cur==list.size()) cur=0;
}//循环执行完一遍,删除一个元素
list.remove(cur);//删除这个元素以后,cur需要变化一下 删除2以后,变为[0 1 3 4 5 6] 此时cur=2,但是下一次循环中需要cur为2,所以这里先递减一次
cur--;
}
return list.get(0);
}
47.求1+2+3+…+n【递归】
- 代码:
public int Sum_Solution(int n) {
if(n==1) return 1;
return n+Sum_Solution(n-1);
}
48. 【待解决】不用加减乘除做加法【运算符】【进制】
49. 把字符串转换成整数
50. 数组中重复的数字
- 思路:
如果是正确排序,应该是让每个元素处于自己应该在的位置,即numbers[i]=i
才是正确的。所以就不断交换,直到元素到了正确位置,一旦发现交换的两个位置相同了,则说明这个就是重复元素,返回这个元素,并且返回true即可。 - 代码:
public boolean duplicate(int numbers[],int length,int [] duplication){
for(int i=0;i<length;i++){
while(numbers[i]!=i){ //不断的交换位置,让数字处于属于自己该有的位置处
//不在它应该在的位置,就交换元素,交换过程中一旦发现,这两个元素相同,那么这就是重复元素了。
int val=numbers[i]; //多定义一个变量可以省每次去查找数组的时间
if(val==numbers[val]){
duplication[0]=numbers[i];
return true;
}else{
// int temp=numbers[i];
numbers[i]=numbers[val];
numbers[val]=val;
}
}
}
return false;
}
51.构建乘积数组
- 思路:
摆矩阵,横坐标是A,纵坐标是B,参考:披萨大叔
先计算下三角,再计算上三角。 - 代码:
public int[] multiply(int[] A) {
//利用矩阵法,画图解析一下,先计算下三角,再计算上三角
int[] B=new int[A.length];
//先计算下三角
B[0]=1;
for(int i=1;i<B.length;i++){
B[i]=B[i-1]*A[i-1];
}
//计算上三角
int temp=1;
for(int i=B.length-2;i>=0;i--){
temp=temp*A[i+1]; //先计算右三角,再让右三角和B相乘
B[i]=B[i]*temp;
}
return B;
}
52.正则表达式匹配
53.表示数值的字符串
54.字符流中第一个不重复的字符
55.链表中环的入口结点
- 思路:
先判断是否有环,两个指针都从头开始走,一个一次走一步,一个一次走两步,如果有环那么快和慢指针一定会相遇,如果没环,那么快指针同时快指针的next都会指向null。
然后判断环中节点的个数,刚才的两个相遇的指针一定是环中的点,当这个环的点再次回到这个点,走了几步就是环中节点的个数。
然后一个指针从头开始,另一个从环的节点个数那里开始,两者相遇就是环的开端。 - 代码:
if(pHead == null){
return null;
}
// 判断是否是环,两个指针从头节点开始,slow一次走一个,fast一次走俩个,
//如果是环,fast总会返回来和slow相遇,如果不是环,fast走到尾节点也不会和slow相遇
boolean isHuan = false; //默认不是环
ListNode slow=pHead;
ListNode fast=pHead;
while(fast!=null && fast.next!=null){ //fast没到尾节点,就一直往后走,到尾节点,也没相遇,那就不是环
slow=slow.next;
fast=fast.next.next;
if(slow == fast){
isHuan = true;
break;
}
}
//如果不是环,那么返回null
if(!isHuan) {
return null;
}else{
//如果是环那么,确定环中节点的个数,刚才相遇的节点一定是环中的节点,
//所以从这个节点开始,在回到这个节点所走的步数也就是环中节点的个数了
int n=1;
// fast和slow现在指的是同一个节点,相遇的那个节点
fast=fast.next;
while(fast!=slow){
fast=fast.next;
n++;
} //n是环中节点的个数
//找到节点的入口,一个节点从头开始,另一个节点从节点长度那么长开始,相遇的节点就是环的入口
slow = pHead;
fast = pHead;
for(int i=0;i<n;i++){
fast=fast.next;
}
//然后每个节点走一步,直到相遇,则为节点的入口
while(slow!=fast){
slow=slow.next;
fast=fast.next;
}
}
return fast;
}
56. 删除链表中重复的结点
- 思路:
有一点很重要的是,循环链表的时候,需要注意最后一个节点,当p指向最后一个节点的时候,p的next是null,如果你使用p.next!=null
作为判断条件是可以的,但是如果你后面写p.next.val会发生异常,因为没有val这个值,所以有时我们考虑的是判断while (p.next!=null)
- 代码:
public ListNode deleteDuplication(ListNode pHead){
HashSet<Integer> set=new HashSet<>();
ListNode p=pHead;
while (p.next!=null){
//到最后一个指针的时候,是p.next为null,因为后面用到了p.next.val,空指针是没有val的,所以这里只能写p.next
if(p.val == p.next.val) set.add(p.val);
p=p.next;
}
//重新组合一个链表作为返回值,首先单独处理头节点
while (pHead!=null && set.contains(pHead.val)){
pHead=pHead.next;
}
if(pHead==null) return null;
ListNode pre=pHead;
ListNode cur=pHead.next;
while (cur!=null){
if(set.contains(cur.val)){
pre.next=cur.next;
cur=cur.next;
}else{
pre=pre.next;
cur=cur.next;
}
}
return pHead;
}