刷刷编程基础题~(1)
来源:互联网 发布:淘宝轮播图片素材950 编辑:程序博客网 时间:2024/04/30 11:39
咳咳咳,今晚开始刷剑指offer,以前做过一部分,这次认真再来一下
1.二维数组中的查找
题目描述
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解题思路:
第一种方法:
把每一行看成有序递增的数组,
利用二分查找,
通过遍历每一行得到答案,
时间复杂度是nlogn
第二种方法:
从左下角的元素开始比较,target大的话row++,target小的话col--
//第一种public class Solution { public boolean Find(int [][] array,int target) { //对每列二分检索(每列每行都一样) for(int i=0;i<array.length;i++){ int low=0; int high=array[0].length-1; while(low<=high){//别忘了这里 int mid=(low+high)/2; if(target==array[i][mid]) { return true; }else{ if(target>array[i][mid]){ low=mid+1; }else{ high=mid-1; } } } } return false; }}
//第二种public class Solution { public boolean Find(int [][] array,int target) { int row=0; int col=array[0].length-1; while(row<array.length&&col>=0){//边界这里其实有点问题 if(target==array[row][col]){ return true; }else if(target>array[row][col]){ row++; }else{ col--; } } return false; }}
2.替换空格
题目描述
请实现一个函数,将一个字符串中的空格替换成“%20”。
例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
解体思路:
这种题有很多种方法,可以巧妙地用已有的方法
第一种:
(简单粗暴)正则表达式
题中给定的参数传的是StringBuffer类的,先str.toString()转化为String类【那个括号不要忘了】
然后用String类的replaceAll("\\s","%20");
\s表示空格,前面的 \ 用来转义第二个 \
!!注意:replaceAll方法返回一个String,而不是更改原来的字符串,所以要新定义一个String a=s.replaceAll("","")
第二种:
转化为String类后,在转化成char[]数组,遍历,声明一个StingBuffer类,遇到空格,加上%20,否则加原来的字符
//第一种public class Solution { public String replaceSpace(StringBuffer str) { return str.toString().replaceAll("\\s", "%20"); }}
//第二种public class Solution { public String replaceSpace(StringBuffer str) { String s=str.toString(); char[] c=s.toCharArray(); StringBuffer sb=new StringBuffer(); for(int i=0;i<c.length;i++){ if(c[i]==' '){ sb.append("%20"); }else{ sb.append(c[i]); } } return sb.toString(); }}
3.从尾到头打印链表
题目描述
输入一个链表,从尾到头打印链表每个节点的值。
输入描述:
输入为链表的表头
输出描述:
输出为需要打印的“新链表”的表头
解题思路:
第一种:
利用堆栈"先进后出"
第二种:
递归【!!!二刷的时候还不熟悉】
判断当前结点是否为空,不空的话,递归调用该函数,不停地找next,到了尾节点,其next为空,此时,将尾节点添加进list中,递归开始往回了,不停地倒着加入节点
//第一种/*** 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) { Stack<Integer> stack=new Stack<Integer>(); while(listNode!=null){ stack.push(listNode.val); listNode=listNode.next; } ArrayList<Integer> arr=new ArrayList<Integer>(); while(!stack.isEmpty()){ arr.add(stack.pop()); } return arr; }}
//第二种/*** public class ListNode {* int val;* ListNode next = null;** ListNode(int val) {* this.val = val;* }* }**/import java.util.ArrayList;public class Solution { ArrayList<Integer> list=new ArrayList<Integer>();//在函数外面声明 public ArrayList<Integer> printListFromTailToHead(ListNode listNode) { if(listNode!=null){ this.printListFromTailToHead(listNode.next);//用this调用 list.add(listNode.val); } return list; }}
4.重建二叉树
题目描述
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。
假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解题思路:
先判断这两种遍历数组是否为空,不要用pre==null,用pre.length==0
根据题目给出的前序遍历、后序遍历数组,
首先找出根节点,然后再根据中序遍历找到左子树和右子树的长度,
分别构造出左右子树的前序遍历和中序遍历序列,
最后分别对左右子树采取递归,递归跳出的条件是序列长度为1.
先序遍历第一个位置肯定是根节点node,
中序遍历的根节点位置在中间p,这样就可以知道左子树和右子树的长度
用Arrays.copyOfRange(int[] ,int from,int to)【取不到to】
递归递归【递归递归!!二刷的时候还是不够熟练】
/** * Definition for binary tree * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode(int x) { val = x; } * } */import java.util.*;public class Solution { public TreeNode reConstructBinaryTree(int [] pre,int [] in) { if(pre.length==0||in.length==0){ return null; } TreeNode node=new TreeNode(pre[0]); for(int i=0;i<in.length;i++){ if(pre[0]==in[i]){ node.left=reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i)); node.right=reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,pre.length),Arrays.copyOfRange(in,i+1,in.length)); } } return node; }}
5.用两个栈实现队列
题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
解题思路:
这个题之前整理过,主要注意的是,一个栈负责压入,一个栈负责弹出
弹出时,那个栈要保证里面是空的,从压入栈中压入后,弹出
压入时,也要保证压入栈里没有东西,这两点一样,【弹出压入这两点一样,只要实现一个就行,全都放在弹出那】
注意到要将int类型转为Integer类型!!!
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(new Integer(node));//注意到要将int类型转为Integer类型//不一定用转换,直接也行 } public int pop() { if(stack2.isEmpty()){//判断栈空不空,用isEmpty()!!! while(!stack1.isEmpty()){ stack2.push(stack1.pop()); } } return stack2.pop().intValue();//注意到要将Integer类型转为int类型//不一定用转换,直接也行 }}
6.旋转数组的最小数字【有点疑惑】
题目描述
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
【其实这个题就是求数组中最小元素,只是因为它自身的特性可以减小时间复杂度来求】
解题思路:
根据题意说明是一个递增数组的旋转,所以如题所示【3,4,5】,【1,2】还是局部递增的,
在这种的数组中查找,一般选择二分的方法;
基本模型有了,下面试着分析:
1.先取出中间的数值,和最后一个比较5>2 说明mid之前的某些部分旋转到了后面,
所以下次寻找 low = mid+1 开始;
2.取出的中间值要是小于high,说明mid-high之间都应为被旋转的部分,所以最小应该在mid的前面,
但是也有可能当前的mid 就是最小的值 所以下次需找的应该 从mid开始,也即high = mid 开始
3.当*mid == *high的时候,说明数组中存在着相等的数值,
可能是这样的形式 【2,2,2,2,1,2】所以应该选择的high 应该递减1 作为下次寻找的上界。
import java.util.ArrayList;public class Solution { public int minNumberInRotateArray(int [] array) { int low=0; int high=array.length-1; while(low<high){ int mid=low+(high-low)/2;//最好用这种,不用(high+low)/2 if(array[mid]>array[high]){ low=mid+1; }else if(array[mid]<array[high]){ high=mid; }else { high=high-1; } } return array[low];//返回最小 }}
7.斐波那契数列
现在要求输入一个整数n,请你输出斐波那契数列的第n项。
n<=39
解题思路:
迭代方法,用两个变量记录fn-1和fn-2
还有P.S. f(n) = f(n-1) + f(n-2),第一眼看就是递归啊,简直完美的递归环境
if(n<=1) return n;else return Fibonacci(n-1)+Fibonacci(n-2);
public class Solution { public int Fibonacci(int n) { if(n<2){ return n; } int numfn1=0; int numfn2=1; int current=0; for(int i=2;i<=n;i++){ current=numfn1+numfn2; numfn1=numfn2; numfn2=current; } return current; }}
8.跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
解题思路:
f(n) = f(n-1) + f(n-2)同上同上
最后一次可能跳一阶,可能跳两阶,
public class Solution { public int JumpFloor(int target) { if(target<=2){ return target; } int f1=2;// 当前台阶后退一阶的台阶的跳法总数(初始值当前台阶是第3阶) int f2=1;// 当前台阶后退二阶的台阶的跳法总数(初始值当前台阶是第3阶) int current=0; for(int i=3;i<=target;i++){ current=f1+f2; f2=f1;//后退一阶在下一次迭代变为后退两阶 f1=current;// 当前台阶在下一次迭代变为后退一阶 } return current; }}
9.变态跳台阶
题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。
求该青蛙跳上一个n级的台阶总共有多少种跳法。
题目解析:
重要的是分析化简,,,
关于本题,前提是n个台阶会有一次n阶的跳法。分析如下:
f(1) = 1
f(2) = f(2-1) + f(2-2) //f(2-2) 表示2阶一次跳2阶的次数。
f(3) = f(3-1) + f(3-2) + f(3-3)
...
f(n) = f(n-1) + f(n-2) + f(n-3) + ... + f(n-(n-1)) + f(n-n)
说明:
1)这里的f(n) 代表的是n个台阶有一次1,2,...n阶的 跳法数。
2)n = 1时,只有1种跳法,f(1) = 1
3) n = 2时,会有两个跳得方式,一次1阶或者2阶,这回归到了问题(1) ,f(2) = f(2-1) + f(2-2)
4) n = 3时,会有三种跳得方式,1阶、2阶、3阶,
那么就是第一次跳出1阶后面剩下:f(3-1);第一次跳出2阶,剩下f(3-2);第一次3阶,那么剩下f(3-3)
因此结论是f(3) = f(3-1)+f(3-2)+f(3-3)
5) n = n时,会有n中跳的方式,1阶、2阶...n阶,得出结论:
f(n) = f(n-1)+f(n-2)+...+f(n-(n-1)) + f(n-n) => f(0) + f(1) + f(2) + f(3) + ... + f(n-1)
6) 由以上已经是一种结论,但是为了简单,我们可以继续简化:
f(n-1) = f(0) + f(1)+f(2)+f(3) + ... + f((n-1)-1) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2)
f(n) = f(0) + f(1) + f(2) + f(3) + ... + f(n-2) + f(n-1) = f(n-1) + f(n-1)
可以得出:
f(n) = 2*f(n-1)
7) 得出最终结论,在n阶台阶,一次有1、2、...n阶的跳的方式时,总得跳法为:
| 1 ,(n=0 )
f(n) = | 1 ,(n=1 )
| 2*f(n-1),(n>=2)
【注!!】
也可以看成,最后一步走1,2,3,n阶
则f(n)=f(n-1)+f(n-2)+....+f(n-n);
而f(n-1)刚好等于f(n-2)+...f(n-n)
所以得出f(n)=2*f(n-1)
public class Solution { public int JumpFloorII(int target) { if (target <= 0) { return -1; } else if (target == 1) { return 1; } else { return 2 * JumpFloorII(target - 1); } }}
10.矩形覆盖
我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。
请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
解题思路
依旧是斐波那契数列
2*n的大矩形,和n个2*1的小矩形
其中target*2为大矩阵的大小
有以下几种情形:
1、target = 1大矩形为2*1,只有一种摆放方法,return1;
2、target = 2 大矩形为2*2,有两种摆放方法,return2;
3、target = n 分为两步考虑:
如果第一格竖着放,只占一个格,还剩n-1格 f(target-1)种方法
如果前两格横着放两个,占两个格,还剩n-2格 f(target-2)种方法
public class Solution { public int RectCover(int target) { if(target==0||target==1||target==2){ return target; }else if(target<0){return -1;} else{ return RectCover(target-1)+RectCover(target-2); } } }
11.二进制中1的个数
题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
解题思路:
第一种:
如果一个整数不为0,那么这个整数至少有一位是1。
如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,
原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。
举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。
减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.
我们发现减1的结果是把最右边的一个1开始的所有位都取反了。
这个时候如果我们再把原来的整数和减去1之后的结果做与运算,
从原来整数最右边一个1那一位开始所有位都会变成0。
如1100&1011=1000.
也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.
那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
第二种:
Java自带的函数
Java.lang.Integer.bitCount()方法
统计参数i转成2进制后有多少个1
【要不是这道题还真不知道这个函数呢】
第三种:
把这个数逐次 右移 然后和1 与,
就得到最低位的情况,其他位都为0,
如果最低位是0和1与 之后依旧 是0,如果是1,与之后还是1。
对于32位的整数 这样移动32次 就记录了这个数二进制中1的个数了
public class Solution { public int NumberOf1(int n) { int count = 0; while(n!= 0){ count++; n = n & (n - 1);//!!与运算 } return count; }}
public class Solution {public int NumberOf1(int n) { return Integer.bitCount(n); }}
//这个方法也是很6的public class Solution { public int NumberOf1(int n) { return Integer.toBinaryString(n).replaceAll("0","").length(); }}
public class Solution {public int NumberOf1(int n) { int count=0; for(int i=0;i<32;i++){ if((n>>i&1)==1){//!!!注意这里用i标记,每次循环,就重新右移i位 count++; } } return count; }}
12.数值的整数次方
题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
解题思路:
第一种:
咳咳,Java自带的函数~
第二种:
算法的本质就是模拟数学规律,我们可以先模拟一下幂运算就是乘法的连乘,那么用算法写出来,然后再考虑几个测试用例的极端情况,如exponent==0或者exponent<0的情况,然后按逻辑写出你的代码
public class Solution { public double Power(double base, int exponent) { return Math.pow(base,exponent); }}
public class Solution { public double Power(double base, int exponent) { if(exponent==0){ return 1; }else if(exponent>0){ double num=base; for(int i=1;i<exponent;i++){ num=num*base; } return num; }else{ double num2=base; int flag=-exponent; for(int i=1;i<flag;i++){ num2=num2*base; } num2=1/num2; return num2; } }}
13.调整数组顺序使奇数位于偶数前面
题目描述
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解题思路:
第一种:
空间换时间,创建一个数组,奇数从头放,偶数从奇数个数的末尾开始放
或者,直接两个数组
第二种:
在一个数组中,类似冒泡算法,前偶后奇数就交换
import java.util.*;public class Solution { public void reOrderArray(int [] array) { ArrayList<Integer> odd=new ArrayList<Integer>();//奇数 ArrayList<Integer> even=new ArrayList<Integer>();//偶数 for(int i=0;i<array.length;i++){ if(array[i]%2==0){ even.add(array[i]); }else{ odd.add(array[i]); } } for(int i=0;i<odd.size();i++){ array[i]=odd.get(i); } for(int i=odd.size();i<array.length;i++){ array[i]=even.get(i-odd.size()); } }}
public class Solution { public void reOrderArray(int [] array) { for(int i=0;i<array.length;i++){ for(int j=array.length-1;j>i;j--){ if(array[j]%2==1&&array[j-1]%2==0){ int temp=array[j]; array[j]=array[j-1]; array[j-1]=temp; } } } }}
14.链表中倒数第k个结点
题目描述
输入一个链表,输出该链表中倒数第k个结点。
解题思路:
两个指针,先让第一个指针和第二个指针都指向头结点,然后再让第一个指针走(k-1)步,到达第k个节点。然后两个指针同时往末尾移动,当第一个结点到达末尾的时候,第二个结点所在位置就是倒数第k个节点了
/*public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; }}*/public class Solution { public ListNode FindKthToTail(ListNode head,int k) { if(head==null||k<=0){ return null; } ListNode pre=head;//先走的那个 ListNode last=head;//之后走的那个人 for(int i=1;i<k;i++){//先让第一个走到k if(pre.next!=null){//k大于链长就返回空 pre=pre.next; }else{ return null; } } while(pre.next!=null){ pre=pre.next; last=last.next; } return last; }}
15.反转链表
题目描述
输入一个链表,反转链表后,输出链表的所有元素。
解题思路:
三个指针,先记录当前结点的下一个节点
让当前结点指向前一个节点,
让前一个节点取代当前节点,因为还要继续向下走
让当前节点挪到下一个节点的位置,这是已经继续向下走了
循环,当前节点为空时,正好前一个节点为最后一个节点,而且所有节点的指向已经都反转过来了
1-->2-->3-->4-->5
1<--2<--3<--4<--5
/*public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; }}*/public class Solution { public ListNode ReverseList(ListNode head) { ListNode pre=null; ListNode next=null; ListNode cur=head; while(cur!=null){ next=cur.next; cur.next=pre; pre=cur; cur=next; } return pre; }}
16.合并两个排序的链表
题目描述
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
解题思路:
第一种
新建一个头节点用来存储新的链表
比较两个链表的值,哪个小就放到新链表中
第二种:
递归
/*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=new ListNode(0);//必须new一个,构造函数必须有参数 head.next=nul;//有没有这个都行 ListNode root=head;//head是要跟着动的,留一个root是不动的,最后返回时要用 while(list1!=null&&list2!=null){ if(list1.val<list2.val){ head.next=list1;//head是最前面一个无用的节点,从list1或者list2开始 head=list1;//之后head还有list1节点继续往后挪,!!!!head和list1一块往后移, list1=list1.next; }else{ head.next=list2; head=list2; list2=list2.next; } } //把未结束的链表连接到合并后的链表尾部 if(list1!=null){ head.next=list1; } if(list2!=null){ head.next=list2; } return root.next;//其实没有办法确定第一个节点是谁,所以从第一个节点前面的节点声明,最后结果是root.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) { if(list1==null){return list2;} else if(list2==null){return list1;} ListNode head=null; if(list1.val<list2.val){ head=list1; head.next=Merge(list1.next,list2); }else{ head=list2; head.next=Merge(list2.next,list1); } return head; }}
17.树的子结构
题目描述
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
解题思路:
1、首先设置标志位result = false,因为一旦匹配成功result就设为true,
剩下的代码不会执行,如果匹配不成功,默认返回false
2、递归思想
如果根节点相同则递归调用DoesTree1HaveTree2(),
如果根节点不相同,则判断tree1的左子树和tree2是否相同,再判断右子树和tree2是否相同
3、注意null的条件,HasSubTree中,如果两棵树都不为空才进行判断,
DoesTree1HasTree2中,如果Tree2为空,则说明第二棵树遍历完了,即匹配成功,
tree1为空有两种情况
(1)如果tree1为空&&tree2不为空说明不匹配,
(2)如果tree1为空,tree2为空,说明匹配。
1.首先需要递归pRoot1树,找到与pRoot2根一样的节点,这需要一个遍历
2.找到相同的根节点后,要判断是否子树,仍需要一个一个遍历对比
树的遍历我们一般就用递归来做,那么根据分析需要两个递归函数如下:
!!!!!关于树的问题还要再加强!!!!
/**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) { boolean result=false; //当tree1和tree2都不为空是,才进行比较,否则直接返回false if(root1!=null&&root2!=null){ //如果找到了对应tree2的根节点 if(root1.val==root2.val){ //以这个根节点为起点判断是否包含tree2 result=DoesTree1HaveTree2(root1,root2); } //如果找不到,那么就再去root1的左儿子当作起点 if(!result){ result=HasSubtree(root1.left,root2); } //如果还找不到,那么就再去root1的有儿子当作起点 if(!result){ result=HasSubtree(root1.right,root2); } } return result; } //判断tree1是否包含tree2 public static boolean DoesTree1HaveTree2(TreeNode node1,TreeNode node2){ //如果tree1已经遍历完了,都能对应的上,返回true if(node2==null){ return true; } //如果tree2还没有遍历完,tree1却遍历完了,返回false if(node1==null){ return false; } //如果其中有一个点没有对应上,返回false if(node1.val!=node2.val){ return false; } //如果根节点对应的上,那么久分别去自子节点里面匹配 return DoesTree1HaveTree2(node1.right,node2.right)&&DoesTree1HaveTree2(node1.left,node2.left); }}
18.二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
解题思路:
第一种:
递归,关于树的很多都可以用递归来解
第二种:
非递归
层次遍历
用一个栈,压入根节点,弹出后交换其左右子树,再将左右子树压入,直到遍历完所有节点
/**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 void Mirror(TreeNode root) { if(root == null){ return; } //用堆栈 Stack<TreeNode> stack = new Stack<TreeNode>(); stack.push(root);//压入根节点 while(!stack.isEmpty()){ TreeNode node = stack.pop(); //至少有一边子树不为空时,交换左右子树 if(node.left != null||node.right != null){ TreeNode temp = node.left; node.left = node.right; node.right = temp; } //左子树不为空时,把左子数压入栈中 if(node.left!=null){ stack.push(node.left); } //右子树不为空时,把右子树压入栈中 if(node.right!=null){ stack.push(node.right); } } }}
/**public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; }}*/public class Solution { public void Mirror(TreeNode root) { if(root!=null){//!要记得判断要不然出不去了 TreeNode temp=null; temp=root.right; root.right=root.left; root.left=temp; Mirror(root.right); Mirror(root.left); } }}
19.顺时针打印矩阵【等下再看,,】
题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,
例如,如果输入如下矩阵:
1 2 3 4
5 6 7 8
9 10 11 12
13 14 15 16
则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
解题思路:
把绕的每一圈拆成四步
可以用所有数字来衡量,当所有数字都走完就退出
!!增加一种方法!!
一圈一圈打印
import java.util.ArrayList;public class Solution { static ArrayList<Integer> list=new ArrayList<Integer>(); public static ArrayList<Integer> printMatrix(int [][] matrix) { int r1=0; int c1=0; int r2=matrix.length-1; int c2=matrix[0].length-1; while(r1<=r2&&c1<=c2){ printEdge(matrix,r1++,c1++,r2--,c2--); } return list; } public static void printEdge(int[][] matrix,int r1,int c1,int r2,int c2){ if(r1==r2){//只有一行 for(int i=c1;i<=c2;i++){ list.add(matrix[r1][i]); } }else if(c1==c2){//只有一列 for(int i=r1;i<=r2;i++){ list.add(matrix[i][c1]); } }else{ int curr=r1; int curc=c1; while(curc!=c2){ list.add(matrix[r1][curc]); curc++; } while(curr!=r2){ list.add(matrix[curr][c2]); curr++; } while(curc!=c1){ list.add(matrix[r2][curc]); curc--; } while(curr!=r1){ list.add(matrix[curr][c1]); curr--; } } }}
import java.util.ArrayList;public class Solution { public ArrayList<Integer> printMatrix(int [][] matrix) { ArrayList<Integer> ls = new ArrayList<Integer>(); int colStart = 0; int colEnd = matrix[0].length; int lineStart = 0; int lineEnd = matrix.length; int count = lineEnd * colEnd; if (matrix == null) return ls; while (count != 0) { for(int i = colStart;i<colEnd;i++){ ls.add(matrix[lineStart][i]); count--; } lineStart++; if(count==0) break; for(int i = lineStart;i<lineEnd;i++){ ls.add(matrix[i][colEnd-1]); count--; } colEnd--; if(count==0) break; for(int i = colEnd-1;i>=colStart;i--){ ls.add(matrix[lineEnd-1][i]); count--; } lineEnd--; if(count==0) break; for(int i = lineEnd-1;i>=lineStart;i--){ ls.add(matrix[i][colStart]); count--; } colStart++; if(count==0) break; } return ls; }}
import java.util.ArrayList;public class Solution { public ArrayList<Integer> printMatrix(int [][] matrix){ArrayList<Integer> list=new ArrayList<Integer>();if(matrix.length==0) return list; int n=matrix.length;int m=matrix[0].length; if(m==0) return list;int layer=(Math.min(m,n)-1)/2+1;//循环几圈for(int i=0;i<layer;i++){//从左上到右上for(int j=i;j<m-i;j++){list.add(matrix[i][j]);}//从右上到右下for(int k=i+1;k<n-i;k++){list.add(matrix[k][m-1-i]);}//从右下到左下//考虑奇数圈的情况,最后一圈只走了上面一行,要对最后两步进行限制for(int j=m-i-2;(j>=i)&&(n-i-1!=i);j--){list.add(matrix[n-i-1][j]);}//从左下到左上for(int k=n-i-2;k>i&&(m-i-1!=1);k--){list.add(matrix[k][i]);}}return list;}}
20.包含min函数的栈
题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
import java.util.Stack;public class Solution { Stack<Integer> data=new Stack<Integer>(); Stack<Integer> min=new Stack<Integer>(); public void push(int node) { data.push(node); if(min.isEmpty()){//这里要注意!栈要用isEmpty(),不要用null min.push(node); }else{ int temp=min.pop();//这里需要知道现在的最小值,pop出来之后记得放回去 min.push(temp); if(node<temp){ min.push(node); } } } public void pop() {//弹出这里,只用考虑弹出的那个值是不是最小值就行,不用想弹出他后,还在最小值的栈里 int num=data.pop(); int num1=min.pop(); if(num!=num1){ min.push(num1); } } public int top() { int num=data.pop(); data.push(num); return num; } public int min() { int num=min.pop(); min.push(num); return num; }}
21.栈的压入、弹出序列【还要看!!】
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
假设压入栈的所有数字均不相等。
例如序列1,2,3,4,5是某栈的压入顺序,
序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。
(注意:这两个序列的长度是相等的)
解题思路:
借用一个辅助的栈,遍历压栈顺序,先将第一个放入栈中,这里是1,
然后判断栈顶元素是不是出栈顺序的第一个元素,这里是4,很显然1≠4,所以我们继续压栈,
直到相等以后开始出栈,出栈一个元素,则将出栈顺序向后移动一位,
直到不相等,这样循环等压栈顺序遍历完成,如果辅助栈还不为空,说明弹出序列不是该栈的弹出顺序。
举例:
入栈1,2,3,4,5
出栈4,5,3,2,1
首先1入辅助栈,此时栈顶1≠4,继续入栈2
此时栈顶2≠4,继续入栈3
此时栈顶3≠4,继续入栈4
此时栈顶4=4,出栈4,弹出序列向后一位,此时为5,,辅助栈里面是1,2,3
此时栈顶3≠5,继续入栈5
此时栈顶5=5,出栈5,弹出序列向后一位,此时为3,,辅助栈里面是1,2,3
….
依次执行,最后辅助栈为空。如果不为空说明弹出序列不是该栈的弹出顺序。
import java.util.ArrayList;import java.util.Stack;public class Solution { public boolean IsPopOrder(int [] pushA,int [] popA) { while(pushA.length==0||popA.length==0){ return false; } Stack<Integer> stack=new Stack<Integer>(); int index=0; for(int i=0;i<pushA.length;i++){ stack.push(pushA[i]); while(!stack.empty()&&stack.peek()==popA[index]){ stack.pop(); index++; } } return stack.empty(); }}
22.从上往下打印二叉树
题目描述:
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
解题思路:
二叉树层次遍历
import java.util.ArrayList;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 { public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) { Queue<TreeNode> queue=new LinkedList<TreeNode>();//队列里存的是TreeNode类型的节点! ArrayList<Integer> list=new ArrayList<Integer>(); if(root==null){//为空的条件一定要考虑到!!! return list; } queue.offer(root);//队列中添加最好用offer,将根节点压入队列 while(!queue.isEmpty()){//每次都会压入当前结点的左右子树,所以直到最后全都压完队列才会变空 TreeNode temp=queue.poll();//每次取出最后一个节点 //放入左右子树 if(temp.left!=null){ queue.offer(temp.left); } if(temp.right!=null){ queue.offer(temp.right); } list.add(temp.val); } return list; }}
23.二叉搜索树的后序遍历序列
题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。
如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
解题思路:
后序遍历,最后一个数是根节点
public class Solution { public boolean VerifySquenceOfBST(int [] sequence) { //数组为空时返回 if(sequence.length==0){ return false; } return subtree(sequence,0,sequence.length-1); } public static boolean subtree(int[] a,int st,int root){ //不停地分割数组,最后st>=root时说明分完了,所有的都满足要求,返回true if(st>=root) return true; //最后一个数是根节点,从它前面那个数开始看 int i=root-1; //根节点前面那个的节点开始找,找到第一个比根节点小的数 while(i>st&&a[i]>a[root]){////!!!!这里要判断i>st i--; } //判断从头到刚刚找到的那个节点之间没有比根结点大的 for(int j=st;j<i;j++){///!!!!这里判断的时候j<i不能有等号!!!!! if(a[j]>a[root]){//有的话说明不符合二叉搜索树,返回false return false; } } //判断完后分别看左右子树是否满足二叉搜索树 return subtree(a,st,i)&&subtree(a,i+1,root-1); }}
24.二叉树中和为某一值的路径【!!!】
题目描述
输入一颗二叉树和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。
路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。
解析:原来的那个不是特别好理解~再来一种
在原方法中定义好ArrayList<ArrayList<Integer>> paths,之后就一直用这个,
要用一个方法find来递归,find主要是确定当前是不是叶节点而且当前的target是不是已经等于root.val了,如果是的话把这个path放进paths结果集中
如果还没满足条件,则再递归从左右子树开始找,这里要注意,新建一个path2,复制已经放进root的path,然后左右子树用不同的path开始分头找
import java.util.ArrayList;/**public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; }}*/public class Solution { public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) { ArrayList<ArrayList<Integer>> paths=new ArrayList<ArrayList<Integer>>(); if(root==null) return paths; find(paths,new ArrayList<Integer>(),root,target); return paths; } public void find(ArrayList<ArrayList<Integer>> paths,ArrayList<Integer> path,TreeNode root,int target){ if(root==null) return; path.add(root.val); if(root.left==null&&root.right==null&&target==root.val){ paths.add(path); return; } ArrayList<Integer> path2=new ArrayList<Integer>(); path2.addAll(path); find(paths,path,root.left,target-root.val); find(paths,path2,root.right,target-root.val); }}
import java.util.ArrayList;/**public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; }}*/public class Solution { private ArrayList<ArrayList<Integer>> listAll=new ArrayList<ArrayList<Integer>>(); private ArrayList<Integer> list=new ArrayList<Integer>();//声明在外面,因为一直要用 public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) { //递推到最后的叶节点时要用到 if(root==null){ return listAll; } //遍历到这个节点,啥都不管先加到list里面 list.add(root.val); target-=root.val;//更新target //这是说明到达符合要求的叶节点了 if(target==0&&root.left==null&&root.right==null){ listAll.add(new ArrayList<Integer>(list));//新new一个list,原来的list还要一直用 } //向下找左子树右子树 FindPath(root.right,target); FindPath(root.left,target); //遍历到叶节点不符合要求,把它在list里面删掉,回退,然后再继续递归遍历 list.remove(list.size()-1); return listAll; }}
25.复杂链表的复制
题目描述
输入一个复杂链表
(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)
返回结果为复制后复杂链表的head。
(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
解题思路:
map关联
首先遍历一遍原链表,创建新链表(赋值label和next),用map关联对应结点;
再遍历一遍,更新新链表的random指针。(注意map中应有NULL ----> NULL的映射)
/*public class RandomListNode { int label; RandomListNode next = null; RandomListNode random = null; RandomListNode(int label) { this.label = label; }}*/import java.util.HashMap;import java.util.Iterator;import java.util.Map.Entry;import java.util.Set;public class Solution { public RandomListNode Clone(RandomListNode pHead) { //声明一个map用来关联原来的节点和新的链表里的节点 HashMap<RandomListNode,RandomListNode> map = new HashMap<RandomListNode,RandomListNode>(); //原来的链表 RandomListNode p = pHead; //新的链表 RandomListNode q = new RandomListNode(-1);//这里一定要这么定义,不能用null while(p!=null){ //根据原来的链表节点的值,创建节点 RandomListNode t = new RandomListNode(p.label); q.next = t;//把新的链表里的节点连起来 q = t; map.put(p, q);//把原来的链表的节点和新链表里的节点关联起来 p = p.next;//向后移动 } //取得map中的键值对 Set<Entry<RandomListNode,RandomListNode>> set = map.entrySet(); //变成set后用迭代器遍历 Iterator<Entry<RandomListNode,RandomListNode>> it = set.iterator(); while(it.hasNext()){ Entry<RandomListNode, RandomListNode> next = it.next(); //把新链表中的节点的random连上,用了get(),所以连的是新链表的节点 next.getValue().random = map.get(next.getKey().random); } //map中键值对里的值,是新的链表 return map.get(pHead); }}
26.二叉搜索树与双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。
要求不能创建任何新的结点,只能调整树中结点指针的指向。
解题思路:
中序遍历,左中右
最终形成了一个串,左指针指向左边的数,右指针指向右边的数,变成横着的了
跟着程序走一遍
又看了一遍,这道题再换一种方法更好理解
解析:
核心是中序遍历的非递归算法
修改当前遍历节点的指针指向
中序遍历非递归算法是:用一个栈,从根节点开始,一直找左节点,压入栈中,没有左节点后,弹出当前结点,找它的右节点,右节点存在的话,压入栈中,再找右节点的左节点压入栈中,每次都是找不到时弹出当前栈顶节点
/**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; Stack<TreeNode> stack=new Stack<TreeNode>(); TreeNode p=pRootOfTree; TreeNode pre=null;//用于保存中序遍历的上一个节点 boolean isFirst=true;//用这个判断是不是第一次弹出节点,第一次弹出的节点是链表的首节点 TreeNode root=null;//这个是最后链表的头节点,结果返回这个就行 while(!stack.isEmpty()||p!=null){ while(p!=null){ stack.push(p); p=p.left;//只要还有左节点,就一直压入栈中 } p=stack.pop();//找不到左节点时,弹出栈顶元素 if(isFirst){//如果是第一次弹出 root=p;//把这个节点给root,这个就是链表的首节点了 pre=root; isFirst=false; }else{ pre.right=p;//和前一个节点绑定关系 p.left=pre; pre=p;//移动 } p=p.right;//找不到左节点后,弹出栈顶元素要进行一些操作,之后去找右节点 } return root; }}
/**public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; }}*/public class Solution { //!!这两个要声明在外面 TreeNode head=null; TreeNode realhead=null;//最终形成排序链表的头 public TreeNode Convert(TreeNode pRootOfTree) { ConvertSub(pRootOfTree); return realhead; } //不返回东西,只是把每个节点的左右指针调整成平的,一个挨着一个 public void ConvertSub(TreeNode pRootOfTree){ if(pRootOfTree==null){ return; } //先调整左子树 ConvertSub(pRootOfTree.left); //这一步就是确定最左边的那个realhead if(head==null){ head=pRootOfTree; realhead=pRootOfTree; }else{ //以后的每一步走的都是这里 head.right=pRootOfTree;//让原来的head的右指针指向现在的根 pRootOfTree.left=head;//让现在的根的左指针指向原来的head head=pRootOfTree;//把原来的head更新一下 } ConvertSub(pRootOfTree.right); }}
27.字符串的排列
输入一个字符串,按字典序打印出该字符串中字符的所有排列。
例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
结果请按字母顺序输出。
输入描述:
输入一个字符串,长度不超过9(可能有字符重复),字符只包括大小写字母。
解题思路:
有重复元素的全排列
import java.util.ArrayList;import java.util.*;public class Solution { public ArrayList<String> Permutation(String str) { ArrayList<String> list=new ArrayList<String>(); if(str!=null&&str.length()>0){//哎呀呀记住一定要判断是否为空 PermutationHelper(str.toCharArray(),0,list); Collections.sort(list);//用Collection自带的sort,这次就不用考虑顺序的问题了 } return list; } public void PermutationHelper(char[] a,int start,ArrayList<String> list){ //交换到最后一个数时,将这个序列输出,添加进list中 if(start==a.length){ list.add(String.valueOf(a));//有char数组转化为String } for(int i=start;i<a.length;i++){ //比原来的全排列多了一个判断条件,因为可能有重复的数 if(a[i]!=a[start]||i==start){//要有i==start的判断,否则当所有元素相等时就直接都lue'guo'le swap(a,i,start); PermutationHelper(a,start+1,list); swap(a,i,start); } } } public static void swap(char[] list, int start, int i) { char temp; temp = list[start]; list[start] = list[i]; list[i] = temp; } }
28.数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半
因此输出2。如果不存在则输出0。
解题思路:
用hashmap
import java.util.*;public class Solution { public int MoreThanHalfNum_Solution(int [] array) { HashMap<Integer,Integer> hash=new HashMap<Integer,Integer>(); for(int i=0;i<array.length;i++){ Integer temp=hash.get(array[i]); if(temp==null){ hash.put(array[i],1); temp=1; }else{ hash.put(array[i],temp+1); temp++; } if(temp>array.length/2){ return array[i]; } } return 0; }}
29.最小的K个数【还有更好的方法,记得看一下】
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
import java.util.*;public class Solution { public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) { bubble(input,input.length); ArrayList<Integer> list=new ArrayList<Integer>(); if(input==null || input.length<=0 || input.length<k){//为空的时候一定要注意!每种情况都考虑一下 return list; } for(int i=0;i<k;i++){ list.add(input[i]); } return list; } public static void bubble(int[] a,int n){ for(int i=0;i<n-1;i++){ for(int j=1;j<n-i;j++){ if(a[j-1]>a[j]){ int temp=a[j-1]; a[j-1]=a[j]; a[j]=temp; } } } }}
30.整数中1出现的次数(从1到n整数中1出现的次数)
题目描述:
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?
为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。
可以很快的求出任意非负整数区间中1出现的次数。
public class Solution { public int NumberOf1Between1AndN_Solution(int n) { int count=0;//记录1出现的次数 for(int i=0;i<=n;i++){ String str=String.valueOf(i); int len=str.length(); for(int j=0;j<len;j++){ if(str.charAt(j)=='1'){ count++; } } } return count; }}
31.把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
解题思路:
先把整型数组换成字符串型数组,元素都变成String类型,然后给它们排序,重写Collections或者Arrays的sort方法,重新定义一下排序规则,然后直接用sort排序,排好序之后,连接起来输出
import java.util.ArrayList;import java.util.*;public class Solution { public String PrintMinNumber(int [] numbers) { int len=numbers.length; StringBuilder sb=new StringBuilder(); String str[]=new String[len]; for(int i=0;i<len;i++){ str[i]=String.valueOf(numbers[i]); } Arrays.sort(str,new Comparator<String>(){ public int compare(String s1,String s2){ String c1=s1+s2; String c2=s2+s1; return c1.compareTo(c2); } }); for(int i=0;i<len;i++){ sb.append(str[i]); } return sb.toString(); }}
32.丑数
把只包含因子2、3和5的数称作丑数(Ugly Number)。
例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
解题思路:
后面的一个丑数是由前面某一个丑数乘以2,3,4得来的,可以用动态规划去解
public class Solution { public int GetUglyNumber_Solution(int index) { if(index<=0) return 0; if(index==1) return 1; //创建一个数组保存所有的丑数 int[] a=new int[index]; a[0]=1; int t2=0,t3=0,t5=0;最开始的时候是,这三个数哪一个也没用到 for(int i=1;i<index;i++){ a[i]=Math.min(a[t2]*2,Math.min(a[t3]*3,a[t5]*5)); if(a[i]==a[t2]*2)//确定这个数乘以2得到的,t2++记录上, t2++; if(a[i]==a[t3]*3) t3++; if(a[i]==a[t5]*5) t5++; } return a[index-1]; }}
33.找出字符串中第一个只出现一次的字符
找出字符串中第一个只出现一次的字符
详细描述:
接口说明
原型:
bool FindChar(char* pInputString, char* pChar);
输入参数:
char* pInputString:字符串
输出参数(指针指向的内存区域保证有效):
char* pChar:第一个只出现一次的字符
如果无此字符 请输出'.'
输入描述:
输入一串字符,由小写字母组成
输出描述:
输出一个字符
输入例子:
asdfasdfo
输出例子:
o
解题思路:
这道题最后写成了整个程序,没有单独写接口或者函数
用hashMap,而且是LinkedHashMap!!!这个可以保持元素有序!!
import java.util.*;public class Main{ public static char FindChar(String str){ LinkedHashMap<Character,Integer> map=new LinkedHashMap<Character,Integer>(); int n=str.length(); for(int i=0;i<n;i++){ if(map.containsKey(str.charAt(i))){//containsKey这个方法记一下 int time=map.get(str.charAt(i));// map.put(str.charAt(i),++time); }else{ map.put(str.charAt(i),1); } } char pos='.'; int i=0; for(;i<n;i++){ char c=str.charAt(i); if(map.get(c)==1){ return c;//找到了就在这里返回就行 } } return pos;//如果没有找到,最后返回空 } public static void main(String[] args){ Scanner sc=new Scanner(System.in); while(sc.hasNext()){ String str=sc.next(); System.out.println(FindChar(str)); } }}
34.数组中的逆序对
输入描述:
题目保证输入的数组中没有的相同的数字
数据范围:
对于%50的数据,size<=10^4
对于%75的数据,size<=10^5
对于%100的数据,size<=2*10^5
输入例子:
1,2,3,4,5,6,7,0
输出例子:
7
解析:
归并排序的改进,把数据分成前后两个数组(递归分到每个数组仅有一个数据项),
合并数组,合并时,出现前面的数组值array[i]大于后面数组值array[j]时;则前面
数组array[i]~array[mid]都是大于array[j]的,count += mid+1 - i
参考剑指Offer,但是感觉剑指Offer归并过程少了一步拷贝过程。
还有就是测试用例输出结果比较大,对每次返回的count mod(1000000007)求余
跟着归并排序走一遍就能明白,主要的一步是merge合并的时候要比较一下,那时候如果前面的数大于后面的数就要记录count
之前不知道要怎么把count放进里面,涉及到返回什么样的数据,后来发现,变成全局变量就行,自始至终都在,最后返回count就行
有count的地方记得取mod
public class Solution {int count;//比普通的归并排序多了一个count来记录public int InversePairs(int[] array){count=0;if(array!=null){sort(array,0,array.length-1);}return count;}//归并排序public void sort(int[] a,int low,int high){int mid=(low+high)/2;if(low<high){sort(a,low,mid);sort(a,mid+1,high);merge(a,low,mid,high);}}public void merge(int[] a,int low,int mid,int high){int i=low;int j=mid+1;int k=0;int temp[]=new int[high-low+1];while(i<=mid&&j<=high){if(a[i]>a[j]){temp[k++]=a[j++];count+=mid-i+1;count%=1000000007;}else{temp[k++]=a[i++];}}while(i<=mid){temp[k++]=a[i++];}while(j<=high){temp[k++]=a[j++];}for(int k2=0;k2<temp.length;k2++){a[k2+low]=temp[k2];}}}
35.数字在排序数组中出现的次数
解析:
其实依旧是二分法,改进了一下,找到第一个k和最后一个k
public class Solution { public int GetNumberOfK(int [] array , int k) { if(array.length==0||array==null)//这里的判断不要忘 return 0; int first=getFirstK(array,k); int last=getLastK(array,k); if(first==-1||last==-1)//这个很重要,如果有一个里面返回-1就说明数组中不存在k,要在这里返回0 return 0; return last-first+1;//否则一切正常,就用最后一个的索引-第一个索引+1 } public int getFirstK(int[] a,int k){ int low=0; int high=a.length-1; while(low<=high){ int mid=(low+high)/2; if(a[mid]>k)//这里和二分检索一样 high=mid-1; else if(a[mid]<k){//这里和二分检索也一样 low=mid+1; }else{//只有相等时要多判断一下,是不是第一个k if(mid==0||(mid>0&&a[mid-1]!=k)) return mid; else high=mid-1; } } return -1; } public int getLastK(int[] a,int k){ int low=0; int high=a.length-1; while(low<=high){ int mid=(low+high)/2; if(a[mid]>k) high=mid-1; else if(a[mid]<k){ low=mid+1; }else{ if(mid==a.length-1||(mid<a.length-1&&a[mid+1]!=k)) return mid; else low=mid+1; } } return -1; }}
36.二叉树的深度
解析:
递归求解:
假如是空节点,则返回0;
否则,原树的深度由左右子树中深度较的深度加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 pRoot) { if(pRoot==null) return 0; return Math.max(1+TreeDepth(pRoot.left),1+TreeDepth(pRoot.right)); }}
37.平衡二叉树
/* * 输入一棵二叉树,判断该二叉树是否是平衡二叉树 */public class BalanceTree {class TreeNode{TreeNode left;TreeNode right;}//声明了一个全局变量来判断是否是平衡树private boolean isBalanced=true; public boolean IsBalanced_Solution(TreeNode root) { getDepth(root); return isBalanced; } //这个函数其实是在求树的高度 public int getDepth(TreeNode root){ if(root==null)//递归到底部的时候 return 0; int left=getDepth(root.left);//递归来求树的高度,递归就相当于自底向上了 int right=getDepth(root.right); //每次求完子树的高度,就判断一下 if(Math.abs(left-right)>1) isBalanced=false;//不是记为false,但无论如何都要遍历一遍,最后把isBalanced输出来 return right>left?right+1:left+1; }}
/* * 输入一棵二叉树,判断该二叉树是否是平衡二叉树 */public class BalanceTree2 {class TreeNode{TreeNode left;TreeNode right;}//这里用了一个class来传值,声明对象后就可以根据对象来保存这个值n//这里n其实相当于树的深度private class Holder{int n;}public boolean IsBalanced_Solution(TreeNode root){return judge(root,new Holder());}public boolean judge(TreeNode root,Holder h){if(root==null)return true;Holder l=new Holder();Holder r=new Holder();//这边的两个judge是在递归,先跑到树的最下面//如果最下面的两个子数都是平衡树,那就判断一下他俩的高度差if(judge(root.left,l)&&judge(root.right,r)){if(Math.abs(l.n-r.n)>1)return false;h.n+=(l.n>r.n?l.n:r.n)+1;return true;}return false;}}
38.数组中只出现一次的数字
解析:
自己的方法有点笨,就是都存在hashmap里,然后再遍历找到value为1,想想都感觉好笨,,
还看到一种方法是,用map.containsKey()判断key有没有,没有的话,先放进去,在发现有的话,remove出来,这样,最后就剩两个数了
最正规的做法:
异或运算的性质:任何一个数字异或它自己都等于0 。
也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,
因为那些出现两次的数字全部在异或中抵消掉了。
如果能够把原数组分为两个子数组。
在每个子数组中,包含一个只出现一次的数字,而其它数字都出现两次。
如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。
我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。
因为其它数字都出现了两次,在异或中全部抵消掉了。
由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,也就是说在这个结果数字的二进制表示中至少就有一位为1 。
我们在结果数字中找到第一个为1 的位的位置,记为第N 位。
现在我们以第N 位是不是1 为标准把原数组中的数字分成两个子数组,
第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。
现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。
[2*8比2<<3快]
2<<3是左移,是2进制的运算
2*8,还要做很多的运算。
一般,我们都说2进制运算最快。
["<<"这个是左移位运算符],"2<<3"表示2左移3位
2的二进制是00000000 00000000 00000000 00000010
2左移3位,高位的移出,低位的用0填充。
结果:00000000 00000000 00000000 00010000
这个数是16
[m<<n]: 等于m*(2的n次方)
5可以表示为:00000101(最高位表示符号,0位正,1为负)
[>>右移]后为00000010
^ 按位异或。[相同为0不同为1]
比如二进制 1001 ^ 1100 = 0101
0^0=0,1^1=0 ,1^0 = 1,0^1=1。
& 按位与
int a = 10;
int b =2;
a&b=2 ,按位与,算术运算..1010&0010 = 0010
a&&b = true 并且,逻辑运算.
/* * 一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。 */public class xorProblem {public void FindNumsAppearOnce(int[] array,int num1[],int num2[]){if(array==null||array.length==0)return;int temp=0;for(int i=0;i<array.length;i++){temp^=array[i];//异或数组中所有的数字}//index1是指那两个数异或时第一个出现1的位置int index1=findFirstBitOne(temp);//遍历数组,按那一位是1还是0把数组分成两个数组for(int i=0;i<array.length;i++){if(isBit(array[i],index1))num1[0]^=array[i];elsenum2[0]^=array[i];}}//寻找一个数的二进制表示中,第一个1出现的位置,从右到左开始找public int findFirstBitOne(int num){int index=0;//num中的二进制位不停地和1按位与,index标记位数//不要超过int的bit数就好,int四个字节,每个字节8bit,所以8*4,最好直接写成32while(((num&1)==0)&&(index)<8*4){num=num>>1;//对num进行右移,把最右边比较完的数挤出去++index;}return index;}//给出之前找到的index,判断其他数,这一位是不是1public boolean isBit(int num,int index){num=num>>index;return (num&1)==1;}}
import java.util.ArrayList;public class Solution { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { ArrayList<Integer>list=new ArrayList<Integer>(); for(int i=0;i<array.length;i++) { if(!list.contains(array[i])) list.add(array[i]); else list.remove(new Integer(array[i])); } if(list.size()>1) { num1[0]=list.get(0); num2[0]=list.get(1); } }}
//num1,num2分别为长度为1的数组。传出参数//将num1[0],num2[0]设置为返回结果import java.util.*;public class Solution { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { HashMap<Integer,Integer> map=new HashMap<Integer,Integer>(); for(int i=0;i<array.length;i++){ if(map.containsKey(array[i])){ int val=map.get(array[i]); map.put(array[i],++val); }else{ map.put(array[i],1); } } int a=0; for(int i=0;i<array.length;i++){ if(map.get(array[i])==1&&a==0){ num1[0]=array[i]; a++; }else if(map.get(array[i])==1&&a==1){ num2[0]=array[i]; } } }}
39.和为S的连续正数序列
输出描述:
输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
解析:
根据数学公式计算:(a1+an)*n/2=s n=an-a1+1
(an+a1)*(an-a1+1)=2*s=k*l(k>l)
an=(k+l-1)/2 a1=(k-l+1)/2
还要求排列是从大到小的排列,给2*s开平方,以内k和L不等,一定是一个在平方根的左边一个在平方根的右边
随便找个例子可以发现L越大,a1越小,
所以从平方根开始找L,不停--,直到2
import java.util.ArrayList;public class Solution { public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) { ArrayList<ArrayList<Integer>> list=new ArrayList<ArrayList<Integer>>(); if(sum<3) return list; int s=(int)Math.sqrt(2*sum); //这里i相当于L,等于1的时候序列里面只有一个数 for(int i=s;i>=2;i--){ if(2*sum%i==0){ int k=(2*sum)/i; if(k%2==0&&i%2==1||k%2==1&&i%2==0){//想保证下面式子成立,k和L一定一奇一偶 int a1=(k-i+1)/2; int an=(k+i-1)/2; ArrayList<Integer> arr=new ArrayList<Integer>(); for(int j=a1;j<=an;j++){ arr.add(j); } list.add(arr); } } } return list; } }
40.和为S的两个数字
输出描述:
对应每个测试案例,输出两个数,小的先输出。
解析:
原来用了二分查找,比较笨的方法,在输出时还出现了错误,总是大的在前面,其实有比较简单的方法
因为是排好序的数组,所以从两边开始找,比sum大的话,high--,比sum小的话,low++
从两边一夹很容易就找得到
import java.util.ArrayList;public class Solution {public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) { ArrayList<Integer> list=new ArrayList<Integer>(); while(array==null||array.length<2) return list; int i=0; int j=array.length-1; while(i<j){ if(array[i]+array[j]==sum){ list.add(array[i]); list.add(array[j]); return list; }else if(array[i]+array[j]>sum){ j--; }else i++; } return list; }}
41.左旋转字符串
解析:
用subString的话还是很简单的
public class Solution { public String LeftRotateString(String str,int n) { if(str==null||str.length()==0) return "";//因为要返回string类型,这里不能写成null int t=n; if(n>str.length()){ t=n%str.length(); } String s=str.substring(n,str.length())+str.substring(0,t);//记住substring是这么写,没有大写字母 return s; }}
42.翻转单词顺序列
public class Solution {public String ReverseSentence(String str){StringBuffer sb=new StringBuffer();//字符串拼接时用StringBuffer吧if(str.length()==0||str.length()==1||str.trim().equals("")){//这里很重要,trim()去掉两边的空格return str;}String[] s=str.split(" ");int len=s.length;for(int i=len-1;i>0;i--){sb.append(s[i]+" ");//先不加上最后一个数,否则还要考虑空格的问题}sb.append(s[0]);return sb.toString();}}
43.[编程题]求1+2+3+...+n
解析:
1.需利用逻辑与的短路特性实现递归终止。
2.当n==0时,(n>0)&&((sum+=Sum_Solution(n-1))>0)只执行前面的判断,为false,然后直接返回0;
3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。
public class Solution { public int Sum_Solution(int n) { int sum=n;//要等于n不是等于0,想不明白的时候举个例子 boolean t=(n>0)&&((sum+=Sum_Solution(n-1))>0);//这里必须写成一个语句,所以声明了t, return sum; }}
44.孩子们的游戏(圆圈中最后剩下的数)
解析:
约瑟夫环,可以找规律,但感觉通过数组或链表来模拟环还是很方便的
public class Solution { public int LastRemaining_Solution(int n, int m) { if(n<1||m<1)//先判断这个 return -1; int count=n;//现在还剩下的人数 int step=0;//每轮走的步数! int i=-1;//指针,跟着绕,保证环的特征 int[] a=new int[n]; //因为要求出最后剩下的那个的编号,所以不能在删除倒数第二个点的时候就退出, //要接着去删最后一个点,就可以得到位置了 while(count>0){ i++;//指向数组中的数 if(i>=n)//记住这是一个环,通过i来保持环的特征,编号为0~n-1,所以i变为n时就要清回0了 i=0; if(a[i]==-1) continue;//删除点的时候将值变为-1.看到-1,就结束本次循环,直接下次循环 step++; if(step==m){ a[i]=-1; step=0; count--; } } return i; }}
45.扑克牌顺子
如果Set集合中不包含要添加的对象,则add添加对象并返回true;否则返回false。
set是接口,不能实例化,所以不能有Set s = new Set();set是接口, 是不能创建对象的. 所以不能有 new Set()这样的写法...
只能实例化接口的实现类,比如HashSet
List list = new ArrayList();
用接口去引用去实现类,是针对接口编程可以很容易的改为其他实现类,比如 LinkedList
只是 List list = new ArrayList() 这么写.. 对于代码的重构有点好处而已...
而且仅仅适用于, 你只是用了List的接口, 而没有用ArrayList自己单独有的属性和方法...
import java.util.*;public class Solution { //判断除0外没有相同元素,而且max-min<5 public boolean isContinuous(int [] numbers) { if(numbers==null||numbers.length<5) return false; Set<Integer> set=new HashSet<Integer>();//set要注意一下 int max=-1; int min=14; for(int t:numbers){ if(!set.add(t)&&t!=0)//这个数不为0,而且已经存在,用set很方便 return false; if(t!=0){ max=Math.max(max,t); min=Math.min(min,t); } } if(max-min<5) return true; return false; }}
46.
不用加减乘除做加法
解析:
一看到这种题就知道要用位运算来解决
let's have a good look
首先看十进制是如何做的:5+7=12
第一步:相加各位的值,不算进位,得到2
第二步:计算进位值,得到10,如果这一步的进位值为0,那么在这一步终止,得到最终结果
第三步:重复上两步,相加的值变成了2+10,得到12,没有进位,进位值为0,循环终止,否则一直相加,直到进位值为0
同样,用这三步来计算二进制相加:5->101,7->111,
第一步:相加各位的值,不算进位,二进制每位相加就相当于做异或操作,101^111=010[相同为0不同为1]
第二步:计算进位值,相当于二进制各位数与,再向左移一位,(101&111)<<1 = 101<<1=1010
第三步:重复上两步:010^1010=1100,(010&1010)<<1= 0,进位值为0,跳出循环,1100为最终结果
<span style="font-family:Microsoft YaHei;font-size:14px;">public class Solution { public int Add(int num1,int num2) { while(num2!=0){ int temp=num1^num2; num2=(num1&num2)<<1; num1=temp; } return num1; }}</span>
47.把字符串转换成整数
输入描述:
输入一个字符串,包括数字字母符号,可以为空
输出描述:
如果是合法的数值表达则返回该数字,否则返回0
输入例子:
+2147483647 1a33
输出例子:
2147483647 0
解析:
一、按题意的解法:
把String字符串换成char[] c数组
判断c[0]是否为'-',是的话用记录sign下来,之后算完数的时候要和它相乘,否则默认为正的
然后循环判断里面的每一个字符
先判断c[0]是否为'-'或'+',如果是的话,从c[1]开始循环
判断c[i]在'0'-'9'之间,否则直接return 0
然后想一下怎么按照每位的数字算出最终的数字
每位数字:c[i]-'0'
比如1234
初始化sum=0,开始循环:sum=0*10+1=1,sum=1*10+2=12,sum=12*10+3=123,sum=123*10+4=1234
所以:sum=sum*10+c[i]-'0'
最后循环结束,返回sum*sign
public class Solution { public int StrToInt(String str) { if(str==""||str.length()==0){ return 0; } char[] c=str.toCharArray(); int sign=1; if(c[0]=='-') sign=-1; int sum=0; for(int i=(c[0]=='-'||c[0]=='+')?1:0;i<c.length;i++){ if(!(c[i]>='0'&&c[i]<='9')) return 0; sum=sum*10+c[i]-'0'; } return sum*sign; }}
二、用Java自带的函数~
String s="";
int num=Integer.parseInt(s);
int num=Integer.valueOf(s);
public class stringConvertInt {public static void main(String[] args){String s="1234";int num1=Integer.parseInt(s);int num2=Integer.valueOf(s);System.out.println(num1);System.out.println(num2);}}
48.数组中重复的数字
解析:
因为所有数字都在0到n-1的范围内,所以可以新建一个大小为n的boolean数组b[]
遍历原数组,比如,遇到1,就去看b[1]是否为true,如果已经是true说明之前访问过1,原数组中有1,直接把这个1赋给结果数组
否则,b[1]=true,继续遍历,如果遍历完也没有,就返回false,表示没有重复的数字
public class Solution { // Parameters: // numbers: an array of integers // length: the length of array numbers // duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation; // Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++ // 这里要特别注意~返回任意重复的一个,赋值duplication[0] // Return value: true if the input is valid, and there are some duplications in the array number // otherwise false public boolean duplicate(int numbers[],int length,int [] duplication) { boolean[] b=new boolean[length]; for(int i=0;i<length;i++){ if(b[numbers[i]]==true){ duplication[0]=numbers[i]; return true; } b[numbers[i]]=true; } return false; }}
49.构建乘积数组
解析:
第一种方法:
从左到右计算A[0]*A[1]*...*A[i-1]
从右到左计算A[i+1]*...*A[n-1]
比如n=3
b[0]=1 * A[1]*A[2]
b[1]=A[0] * A[2]
b[2]=A[0]*A[1] * 1
用一个数res来记录不停相乘的A[i],初始化res=1,
循环的时候先令b[i]=res,然后再res=res*A[i],这样现在每个b[i]的值都是A[0]*A[1]*...*A[i-1],【如果是b[0]就先等于1】
然后res再变为1,开始从右到左循环,先令b[i]=b[i]*res,然后再res=res*A[i],现在的res是A[i+1]*...*A[n-1]
import java.util.ArrayList;public class Solution { public int[] multiply(int[] A) { int b[]=new int[A.length]; int res=1; for(int i=0;i<A.length;i++){ b[i]=res; res=res*A[i]; } res=1; for(int i=A.length-1;i>=0;i--){ b[i]=b[i]*res; res=res*A[i]; } return b; }}
50.正则表达式匹配
解析:
这道题主要是分析好有哪些情况
然后用递归实现!!
一、当模式中第二个字符不是 ' * ' 时
先匹配第一个字符,如果两个字符相等或者,pattern中第一个字符为 ' . ' ,继续向下匹配
如果第一个字符匹配不成功,直接return false
二、当模式中第二个字符是 ' * ' 时【注意,这里要向后看两位有没有*,所以要注意看看有没有超出pattern长度】
[因为,a*可以匹配0~n个字符]
如果第一个字符匹配,则字符串后移1位,pattern后移两位,继续匹配[*匹配1个字符];
或者字符串后移一位,pattern不动,继续匹配[*匹配多个字符];
或者字符串不移动,pattern后移两位,字符串不移动[*匹配0个字符]
如果第一个字符不匹配,[*匹配了0个字符]那么pattern后移两位,字符串不移动,继续匹配
三、得到结果:
当字符串和pattern都匹配结束,返回true
pattern先用完,字符串还剩下没有匹配的,直接返回false
这里没有关于字符串长度的限制,所以在中间匹配时要注意str有没有超过长度
public class Solution { public boolean match(char[] str, char[] pattern) { if(str==null||pattern==null) return false; int strindex=0; int patindex=0; return regex(str,strindex,pattern,patindex); } public boolean regex(char[] str,int strindex,char[] pattern,int patindex){ if(strindex==str.length&&patindex==pattern.length) return true; if(patindex==pattern.length&&strindex!=str.length) return false; if(patindex+1<pattern.length&&pattern[patindex+1]=='*'){ if(strindex!=str.length&&pattern[patindex]==str[strindex]||(pattern[patindex]=='.'&&strindex!=str.length)){ return regex(str,strindex,pattern,patindex+2)||regex(str,strindex+1,pattern,patindex+2)||regex(str,strindex+1,pattern,patindex); }else{ return regex(str,strindex,pattern,patindex+2); } } if(strindex!=str.length&&(pattern[patindex]==str[strindex]||pattern[patindex]=='.')){ return regex(str,strindex+1,pattern,patindex+1); }else return false; }}
51.
表示数值的字符串
解析:
一开始在分析各种各样的情况,最后发现用正则表达式最方便
正则表达式:
(1)+、-这两个中只能出现1次或0次:[\\+-]?
(2)无论是在e前面还是在+-后面,还是在小数点前面,都要有数字:[0-9]+
(3)小数点和后面的数字,可能出现一次,或者不出现:(\\.[0-9]+)?
(4)带e的部分是一个整体,可能出现一次,或者不出现:([eE][\\+-]?[0-9]+)?
最后:[\\+-]?[0-9]+(\\.[0-9]+)?([eE][\\+-]?[0-9]+)?
public class Solution { public boolean isNumeric(char[] str) { String s=String.valueOf(str); return s.matches("[\\+-]?[0-9]+(\\.[0-9]+)?([eE][\\+-]?[0-9]+)?"); }}
52.字符流中第一个不重复的字符
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。
解析:
类似这种判断出现一次两次的问题都可以用hashmap来解决,这里可以用int数组来模拟一下
每个字符占8位,所有字符一共有2^8=256个,所以一个256大的int数组hashtable就够了
Insert的时候,因为插入的是char类型字符
在全局最外面声明一个StringBuffer的对象,每次插入字符,都s.append(ch)【好神奇可以append一个char类型的!!】
然后hashtable[ch]++
只要按原来字符的顺序寻找,看hashtable的值,第一个hashtable[i]=1的字符就是!!
public class Solution { int[] hashtable=new int[256]; StringBuffer sb=new StringBuffer(); //Insert one char from stringstream public void Insert(char ch) { sb.append(ch); hashtable[ch]++; } //return the first appearence once char in current stringstream public char FirstAppearingOnce() { char[] c=sb.toString().toCharArray();//要记得先转换成String类型,在变成char数组 for(char t:c){ if(hashtable[t]==1) return t; } return '#'; }}
53.链表中环的入口结点
解析:
第一种方法:【链表问题常用的方法】
一、两个指针p1和p2,p1每次走1步,p2每次走2步,它们俩一定会在环内的某一处相遇,
假设p1走了x步,那么p2就走了2x步
p2刚好比p1多走了一个环的距离才又赶上p1
环的长度n=2x-x=x
p1其实在环外走了x1步,又在环内走了x-x1步,[还差n-(x-x1)=x1步就走到了入口]
二、现在把p2放回表头,让它们俩同时一起一步一步走,p2走x1步走到入口,p1走x1步也走到入口
不用单独求x1是什么,只需要把p2放在表头,然后让他们移动,相遇的地方就是入口!!
时间复杂度O(N),额外空间复杂度O(1)
【不明白的时候画一个图】
/* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; }}*/public class Solution { public ListNode EntryNodeOfLoop(ListNode pHead) { if(pHead==null||pHead.next==null) <span style="white-space:pre"></span> return null; ListNode p1=pHead; ListNode p2=pHead; while(p2.next!=null&&p2.next.next!=null){ <span style="white-space:pre"></span> p1=p1.next; <span style="white-space:pre"></span> p2=p2.next.next; <span style="white-space:pre"></span> if(p1==p2) <span style="white-space:pre"></span> break; } if(p2.next==null||p2.next.next==null) <span style="white-space:pre"></span> return null; p2=pHead; while(p2!=p1){ <span style="white-space:pre"></span> p1=p1.next; <span style="white-space:pre"></span> p2=p2.next; } return p1; } }
第二种方法:
用hashmap记录节点,ListNode-boolean类型的,如果没有这个节点就放进去,并放一个true,当发现这个节点已经有过了,containsKey()
就说明这个节点是入口
/* 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) { HashMap<ListNode,Boolean> map=new HashMap<ListNode,Boolean>(); while(pHead!=null){ if(map.containsKey(pHead)) return pHead; map.put(pHead,true); pHead=pHead.next; } return null; }}
54.删除链表中重复的结点
解析:
这里是用递归解决的
/* 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) return null; if(pHead.next==null&&pHead!=null) return pHead;//前面两部分也可以直接换成if(pHead==null||pHead.next==null)return pHead; ListNode cur=pHead; if(pHead.val==pHead.next.val){ cur=pHead.next.next; while(cur!=null&&cur.val==pHead.val){//!!注意!!cur!=null一定要写在前面!!&&这个东西很艮的,只看前面,如果前面的条件不成立,直接不看后面的 cur=cur.next; } return deleteDuplication(cur); }else{ cur=pHead.next; pHead.next=deleteDuplication(cur); return pHead; } }}
55.二叉树的下一个结点
解析:
画一个二叉树,求出该二叉树的中序遍历
(1)如果树为空,return null
(2)如果树有右子树,找到右子树最左节点
(3)如果没有右节点,它的下一个节点应该是,以自己为左孩子的父节点,如果自己不是自己父节点的左孩子,就继续向上找
/*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==null) return null; if(pNode.right!=null){//如果右子树不为空 pNode=pNode.right; if(pNode.left!=null) pNode=pNode.left; return pNode; } while(pNode.next!=null){//因为要一直往上找 if(pNode.next.left==pNode) return pNode.next; pNode=pNode.next; } return null; }}
56.对称的二叉树
解析:
一看到这种题,就有感觉要用递归来做
判断两个子树是否对称
左子树的左子树应该等于右子树的右子树
左子树的右子树应该等于右子树的左子树
<span style="font-family:Microsoft YaHei;">/*public class TreeNode { int val = 0; TreeNode left = null; TreeNode right = null; public TreeNode(int val) { this.val = val; }}*/public class Solution { boolean isSymmetrical(TreeNode pRoot) { if(pRoot==null)//记得判断为空的情况,这个在递归里面判断不了 return true; return same(pRoot.left,pRoot.right); } boolean same(TreeNode leftNode,TreeNode rightNode){ if(leftNode==null&&rightNode!=null)return false; if(leftNode!=null&&rightNode==null)return false; if(leftNode==null&&rightNode==null)return true; if(leftNode.val==rightNode.val) return same(leftNode.left,rightNode.right)&&same(leftNode.right,rightNode.left); return false; }}</span>
57.按之字形顺序打印二叉树
解析:
层次遍历+每一层单行输出,这个是之前做过的题,改编一下
定义两个变量,curNum和nextNum来分别保存当前层的节点数和下一层的节点数
初始化时curNum=1,nextNum=0
层次遍历用队列,先把根节点压入,开始循环
弹出队列头节点,输出,curNum--,判断弹出的节点有没有左右孩子,有的话压入队列,每压入一个,nextNum++
判断if(curNum==0),等于0的话说明,这一行已经遍历完了,curNum=nextNum,nextNum=0;
这里每输出一层,按顺序存放在arrayList中,可以用一个标志位flag,true的时候从左到右,false的时候从右到左,每当curNum==0时转换,
转换之前判断是true还是false,true的话正常把这一次的arraylist加到总的结果中,否则,反转这个arraylist后再添加进去
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 { public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) { ArrayList<ArrayList<Integer> > res=new ArrayList<ArrayList<Integer> >(); if(pRoot==null)//不要忘记判断 return res; int curNum=1; int nextNum=0; boolean flag=true; ArrayList<Integer> list=new ArrayList<Integer>(); Queue<TreeNode> queue=new LinkedList<TreeNode>();//记得这里是链表,才能生成的队列 queue.add(pRoot); while(!queue.isEmpty()){ TreeNode node=queue.poll(); list.add(node.val); curNum--; if(node.left!=null){ queue.add(node.left); nextNum++; } if(node.right!=null){ queue.add(node.right); nextNum++; } if(curNum==0){ if(flag) res.add(list); else{ res.add(reverse(list)); } flag=!flag; curNum=nextNum; nextNum=0; list=new ArrayList<Integer>();//只有打印完一行才再重新声明 } } return res; } public ArrayList<Integer> reverse(ArrayList<Integer> list){ int len=list.size(); ArrayList<Integer> list2=new ArrayList<Integer>(); for(int i=len-1;i>=0;i--){ list2.add(list.get(i)); } return list2; }}
58.
把二叉树打印成多行
解析:这个就是层次遍历按层输出的那个
<span style="font-family:Microsoft YaHei;">import java.util.ArrayList;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> > Print(TreeNode pRoot) { ArrayList<ArrayList<Integer> > res=new ArrayList<ArrayList<Integer> >(); if(pRoot==null) return res; ArrayList<Integer> list=new ArrayList<Integer>(); Queue<TreeNode> queue=new LinkedList<TreeNode>(); int curNum=1; int nextNum=0; queue.add(pRoot); while(!queue.isEmpty()){ TreeNode node=queue.poll(); list.add(node.val); curNum--; if(node.left!=null){ queue.add(node.left); nextNum++; } if(node.right!=null){ queue.add(node.right); nextNum++; } if(curNum==0){ res.add(list); curNum=nextNum; nextNum=0; list=new ArrayList<Integer>(); } } return res; } }</span>
59..序列化二叉树
解析:
第一种方法:
序列化二叉树,就是把二叉树变成一串字符串,先序遍历,最开始str="",不停往上面加,最后返回str
每遍历一个节点后面加上!,遇到空节点输出#!
遇到空节点返回#!,先加上根节点!,再对根节点的左孩子调用这个方法,然后对根节点的右孩子调用这个方法,最后返回str,递归
反序列化:对用先序遍历序列化的字符串进行反序列化
用到队列,先把字符串用split("!")变成字符串数组,遍历把每个元素添加到队列中,空的时候返回null,这个在后面的递归中要用到
弹出的第一个节点是根节点,然后根节点的左孩子是队列中剩下的元素调用这个函数得到的节点,遍历到#后,说明当前结点的左孩子后面没有东西了,返回当前结点,看他的右孩子,再遍历到#后,再回到当前结点,找到当前结点的父节点,也使用递归实现
!!注意,因为是递归,所以每轮只算自己的!!不用哪一步都str+=
/*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 { //序列化 String Serialize(TreeNode root) { if(root==null){ return "#!";//这里return#!就行,不用加上str } String str=root.val+"!";//每次只算自己的!! str+=Serialize(root.left); str+=Serialize(root.right); return str; } //反序列化! TreeNode Deserialize(String str) { Queue<String> queue=new LinkedList<String>();//这里存放的是String类型 String[] s=str.split("!"); for(int i=0;i<s.length;i++){ queue.add(s[i]); } return reconPre(queue); } public TreeNode reconPre(Queue<String> queue){ String value=queue.poll(); if(value.equals("#")) return null; TreeNode head=new TreeNode(Integer.valueOf(value)); head.left=reconPre(queue); head.right=reconPre(queue); return head; }}
第二种方法:
层次遍历来序列化:序列化的时候要用到队列,序列化的字符串是str,初始时为空
最开始判断是否为空,为空的话return #!
先将节点压入队列,队列是TreeNode类型的,然后层次遍历
先压入头节点,当队列不为空的时候,循环,弹出队首的元素,将val!加入到str中,
看看这个节点有没有左孩子右孩子,有的话分别压入队列,没有的话str+"#!" , 循环,
反序列化:先把字符串split("!")拆成String数组
其实反序列化就相当于再来一遍层次遍历,之前用先序遍历时就相当于再来一遍先序遍历。遇到#,表示是null,其他的就是正常节点
声明一个队列,TreeNode类型的,把String数组中第一个数放进去,然后index++,队列不为空的时候循环
取出队首元素,通过一个函数把它变成节点,然后按理来说后面两个是它的左右孩子,判断一下是不是空节点,是的话不要管了,
是正常节点的话以左孩子或者右孩子的身份添加到队列中
思路一定要清晰!!
/*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 { String Serialize(TreeNode root) { Queue<TreeNode> queue=new LinkedList<TreeNode>(); if(root==null) return "#!"; String str=root.val+"!"; queue.add(root); while(!queue.isEmpty()){ root=queue.poll(); if(root.left!=null){ str+=root.left.val+"!"; queue.add(root.left); }else{ str+="#!"; } if(root.right!=null){ str+=root.right.val+"!"; queue.add(root.right); }else{ str+="#!"; } } return str; } TreeNode Deserialize(String str) { String[] s=str.split("!"); Queue<TreeNode> queue=new LinkedList<TreeNode>(); int index=0;//指针在数组中移动 TreeNode head=createNode(s[index++]); if(head!=null){ queue.add(head); } TreeNode node=null;//这个node要放在外面,因为里面一直用一个 while(!queue.isEmpty()){ node=queue.poll(); node.left=createNode(s[index++]); node.right=createNode(s[index++]); if(node.left!=null){ queue.add(node.left); } if(node.right!=null){ queue.add(node.right); } } return head; } public TreeNode createNode(String a){ if(a.equals("#")) return null; return new TreeNode(Integer.valueOf(a)); }}
60.数据流中的中位数
解析:一个最大堆,一个最小堆,最小堆直接就是优先级队列,最大堆要重新定义comparator
想要得到中位数,也就是一个有序数列中的中间那个数,或者中间两个数的平均数
分成两边,平均分放在两个堆里,左边是最大堆,右边是最小堆,保证最小堆里的数一定大于最大堆里的数,
那最后中位数就是,总数为奇数时为最大堆的堆顶(也可以是最小堆的堆顶,看怎么分了),总数为偶数时为最大堆堆顶和最小堆堆顶的平均数
初始时count=0
判断count是奇数还是偶数,【假设最后取中位数,总数为奇数是,中位数为堆顶】,
所以,当前为偶数时,先放进最小堆筛选一下,选出最小的放入最大堆!,然后count++
count为奇数时,先放进最大堆筛选一下,选出最大的放入最小堆,然后count++
求中位数的时候根据count判断,然后取值就好,记得double
import java.util.*;public class Solution { private PriorityQueue<Integer> minHeap = new PriorityQueue<>(); private PriorityQueue<Integer> maxHeap=new PriorityQueue<Integer>(15,new Comparator<Integer>(){ public int compare(Integer o1,Integer o2){//这个方法要public!!! return o2-o1; } }); private int count=0; public void Insert(Integer num) { if(count%2==0){ minHeap.offer(num); int min=minHeap.poll(); maxHeap.offer(min); count++; }else{ maxHeap.offer(num); int max=maxHeap.poll(); minHeap.offer(max); count++; } } public Double GetMedian() { if(count%2==0){ return new Double((minHeap.peek()+maxHeap.peek()))/2; }else return new Double(maxHeap.peek()); }}
61.滑动窗口的最大值
解析:
用一个双端队列,队列的第一个位置保留窗口最大值,当窗口滑动一次
1.判断当前最大值是否过期
2.新增加值从队尾开始比较,把所有比它小的值丢掉
比如:[2,3,4]中,4放在队列第一个
双端队列中存放的是下标
关于begin 比如序列2 3 4 2 6 2 5 1 ,首先窗口移动3次都还是原来那三个值,不存在过期的问题,当到了第四个数也就是i到了3的时候,双端队列中的头得和begin=0进行比较,如果头头等于0或者小于0,说明最大值过期了,这个begin其实就是i-size+1,
确定
import java.util.*;public class Solution { public ArrayList<Integer> maxInWindows(int [] num, int size) { ArrayList<Integer> res=new ArrayList<Integer>(); if(size==0) return res; ArrayDeque<Integer> q=new ArrayDeque<Integer>(); int begin; for(int i=0;i<num.length;i++){ begin=i-size+1;//这是一个记录,方便后面的边界判断,当前为i的时候,begin是多少会让它失效 if(q.isEmpty()){ q.add(i); }else if(begin>q.peekFirst()){//判断当前最大值是否过期 q.pollFirst();//最大值已经失效,就拿出来 } while(!q.isEmpty()&&num[q.peekLast()]<=num[i])//看队列末尾的数是否小于新加入的数,记住是peek不是poll q.pollLast(); q.add(i); if(begin>=0){//begin=0后每移动一次,就是一个新的窗口 res.add(num[q.peekFirst()]); } } return res; }}
62.矩阵中的路径
解析:
回溯的本质就是标记后再去标记,以返回最初的状态。!!
因为任意一个点都可能是起点,所以遍历矩阵中的每一个元素,对每个点进行判断递归,每个点走过之后要标记上,所以用一个int数组flag,1走过,0没走过
每个递归里面用index表示当前走到矩阵中的哪个元素,用k表示匹配到字符串的哪个元素
每次递归里面先求index,
然后判断,i,j,又没有超过边界,还有当前矩阵元素的值与字符串当前的值是否相等,还有flag[index]如果等于1,说明已经走过了,这些都直接返回false
如果已经匹配到字符串的最后一个元素,说明匹配成功,所以return true
出去上面那些情况,只是匹配了当前结点,让flag[index]=1,然后去判断上下左右走能不能正常匹配,这里是递归,如果有一个能走,就返回true
否则,当前结点就算匹配上了也不好使,让flag[index]=0;然后return false
public class Solution { public boolean hasPath(char[] matrix, int rows, int cols, char[] str) { int[] flag=new int[matrix.length];//用flag标记这个点有没有走到过 for(int i=0;i<rows;i++){ for(int j=0;j<cols;j++){ if(helper(matrix,rows,cols,i,j,0,str,flag)) return true; } } return false; } public boolean helper(char[] matrix,int rows,int cols,int i,int j,int k,char[] str,int[] flag){ int index=i*cols+j;//当前走到哪个点 if(i<0||i>=rows||j<0||j>=cols||matrix[index]!=str[k]||flag[index]==1) return false; if(k==str.length-1)//字符串中的最后一个数也匹配上了 return true; flag[index]=1;//匹配上说明这个点已经走过了 //继续分别看他的上下左右 if(helper(matrix,rows,cols,i-1,j,k+1,str,flag)||helper(matrix,rows,cols,i+1,j,k+1,str,flag)|| helper(matrix,rows,cols,i,j-1,k+1,str,flag)||helper(matrix,rows,cols,i,j+1,k+1,str,flag)) return true; flag[index]=0;//它的上下左右的不行,所以回退,把之前的标记取消,这一步是最典型的回溯 return false; }}
63.机器人的运动范围
解析:
其实return那边我有点不太懂,为什么都要加上,,,,
这道题和上面不一样的地方是,这里不用回退,如果标记了1就是走过了,不会让他再变回来
public class Solution { public int movingCount(int threshold, int rows, int cols) { int[] flag=new int[(rows+1)*(cols+1)]; return helper(threshold,rows,cols,0,0,flag); } public int helper(int threshold,int rows,int cols,int i,int j,int[] flag){ int index=i*cols+j; if(i<0||i>=rows||j<0||j>=cols||add(i,j)>threshold||flag[index]==1) return 0; flag[index]=1; return 1+helper(threshold,rows,cols,i+1,j,flag)+ helper(threshold,rows,cols,i-1,j,flag)+ helper(threshold,rows,cols,i,j+1,flag)+ helper(threshold,rows,cols,i,j-1,flag); } public int add(int i,int j){ int a=i%10+i/10; int b=j%10+j/10; return a+b; }}
- 刷刷编程基础题~(1)
- 刷刷笔试题--[链表类编程]
- 刷刷笔试题--[位运算编程题]
- 刷刷笔试题~(1)
- 刷刷笔试题~(4)编程
- 刷刷笔试题~~[字符串类编程]
- 刷刷笔试题~~[算数类编程]
- 刷刷笔试题~~[二叉树编程]
- 刷刷笔试题~~[递归类编程]
- 刷刷笔试题~~[递归]
- 刷刷笔试题~~[概率]
- 刷刷笔试题~~[数组]
- 刷刷笔试题~~[sql]
- 刷刷笔试题~~ [Java]
- 刷刷笔试题~~[矩阵]
- 刷刷笔试题~~[操作系统!!!]
- 刷刷水题
- 剑指offer刷刷题
- codeforces CF703D Mishka and Interesting sum 树状数组
- CF 706E Working routine
- 企业发放奖金根据企业的当年利润决定。
- JZOJ3072. 【NOIP2012模拟10.31】掷骰子
- PhpStorm + Xdebug 远程调试(phpstorm2016)
- 刷刷编程基础题~(1)
- JSON学习
- 第4章第1节练习题9 反向层次遍历算法
- 标签栏主界面实现(一)
- 第4章第1节练习题10 判断某二叉树是否为完全二叉树
- 88. Merge Sorted Array
- Git入门(一)——简介
- Linux常用命令
- 小马哥---高仿苹果6sp主板型号 W3215 6582刷机拆机主板图