剑指offer刷题笔记
二维数组中的查找:中等
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
public class Solution {
public boolean Find(int target, int [][] array) {
int col=array[0].length;
int row=array.length;
//固定的边界大小col和row可能不符合要求,返回false
if(col==0||row==0){return false;} int R=0;int C=row-1;//这里从右上开始,左下也可以。
//不能从左上开始,不然不知道移动的方向。更不能从任意位置开始
while(R<=row-1 && C>=0){
if (target==array[R][C]){return true;}
else if (target >array[R][C]){R++;}//每次判断都能剔除一整行或一整列
else if (target <array[R][C]){C--;}
}
return false;
}
}
替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
public class Solution {
public String replaceSpace(StringBuffer str) {
//return str.toString().replace(" ","%20"); //一共两轮,第一轮是扫描得到space个数
int space=0;
int L1=str.length();//str需要length();数组一般用length
for (int i=0;i<L1;i++)
{
if (str.charAt(i)==' ')space++;
}
int L2=L1+2*space;
str.setLength(L2);
L1--;L2--;//
while (L1>=0&&L2>L1){
if (str.charAt(L1)!=' '){
str.setCharAt(L2--,str.charAt(L1));
}
else{
str.setCharAt(L2--,'0');
str.setCharAt(L2--,'2');
str.setCharAt(L2--,'%');
}
L1--;
}
return str.toString();//需要好好看下Java里面的String部分
}
}
从尾到头打印链表
输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
/**
* 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>mylist=new ArrayList<>();//含有<>和();别忘了new
while(listNode!=null){//直接用null对应listNode就行
mylist.add(0,listNode.val);//list.add(0,value)在list的头部插入值
listNode=listNode.next;//Java这样就不用到->指针了,只会用到STL里面定义过的操作
}
return mylist;
}
} import java.util.ArrayList;//递归的方法(调用栈)运行时间是50ms,非递归是20ms
public class Solution{
public ArrayList<Integer>mylist=new ArrayList<>();
public ArrayList<Integer>printListFromTailToHead(ListNode listNode){
if(listNode!=null){//递归一般使用if判断,而不是while
printListFromTailToHead(listNode.next);
//递归的时候不需要前面的public ArrayList<Integer>,括号里面也不需要listNode前面的类型
mylist.add(listNode.val);
}
return mylist;
}
}
*/
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {
ArrayList<Integer>mylist=new ArrayList<>();//含有<>和();别忘了new
while(listNode!=null){//直接用null对应listNode就行
mylist.add(0,listNode.val);//list.add(0,value)在list的头部插入值
listNode=listNode.next;//Java这样就不用到->指针了,只会用到STL里面定义过的操作
}
return mylist;
}
}
重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
/**
* 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||in.length==0)return null;
TreeNode nroot=new TreeNode(pre[0]);
for(int i=0;i<in.length;i++)
{
if (pre[0]==in[i]){
nroot.left=reConstructBinaryTree(Arrays.copyOfRange(pre,1,i+1),Arrays.copyOfRange(in,0,i));
nroot.right=reConstructBinaryTree(Arrays.copyOfRange(pre,i+1,in.length),Arrays.copyOfRange(in,i+1,in.length));
break;
}
}
return nroot;
}
}
旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。
import java.util.ArrayList;
public class Solution {
public int minNumberInRotateArray(int [] array) {
if (array.length==0)return 0;
int min=array[0];
for (int i=0;i<array.length;i++){
if (array[i]<min)min=array[i];
}
return min;
}
} //二分法的局限性在于,如果有重复值,则可导致在比较:m、m1、m2的时候不能收缩,从而导致超时
//import java.util.ArrayList;
//public class Solution{
// public int minNumberInRotateArray(int [] array){
// int len=array.length;
// if (len==0)return 0;
// int left=0; int right=len-1;
// int min=array[0];
// while (len>0){
// int mid=(right+left)/2;
// int m=array[mid];
// int m1=array[(left+mid)/2];
// int m2=array[(mid+right)/2];
// if (m<m1&&m<m2){left=(left+mid)/2;right=(mid+right)/2;min=array[m];}
// else if (m1<m2&&m1<m){right=mid-1;min=array[m1];}
// else if (m2<m1&&m2<m){left=mid+1;min=array[m2];}
// len=right-left+1;
// }
// return min;
// }
//}
用两个栈实现队列
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
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);
} public int pop() {
if (stack2.size()<=0)
while (stack1.size()>0) stack2.push(stack1.pop());
return stack2.pop();
}
}
斐波那契数列
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。n<=39
public class Solution {
public int Fibonacci(int n) {
int fi[]=new int [40];//注意初始化数组
fi[0]=0;fi[1]=1;
for (int i=2;i<=n;i++){
fi[i]=fi[i-1]+fi[i-2];
}
return fi[n];
}
}
跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
public class Solution {
public int JumpFloor(int target) {
int frog[]=new int[100];
frog[1]=1;frog[2]=2;
for (int i=3;i<=target;i++){
frog[i]=frog[i-1]+frog[i-2];
}
return frog[target];
}
}
变态跳台阶
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
public class Solution {
public int JumpFloorII(int target) {
int x=1;for (int i=1;i<target;i++)x*=2;return x;
}
}
矩形覆盖
public class Solution {
public int RectCover(int target) {
int rec[]=new int [100];
rec[0]=0;rec[1]=1;rec[2]=2;
for (int i=3;i<=target;i++){
rec[i]=rec[i-1]+rec[i-2];
}
return rec[target];
}
}
数值的整数次方
public class Solution {
public double Power(double base, int exponent) {
double x=1;
if (base==0)return 0;
if (exponent==0)return 1;
if(exponent<0){
for (int i=0;i<-exponent;i++)x*=base;
x=1/x;
return x;
}
else {
for (int i=0;i<exponent;i++)x*=base;
return x;
}
}
}
求1+2+3+...+n
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
class Solution {
public:
int Sum_Solution(int n) {
int x=0; //1.需要重复结构->使用递归 2.需要判断->使用&&特性
n>=1&&(x=Sum_Solution(n-1)+n);
return x;
}
};
连续子数组的最大和
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)
class Solution {
public:
int FindGreatestSumOfSubArray(vector<int> array) { int MAX=-2147483647;int max=0;
for (int i=0;i<array.size();i++){
max+=array[i];
if (max>MAX)MAX=max;
if (max<0)max=0;
}
return MAX;
}
};
数组中重复的数字
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
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) {
if (numbers==null||length==0)return false;
//for (int i=0;i<length;i++){
// if(numbers[i]>=length||numbers[i]<0)return false;
//}
for (int i=0;i<length;i++){
while (numbers[i]!=i){
if (numbers[i]==numbers[numbers[i]]){
duplication[0]=numbers[i];
return true;
}
else{
int temp=numbers[numbers[i]];//这种嵌套的模式来交换,中间可能会出现干扰
numbers[numbers[i]]=numbers[i];
numbers[i]=temp;
}
}
}
return false;
}
}
数组中出现次数超过一半的数字
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
public class Solution {
public int MoreThanHalfNum_Solution(int [] array) {
int len=array.length;
int target=1+len/2;
for(int i=0;i<len;i++){
int temp=array[i];
int num=target;
for (int j=0;j<len;j++){
if (array[j]==temp)num--;
}
if (num<=0)return temp;
}
return 0;
}
}
调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
public class Solution {
public void reOrderArray(int [] array) {
int array0[]=new int[100];int k0=0;
int array1[]=new int[100];int k1=0;
for(int i=0;i<array.length;i++){
if (array[i]%2==0)array0[k0++]=array[i];
else if(array[i]%2==1)array1[k1++]=array[i];
}
for (int i=0;i<k1;i++){
array[i]=array1[i];
}
for (int i=k1;i<array.length;i++){
array[i]=array0[i-k1];
}
}
}//在保证奇数偶数内部相对顺序的情况下,这种方法就很好,时间、空间都是O(n)
//书上的题目要求仅仅是将奇偶数分开,那么用类似快排的two pointer就行,时间O(n),空间是O(1)
丑数
把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
import java.util.Arrays;
public class Solution {
public int GetUglyNumber_Solution(int index) {
if (index<=0)return 0;
int array[]=new int[2000];
array[0]=1;
for (int i=1;i<index;i++){
int bound=array[i-1];//现存的最大值
int m2=1;int m3=1;int m5=1;
for(int k=0;;k++){
if (2*array[k]>bound){m2=2*array[k];break;}
}
for(int k=0;;k++){
if (3*array[k]>bound){m3=3*array[k];break;}
}
for(int k=0;;k++){
if (5*array[k]>bound){m5=5*array[k];break;}
}
int min=m2;
if (m3<min)min=m3;
if (m5<min)min=m5;
array[i]=min;
}
return array[index-1];
}
}
最小的K个数
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result =new ArrayList<Integer>();//构造题目要求的结构
if(k>input.length||k<=0)return result;
int topk[]=new int[1000];
for (int i=0;i<k;i++)topk[i]=2147483647;//初始化
for (int i=0;i<input.length;i++){
int t=0;
for (int j=k-1;j>=0;){
if (topk[j]>input[i]){topk[j+1]=topk[j];j--;t=j;}
else {t=j;break;}
}
topk[t+1]=input[i];
}
for(int i=0;i<k;i++){
result.add(topk[i]);
}
return result;
}
}
顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 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 {
public ArrayList<Integer> printMatrix(int [][] matrix) {
ArrayList<Integer> result=new ArrayList<Integer>();
int up=0;
int down=matrix.length-1;
int left=0;
int right=matrix[0].length-1;
if (down<0||right<0)return result;
while (true){
for (int y=left;y<=right;y++){
result.add(matrix[up][y]);
}
up++;
if (up>down)break;
for (int x=up;x<=down;x++){
result.add(matrix[x][right]);
}
right--;
if (left>right)break;
for (int y=right;y>=left;y--){
result.add(matrix[down][y]);
}
down--;
if (up>down)break;
for (int x=down;x>=up;x--){
result.add(matrix[x][left]);
}
left++;
if (left>right)break;
}
return result;
}
}
二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
public class Solution {
public int NumberOf1(int n) {
int count =0;
while (n!=0){
n=n&(n-1);
count++;
}
return count;
}
}
数字在排序数组中出现的次数
统计一个数字在排序数组中出现的次数。
public class Solution {
public int GetNumberOfK(int [] array , int k) {
if(array.length==0)return 0;
int left=0;
int right=array.length-1;
int index=0;
int flag=0;
while (left<=right){
int mid=(left+right)/2;
if (array[mid]==k){
index=mid;flag=1;break;
}
else if(array[mid]>k)right=mid-1;
else if(array[mid]<k)left=mid+1;
}
if (flag==0)return 0;
while (index-1>=0&&array[index-1]==k)index--;
int count=0;
while (index<array.length&&array[index++]==k)count++;
return count;
}
}
链表中倒数第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 p1=head;
ListNode p2=head;
for(int i=0;i<k;i++){
if (p1==null)return null;
p1=p1.next;
}
while (p1!=null){
p1=p1.next;
p2=p2.next;
}
return p2;
}
}
反转链表
输入一个链表,反转链表后,输出新链表的表头。
public class Solution {//设置p1/p2/p3
public ListNode ReverseList(ListNode head) {
if(head==null)return null;
if(head.next==null)return head;
ListNode p1=head;
ListNode p2=head.next;
while (p2.next!=null){
ListNode p3=p2.next;
p2.next=p1;
p1=p2;
p2=p3;
}
p2.next=p1;
head.next=null;
return p2;
}
}
合并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
public class Solution {
public ListNode Merge(ListNode list1,ListNode list2) {
if(list1==null&&list2==null)return null;
if(list1==null)return list2;
if(list2==null)return list1;
ListNode p1=list1;
ListNode p2=list2;
ListNode phead=null;
if (list1.val<list2.val){phead=list1;p1=p1.next;}
else {phead=list2;p2=p2.next;}
ListNode p=phead;
while (p1!=null&&p2!=null){
if (p1.val<p2.val){p.next=p1;p=p.next;p1=p1.next;}
else {p.next=p2;p=p.next;p2=p2.next;}
}
if (p1==null)p.next=p2;
if (p2==null)p.next=p1;
return phead;
}
}
二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
public class Solution {
public void Mirror(TreeNode root) {
if (root!=null){
TreeNode temp=root.left;
root.left=root.right;
root.right=temp;
Mirror(root.left);
Mirror(root.right);
}
}
}
二叉树的深度
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
public class Solution {
public int TreeDepth(TreeNode root) {
if(root==null)return 0;
else{
int left=TreeDepth(root.left);
int right=TreeDepth(root.right);
return left>right?left+1:right+1;
}
}
}
数组中只出现一次的数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
public class Solution {
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) {
int xor=0;
for (int i=0;i<array.length;i++){
xor=xor^array[i];
}
int test=1;
while((test&xor)==0){
test=test<<1;
}
for (int i=0;i<array.length;i++){
if ((test&array[i])==0){
num1[0]=num1[0]^array[i];
}
else{
num2[0]=num2[0]^array[i];
}
}
}
}//与运算&//异或运算^//按位运算的优先级低于==,所以要加括号
删除链表中重复的结点:较难
在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。
例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
public class Solution {
public ListNode deleteDuplication(ListNode pHead)
{
if(pHead==null||pHead.next==null)return pHead;
ListNode p0=new ListNode(-);//构造前站节点来防止第一个节点被删除的惨剧
ListNode p1=p0;
ListNode p2=pHead;
int temp=pHead.val;
int dup=;//dup==1表示有重复
while (p2.next!=null){//循环外还有处理?
if(p2.next.val==temp){
dup=;
p2=p2.next;//
}
else{
if (dup==){
p1.next=p2;
p1=p1.next;
}
dup=;
temp=p2.next.val;
p2=p2.next;
}
}
if (dup==)p1.next=p2;
if (dup==)p1.next=null;//p1每次接收都是后面一串 p0=p0.next;//过河拆桥,去掉头部哨兵
return p0;
}
}
包含min函数的栈:中等
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
import java.util.Stack;
public class Solution {
//成员变量:
private static Stack<Integer> stack1=new Stack<Integer>();//stack1这个是基础栈,用于栈的基础功能
private static Stack<Integer> stack2=new Stack<Integer>();//这个是保存stack1对应位置的最小值的栈,和stack1的大小始终保持一致
//例子:stack1:342053
//例子:stack2:332000
private Integer min=new Integer(Integer.MAX_VALUE);//这里使用Integer.MAX_VALUE来初始化,其实使用任意值都可以,但是有括号的话里面不能为空
//也可以用:private Integer min=Integer.MAX_VALUE
//也可以用:private Inreger min=0;
//4个方法:
public void push(int node) {
if(stack1.empty()){
min=node;
stack1.push(node);
stack2.push(min);
}
else{
if (node<min){
min=node;
}
stack1.push(node);
stack2.push(min);
}
} public void pop() { //pop()不用返回值
if (stack1.empty()){
return ;
}
stack1.pop();
stack2.pop();//pop()的意思是:将栈顶的值除掉。这题里面不返回值(void类型)
} public int top() {
return stack1.peek();//peek()的意思是:不改变栈,观察栈顶的值
} public int min() {
return stack2.peek();
}
}
两个链表的第一个公共结点
输入两个链表,找出它们的第一个公共结点。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
public class Solution {
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if (pHead1==null||pHead2==null)return null;
int len1=0;
int len2=0;
ListNode p1=pHead1;
ListNode p2=pHead2;
while (p1!=null){
len1++;
p1=p1.next;
}
while(p2!=null){
len2++;
p2=p2.next;
}
int diff=0;
p1=pHead1;p2=pHead2;//重新初始化
if (len1>len2){
diff=len1-len2;
for (int i=0;i<diff;i++)p1=p1.next;
}
else {
diff=len2-len1;
for (int i=0;i<diff;i++)p2=p2.next;
}
while(p1!=null && p2!=null){
if (p1.val==p2.val) return p1;
p1=p1.next;
p2=p2.next;
}
return null;
}
}
整数中1出现的次数(从1到n整数中1出现的次数)
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
public class Solution {
public int NumberOf1Between1AndN_Solution(int n) {
int count=0;
for(int i=1;i<=n;i++){
int j=i;
while (j!=0){
if(j%10==1)count++;
j=j/10;
}
}
return count;
}
}
孩子们的游戏(圆圈中最后剩下的数):中等
每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!^_^)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)
public class Solution {
public int LastRemaining_Solution(int n, int m) {
if (n<1||m<1)return -1;//??
ListNode head=new ListNode(0);
ListNode p=head;
for (int i=1;i<n;i++){
ListNode kids=new ListNode(i);
p.next=kids;
p=p.next;
}
p.next=head;//p回到开头位置的前一个,做准备
while(p.next!=p){//注意这里的条件是:环形链中p不指向自身
for (int i=0;i<m-1;i++){
p=p.next;
}
p.next=p.next.next;//java会自动回收,所以不管那个被删除的节点
}
return p.val;
}
}
复杂链表的复制:较难
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
ps:原链表保持不变(改动后要还原)
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null; RandomListNode(int label) {
this.label = label;
}
}
*/
public class Solution {
public RandomListNode Clone(RandomListNode pHead)
{
if (pHead==null)return null;
RandomListNode p1=pHead;//沿着主链
RandomListNode p2=pHead.next;//在前
while(p1!=null){
RandomListNode node=new RandomListNode(p1.label);
p1.next=node;
p1.next.next=p2;
p1=p2;
if (p2!=null)p2=p2.next;//用末尾来验证,几乎每个while循环的末尾都要画图验证!!!
}
p1=pHead;
while (p1!=null){
if (p1.random==null)p1.next.random=null;//要判断下,防止为空
else{
p1.next.random=p1.random.next;//太强了
}
p1=p1.next.next;//可以√
} RandomListNode head=pHead.next;
p2=pHead.next;
p1=pHead;//需要保持原有的链表为初始状态(还原)
while (p2.next!=null){
p1.next=p1.next.next;//
p1=p1.next;//
p2.next=p2.next.next;
p2=p2.next;
}
p1.next=null;////(终止的时候要画图来试一下) /*
p2=pHead.next;
p1=pHead;
while(p1!=null){
p1.next=p2.next;
p1=p1.next;
if (p2.next!=null)p2.next=p2.next.next;
p2=p2.next;
}
*/ return head; }
}
链表中环的入口结点:中等
import java.util.ArrayList;//
public class Solution {
public ListNode EntryNodeOfLoop(ListNode pHead)
{
ArrayList<ListNode> list=new ArrayList<>();//注意初始化的模式,链表节点可以在<>中,类比int
if(pHead==null)return null;
ListNode p=pHead;
while(p!=null){
if (list.contains(p))return p;//list.contains()和list.add()的运用
list.add(p);
p=p.next;
}
return null;
}
} /*
public class Solution { //如果可以修改val的值,那么就很简单了,如下:
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if(pHead==null)return null;
ListNode p=pHead;
while(p!=null){
if (p.val==2199)return p;
p.val=2199;
p=p.next;
}
return null;
}
}
*/
和为S的连续正数序列:中等
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!
import java.util.ArrayList;
public class Solution {
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum) {
ArrayList<ArrayList<Integer> > list_2d=new ArrayList<ArrayList<Integer> >();//二级列表(数组)。//不知道能不能用下标灵活控制二级数组??
if(sum<=0)return null;//
int L=1;int R=1;int SUM=1;
//int k=0;//数组行数
while(L<=sum/2){//
if(SUM==sum){
//int l=0;//数组列数
ArrayList<Integer> list_1d=new ArrayList<Integer>();
for(int i=L;i<=R;i++){
//list[k][l++]=i;
list_1d.add(i);//一维数组add数字
}
list_2d.add(list_1d);//二维数组add一维数组
//k++;
}
if(SUM<=sum){R++;SUM=SUM+R;}//two pointer滑动窗口机制-右边R
else{SUM=SUM-L;L++;} //two pointer滑动窗口机制-左边L
}
return list_2d;
}
}
和为S的两个数字:简单
输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
ps:和上面一题类似。
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum) {
ArrayList <Integer>list=new ArrayList <Integer>();
if (array.length<2)return list;//在不符合条件的时间要返回"空的list",而不是直接返回null
int L=0;int R=array.length-1;
while(L<R){
int SUM=array[L]+array[R];
if (SUM==sum){
list.add(array[L]);
list.add(array[R]);
return list;
}
if(SUM<sum)L++;
if(SUM>sum)R--;
}
return list;
}
}
栈的压入、弹出序列:简单
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
import java.util.ArrayList;
import java.util.Stack;//java.util.Stack public class Solution {
public boolean IsPopOrder(int [] pushA,int [] popA) {
Stack<Integer> stack=new Stack<Integer>();
int k1=0;int k2=0;
while(true){
stack.push(pushA[k1++]);
while(stack.size()!=0 && stack.peek()==popA[k2]){stack.pop();k2++;}//栈空时不能使用peek()
if (k1==pushA.length && stack.size()!=0)return false;
if (k1==pushA.length && stack.size()==0)return true;
}
}
}
第一个只出现一次的字符:适应字符串类型,中等/简单
在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置, 如果没有则返回 -1(需要区分大小写).
public class Solution {
public int FirstNotRepeatingChar(String str) {
if (str==null)return -1;
int [] hash=new int[128];//类型为int.//大小写字母的范围在0~127内,允许部分冗余空间
for (int i=0;i<str.length();i++){//将String类型的str用i访问,类似数组;用.length()获得长度,容器STL的规范,而不是数组规范
hash[str.charAt(i)]++;//感觉很奇怪,hash是int类型的数组,竟然填进了char类型的下标。//注意str.charAt(i)的使用
}
for (int i=0;i<str.length();i++){
if (hash[str.charAt(i)]==1)return i;//容器STL的方法名称,连写的时候:第一个词开头小写,后面词之间内部的词大写 首字母。
}
return -1;
}
}
字符流中第一个不重复的字符:中等
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
//通过率20%,
用例: "helloworld" 对应输出应该为:
"hhhhhhhhhh" 你的输出为:
"hhlhhhhhhh" 不知道哪里有问题。。。。。
public class Solution {
int [] hash=new int[128];//经典128 ASCII码,128~255是一些符号扩展
//错误示范:int hash[128]=new int ();//不需要括号,并且指定的大小 写在后面而不是前面。
String str=null;//用null初始化就行了
//Insert one char from stringstream
public void Insert(char ch)//这里的意思应该是多次调用Insert函数,每次插入一个char
{
str=str+ch;//直接用String类型 + char类型的字符//用String记录下来一个个ch,在下面的函数遍历时候有用
hash[ch]++;
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()//这个函数和上面的函数任意独立穿插调用使用
{
for (int i=0;i<str.length();i++){
if (hash[str.charAt(i)]==1)return str.charAt(i);
}
return '#';//不能写return #,要写return '#';表示是ASCII字符而不是普通含义
}
}
用下面的方法,定义LinkedHashMap<Character, Integer>
import java.util.*;//别忘了,STL必备
public class Solution {
//Insert one char from stringstream
LinkedHashMap <Character,Integer> hashmap=new LinkedHashMap<>();//LinkedHashMap和Map的区别??系统学习STL
//放在最前面,不要写在方法里面了。。。
public void Insert(char ch)//这里的意思应该是多次调用Insert函数,每次插入一个char
{
if (hashmap.containsKey(ch)){//时间效率:O(n) ->需要遍历才能获取
hashmap.put(ch,hashmap.get(ch)+1);//不能用++,要用+1
}
else{
hashmap.put(ch,1);
}
}
//return the first appearence once char in current stringstream
public char FirstAppearingOnce()//这个函数和上面的函数任意独立穿插调用使用
{
for(Character ch:hashmap.keySet()){//for的第二种写法 Character ch:CH
//所有的Character都要大写 //.keySet()获取map里面所有的key
if (hashmap.get(ch)==1)return ch;
}
return '#';//不能写return #,要写return '#';表示是ASCII字符而不是普通含义
}
}
左旋转字符串:简单
汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!
public class Solution {
public String LeftRotateString(String str,int n) {
if (str.length()<=)return "";//对于str不能用null,要用""
int N=n%str.length();
return str.substring(N,str.length())+str.substring(,N);//str.substring(left,right)方法
} //复制Sting子串,左闭右开区间[left,right)
}
字符串的排列:较难
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
ps:提交前要排序
import java.util.ArrayList;
import java.util.Collections;
public class Solution {
public ArrayList<String> Permutation(String str) {
ArrayList<String>list=new ArrayList<String>();
if(str==null||str.length()==)return (ArrayList)list;
//太坑了,还有""的情况,length=0但str不是null //而且还要强制返回(ArrayList)的格式
fun(str.toCharArray(),,list);//String格式转化为char数组,方便交换操作
Collections.sort(list);//最终输出前,要一次排序,不然每个人的答案不一样OG难以判断
return (ArrayList)list;//强制转化为(ArrayList)类型输出
}
public void fun(char[] ch,int i,ArrayList<String>list){
if (i==ch.length-){//i到了char数组的最后一个
if(!list.contains(new String(ch))){//判重,复杂度O()??
list.add(new String(ch));//要用new String来封装char数组
}
}
else{
for (int j=i;j<ch.length;j++){
swap(ch,i,j);
fun(ch,i+,list);
swap(ch,i,j);
}
}
}
public void swap(char[]ch,int i,int j){
char temp=ch[i];
ch[i]=ch[j];
ch[j]=temp;
}
}
正则表达式匹配:较难
请实现一个函数用来匹配包括'.'和'*'的正则表达式。模式中的字符'.'表示任意一个字符,而'*'表示它前面的字符可以出现任意次(包含0次)。 在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式"a.a"和"ab*ac*a"匹配,但是与"aa.a"和"ab*a"均不匹配
(1)C++ 解法(使用指针对数组的操作很方便):递归
//子结构有着相同算法的,使用递归,使代码简洁; //子结构计算无重复,算法不会产生递归的副作用。
class Solution {
public:
bool match(char* str, char* pattern)
{
if (*str=='\0'&&*pattern=='\0')return true;//都空,返回true
if (*str!='\0'&&*pattern=='\0')return false;//'\0'反斜杠不要写错了
if (*(pattern+)!='*'){
if(*pattern=='.'&&*str!='\0'||*pattern==*str)return match(str+,pattern+);
else return false;
}
else{
if(*pattern=='.'&&*str!='\0'||*pattern==*str)
return (match(str,pattern+)||match(str+,pattern));//两种可能分支(树状分叉递归)
else return match(str,pattern+);//只有一种可能,就是跳过_*
}
}
};
(2)JAVA 解法:使用动态规划
//
把字符串转换成整数:简单
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为0或者字符串不是一个合法的数值则返回0。
例如:+2147483647 -> 2147483647
ps:没有科学计数法
public class Solution {
public int StrToInt(String str) {
if (str==null||str.length()==0)return 0;
char[] s=str.toCharArray();//将String转化为Char数组,方便操作//toCharArray()
int len=str.length();
int symbol=1;//符号,初值为1
int result=0;
if(s[0]=='+'){
s[0]='0';//不要写成==
}
if(s[0]=='-'){
s[0]='0';symbol=-1;//
}
for (int i=0;i<len;i++){
if('0'<=s[i]&&s[i]<='9'){
result+=s[i]-'0';
if(i<len-1)result*=10;//最后一次不用乘10
}
else return 0;
}
result*=symbol;
if(symbol==-1&&result>0)return 0;//溢出导致符号改变//??
if(symbol==+1&&result<0)return 0;
return result;
}
}
表示数值的字符串:中等
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。
ps:都是固定格式套路
(1)正则表达式 解法
public class Solution {//比较死板的规则可以用正则化表达式,如果过于复杂就要写if else for的语句了
public boolean isNumeric(char[] str) {
String s = String.valueOf(str);//String.valueOf()将字符数组转为String //不是s.valueOf()而且此时没有s的值
return s.matches("[\\+\\-]?\\d*(\\.\\d+)?([eE][\\+\\-]?\\d+)?");//正则表达式 //别忘了双引号"",因为s匹配的是字符串
}//正则表达式:[]表示并列选其一;()表示同时全都要; ?与[]、()同时使用,表示有/无
//d表示[0-9],要是需要字母直接写在[]里面,比如[d]表示'd'; \\d表示数字,因为是反斜杠转义;
//标点符号如:+-*/.等都要反斜杠\\来转义
}
(2)普通流程法
//普通方法又臭又长,看不上,先不写了...
不用加减乘除做加法:中等
写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。
//位操作
public class Solution {
public int Add(int num1,int num2) {//num1和num2是形参,在函数里面可以直接用
int and = 2199;//与,进位值 //初值设为非零
int xor = 0;//异或,非进位值
while(and!=0){//当and==0的时候退出循环,此时xor的值就是最终的和【核心思想】
and=(num1&num2)<<1;//先与,再左移,表示进位。
xor=(num1^num2);
num1=and; num2=xor;
}
return xor;
}//总结一下4种位操作和<<移位操作【JAVA 6种】:与(&) 或(|) 非/取反(~) 异或(^) 左移(<<) 右移(>>)
}// and or not xor
//位操作只能用于整形,不能用于float和double
//位操作的优先级比较靠后,比加减还要后,所以要多打括号。
翻转单词顺序列:中等
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“student. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a student.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
方法1:
//从后向前扫描,将单词转移到另一个O(n)空间String
public class Solution {
public String ReverseSentence(String str) {
if(str==null||str.length()==0)return "";//空的时候不要返回null,要用""
char[]s=str.toCharArray();
if(s[0]==' ')return str;//这行 是看别人题解里面的补丁,真是觉得题目故意搞这些奇怪的测试用例。。醉醉的。。
String result=new String();//""//new String(),new的时候,不要忘记这个括号
for (int i=str.length()-1;i>=0;i--){
if(i==0||s[i-1]==' '){//i==0写在前面,防止i-1溢出
int k=i;//
while(k!=str.length()&&s[k]!=' '){
result+=s[k];//String直接加char
k++;
}
if(i!=0)result+=' ';//原句最前面一个词后面不加空格
}
}
return result;
}
}
方法2:两轮翻转法
//首先完全翻转,然后逐个单词翻转
//空间复杂度:C++直接操作string,复杂度O(1) Java要toCharArray,复杂度O(n)
//复习时候考虑再写下,先过了
把数组排成最小的数:中等
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
方法1:base on 冒泡排序
public class Solution {
public String PrintMinNumber(int [] numbers) {
for(int i=0;i<numbers.length-1;i++){//base on 冒泡排序
for (int j=0;j<numbers.length-1-i;j++){//排成最小数字==>"小的"放前面 "大的"放后面
//long x1=Integer.valueOf(numbers[j]+""+numbers[j+1]);//中间的位置有个""空字符串,不然int直接相加了
//long x2=Integer.valueOf(String.valueOf(numbers[j+1])+""+String.valueOf(numbers[j]));//两种写法,本行是规范版
//String->int,使用Integer.valueOf(); int->String使用String.valueOf() //两次转换
String x1=numbers[j]+""+numbers[j+1];
String x2=numbers[j+1]+""+numbers[j];
if(x1.compareTo(x2)>0){//举例子知道 大于小于号
//对于String之间的比较,使用str1.compareTo(str2) 若str1>str2则返回正。 1<2负 1==2返回0 //if()里面必须boolean类型
int temp=numbers[j];
numbers[j]=numbers[j+1];
numbers[j+1]=temp;
}
}
}
String result="";
for (int i=0;i<numbers.length;i++){
//result+=String.valueOf(numbers[i]);//String.valueOf() 将int转化为String
result+=numbers[i];//可以直接将int加给String
}
return result;
}
}
方法2:base on 快排
//二轮再搞
数组中的逆序对:中等/较难
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
方法:参照归并排序:逆序->升序 的过程中不断求局部的逆序数
public class Solution {//首先想到求逆序对的过程就是:将原始数组调整顺变成升序的排列,也就是排序。
//从三种O(nlogn)的方法里面找,发现快排、堆排序都会产生“越过不该越过”的数字的现象,故排除;
int result;//逆序数
public int InversePairs(int [] array) {
if(array.length<=0)return 0;//
divide(array,0,array.length-1);
return result;
}
private void divide(int [] array,int low ,int high){
if(low>=high)return;//递归结束的条件
int mid=(low+high)/2;
divide(array,low,mid);
divide(array,mid+1,high);
merge(array,low,mid,high);
}
private void merge (int []array, int low, int mid, int high){
int[] temp=new int[high-low+1];
int k=0;//与temp[]数组配套,归并后的大数组
int i=low;int j=mid+1;//i对应左数组,j对应右数组 //本函数的全局变量
while(i<=mid && j<=high){
if(array[i]<array[j]){
temp[k++]=array[i++];//k++、i++别漏了,这里是while循环
}
else{
temp[k++]=array[j++];
result=(result+((mid+1)-i))%1000000007;//取余数
//右数组穿过左数组的元素的个数(==左数组剩下元素个数)【全文重点】
}
}
while(i<=mid)temp[k++]=array[i++];//不用去判断什么,直接上这两个循环
while(j<=high)temp[k++]=array[j++];
for(int I=0;I<temp.length;I++){
array[low+I]=temp[I];//赋值回到array
}
}
}
扑克牌顺子
LL今天心情特别好,因为他去买了一副扑克牌,发现里面居然有2个大王,2个小王(一副牌原本是54张^_^)...他随机从中抽出了5张牌,想测测自己的手气,看看能不能抽到顺子,如果抽到的话,他决定去买体育彩票,嘿嘿!!“红心A,黑桃3,小王,大王,方片5”,“Oh My God!”不是顺子.....LL不高兴了,他想了想,决定大\小 王可以看成任何数字,并且A看作1,J为11,Q为12,K为13。上面的5张牌就可以变成“1,2,3,4,5”(大小王分别看作2和4),“So Lucky!”。LL决定去买体育彩票啦。 现在,要求你使用这幅牌模拟上面的过程,然后告诉我们LL的运气如何, 如果牌能组成顺子就输出true,否则就输出false。为了方便起见,你可以认为大小王是0。
方法1:朴素法——king补充空位
public class Solution {
public boolean isContinuous(int [] numbers) {//numbers:0:大小王; 1~13 普通牌
if (numbers.length<5)return false;//不够牌false
int king=0;
int[] hash=new int[14];//0~13,卡槽 //初值为零??
for (int i=0;i<5;i++){
if (numbers[i]==0){king++;continue;}//别漏了continue,会导致统计hash[0]的个数
if(hash[numbers[i]]==0)hash[numbers[i]]++;
else return false;//有重复,直接false,不可能同花顺了
} int count=0;//连续的个数
for(int i=1;i<=13;i++){//检查hash数组
if(hash[i]==1)count++;
if(hash[i]==0 && count!=0){
if(king==0)return false;
else {king--;count++;}
}
if(i>=9 && count<i-8){//补充条款:末尾没有count,也要提前用king的情况 //题目的测试样例没有覆盖到补充条款~~
if(king==0)return false;
else {king--;count++;}
}
if(count==5)return true;
}
return false;
}
}
方法2:利用特性——卡不重复时,max-min<=4即可
public class Solution {
public boolean isContinuous(int [] numbers) {
if(numbers.length!=5)return false;
int max=0;
int min=14;//不是max14!!!把min设置为最大,max设置最小!!!
int []hash=new int[14];
for (int i=0;i<5;i++){
int num=numbers[i];//简记
if(num==0)continue;//king不怕重复
hash[num]++;
if(hash[num]>1)return false;
if(num>max)max=num;
if(num<min)min=num;
if(max-min>=5)return false;//min与max满足的条件
}
return true;
}
}
树的子结构:中等 / 较难
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
ps:大的思路不算难,但这里面细节比较多
public class Solution {
public boolean HasSubtree(TreeNode root1,TreeNode root2) {
//分两步,首先找到与B的根节点匹配之处
if(root1==null||root2==null)return false;//递归结束的条件 //root1、root2有一个完了,就此分支不通
if(root1.val==root2.val){
//return judge(root1,root2);//这句不行,用下面这句
if(judge(root1,root2))return true;//和上面的区别是:只有true才返回;false时候不返回。
//因为有很多候选的发起点,但只要有一个成功就OK,不需要每个都OK
}
return HasSubtree(root1.left,root2)||HasSubtree(root1.right,root2);//这里的关系是"或"
//表示整个树的所有分支只要有一个末端分支(上方递归结束条件)就可以。
}
//第二步进行整个子树的匹配
private boolean judge(TreeNode tree,TreeNode subtree){
if(subtree==null)return true;//在前,因为有tree和subtree都为null的情况。
if(tree==null)return false;//在后,相当与tree==null&&subtree!=null
if(tree.val==subtree.val){//不断向下判断val
return judge(tree.left,subtree.left)&&judge(tree.right,subtree.right);//"与"的关系,表示子树所有分支全部都要满足。judge到天荒地老
}
return false;
}
}
平衡二叉树 :中等
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
ps:题目有漏洞,没有判断树的中序遍历,只判断了树的深度一个方面。
方法1:朴素求深度
public class Solution {
private boolean flag=true;//全局变量最好放在主函数外面,这样不用在函数里面传来传去,直接所有函数都能用
public boolean IsBalanced_Solution(TreeNode root) {//题目中只需要判断深度符合平衡就OK了
deepth(root);
return flag;
}
private int deepth(TreeNode node){//depth
if(node==null)return 0;
int x1=deepth(node.left);
int x2=deepth(node.right);
if((x1-x2)>1||(x2-x1)>1)flag=false;//表示高度差失衡
//也可以用 Math.abs(x1-x2)>1
return x1>x2?x1+1:x2+1; //也可以用:1+Math.max(x1,x2)
}
}
方法2:剪枝大法
public class Solution {
public boolean IsBalanced_Solution(TreeNode root) {
return depth(root)!=-1;//是个判定语句,将int转化为true/false
}
private int depth(TreeNode node){//当高度失衡的时候直接返回-1,log层结束递归,而不是继续返回depth
if(node==null)return 0;
int x1=depth(node.left);
if(x1==-1)return -1;//如果后序遍历发现问题,就直接从发现问题的点log次向上,直接返回了。左子树后面直接有return-1的地方,不用等右子树。
int x2=depth(node.right);
if(x2==-1)return -1; int diff=Math.abs(x1-x2);//Math不用import
int deep=Math.max(x1,x2)+1;
return diff>1?-1:deep;
}
}
对称的二叉树 :中等
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
public class Solution {
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot==null)return true;//
return Sym(pRoot.left,pRoot.right);
}
boolean Sym(TreeNode node1,TreeNode node2){//函数设置为两个node指针,这样可以在递归的时候爆炸扩散
if(node1==null&&node2==null)return true;//每当遇到return时,意味着(本递归分支)不再需要向下进行了。(确定本分支一定为true了)
if(node1==null||node2==null)return false;//在上一句不全为null的前提下,1空1实 =>false //if(node1.left.val!=node2.right.val)return false;
//if(node1.right.val!=node2.left.val)return false; //不能这样超前向下,还没检验本层呢
if(node1.val!=node2.val)return false; //(确定本分支一定为false了)
//如果相等(则不能确定是true还是false),则什么都不用返回,而是继续向下递归,执行下面的语句:
return Sym(node1.left,node2.right)&&Sym(node1.right,node2.left);
}
}
把二叉树打印成多行
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
方法1:层次遍历
后面有层次遍历的题目,这里略过。
方法2:递归法
由于有ArrayList记录,使用递归的方法遍历 //无论先序、中序、后序,对于某一层而言,都是严格一个个从左到右
import java.util.ArrayList;//由于有ArrayList记录,使用递归的方法遍历 //无论先序、中序、后序,对于某一层而言,都是严格一个个从左到右
public class Solution {
public ArrayList<ArrayList<Integer> > list=new ArrayList<ArrayList<Integer> >();//全局 //初始化别忘了new、括号()
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
int depth=0;//主函数里面不用depth,只有pre函数里面用,所以这句也可以省略
pre(pRoot,0);
return list;
}
private void pre(TreeNode node, int depth){
if(node==null)return;//void的:return;
if(list.size()<=depth){//list.size(),STL的方法 别忘了括号
list.add(new ArrayList<Integer>());//new完了记得有括号()
}
list.get(depth).add(node.val);//list.get(int)与list.add()连用; // .get(int)就相当于C++里面使用的数组下标:A[int]
pre(node.left,depth+1);
pre(node.right,depth+1);
}
}
二叉搜索树的后序遍历序列:中等/思路较难
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
//这种比较抽象的题目,最好画个图。无论是“整体构思”还是“细节把控”都会好很多
public class Solution {
public boolean VerifySquenceOfBST(int [] sequence) {//输入的是int[]数组
if(sequence.length==0)return false;//??
return fun(sequence,0,sequence.length-1);//array.length //Java不用array.size()
}
private boolean fun(int []a,int l,int r){
if(l>=r)return true;//分支结束两种情况:l==r单个叶;l>r(即r-l==-1)为空 //这两个情况都是true
int i=r-1;
while(l<=i && a[i]>a[r])i--;//找到分割左右子树的位置i,然后下面换j来作循环参数
for (int j=i;j>=l;j--)if(a[j]>=a[r])return false;
return fun(a,l,i)&&fun(a,i+1,r-1);//这里的分组不包括根节点,所以应该是r-1,而不是r
}
}
二叉树的下一个节点:构思较难,代码不长
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
public class Solution {
public TreeLinkNode GetNext(TreeLinkNode pNode)//不用递归可以完成O(logn);递归复杂度为O(n)
{
if(pNode==null)return null;
//根据pNode.right【有没有右子树?】划分为两大类:
if(pNode.right!=null){//pNode有右孩子,(中序后继)则一定在右子树里面的(最左边的一个)
pNode=pNode.right;//这句别忘了,要转移到右子树开始循环。
while(pNode.left!=null)pNode=pNode.left;
return pNode;
}
else{//这个else只为了层次清晰,无实际意义。表示pNode没有右孩子的情况。
while(pNode.next!=null){
if((pNode.next).left!=pNode)pNode=pNode.next;//向上溯源,找第一个node是“node父节点的左孩子”的(中序能戳到屁股)
else return pNode.next;
}
return null;//退到根节点 仍然找不到后继,返回null的情况
}
}
} ////特殊的节点定义如下:(比平常的节点多一个指向父节点的指针)
////如果没有这个特殊的指针,就只能中序递归了
/*
public class TreeLinkNode {
int val;
TreeLinkNode left = null;
TreeLinkNode right = null;
TreeLinkNode next = null; TreeLinkNode(int val) {
this.val = val;
}
}
*/
从上往下打印二叉树:简单
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
ps:层次遍历
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer> result=new ArrayList<Integer>();//Integer//存储结果,只进不出
ArrayList<TreeNode> queue=new ArrayList<TreeNode>();//TreeNode//用于层次遍历的队列:出1进0~2
if(root==null)return result;//不要直接返回null,不符合格式,要返回空的ArrayList<Integer>result;
queue.add(root);
int q=0;//计数queue
//int r=0;//计数result
while(q<=queue.size()-1){//ArrayList[STL]没有.length(),只有.size() ??
TreeNode node=queue.get(q);//当前node节点
result.add(node.val);//
if(node.left!=null)queue.add(node.left);
if(node.right!=null)queue.add(node.right);
q++;//就不弹出了,直接往下走
}
return result;
}
}
上面是辅助队列queue不删除用过节点,
下面用完就删(理论上空间效率提高、时间效率下降):
import java.util.ArrayList;
public class Solution {
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
ArrayList<Integer>result=new ArrayList<>();
ArrayList<TreeNode>queue=new ArrayList<>();
if(root==null)return result;
queue.add(root);
while(queue.size()!=0){
TreeNode node=queue.get(0);//永远从queue的第一个node开始
queue.remove(0);//用完删除 //使用remove来删除 //没有delete这个方法。。
result.add(node.val);
if(node.left!=null)queue.add(node.left);
if(node.right!=null)queue.add(node.right);
}
return result;
}
}
ps:实际上OG里面,两种方法时间、空间都差不多。
二叉树中和为某一值的路径:中等/较难
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
方法1:使用void函数(方法)来递归修改全局变量
import java.util.ArrayList;
public class Solution {
private ArrayList<ArrayList<Integer>> result=new ArrayList<ArrayList<Integer>>();//全局一个版本,被所有递归分支修改,不用传来传去
private ArrayList<Integer> list=new ArrayList<Integer>();//【借鉴精华】全局设置一个list,被所有递归分支改来改去
//【借鉴精华】全局设置一个list,只要在离开一个节点的时候(左右子树递归完成并彻底离开)删除这个节点的val就可以始终保持为单分支的路径
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root==null)return result;
Find(root,target);
return result;//如果没有改动,即没有找到路径,则result保持为空ArrayList。
}
private void Find(TreeNode node, int target){//void类型,只需要修改全局的result和list
if(node!=null){
target-=node.val;
list.add(node.val);//路径list入值
if(target==0 && node.left==null && node.right==null){//找到了【必须满足左右子树为null】
int i=result.size()-1;//要放在外面,因为不止for循环里面要用
while(i>=0 && result.get(i).size()<list.size())i--;//从原先的最后一个开始,一直比到第0个 //如果原先为空,则不会进入while循环
//找到位置 //list放入result
result.add(i+1,new ArrayList<Integer>(list));//i+1的位置//add(index,内容) 指定位置的add
}//【add添加的是引用,如果不new一个的话,后面的操作会更改这个list】这里面要用new ArrayList<Integer>(list)【这样等于有备份】,不能直接用list
//不能直接用list,不然最后list不断改变时,result里面的引用全部指向最终的list,这也就是我一开始百思不得其解的原因
if(target>0){//继续向下//如果不判断的话,也不影响结果(如果val非负)。但是这可以剪枝target<0的情况,不然全部都要递归到叶节点,浪费很多时间。
Find(node.left,target);
Find(node.right,target);
}
list.remove(list.size()-1);//【离开时删除(回退),全文重点】
} //target<0时,不需要任何操作
}
}
方法2:使用return result来递归(不用去新建一个函数)
import java.util.ArrayList;//和上面那个方法差不多,不用分出来写个函数
public class Solution {
private ArrayList<ArrayList<Integer>> result =new ArrayList<ArrayList<Integer>>();
private ArrayList<Integer> list=new ArrayList<Integer>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target) {
if(root==null)return result;
target-=root.val;
list.add(root.val);
if(target==0 && root.left==null && root.right==null){
int i=result.size()-1;
while(i>=0 && result.get(i).size()<list.size())i--;
result.add(new ArrayList<Integer>(list));
}
if(target>0){
FindPath(root.left,target);
FindPath(root.right,target);
}
list.remove(list.size()-1);
return result;
}
}
二叉搜索树的第k个结点:简单
给定一棵二叉搜索树,请找出其中的第k小的结点。例如,(5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。
方法1:双函数(新建的void函数 用于修改全局变量result;主函数负责return)
public class Solution {
private int i;//相当于:private int i=0; //全局变量可以不初始化,int默认为0
private TreeNode result;//节点默认为null,相当于private TreeNode result=null; //设为全局,就不用(在函数头)传值了
//但全局变量不能这样初始化:private TreeNode result=new TreeNode();这种方法用于函数内初始化局部变量;
TreeNode KthNode(TreeNode pRoot, int k){
Find(pRoot,k);
return result;
}
private void Find(TreeNode pRoot,int k){//我感觉除了node在函数里面可以向不同分支传不同的值(node.left/node.right),【其他都是传递定值】;
//不同值:node.left/node.right,【但这都不是去改变值,而是原有结构选择不同分支!!】
//【要是需要变化的值到各个分支,最好用全局变量修改,不然会被改乱掉(Java里面)】
if(pRoot!=null){
Find(pRoot.left,k);
if(++i==k)result=pRoot;//缺点是(双函数):找到result也没法及时停止,进行剪枝
Find(pRoot.right,k);
}
}//没有return的void辅助函数,在递归时:逻辑轻松简洁,不容易错
}
方法2:单个函数(与方法1无优劣之分)
public class Solution {
private int i=0;
TreeNode KthNode(TreeNode pRoot, int k)//本题单函数:代码短些,不过逻辑复杂些
{
if(pRoot!=null){
TreeNode node1=KthNode(pRoot.left, k);//左子树分支总返回给node1【左子树不断向下递归】
if(node1!=null)return node1;//将node1 return给上一层
if(++i==k)return pRoot;//【一旦找到,就会剪枝、logn时间迅速向上】
TreeNode node2=KthNode(pRoot.right, k);//右子树分支总返回给node2
if(node2!=null)return node2;
}
return null;//【此分支结束(结束向下),向上一层返回(不是向最上层返回)】 //递归类似于金字塔,一层层向上返回,最顶层返回最终result
}
}
按之字形顺序打印二叉树:较难
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
ps:(1)本题类似于:层次遍历二叉树。 (2)"之" 就是 "S"型 蛇形。
方法1:使用一个队列(用于层次遍历),加一个栈(用于高效翻转)
import java.util.ArrayList;
import java.util.Stack;//java.util.*;
public class Solution {
private int depth=0;//记录深度,全局变量
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer> > result=new ArrayList<ArrayList<Integer> >();
ArrayList<TreeNode> queue=new ArrayList<TreeNode>();//不要双层,要单层的queue
Stack<Integer> stack=new Stack<Integer>();//辅助栈 用于翻转偶数行的Integer //外层类型是Stack,不能像queue一样用Arraylist
//ArrayList<Integer> stack=new ArrayList<Integer>() //错误实例:“假”stack
if(pRoot==null)return result;
queue.add(pRoot);//根入队
depth++;
while(!queue.isEmpty()){//.empty()判断是否为空 比.size()==0好 //Stack用.isEmpty();ArrayList用.empty()
int size=queue.size();//提前写出来,因为size会变
result.add(new ArrayList<Integer>());//新建行
for(int i=0;i<size;i++){
TreeNode node=queue.get(0);//一定要新建node副本,不然是引用会变
queue.remove(0);//O(n), 这里用ArrayList就不如Queue时间效率高了。。
if(depth%2==1){//奇数行不用栈,直接加入result //层数layer=depth+1,这里带入depth-1则正好是depth-1+1。
result.get(depth-1).add(node.val);
}
if(depth%2==0){//偶数行,需要栈翻转
stack.push(node.val);//Stack推荐用push() ,其实add()也能用
}
if(node.left!=null)queue.add(node.left);
if(node.right!=null)queue.add(node.right);
}
if(depth%2==0){
while(!stack.empty()){//非空:一直循环
result.get(depth-1).add(stack.peek());//peek(),取栈顶值 //??二级数组的操作//不行再建立list
stack.pop();//pop(),出栈
}
}
depth++;//深度向下1层
}
return result;
}
}
方法2:使用一个队列(用于层次遍历),加一个数组list(存单层数据)
import java.util.ArrayList;//使用一个queue和一个list
public class Solution {
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot) {
ArrayList<ArrayList<Integer> >result=new ArrayList<ArrayList<Integer> >();
ArrayList<TreeNode> queue=new ArrayList<TreeNode>();
if(pRoot==null)return result;
queue.add(pRoot);
int layer=1;//第一层
while(!queue.isEmpty()){//这里是【isEmpty】,不是empty
int size=queue.size();
ArrayList<Integer> list=new ArrayList<Integer>();
for (int i=0;i<size;i++){
TreeNode node=queue.get(0);
queue.remove(0);
if(node.left!=null)queue.add(node.left);
if(node.right!=null)queue.add(node.right);
if(layer%2==1){//奇数层
list.add(node.val);//在尾部插入,正常的顺序
}
if(layer%2==0){//偶数层
list.add(0,node.val);//【在头部插入,逆序翻转】 【简洁但是效率比Stack低】
}
}
result.add(list);//填入
layer++;
}
return result;
}
}
二叉搜索树与双向链表 ***
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
方法1:非递归(用栈)
import java.util.Stack;//
public class Solution {//二叉树本来就有left、right两个指针位置,初始状态有的为空有的为null
public TreeNode Convert(TreeNode root) {//pRootOfTree->root 可以把原来的名字改短
if(root==null)return null;
TreeNode p=root;
TreeNode pre=null;//双指针,p前面一个指针
TreeNode result=null;//返回中序第一个节点
Stack<TreeNode> stack=new Stack<TreeNode>();//中序【非递归】,使用【辅助栈】
while(p!=null||!stack.isEmpty()){//两个不都为空 //作为STL的stack进行判断时,一般不会直接判断:stack!=null,而是用isEmpty()
//-----------------------------------------------------向左0~n次
while(p!=null){//【记住:这里的判断语句是 p!=null】(经验),而不是p.left!=null,不然后面向右转后 会出问题
stack.push(p);//push进栈,保存节点 //注意与下面一句的先后顺序
p=p.left;
}
//-----------------------------------------------------中序的处理
p=stack.pop();//【pop: remove &return】 //peek:return
if(pre==null){//首次
result=p;//需要设置头部result
}
else{//中间和末尾情况下:操作一样,所以不用拆分 //画图分析:【先考虑中间部分,再考虑头尾】
pre.right=p;
p.left=pre;
}
pre=p;//所有情况都需要//此时p能保证不为null
//------------------------------------------------------向右1次
p=p.right;//可能为null,此时到下一轮直接miss向左的过程
}
return result;
}
}
方法2:中序递归
//一次循环中return的情况比较多,头大~
//后面建议写一下 (直接参考题解默写):
链接:https://www.nowcoder.com/questionTerminal/947f6eb80d944a84850b0538bf0ec3a5?f=discussion
方法3:先全按顺序存数组,然后再设置指针
//中序遍历二叉树,然后用一个ArrayList类保存遍历的结果,这样在ArratList中节点就按顺序保存了,然后再来修改指针
//参考 https://blog.nowcoder.net/n/1b50488ba84a4adeaa55282936ebe2cb?f=comment
序列化二叉树 ****
请实现两个函数,分别用来序列化和反序列化二叉树:
//import java.lang.String;//可以不写
public class Solution {//题目要求两个功能-两个函数,导致递归不能拆分出来一个辅助函数:提高效率、改进结构 //StringBuffer和StringBuilder: Buffer加锁同步、线程安全;Builder速度快、线程不安全
//最外层的sb,StringBuilder来整合一个个String //sb不能用全局变量!!!
String Serialize(TreeNode root) {//先序
StringBuilder s =new StringBuilder();//递归时,每层调用都增长s,最根部的调用只一个字符
if(root==null){
s.append("#,");
return s.toString();
}
s.append(Integer.toString(root.val)+",");//转换Integer为String
s.append(Serialize(root.left));
s.append(Serialize(root.right));
return s.toString();
} int i=-;//全局变量
int flag=;//避免重复str.split
String[] S;
TreeNode Deserialize(String str) {
i++;//必须在前面,不能在最后:因为这不是循环,这是递归!放在后面就轮不上了!!
if(flag==){
S =str.split(",");//将String用','分割split开,存在String数组里
flag=;
}
//if(i>=S.length)return null;
TreeNode node=null;//创建node,不要用new TreeNode(),因为构造的时候括号里面的值不能少
if(!S[i].equals("#")){//不能直接用:S[i]!='#'进行比较!!!???
node=new TreeNode(Integer.valueOf(S[i]));//Integer.valueOf
node.left=Deserialize(str);//名存实亡的str传值;
node.right=Deserialize(str);
}
return node;
}
}
构建乘积数组 **
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * ... * A[n-1],B[n-1] = A[0] * A[1] * ... * A[n-2];)
public class Solution {
public int[] multiply(int[] A) {
int N=A.length;//A中的元素个数记为N
int[] B=new int[N];//B的长度与A相等
B[]=;//【正三角】
for(int i=;i<=N-;i++){
B[i]=B[i-]*A[i-];
}
int Tri=;//【倒三角】
for(int i=N-;i>=;i--){
Tri*=A[i+];
B[i]*=Tri;
}
return B;
}
}//这个题目边界条件贼多,画个实例图,就不容易错了。。lucky一次过
数据流中的中位数
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
总体思路:
设立一个大根堆,一个小根堆。大根堆保存较小的半边,小根堆大的半边;
堆的特性,插入仅需要O(logn)的时间;;;而数组需要O(n)的时间(插入排序,而我们只需要中位数,不需要整体都有序,所以浪费了性能)
当需要取中位数时,从堆顶即可获取,时间复杂度O(1)
import java.util.PriorityQueue;//为什么不直接用堆,而是使用PriorityQueue优先级队列???
public class Solution {
PriorityQueue<Integer> rightHeap=new PriorityQueue<Integer>();//右堆是【小根堆】小根堆升序-》不用调整;右堆所有元素min大于左堆的max
PriorityQueue<Integer> leftHeap=new PriorityQueue<Integer>((x,y)->y-x);//【大根堆】:降序,所以要用【lambda表达式】调整顺序
boolean isOdd=true;//可以设置boolean,也可以设置一个Int类型的i++
public void Insert(Integer num) {//输入的数字都是Int类型的
if(isOdd){
rightHeap.add(num);//插入右堆,复杂度O(logn)
leftHeap.add(rightHeap.poll());//右堆的最小值,给到左堆
}
else{
leftHeap.add(num);
rightHeap.add(leftHeap.poll());//这句和上面那句对应,共同保持:【右堆min大于左堆max】
}
isOdd=!isOdd;//反转
} public Double GetMedian() {//输出的类型是Double
if(!isOdd){//在上面一个函数最后,转换了,所以前面有一个:! 别忘了!!!
return leftHeap.peek()/1.0;//【强制转换】成Double
}
else{
return (leftHeap.peek()+rightHeap.peek())/2.0;//中位数可能为两个值的平均
}
}
}
//总结一下PriorityQueue: add()添加; poll()弹出顶端元素; peek()查看顶端元素;
//【poll】比较特别,相当于栈的pop
【无序数组中,找中位数】
在无序的数组中,可以在O(n)的时间找到中位数。全排序是O(nlogn)时间。
方法:每一轮都使用类似"快排"的方法对无序数组进行分割,但是与快排不同的是:每次分割完之后,只需要对一边进行继续寻找(快排是两边都要)。
所以时间复杂度:O(n)+O(n/2)+O(n/4)+.......+O(1)=O(2n)=O(n)
快排的复杂度:O(n)+O(2*n/2)+O(4*n/4)+......+O(n*1)=logn *O(n)=O(nlogn)
【寻找一个 top-k】
过程同理,每轮划分之后,选择一个partition的时候,
选择top-k所在的区间就行了。
总的时间复杂度也是O(n)
剪绳子 ***
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
方法一:数学函数求导法:针对性规律(特定本题)
result= f(m) = (n/m) ^m,设n为定值,m为自变量,f(m)为乘积结果。
max{ f(m) }= max{ ln f(m) },取对数。
求 ln f(m)= m*( ln n- ln m )最值点的m值,求导并令f(m)'=0,得到m=n/e.
e=2.718,然后因为取整数,所以是拆成一堆2、3;
具体看下:4>>>2*2;5>>>2*3;6>>>3*3 符合分析的结果。
public class Solution { //数学函数求导法,得到:m=n/e (小数的情况下),也就是说尽量拆成一大堆:2、3
public int cutRope(int target) {
if(target==2)return 1;//因为题目要求最少拆成2份(m>1)
if(target==3)return 2;// int n=target/3;
int mod=target%3;
int result=1;
for(int i=0;i<n;i++)result*=3;//代替乘方运算
//3种情况:
if(mod==0);
if(mod==1)result=result*4/3;//拆一个3,将3+1=》2+2
if(mod==2)result=result*2;
return result;
}
}//时间复杂度O(N),空间复杂度O(1)
方法二:动态规划(考虑所有情况。效率虽然低,但具有普适性)
public class Solution {
public int cutRope(int target) {
if(target==2)return 1;//特殊情况:段数>=2,强制分段
if(target==3)return 2;
int[] dp=new int[target+1];
dp[1]=1;
dp[2]=2;
dp[3]=3;//在target>=4的前提下,dp数组的1~3对应的值。
for(int i=4;i<=target;i++){
int max=0;
for(int j=1;j<=i/2;j++){//注意j范围:1~i/2
max=Math.max(max,dp[j]*dp[i-j]);//动态规划化的重点就是找到【最优子结构的递推公式】
}//Math.max()的使用
dp[i]=max;//存储这个i的最大值(从循环中j个情况中选的)
}
return dp[target];
}
}//时间复杂度O(N^2);空间复杂度O(N)
滑动窗口的最大值
import java.util.*;
public class Solution {
public ArrayList<Integer> maxInWindows(int [] num, int size)
{
ArrayList<Integer> result=new ArrayList<Integer>();
if(num==null||size<=0||num.length<size)return result;//特殊情况,注意num.length<size的情况
//朴素的数组,用length,不用size //不用size==null判断,Int类型不能与null比较
ArrayDeque<Integer> qmax=new ArrayDeque<Integer>();//用于存序号//ArrayDeque类型,双端队列,两边都可以增/删; for(int i=0;i<num.length;i++){
while(!qmax.isEmpty() && num[qmax.peekLast()]<num[i]){//非空一直进行 //这里要判断一下不然右边直接用qmax可能报错
//这里执行的条件和退出的条件是一样的,所以两个条件全部放在while里面;如果将右边的条件放在循环里面的话,执行的条件没问题,但是退出的条件就不对了,造成死循环
qmax.pollLast();//去除num[i]左边相邻的一排较小值,这样能保证pollFirst后,最左边是全局最大值【全文关键点】
}
qmax.addLast(i);//??add在last
if(qmax.peekFirst()==i-size){
qmax.pollFirst();//左边强制删除,若删除,一定是当前最大值。不用担心,剩下最左边的一定是次大值,直接继位最大值。【全文关键点】
}
if(i>=size-1){
result.add(num[qmax.peekFirst()]);//最左边一定是当前的最大值,进入结果数组
}
}
return result;
}
}
//总的时间复杂度为O(n); 最粗犷的方法是O(n*size)
//单轮最多O(size)因为有while循环,但是整体上看,最坏也是平均每轮O(1);因为原材料不够每轮狂删的;
矩阵中的路径
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。例如:
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
回溯法
public class Solution {
public boolean hasPath(char[] matrix, int rows, int cols, char[] str)
{
//if(matrix==null||rows<=0||cols<=0||str==null)return false;//可能不需要检验
boolean[] flag=new boolean[matrix.length];//flag数组,初始化为false:表示没有经过的路径
//【要点】flag数组:这里的需求是每一个递归分支里面的状态都不一样,所以用递归函数传值;所以不能用全局变量。//不用麻烦每个循环都初始化,是因为每次试探后,都会复原flag数组
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){//每行每列的全部格子作为起点,开始尝试;
if(Try(matrix,rows,cols,str,i,j,0,flag)==true)return true;//如果找到一个,则完成任务+停止尝试,立即返回true
}
}
return false;//全部失败,返回false //单个尝试的失败不会有任何返回
}
private boolean Try(char[] matrix, int rows, int cols, char[] str, int i, int j, int depth, boolean[] flag){
int index=i*cols+j;//(i,j)对应线性数组的位置index if(i<0||j<0||i>=rows||j>=cols||flag[index]==true||matrix[index]!=str[depth]) return false;//本节点不行就直接剪枝,下面递归结构类似完全4叉树的递归:
if(depth+1==str.length)return true;//匹配完整长度,则主动停止 //末端叶节点:【直接返回true】,全文仅此一处,是true的源头 flag[index]=true;//【尝试改flag】(与下文还原flag对应)
if(Try(matrix,rows,cols,str,i,j+1,depth+1,flag)||
Try(matrix,rows,cols,str,i,j-1,depth+1,flag)||
Try(matrix,rows,cols,str,i+1,j,depth+1,flag)||
Try(matrix,rows,cols,str,i-1,j,depth+1,flag))//上下左右一共4个方向都试一下,“或”的关系
return true;//depth+1层,【递归返回】中,有1个true就完成
flag[index]=false;//【还原flag】(出节点,此路不通,还原状态) return false;//此路不通 //不在上面4个并列Try的地方直接用return,是因为返回代表着此分支的终止:true成功了可以返回,false时候不能直接return而是最后return,因为需要调整flag状态【要点】
}
}//复杂度:O(rows*cols*4^str.length),最粗劣的方法,计算量巨大。
//如果有类似线性匹配的KMP模式串的优化,会快一些;或者用空间换时间;
机器人的运动范围
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
回溯法(和上一题类似)
public class Solution {
int result=0;//全局变量
public int movingCount(int threshold, int rows, int cols)
{
//if(threshold<0||rows<0||cols<0)return 0;
boolean[] flag=new boolean[rows*cols];//标记方格有没有来过,这里的flag不可逆
int i=0;int j=0;//坐标(i,j) Mov(threshold,rows,cols,0,0,flag);//从(0,0)开始探索。。 return result;
}
private void Mov(int threshold, int rows, int cols, int i,int j,boolean[] flag){
int index=i*cols+j;
if(i<0||j<0||i>=rows||j>=cols||flag[index]==true)return;//此分支不通,不用往下了 int sum=0;int I=i;int J=j;
while(I!=0){sum+=I%10;I/=10;}
while(J!=0){sum+=J%10;J/=10;}
if(sum<=threshold){
result++;
flag[index]=true;//设置flag为true,表示已经占用位置,后面不能再走这个格子了。 Mov(threshold,rows,cols,i,j+1,flag);
Mov(threshold,rows,cols,i,j-1,flag);
Mov(threshold,rows,cols,i+1,j,flag);
Mov(threshold,rows,cols,i-1,j,flag);//上下左右试探:在if语句{}里面,因为只有满足sum<=threshold才需要继续探索
}
}
}//从(0,0)开始,每次只能试探相邻的方格;所以使用“尝试”的方法;//有些方格就算符合threshold也不行,因为“不相邻”
//感觉改进一下就是迷宫找路
//太强了,在上一题的基础上,【自主完成,几乎一遍过!!!】
ps:写一些经验
1.不同模块之间适当空行,使结构清晰
2.复杂问题,可以先写几个大思路然后再去填充,比如【先构思】:
private void Mov(int threshold, int rows, int cols, int i,int j,boolean[] flag){ if()return;
//【空行】:方便后面填充细节
while(){}
while(){}
if(sum<=threshold){
result++;
flag;
//【空行】:使模块间结构清晰
Mov();
M
M
M
} }
之后【填充细节】:
private void Mov(int threshold, int rows, int cols, int i,int j,boolean[] flag){
int index=i*cols+j;
if(i<0||j<0||i>=rows||j>=cols||flag[index]==true)return;//此分支不通,不用往下了 int sum=0;int I=i;int J=j;
while(I!=0){sum+=I%10;I/=10;}
while(J!=0){sum+=J%10;J/=10;}
if(sum<=threshold){
result++;
flag[index]=true;//设置flag为true,表示已经占用位置,后面不能再走这个格子了。 Mov(threshold,rows,cols,i,j+1,flag);
Mov(threshold,rows,cols,i,j-1,flag);
Mov(threshold,rows,cols,i+1,j,flag);
Mov(threshold,rows,cols,i-1,j,flag);//上下左右试探:在if语句{}里面,因为只有满足sum<=threshold才需要继续探索
}
}
注意对比上面两个代码~
终于完成了,哈哈哈!!!
剑指offer刷题笔记的更多相关文章
- 剑指offer刷题(Tree)
开篇 二刷剑指offer了,本来用Tyora记的笔记,发现字数到四万了就变得好卡o(╥﹏╥)o,刚好开始写博客,就转过来吧,记下来子自己看.不废话,开刷... JZ26. 树的子结构 输入两棵二叉树A ...
- 剑指offer刷题
1.面试题43. 1-n整数中1出现的次数 输入一个整数 n ,求1-n这n个整数的十进制表示中1出现的次数. 例如,输入12,1-12这些整数中包含1 的数字有1.10.11和12,1一共出现了5次 ...
- 牛客网剑指offer刷题总结
二维数组中的查找: 题目描述:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 两 ...
- LeetCode剑指Offer刷题总结(一)
LeetCode过程中值得反思的细节 以下题号均指LeetCode剑指offer题库中的题号 本文章将每周定期更新,当内容达到10题左右时将会开下一节. 二维数组越界问题04 public stati ...
- 剑指offer ------ 刷题总结
面试题3 -- 搜索二维矩阵 写出一个高效的算法来搜索 m × n矩阵中的值. 这个矩阵具有以下特性: 1. 每行中的整数从左到右是排序的. 2. 每行的第一个数大于上一行的最后一个整数. publi ...
- 剑指offer刷题记录
目录 二维数组中的查找 替换空格 从尾到头打印链表 反转链表 重建二叉树 用两个栈实现队列 旋转数组的最小数字 斐波拉切数列 跳台阶 变态跳台阶 矩形覆盖 二进制中1的个数 数值的整次方 链表中倒数第 ...
- 剑指offer刷题总结
★ 二维数组的查找 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否 ...
- 剑指offer刷题(算法类_2)
排序 035-数组中的逆序对(归并排序) 题目描述 题解 代码 复杂度 029-最小的K个数(堆排序) 题目描述 题解 代码 复杂度 029-最小的K个数(快速排序) 题目描述 题解 代码 复杂度 位 ...
- 剑指offer刷题(算法类_1)
斐波那契数列 007-斐波拉契数列 题目描述 题解 代码 复杂度 008-跳台阶 题目描述 题解 代码 复杂度 009-变态跳台阶 题目描述 题解 代码 复杂度 010-矩形覆盖 题目描述 题解 代码 ...
随机推荐
- Redis实战 | 持久化、主从复制特性和故障处理思路
前言 前面两篇我们了解了Redis的安装.Redis最常用的5种数据类型.本篇总结下Redis的持久化.主从复制特性,以及Redis服务挂了之后的一些处理思路. 前期回顾传送门: Linux下安装Re ...
- 大量SQL的解决方案——sdmap
大量SQL的解决方案--sdmap 最近看到群里面经常讨论大型应用中SQL的管理办法,有人说用EF/EF Core,但很多人不信任它生成SQL的语句:有人说用Dapper,但将SQL写到代码中有些人觉 ...
- FastJSON将Java对象转为json,日期显示时间戳未格式化解决办法
JSON版本:FastJson Java 对象转换为 JSON 格式 定义以下 Person JavaBean: public class Person { @JSONField(name = &qu ...
- linux入门系列5--新手必会的linux命令
上一篇文章"linux入门系列4--vi/vim编辑器"我们讨论了在linux下如何快速高效对文本文件进行编辑和管理,本文将进一步学习必须掌握的linux命令,掌握这些命令才能让计 ...
- Java解析文件内容
本文主要实现对.chk文件的解析,将其内容读出来,存入到一个Map中,文件内容实例为: A0500220140828.CHK A05002 |34622511 |373532879 |3 识别分隔符| ...
- linux入门系列7--管道符、重定向、环境变量
前面文章我们学习了linux基础命令,如果将不同命令组合使用则可以成倍提高工作效率.本文将学习重定向.管道符.通配符.转义符.以及重要的环境变量相关知识,为后面的shell编程打下基础. 一.IO重定 ...
- load文件到hive,并保存
DataFrame usersDF = sqlContext.read().load("hdfs://spark1:9000/users.parquet"); usersDF.se ...
- 学习 lind api 十月 第5弹
继续 四弹里的 自定义的api response message 但是 那上面的 那些值得也是包含
- KALI美化-设置CONKY开机启动
简介 Conky 是一个应用于桌面环境的系统监视软件,可以在桌面上监控系统运行状态.网络状态等一系列参数 https://github.com/brndnmtthws/conky/ 详细配置文档:ht ...
- Centos 7搭建Nginx负载均衡,最简单。
1.安装Nginx 1.1.下载Nginx安装包 Nginx 官网(https://nginx.org) 本次选择的是nginx-1.6.3.tar.gz版本,安装环境是centos7. 然后把下载好 ...