面试题3:数组中重复的数字
import java.util.Arrays;
/**
* P39 面试题3:数组中重复的数字
* 在一个长度为n的数组里所有数字都在0~n-1的范围内 数组中某些数字是重复的 但不知道有几个数字重复了
* 也不知道每个数字重复了几次 请找出数组中任意一个重复的数字
* 例如 如果输入长度为7的数组{2,3,1,0,2,5,3}
* 那么对应的输出是重复的数字2或者3
* @author tzy
*
*/
public class T3
{
public static void main(String[] args)
{
int[] duplication=new int[1]; //保存答案的数组 结果保存在[0]
int[] inputarr={11,22,1,0,2,5,5};
}
/*
* 解法1:排序数组后找到重复值
* 排序算法的时间复杂度为O(nlogn)
*/
public static boolean solution1(int[] numbers,int length,int[] duplication)
{
if(numbers==null||length<=0)
{
return false;
}
Arrays.parallelSort(numbers);
for(int i=0;i<numbers.length-1;i++)
{
if(numbers[i]==numbers[i+1])
{
duplication[0]=numbers[i];
return true;
}
}
return false;
}
/*
* 解法2:使用哈希表
* 时间复杂度O(N) 空间复杂度O(N)
*/
public static boolean solution2(int[] numbers,int length,int[] duplication)
{
int temp[]=new int[length+1];
if(numbers==null||length<=0)
{
return false;
}
for(int i=0;i<length;i++)
{
temp[numbers[i]]++;
}
for(int j=0;j<length;j++)
{
if(temp[j]>1)
{
duplication[0]=j;
return true;
}
}
return false;
}
/*
* 解法3 推荐解法
* 时间复杂度O(N) 空间复杂度O(1)
* 通过将下标为i的数字(称为m)调整到属于它的下标的位置
*/
public static boolean solution3(int[] numbers,int length,int[] duplication)
{
if(numbers==null||length<=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;
}
int temp=numbers[i];
numbers[i]=numbers[temp];
numbers[temp]=temp;
}
}
return false;
}
}
面试题4:二维数组中的查找
/*
* P44面试题4:二维数组中的查找
* 题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序
* 请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数
*/
public class T4
{
/*
* 思路:对于二维数组中的每一个数字,他小于他右边的数字,小于他下方的数字
* 所以从右上角的数字(称为m)开始查找
* 若m>target 则剔除m所在的列 因为m是这一列最小的元素 这一列所有的元素将大于target
* 若m<target 则剔除m所在的行 因为m是这一行最小的元素 这一行所有的元素将小于target
*/
public boolean Find(int target, int [][] array)
{
if(array.length==0||array[0].length==0||array==null)
{
return false;
}
//记录行数与列数
int rows=array.length;
int cols=array[0].length;
//从右上角开始搜索
int rownow=0;
int colnow=cols-1;
while(rownow<=rows-1&&colnow>=0)//搜索到最左下角的数字是边界条件
{
if(array[rownow][colnow]==target)
{
return true;
}
else if(array[rownow][colnow]>target)
{
colnow--;
}
else
{
rownow++;
}
}
return false;
}
}
面试题5:替换空格
/*
* P51面试题5:替换空格
* 题目:请实现一个函数,把字符串中的每个空格替换成"%20"
* 例如输入"We are happy."
* 则输出"We%20are%20happy."
*/
public class T5
{
/*
* 解题思路:
* 先遍历一遍数组 找到一共有多少个空格 在尾部添加2*空格个数的任意字符
* 令P1指向原末尾,P2指向延长后的末尾,两指针均从后往前遍历(P1开始)
* 每当P1遍历到一个字符时 将它复制到P2 两指针同时前移一位
* 当P1遍历到空格时 P1向前移一位 P2向前移动三位并填充02%
* 当P1与P2重合时 则结束
* 从后向前遍历是为了在改变P2所指向的内容时 不会影响到P1遍历原来的字符串内容
*/
public String replaceSpace(StringBuffer str)
{
int P1=str.length()-1;
for(int i=0;i<=P1;i++)
{
if(str.charAt(i)==' ')
{
str.append(" ");
}
}
int P2=str.length()-1;
while(P1!=P2)
{
if(str.charAt(P1)!=' ')
{
str.setCharAt(P2, str.charAt(P1));
P1--;
P2--;
}
else
{
P1--;
str.setCharAt(P2--, '0');
str.setCharAt(P2--, '2');
str.setCharAt(P2--, '%');
}
}
return str.toString();
}
}
面试题6:从尾到头打印链表
import java.util.ArrayList;
import java.util.Collections;
import java.util.Stack;
/*
* P58面试题6:从尾到头打印链表
* 题目:输入一个链表的头结点,从尾到头反过来打印出每个节点的值
*/
public class T6
{
/*
* 解法一:使用栈
* 由于栈的“后进先出特性” 对链表进行一次遍历后依次弹出
* 得到的顺序即为逆序
*/
public ArrayList<Integer> printListFromTailToHead1(ListNode listNode)
{
Stack<Integer> stack=new Stack<Integer>();
while(listNode!=null)
{
stack.add(listNode.val);
listNode=listNode.next;
}
ArrayList<Integer> res=new ArrayList<Integer>();
while(!stack.isEmpty())
{
res.add(stack.pop());
}
return res;
}
/*
* 解法二:使用递归
* 递归的本质就是一个栈结构 所以可以自然地想到用递归来实现
* 具体实现为:每访问到一个节点的时候,先递归输出它后面的节点,再输出该节点自身
* 但该方法若是输入的链表长度非常长时
* 会导致函数调用的层级过深
* 从而导致函数调用栈溢出(java.lang.StackOverflowError)
*/
public ArrayList<Integer> printListFromTailToHead2(ListNode listNode)
{
ArrayList<Integer> res=new ArrayList<Integer>();
if(listNode!=null)
{
res.addAll(printListFromTailToHead2(listNode.next));
res.add(listNode.val);
}
return res;
}
/*
* 解法三:使用头插法
* 由于头插法的性质是每次插入在头部
* 所以使用头插法重新构建链表后即为逆序
* 注意点:
* 头结点是在头插法中使用的一个额外节点,这个节点不存储值
* 第一个节点就是链表的第一个真正存储值的节点
*/
public ArrayList<Integer> printListFromTailToHead3(ListNode listNode)
{
ListNode head=new ListNode(-1);//头结点
while(listNode!=null)
{
ListNode temp=new ListNode(listNode.val);
temp.next=head.next;
head.next=temp;
listNode=listNode.next;
}
ArrayList<Integer> res=new ArrayList<Integer>();
head=head.next;
while(head!=null)
{
res.add(head.val);
head=head.next;
}
return res;
}
/*
* 解法四:使用Collection.reverse()
*/
public ArrayList<Integer> printListFromTailToHead4(ListNode listNode)
{
ArrayList<Integer> res=new ArrayList<Integer>();
while(listNode!=null)
{
res.add(listNode.val);
listNode=listNode.next;
}
Collections.reverse(res);
return res;
}
}
面试题7:重建二叉树
import java.util.HashMap;
import java.util.Map;
/*
* P62面试题7:重建二叉树
* 题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树
*/
public class T7
{
private Map<Integer,Integer> inOrders=new HashMap<Integer,Integer>();
public TreeNode reConstructBinaryTree(int[] pre, int[] in)
{
for(int i=0;i<in.length;i++)
{
inOrders.put(in[i], i);//缓存中序遍历数组每个值对应的索引
}
return core(pre,0,pre.length-1,0);
/*
* 前序遍历序列1 2 4 7 3 5 6 8
* 中序遍历序列4 7 2 1 5 3 8 6举例
*/
}
private TreeNode core(int pre[],int preL,int preR,int inL)
{
if(preL>preR)
{
return null;
}
TreeNode root=new TreeNode(pre[preL]);//根节点为前序遍历的第一个值
int inIndex=inOrders.get(root.val);//在中序遍历中找到上一步的根节点的下标
int leftTreeSize=inIndex-inL;
root.left=core(pre,preL+1,preL+leftTreeSize,inL);//构建左子树
/*
* 第一次:
* 在中序遍历序列中 找到前序遍历的第一个元素“1”的下标为3
* 那么根节点为1的这棵树的左子树的总元素数量为3-0=3个
* 第二次:
* 对于这棵总元素数量为3的左子树 由于在前序遍历中左子树的遍历先于右子树
* 所以preL+1 也就是前序遍历的下一个元素 即为这棵树的根节点
* preR是这棵树元素的终点 即是preL+这棵树的规模
* inL在构建右子树中解释
*/
root.right=core(pre,preL+leftTreeSize+1,preR,inL+leftTreeSize+1);//构建右子树
/*
* 第一次:
* 找到“1”的下标3之后
* 这棵右子树的根节点(也就是在前序遍历序列的起点)为初始结点位置+左子树规模+1
* 即对于5 3 8 6这棵数来说 要找它的根节点
* 需要在前序遍历序列中偏移0+3+1个位置 故在前序遍历中的下标为4
* preR不变 因为在前序遍历序列中 右子树永远在末尾
* inL加上了左子树的规模+1是因为
* 在3 5 6 8这棵树中 去中序遍历中找3的下标 发现为5
* 这棵根节点为3的树 他的左子树的规模在计算时需要知道这棵树的起点
* 而这棵树的起点 即是去掉和他同一深度的左兄弟树的规模 再减去他的父节点占用的1个位置
* 所以inL是用于在构建右子树时计算规模的偏移记录量
* 而在构建左子树时这个偏移量不需要变化是因为中序遍历是以左子树开头的 所以根节点的下标位置的数字就是左子树的规模
* 他的左边没有其他元素占用位置
*/
return root;
}
}
面试题8:二叉树的下一个节点
/*
* P65面试题8:二叉树的下一个节点
* 题目:给定一棵二叉树和其中的一个节点,如何找出中序遍历序列的下一个节点?
* 树中的节点除了有两个分别指向左、右子节点的指针,还有一个指向父节点的指针
*/
public class T8
{
/*
* 情况1:某节点有右子树,下一节点为右子树中的最左子节点
* 情况2:某节点无右子树,且为他父节点的左子节点 下一节点为父节点
* 情况3:某节点无右子树,且为他父节点的右子节点,则向上递归寻找它的父节点直到根节点或某个父节点是它自身父节点的左子节点(即情况2),这个父节点的父节点则为目标点
*/
public TreeLinkNode GetNext(TreeLinkNode pNode)
{
if(pNode.right!=null)//情况1
{
TreeLinkNode res=pNode.right;
while(res.left!=null)
{
res=res.left;
}
return res;
}
//情况2、3
else
{
TreeLinkNode res=pNode;
while(res.next!=null)
{
if(res.next.left==res)
{
return res.next;
}
res=res.next;
}
}
return null;
}
}
面试题9:用两个栈实现队列
import java.util.Stack;
/*
* P68面试题9:用两个栈实现队列
* 题目:用两个栈实现一个队列 实现他的push和pop功能
*/
public class T9
{
/*
* 思路:入栈时所有元素存入stack1
* 出栈时为了颠倒顺序,将stack1中的元素出栈并压入stack2中
* 当stack2不为空时,直接在stack2中出栈
* 直到stack2为空,再次将stack1中所有元素压入stack2
*/
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.isEmpty())
{
while(!stack1.isEmpty())
{
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
}
面试题10.1斐波那契数列
/*
* P74题目一:斐波那契数列
* 写一个函数 输入n 求斐波那契数列的第n项
*/
public class T10_1
{
/*
* 方法一 递归 会重复计算一些子问题
* 例如f(10)需要计算f(9)和f(8) 计算f(9)需要计算f(8)和f(7)
* f(8)被重复计算
*/
public int Fibonacci1(int n)
{
if(n<=1)
{
return n;
}
return Fibonacci1(n-1)+Fibonacci1(n-2);
}
/*
* 方法二 动态规划的做法
* 把子问题的解缓存起来 这样避免了重复求解子问题
*/
public int Fibonacci2(int n)
{
if(n<=1)
{
return n;
}
int res[]=new int[n+1];
//res[0]=0; 默认初始化为0
res[1]=1;
for(int i=2;i<=n;i++)
{
res[i]=res[i-1]+res[i-2];
}
return res[n];
}
/*
* 方法三 在上一方法的基础上改进
* 由于第n项只和第n-1项与n-2项有关
* 因此只需要存储前两项的值
* 空间复杂度从O(N)降至O(1)
* 时间复杂度为O(N)
*/
public int Fibonacci3(int n)
{
if(n<=1)
{
return n;
}
int pre1=1;//前一项为1
int pre2=0;//前两项为0
int fibN=0;
for(int i=2;i<=n;i++)
{
fibN=pre1+pre2;
pre2=pre1;//前进一位
pre1=fibN;
}
return fibN;
}
}
面试题10.2青蛙跳台阶问题
/*
* P77题目二:青蛙跳台阶问题
* 一只青蛙一次可以跳上1级台阶 也可以跳上2级台阶
* 求该青蛙跳上一个n级台阶总共有多少种跳法
*/
public class T10_2
{
/*
* 思路:将到达第n阶台阶共有几种跳法看成是n的函数 即f(n)
* 因为一次可以跳1阶或者2阶
* 所以第n阶的跳法可以看成是n-1阶跳法加上n-2阶跳法的和
* 比如到达第100层的时候,那么上一步要么是在第99层 要么是在第98层
* 所以到达100层的方法是他们俩的和
* 不难看出这实际上是一个斐波那契数列
* 第一项为第一阶的方法 只有1种
* 第二项为第二阶的方法 有两种 即两次跳1级和一次跳两级
*/
public int JumpFloor(int target)
{
if(target<=2)
{
return target;
}
int pre1=2;
int pre2=1;
int now=0;
for(int i=2;i<target;i++)
{
now=pre1+pre2;
pre2=pre1;
pre1=now;
}
return now;
}
}
面试题10.3矩形覆盖
/*
* P79矩形覆盖
* 我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。
* 请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,总共有多少种方法?
*/
public class T10_3
{
/*
* 当把2*n的覆盖方法记录为f(n)时 如f(8)
* 从最左边开始覆盖 当用第一个2*1的小矩形去覆盖时
* 只有两种选择:一是竖着放 那么大矩形就成为了一个2*7的矩形 即f(7)
* 二是横着放 那么就必须再横着放一个2*1的小矩形 那么大矩形就成为了一个2*6的矩形 即f(6)
* 则f(8)=f(7)+f(6)
* 可以看出 这仍然是一个斐波那契数列
*/
public int RectCover(int target)
{
if(target<=2)
{
return target;
}
int pre1=2;
int pre2=1;
int res=0;
for(int i=2;i<target;i++)
{
res=pre1+pre2;
pre2=pre1;
pre1=res;
}
return res;
}
}
面试题10.4变态跳台阶
import java.util.Arrays;
/*
* P78 青蛙跳台阶扩展
* 一只青蛙一次可以跳上 1 级台阶,也可以跳上 2 级... 它也可以跳上 n 级。求该青蛙跳上一个 n 级的台阶总共有多少种跳法
*/
public class T10_4
{
/*
* 思路一:动态规划
* 以f(5)即跳上第五阶的总跳法数量为例
* 终点为第五阶时 上一步可以为第4 3 2 1 0阶
* 即f(5)=f(4)+......+f(0)
* 对于f(4) 又有f(4)=f(3)+f(2)+f(1)+f(0)
*/
public int JumpFloorII(int target)
{
int dp[]=new int[target+1];
Arrays.fill(dp, 1);//每一级都至少有1种跳法
for(int i=1;i<target;i++)
{
for(int j=0;j<i;j++)
{
dp[i]=dp[i]+dp[j];
}
}
return dp[target-1];
}
/*
* 思路二:数学推导
* 由上面的思路 要跳上第n级台阶
* 有:f(n) = f(n-1) + f(n-2) + ... + f(0)
* 要跳上第n-1级台阶
* 有:f(n-1) = f(n-2) + f(n-3) + ... + f(0)
* 两式相减 得:
* f(n)-f(n-1)=f(n-1)
* 即:
* f(n)=2*f(n-1)
* 即
* f(n)=2^(n-1)
*/
public int JumpFloor(int target)
{
return (int)Math.pow(2, target-1);
}
}
面试题11:旋转数组中的最小数字
/*
* P82面试题11:旋转数组的最小数字
* 题目:把一个数组最开始的若干个元素搬到数组的末尾 我们称之为数组的旋转
* 输入一个递增排序的数组的一个旋转 输出旋转数组的最小元素
* 例如 数组 {3, 4, 5, 1, 2} 为 {1, 2, 3, 4, 5} 的一个旋转,该数组的最小值为 1
*/
public class T11
{
/*
* 思路:在不允许重复的情况下 可以用二分查找的思路解决
* 根据旋转数组的思想,旋转后的数组是一个由两个递增排序的数组所组成
* 最小元素为于两个子数组的交界处
* 若某个元素大于最左侧的元素 那么该元素处于前面的递增子数组
* 否则处于后边的递增子数组 以此调整左右指针指向的元素
*/
public int minNumberInRotateArray(int[] array)
{
if(array.length==0)
{
return 0;
}
int l=0;
int r=array.length-1;
/*
* 表述方式一
*/
while(l<r)
{
int m=(l+r)/2;//折半后的元素位置
if(array[m]>array[r])//如果中间元素大于末尾元素 说明中间元素处于第一个递增序列 说明最小值在中间元素的右侧
{
l=m+1;
}
else//如果中间元素小于末尾元素 说明中间元素处于第二个递增序列 说明最小值在中间元素的左侧
{
r=m;
}
}
//return array[l];
/*
* 另一种表述方式
*/
int mid=l;
while(array[l]>=array[r])
{
if(r-l==1)
{
mid=r;
break;
}
mid=(l+r)/2;
if(array[mid]>=array[l])
{
l=mid;
}
else if(array[mid]<=array[r])
{
r=mid;
}
}
return array[mid];
}
/*
* 而如果数组元素允许重复的话 可能出现特殊情况为l、r、m三下标对应的元素相等
* 此时无法确定解在哪个区间 需要转换为顺序查找 在while中加入此判断和对应操作即可
*/
}
面试题12:矩阵中的路径
/*
* P89面试题12:矩阵中的路径
* 题目:请设计一个函数 用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径
* 路径可以从矩阵中的任意一格开始 每一步可以向上下左右任意移动一格
* 不能重复进入某个格子
* 例如
* a b c e
* s f c s
* a d e e
* 这样的3 X 4 矩阵中包含一条字符串"bcced"的路径
* 但是矩阵中不包含"abcb"路径
* 因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子
*/
public class T12
{
/*
* 思路:首先遍历这个矩阵 找到与目标字符串str的首字符相同的矩阵元素ch 将ch作为起点
* 遍历ch的上下左右四个字符 如果有何str中的下一个字符相同的
* 就把那个字符划入路径的下一步 若走到某一步时 这一步的上下左右均没有str的下一个字符
* 那么就需要回退到当前路径的上一步去重新选择 因为矩阵中可以出现重复元素
* 所以说每一步可能不止有一个选择
* 同时为了避免重复访问 需要设置visit矩阵记录每个元素的访问情况
* 在回退时 也要将visit矩阵中这个元素的状态回退为未访问
* 一直重复这个过程 直到路径中所有字符都在矩阵中找到相应的位置
*/
private int rows;
private int cols;//记录矩阵的规格
public boolean hasPath(char[] array, int rows, int cols, char[] str)
{
if(array==null||rows<1||cols<1||str==null)
{
return false;
}
this.rows=rows;
this.cols=cols;
boolean visit[][]=new boolean[rows][cols];
/*
* 原书中给出的矩阵实际上为一维矩阵 通过将row与col相乘再加上col来寻找当前元素
* 将其重建为二维矩阵 可以更为直观的表示寻找过程
*/
char matrix[][]=new char[rows][cols];
int rebuildindex=0;
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
matrix[i][j]=array[rebuildindex++];
}
}
//重建完成
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(hasPathCore(matrix,str,visit,0,i,j))//从每个位置开始尝试
{
return true;
}
}
}
return false;
}
private boolean hasPathCore(char[][] matrix,char[] str,boolean[][] visit,int pathLen,int rownow,int colnow)
{
//该if与下一个if位置不能颠倒 因为下一个if中会访问str[pathLen]
if(str.length==pathLen)
{
return true;//成功条件 即每个str都找到对应的位置
}
/*
* 若当前位置元素不为当前步骤的str元素 或当前位置元素已经被访问 或超出边界
* 超出边界的情况应最先判断 不然会数组越界
*/
if(rownow<0||rownow>=rows||colnow<0||colnow>=cols||matrix[rownow][colnow]!=str[pathLen]||visit[rownow][colnow])
{
return false;
}
visit[rownow][colnow]=true;
if(hasPathCore(matrix,str,visit,pathLen+1,rownow-1,colnow)||//上
hasPathCore(matrix,str,visit,pathLen+1,rownow+1,colnow)||//下
hasPathCore(matrix,str,visit,pathLen+1,rownow,colnow-1)||//左
hasPathCore(matrix,str,visit,pathLen+1,rownow,colnow+1))//右
{
return true;
}
visit[rownow][colnow]=false;//回退
return false;
}
}
面试题13:机器人的运动范围
/*
* P92面试题13:机器人的运动范围
* 题目:地上有一个m行n列的方格。一个机器人从坐标(0,0)的格子开始移动
* 他每次可以向上下左右移动一格 但不能进入行坐标和列坐标的数位之和大于k的格子
* 例如 当k为18时 机器人能够进入方格(35,37)因为3+5+3+7=18
* 但它不能进入方格(35,38)因为和为19
* 请问该机器人能够到达多少个格子?
*/
public class T13
{
private int sum[][];
int rows;
int cols;
int res;
public int movingCount(int threshold, int rows, int cols)
{
this.rows=rows;
this.cols=cols;
this.sum=new int[rows][cols];
//直接将每个方格的数位和算出并保留
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
sum[i][j]=getSum(i)+getSum(j);
}
}
boolean visit[][]=new boolean[rows][cols];
return res;
}
private int getSum(int num)
{
int res=0;
while(num!=0)
{
res+=num%10;
num=num/10;
}
return res;
}
}
面试题14:剪绳子
/*
* P96面试题14:剪绳子
* 给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积
*/
public class T14
{
/*
* 解法一:动态规划
* 令dp[n]为n对应的最大乘积
* 维护这个最优解数组
* 注释中的写法是另一种理解方式
* 当拆分出的一部分的长度为4时 这一段是否需要再进行拆分?
* 4=2*2 而小于4时 例如3 若拆分3 则得到1*2=2 结果将小于不拆分的结果
* 而当大于4时 例如5 拆分为2*3=6 >5 所以4为分界点
* 大于4的部分需要再进行拆分 小于4的部分则不需要拆分
* 此方法的时间复杂度为O(n^2)
*/
public static int integerBreak(int n)
{
int dp[]=new int[n+1];
dp[1]=1;
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
dp[i]=Math.max(dp[i], (i-j)*Math.max(dp[j], j));
}
}
return dp[n];
// int dp[]=new int[n+1];
// dp[1]=1;
// for(int i=2;i<=n;i++)
// {
// for(int j=1;j<i;j++)
// {
// if(j<=4)
// {
// dp[i]=Math.max(dp[i], (i-j)*j);
// }
// else
// {
// dp[i]=Math.max(dp[i],(i-j)*dp[j]);
// }
// }
// }
// return dp[n];
}
/*
* 解法二:贪心
* 首先由均值不等式中的算数平均数>=几何平均数可知
* 将输入的n拆分成多个相等的数时积最大
* 先假设最优拆法所拆出的每一个数都为x
* 那么一共会拆出n/x个数
* 设他们的积为f(x) 则f(x)=x^(n/x)
* 为了求f(x)的最大值 可以对其求导
* 过程省略
* 最终有 x=e时f(x)有极大值
* e=2.7 故优先拆成3 拆不成3则拆2
* 实际上当剩余长度小于等于4时
* 剩余部分不拆分即可(方法一中说明)
* 此方法的时间复杂度为O(lgn)
*/
public static int integerBreak2(int n)
{
if (n < 2)
return 0;
if (n == 2)
return 1;
if (n == 3)
return 2;
int timesOf3=0;
while(n>3)
{
if(n<=4)
break;
timesOf3++;
n-=3;
}
return (int)(Math.pow(3, timesOf3))*(n);
}
/*
* 记录一下另一种看到的思路
*
* class Solution {
public:
int integerBreak(int n) {
if(n==2){return 1;}
if(n==3){return 2;}
n-=2;
return pow(3,n/3)*(n%3+2);
}
};
*/
}
面试题15:二进制中1的个数
/*
* P100面试题15:二进制中1的个数
* 题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数
* 例如 把9表示为二进制是1001 有2位是1 因此该函数返回2
*/
public class T15
{
/*
* 思路一:首先把n和1做与运算 判断n的最低位是不是1
* 接着把1左移一位得到2 再和n做与运算 就能判断n的次低位是不是1
* 如此反复左移 每次都能判断n的其中一位是不是1
* 该算法的循环次数等于整数二进制的位数
* 故时间复杂度为O(n)
*/
public int NumberOf1(int n)
{
int count=0;
int flag=1;
while(flag!=0)
{
if((n&flag)!=0)
{
count++;
}
flag=flag<<1;
}
return count;
}
/*
* 思路二:把一个整数减去1 都是把最右边的1变成0
* 如果它的右边还有0,则所有的0都变成1
* 而它左边的所有位都保持不变 之后把这个n与(n-1)做位与运算
* 就会把该整数最右边的1变成0
* n中有几个1 该过程就会进行几次
* 复杂度为O(M) M为1的个数
*/
public int anotherNumberOf1(int n)
{
int count=0;
while(n!=0)
{
count++;
n=n&(n-1);
}
return count;
}
/*
* 思路三:使用Integer.bigCount()函数
*/
// public int NumberOf1(int n) {
// return Integer.bitCount(n);
// }
}
面试题16:数值的整数次方
/*
* P110面试题16:数值的整数次方
* 题目:给定一个double类型的浮点数base和int类型的整数exponent
* 求base的exponent次方
*/
public class T16
{
/*
* 通过循环使result=result*base进行expontent即可实现
* 同时需要考虑输入的指数小于1的情况
* 如果为负数 即要把结果取倒数
* 另一种效率更高的思路:例如
* 我们的目标是求出一个数字的32次方 如果我们已经知道了它的16次方
* 那么只要在16次方的基础上再平方一次就可以了
* 而16次方是8次方的平方......依次类推
* 可以得到这样一个计算公式 a为数值 n为次方
* 若n为偶数 则a^n=a^(n/2)*a^(n/2)
* 若n为奇数 则a^n=a^((n-1)/2)*a^((n-1)/2)*a
* 用递归的方式求解
* 该方法的时间复杂度为O(logN)
*/
public double Power(double base, int exponent)
{
if(exponent==0)
{
return 1;
}
if(exponent==1)
{
return base;
}
boolean flag=false;//记录次方数是否为负数
if(exponent<0)
{
flag=true;
exponent=-exponent;//若为负数 则取绝对值后正常计算后取倒数
}
double pow=Power(base*base,exponent/2);//偶数情况
if(exponent%2==1)
{
pow=pow*base;//奇数情况
}
return flag ? 1/pow:pow;
}
}
面试题17:打印从1到最大的n位数
/*
* P114面试题17:打印从1到最大的n位数
* 题目:输入数字n 按顺序打印出从1到最大的n位十进制数
* 比如输入3 则打印出1、2、3一直到最大的3位数999
*/
public class T17
{
public static void main(String[] args)
{
Print1ToMaxOfNDigits_1(3);
}
/*
* 由于输入的n可能非常大 所以不能用整形(长整形)来表示
* 可以用int数组/char数组来存储
* 解法一:在数组中模拟加法 在打印时忽略最开始的0
*/
public static void Print1ToMaxOfNDigits_1(int n)
{
int number[]=new int[n+1];
for(int i=n;i>0;i--)
{
for(int j=0;j<Math.pow(10, i)-Math.pow(10, i-1);j++)
{
number[n]++;
printNumber(number);
}
}
}
/*
* 解法二:全排列实现
* 全排列用递归很容易表达 数字的每一位都可能是0~9中的一个数
* 然后设置下一位 递归结束的条件是我们已经设置了数字的最后一位
*/
public static void Print1ToMaxOfNDigitis_2(int n)
{
int number[]=new int[n];
Print1ToMaxCore(number,0);
}
private static void Print1ToMaxCore(int num[],int index)
{
if(index==num.length)
{
printNumber(num);
return;
}
for(int i=0;i<10;i++)
{
num[index]=i;
Print1ToMaxCore(num,index+1);
}
}
/*
* 实现打印功能的函数
*/
public static void printNumber(int num[])
{
boolean flag=true;//出现第一个非0数字的标志
int index=0;
while(flag)
{
if(index>=num.length)
break;
if(num[index]==0)
index++;
else
flag=false;
}
for(int i=index;i<num.length;i++)
{
System.out.print(num[i]);
}
System.out.println();
}
}
面试题18.1删除链表中的节点
/*
* P119面试题18:删除链表的节点
* 题目一:在O(1)时间内删除链表节点
* 给定单向链表的头指针和一个节点指针 定义一个函数在O(1)时间内删除该节点
*/
public class T18_1
{
/*
* 思路:如果该节点不是尾节点 那么将待删节点的下一个节点的值赋给待删节点
* 也就等于将待删节点的值覆盖掉了
* 之后将该节点(待删节点)的下一节点指向他的下下个节点
* 如果该节点是尾节点 那么仍需遍历链表 找到他的前一个节点 然后让前一个节点指向null
* 综上 总的平均时间复杂度仍是O(1)
*/
public ListNode deleteNode(ListNode head, ListNode tobeDelete)
{
if(head==null||tobeDelete==null)
return null;
if(tobeDelete.next!=null)
{
ListNode temp=tobeDelete.next;
tobeDelete.val=temp.val;
tobeDelete.next=temp.next;
}
else
{
ListNode temp=head;
while(temp.next!=tobeDelete)
temp=temp.next;
temp.next=null;
}
return head;
}
}
面试题18.2删除链表中重复的节点
/*
* P122题目二:删除链表中重复的节点
* 在一个排序的链表中 如何删除重复的节点?
*/
public class T18_2
{
/*
* 需要考虑几种特殊情况
* 如 链表只有一个元素 重复的节点是头结点 某个节点重复多次的情况
* 用以下思路处理:
* 对某个节点 如果它的下个节点的值与它的值相等 那么说明该节点重复
* 对于重复的节点 如1-2-3-3-4 3出现2次 要将所有3删除 而不是留下1个
* 找到这个节点的下一个值不相等的节点 将这个节点的上一个节点的下一个节点指向这个找到的节点
* 那么这个已经处理过的节点是目前的“头结点” 将这个节点作为参数传入自身函数
* 递归地处理 直到到达最后一个节点
* 对于不重复的节点 将它的下一个节点作为参数
* 由于这两种情况的传入参数不同(一个为当前节点 一个为当前节点的下一个节点)
* 所以边界条件需要注意
*/
public ListNode deleteDuplication(ListNode pHead)
{
if(pHead==null||pHead.next==null)
{
return pHead;
}
ListNode next=pHead.next;
if(pHead.val==next.val)
{
while(next!=null&&pHead.val==next.val)//需要先判断是否为空
{
next=next.next;//找到下一个
}
return deleteDuplication(next);
}
else
{
pHead.next=deleteDuplication(next);
return pHead;
}
}
}
面试题19:正则表达式匹配
/*
* P124面试题19:正则表达式匹配
* 题目:请实现一个函数用来匹配包含'.'和'*'的正则表达式
* 模式中的字符'.'表示任意一个字符 而'*'表示它前面的字符可以出现任意次(含0次)
* 在本题中 匹配是指字符串的所有字符匹配整个模式
* 例如 字符串"aaa"与模式"a.a" "ab*ac*a"匹配
* 但与"aa.a"和"ab*a"均不匹配
*/
public class T19
{
/*
* 思路一:
* 对字符串与模式串的各个元素进行依次对比 有以下几种情况
* A.首先若模式串中的元素与字符串的匹配 且模式串中这个元素的后一元素不为* 若两者匹配 则下标共同+1 不匹配则结果为不匹配
* B.在上一情况中 模式串中的元素若为. 则下标共同+1
* 若模式串中的某一元素的后一元素为* 则有3种情况 :
* C.*表示模式串中的字符出现0次,则字符串下标不变 模式串下标+2
* D.*表示模式串中的字符出现1次,则字符串下标+1 模式串下标+2
* E.*表示模式串中的字符出现1次以上,则字符串下标+1 模式串下标不变
* 对于CDE三种情况 若当前下标元素两者不匹配 只能进行C操作 但是当匹配时 也可以进行C操作
* 还需要考虑.*连续出现的情况
*/
public boolean match(char[] str, char[] pattern)
{
if(str==null||pattern==null)
return false;
return matchCore(str,pattern,0,0);
}
private boolean matchCore(char[] str,char[] pattern,int index1,int index2)//index1字符串下标 index2模式串下标
{
if(index1>=str.length&&index2>=pattern.length)//都匹配完成
return true;
if(index1<str.length&&index2>=pattern.length)//字符串没完但是模式串完了
return false;
//还有一种情况是 字符串完了 但是这种情况模式串可完可不完 因为模式串可能还有情况C发生 所以不用设置出口
if(index2+1<pattern.length&&pattern[index2+1]=='*')//注意判断顺序
{
/*
* 这种情况就是字符串完了但是模式串没完的情况
*/
if(index1>=str.length)
return matchCore(str,pattern,index1,index2+2);
else
{
if(pattern[index2]==str[index1]||pattern[index2]=='.')
{
return matchCore(str,pattern,index1+1,index2+2)||//D
matchCore(str,pattern,index1+1,index2)||//E
matchCore(str,pattern,index1,index2+2);//C
}
else
return matchCore(str,pattern,index1,index2+2);
}
}
if(index1>=str.length)
return false;
else
{
if(pattern[index2]==str[index1]||pattern[index2]=='.')
return matchCore(str,pattern,index1+1,index2+1);
}
return false;
}
/*
* 思路二:动态规划
* 维护一个布尔型二维数组 二维分别为字符串的长度和模式串的长度
* 例如dp[i][j]的值为true则表示字符串长度为i 模式串长度为j时两者可以匹配
* 具体的计算规则与思路一相似
* 仍需要考虑a* 表示0个、1个、多个a的情况
*/
public boolean match2(char[] str,char[] pattern)
{
boolean dp[][]=new boolean[str.length+1][pattern.length+1];
dp[0][0]=true;
/*
* 以下一个for循环是处理字符串长度为0的对应测试数据
*/
for(int i=1;i<=pattern.length;i++)
{
if(pattern[i-1]=='*')
dp[0][i]=dp[0][i-2];
}
for(int i=1;i<=str.length;i++)
{
for(int j=1;j<=pattern.length;j++)
{
if(str[i-1]==pattern[j-1]||pattern[j-1]=='.')
dp[i][j]=dp[i-1][j-1];
else if(pattern[j-1]=='*')
{
if(pattern[j-2]==str[i-1]||pattern[j-2]=='.')
{
dp[i][j]=dp[i][j]||dp[i][j-2];//*表示出现0次
dp[i][j]=dp[i][j]||dp[i][j-1];//*表示出现1次
dp[i][j]=dp[i][j]||dp[i-1][j];//*表示出现多次
}
else
dp[i][j]=dp[i][j]||dp[i][j-2];//*不匹配时只能选择出现0次
}
}
}
return dp[str.length][pattern.length];
}
}
面试题20:表示数值的字符串
/*
* P127面试题20:表示数值的字符串
* 题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)
* 例如 字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值
* 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是
*/
public class T20
{
/*
* 表示数值的字符串遵循欧式 A[.[B]][e|EC]或者.B[e|EC]
* 其中A为数值的整数部分 B紧跟着小数点为数值的小数部分
* C紧跟着'e'或'E'为数值的指数部分
* 在小数里可能没有数值的整数部分
* 如果一个数没有整数部分 那么它的小数部分不能为空
* 其中A和C都是可能以'+' '-'开头的0~9数位串 B也是0~9的数位串 但前面不能有正负号
*/
public boolean isNumeric(char[] str)
{
if(str==null)
return false;
int index=0;
//首先看是否有符号位
if(str[index]=='+'||str[index]=='-')
index++;
//如果只有符号位 则不是数值
if(index==str.length)
return false;
//接下来有两种情况 一是A存在 二是A不存在 这两种情况均可以扫描直到到达非数值位(小数点或指数符号)或直接到达终点
index=moveIndex(str,index);
//此时可能到达终点 也可能没有
//直接到达终点就结束了 是数值 没到达终点有两种可能 小数点或指数符号
if(index<str.length)
{
if(str[index]=='.')//小数点若出现 则一定在指数符号前 所以先判断小数点
{
index++;
index=moveIndex(str,index);
if(index<str.length)//若还没结束 则说明还有指数符号
{
if(str[index]=='e'||str[index]=='E')
{
index++;
return isExponential(str,index);
}
return false;
}
return true;//小数点部分直接到达终点
}
else if(str[index]=='e'||str[index]=='E')//如果没有小数 直接为指数部分
{
index++;
return isExponential(str,index);
}
return false;
}
return true;
}
/*
* 该函数的作用是扫描数位部分来匹配A、B、C部分
*/
private int moveIndex(char[] str,int index)
{
while(index<str.length&&str[index]>='0'&&str[index]<='9')
index++;
return index;
}
/*
* 该函数的作用是处理指数位
*/
private boolean isExponential(char[] str, int index)
{
if (index < str.length)
{
// 如果是符号,跳到下一个
if (str[index] == '+' || str[index] == '-')
index++;
index = moveIndex(str, index);
if (index == str.length)
return true;
return false;
}
return false;
}
}
面试题21:调整数组顺序使奇数位于偶数前面
/*
* P129面试题21:调整数组顺序使奇数位于偶数前面
* 题目:输入一个整数数组 实现一个函数来调整该数组中数字的顺序
* 使得所有奇数位于数组的前半部分 所有偶数位于数组的后半部分
* 并保证奇数和奇数,偶数和偶数之间的相对位置不变(牛客网额外条件)
*/
public class T21
{
/*
* 思路一:使用两个指针 由于要保证奇数与奇数 偶数与偶数之间的相对位置不变
* 不能按照原书中的方法处理 会打乱相对顺序
* 只能交换相邻的元素 如果相邻的元素为前偶后奇 那么交换他们
*/
public void reOrderArray1(int [] array)
{
for(int i=0;i<array.length;i++)
{
for(int j=array.length-1;j>i;j--)
{
if(array[j-1]%2==0&&array[j]%2==1)
{
int temp=array[j-1];
array[j-1]=array[j];
array[j]=temp;
}
}
}
}
/*
* 另一种用空间换时间的方法是建立一个新数组
* 统计原数组中奇数的个数
* 在新数组中以这个个数为分界线 遍历原数组 遇到奇数则放在新数组首部
* 遇到偶数则放在新数组的分界线之后
*/
public void reOrderArray2(int [] array)
{
int count=0;//奇数个数
for(int i=0;i<array.length;i++)
{
if(array[i]%2==1)
count++;
}
int[] copy = array.clone();
int a= 0;
for(int i=0;i<copy.length;i++)
{
if(copy[i]%2==1)
array[a++]=copy[i];
else
array[count++]=copy[i];
}
}
}
面试题22:链表中倒数第k个节点
/*
* P134面试题22:链表中倒数第k个节点
* 题目:输入一个链表 输出该链表中倒数第k个节点
* 从1开始计数 即链表的尾节点是倒数第一个节点
*/
public class T22
{
/*
* 思路:假设整个链表有n个节点 那么倒数第k个节点就是正数的n-k+1个节点
* 所以只要知道了节点总个数n 那么再往后走n-k+1步就可以了 这样需要遍历2次链表
* 第一次统计出总结点个数 第二遍找到倒数第k个节点
* 也有只需要遍历一次链表的方法:
* 设置两个指针p1与p2 他们初始都指向头结点
* 此时若要找的是倒数第k个节点
* 那么指针p1先出发走k-1步
* 之后从第k步开始 p1走一步 p2也走一步 他们之间的距离保持k-1步
* 直到p1到达尾节点时 p2刚好到达倒数第k个节点 因为n-(k-1)为n-k+1 为正数的第n-k+1个节点
* 还需要考虑越界的情况 比如只有五个节点的链表 要求返回倒数第六个节点
*/
public ListNode FindKthToTail(ListNode head,int k)
{
if(head==null||k==0)
return null;
ListNode p1=head;
for(int i=0;i<k-1;i++)
p1=p1.next;
if(p1==null)
return null;
ListNode p2=head;
while(p1.next!=null)
{
p1=p1.next;
p2=p2.next;
}
return p2;
}
}
面试题23:链表中环的入口节点
/*
* P139面试题23:链表中环的入口节点
* 题目:如果一个链表中包含环 如何找出环的入口节点?
*/
public class T23
{
/*
* 分析链表中存在环的情况 假定有链表1-2-3-4-5-6 其中6又指向3
* 首先要明确的是 如果存在环 那么在链表中指向前面节点的节点 如例子中的节点6
* 这样的节点最多只可能有1个 且在末尾 因为链表的节点只有一个next域
* 那么此时设置两个指针 均从头结点出发
* 快指针每走2步 慢指针走1步 若链表中存在环 则他们必定会在某个节点上重合
* 我们假设 链表开始到环的入口 共有x个节点 从入口到相遇的节点 共有y个节点 从这个相遇的节点 再到入口 共有z个节点
* 那么对于快节点 他走了x+y+z+y步 由于快节点与慢节点每次只差1步
* 所以不存在“快节点在环中跑了很多圈才遇上慢节点”的情况
* 而对于慢节点 他走了x+y步
* 由于速度已经知道 所以2*慢节点步数=快节点步数 得到x=z
* 所以我们得到 链表开始到入口的节点个数 就是相遇处再走到入口的节点个数
* 我们在相遇处保留一个指针 另一个指针放回头结点
* 之后两个指针每次都走1步 当他们相遇时 相遇的节点即为入口节点
*/
public ListNode EntryNodeOfLoop(ListNode pHead)
{
if (pHead == null || pHead.next == null)
return null;
ListNode fast=pHead.next.next;
ListNode slow=pHead.next;//两者均进行了第一次前进
while(fast!=slow)
{
fast=fast.next.next;
slow=slow.next;
}
slow=pHead;//将slow放回起点
while(fast!=slow)
{
fast=fast.next;
slow=slow.next;
}
return fast;
}
}
面试题24:反转链表
/*
* P142面试题24:反转链表
* 题目:定义一个函数 输入一个链表的头结点 反转该链表并输出反转后链表的头结点
*/
public class T24
{
/*
* 迭代方法:从前往后依次处理 直到循环到达链尾
* 为防止链表断开 需要定义三个指针
* 分别指向当前遍历到的节点 它的前一个节点以及后一个节点
* 以原始链表1-->2-->3-->NULL为例
* 令指针P指向节点1 同时新建一个节点 赋值为-1 令指针newHead指向它
* 之后循环执行以下四步操作直到到达结尾
* 1. ListNode tmp=P.next 保存后续节点
* 2. P.next=newHead 此时将1的next连向了-1
* 3. newHead=P 此时更改了newHead指针指向的位置 改为指向1
* 4.P=tmp
*/
public ListNode ReverseList(ListNode head)
{
ListNode reverseList=new ListNode(-1);
while(head!=null)
{
ListNode next=head.next;//保存后续节点
head.next=reverseList.next;
reverseList.next=head;
head=next;
}
return reverseList.next;
}
/*
* 递归是从链尾往链头翻转链表
* 通过递归走到链表的末端 之后逐层进行翻转
* 仍以原始链表1-->2-->3-->NULL为例
* 通过不断调用自身 在走到3时触发if语句 到达最底层
* 此时将指针P与指针newHead指向3 因为3就是翻转之后的头结点 所以newHead指针自始至终都指向3 返回上一层
* 此时P指向2 触发else语句
* 将P指向的地址赋给P.next.next 即令3(就是P.next)的next连向2 那么此时2的next为3 3的next为2 成环
* 所以需要另P.next=null 断开2指向3
* 之后P指向1 令2的next连向1 再断开1连向2
* 完成
*/
public ListNode ReverseList2(ListNode head)
{
if(head==null||head.next==null)
return head;
else
{
ListNode newHead=ReverseList2(head.next);
head.next.next=head;
head.next=null;
return newHead;
}
}
}
面试题25:合并两个排序的链表
/*
* P145面试题25:合并两个排序的链表
* 题目:输入两个递增排序的链表 合并这两个链表并使新链表中的节点仍然是递增排序的
* 例如输入链表一:1-3-5-7 链表二:2-4-6-8
* 则合并之后的链表为1-2-3-4-5-6-7-8
*/
public class T25
{
/*
* 迭代
*/
public ListNode Merge1(ListNode list1, ListNode list2)
{
ListNode head=new ListNode(-1);
ListNode temp=head;
while(list1!=null&&list2!=null)
{
if(list1.val>list2.val)
{
head.next=list2;
list2=list2.next;
head=head.next;
}
else
{
head.next=list1;
list1=list1.next;
head=head.next;
}
}
if(list1!=null)
head.next=list1;
if(list2!=null)
head.next=list2;
return temp.next;
}
/*
* 递归
* 链表1和链表2本身就是递增的 所以合并的过程可以从链表1,2的头结点开始
* 先比较1,2的头结点中值的大小 将小的值的结点(比如为链表1头结点)作为合并后的链表(链表3)的头结点
* 随后可以考虑成链表1的从原链表第二个结点开始 再次重复上面的步骤 这样就变成了一个递归问题
*/
public ListNode Merge(ListNode list1, ListNode list2)
{
if(list1.next==null)
return list2;
if(list2.next==null)
return list1;
if(list1.val>list2.val)
{
list1.next=Merge(list1,list2.next);
return list2;
}
else
{
list2.next=Merge(list1.next,list2);
return list1;
}
}
}
面试题26:树的子结构
/*
* P148面试题26:树的子结构
* 题目:输入两棵二叉树A和B 判断B是不是A的子结构
*/
public class T26
{
/*
* 思路:递归地在树A中查找与B的根节点的值一样的节点
* 之后再判断树A中以B根节点的值为根节点的子树是否与B树具有相同的结构
* 同样使用递归的思路 如果节点R的值和树B的根节点不相同 则肯定不具有相同的节点
* 如果值相同 则递归地判断它们各自的左右节点的值是不是相同
* 终止条件是 到达了树A或者树B的叶子节点
*/
public boolean HasSubtree(TreeNode root1, TreeNode root2)
{
if(root1==null||root2==null)
return false;
return DoesTree1HaveTree2(root1,root2)||HasSubtree(root1.left,root2)||HasSubtree(root1.right,root2);
}
private boolean DoesTree1HaveTree2(TreeNode root1,TreeNode root2)
{
if(root2==null)
return true;
if(root1==null)
return false;
if(root1.val!=root2.val)
return false;
return DoesTree1HaveTree2(root1.left,root2.left)&&DoesTree1HaveTree2(root1.right,root2.right);
}
}
面试题27:二叉树的镜像
/*
* P157面试题27:二叉树的镜像
* 完成一个二叉树 该函数输出它的镜像
*/
public class T27
{
/*
* 逐层递归
*/
public void Mirror(TreeNode root)
{
if(root==null)
return;
swap(root);
Mirror(root.left);
Mirror(root.right);
}
private void swap(TreeNode root)
{
TreeNode temp=root.left;
root.left=root.right;
root.right=temp;
}
}
面试题28:对称的二叉树
/*
* P159面试题28:对称的二叉树
* 请实现一个函数 用来判断一棵二叉树是不是对称的
* 如果一棵二叉树和它的镜像一样 那么它是对称的
*/
public class T28
{
/*
* 思路一:如果一棵二叉树是对称的
* 那么对于同一层的A B两个节点 A节点的左子树等于B节点的右子树 A节点的右子树等于B节点的左子树
* 递归的判断每一层直到均到达叶子节点
*/
boolean isSymmetrical(TreeNode pRoot)
{
if(pRoot==null)
return true;
return core(pRoot.left,pRoot.right);
}
private boolean core(TreeNode root1,TreeNode root2)
{
if(root1==null&&root2==null)
return true;
if(root1==null||root2==null)
return false;
if(root1.val!=root2.val)
return false;
return core(root1.left,root2.right)&&core(root1.right,root2.left);
}
/*
* 思路二:使用面试题27的方法求出这棵树的镜像后 对比两者是否完全一致
* 代码略
*/
/*
* 思路三:对于先序遍历而言 得到的序列是根节点、左、右
* 若自定义一个特殊的先序遍历 得到的序列是根节点、右、左排序
* 那么若两序列完全一致 这这棵树是对称的
* 同时也需要把空节点的位置考虑进去
*/
}
面试题29:顺时针打印矩阵
import java.util.ArrayList;
/*
* P161面试题29:顺时针打印矩阵
* 题目:输入一个矩阵 按照从外向里以顺时针的顺序依次打印出每一个数字
* 例如,如果输入如下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
*/
public class T29
{
/*
* 思路:把矩阵想象为若干个圈 一圈一圈进行打印 打印的方向依次为右 下 左 上 右 下 左 上循环
* 最后一圈的打印有多种情况
* 1.在4X4矩阵中 最后一圈为4个元素组成的正方形 右 下 左 上的过程均需进行
* 2.在4X5矩阵中 最后一圈由2行3列组成 需要进行右 下 左的过程
* 3.在5X3矩阵中 最后一圈由3行1列组成 需要进行右 下的过程
* 4.在3X4矩阵中 最后一圈由1行2列组成 只需要进行向右的过程
* 无论打印的情况如何 均是从这一圈的左上角元素开始
* 定义两对指针 一对初始指向矩阵的左上角 为r1/c1 一对初始指向矩阵的右下角 为r2/c2 用来表示当前圈的边界
* 分析打印的过程 首先向右打印最为简单 保持行号不变的情况下令列号增加 直到到达当前圈的边界
* 之后为了进行向下打印的过程 且由于上一步的最后一个元素 即是本步骤的第一个元素 所以需要使行号+1 保持列号不变 行号增加直到到达当前圈的边界
* 进行向左打印的过程时 使列号先-1
* 进行向上打印的过程时 使行号先-1
* 打印完这四个过程后 令r1、c1均+1 表示进入了下一圈 令r2、c2均-1 用来表示进入的下一圈的边界
* 而上面提到 有的打印过程是不需要的 如何确认这些步骤哪些是需要的?
* 当r1与r2相等时 说明本圈只有一行 这一行会在向右时完成遍历 所以不需要向左
* 当c1与c2相等时 说明本圈只有一列 这一列会在向下时完成遍历 所以不需要向上
* 而向右是一定需要的 向下在某些情况下不需要
*/
public static void main(String[] args)
{
int[][] matrix =
{
{ 1, 2, 3, 4 },
{ 5, 6, 7, 8 },
{ 9, 10, 11, 12 },
{ 13, 14, 15, 16 } };
ArrayList<Integer> res = printMatrix(matrix);
for (int temp : res)
System.out.println(temp);
}
public static ArrayList<Integer> printMatrix(int[][] matrix)
{
ArrayList<Integer> res = new ArrayList<>();
int r1=0,c1=0;
int r2=matrix.length-1;
int c2=matrix[0].length-1;
while(r1<=r2&&c1<=c2)
{
for(int i=c1;i<=c2;i++)
res.add(matrix[r1][i]);
for(int i=r1+1;i<=r2;i++)
res.add(matrix[i][c2]);
if(r1!=r2)
for(int i=c2-1;i>=c1;i--)
res.add(matrix[r2][i]);
if(c1!=c2)
for(int i=r2-1;i>r1;i--)
res.add(matrix[i][c1]);
r1++;
c1++;
r2--;
c2--;
}
return res;
}
}
面试题30:包含min函数的栈
import java.util.Stack;
/*
* P165面试题30:包含min函数的栈
* 题目:定义栈的数据结构 请在该类型中实现一个能够得到栈的最小元素的min函数
* 在该栈中 调用min push 及pop的时间复杂度都是O(1)
*/
public class T30
{
/*
* 思路:使用一个数据栈进行压入弹出操作 使用一个辅助栈来保存最小值
* 类似于动态规划中的维护最优解数组思想
* 辅助栈中始终保存当前数据栈中的最小元素
* 在数据栈入栈时 将该元素与辅助栈栈顶元素对比 若入栈元素更小 则将该元素压入辅助栈 若辅助栈栈顶元素更小 则再次压入一个栈顶元素
* 在数据栈出栈时 同样令辅助栈进行一次出栈操作
*/
private Stack<Integer> dataStack=new Stack<>();
private Stack<Integer> supStack=new Stack<>();
public void push(int node)
{
dataStack.push(node);
if(supStack.isEmpty())
supStack.push(node);
else
supStack.push(Math.min(node, supStack.peek()));
}
public void pop()
{
dataStack.pop();
supStack.pop();
}
public int top()
{
return dataStack.peek();
}
public int min()
{
return supStack.peek();
}
}
面试题31:栈的压入、弹出序列
import java.util.Stack;
/*
* P168面试题31:栈的压入、弹出序列
* 题目:输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序
* 假设压入栈的所有数字均不相等
* 例如 序列{1,2,3,4,5}是某栈的压栈序列 序列{4,5,3,2,1}是该压栈序列对应的一个弹出序列
* 但{4,3,5,1,2}就不可能是该压栈序列的弹出序列
*/
public class T31
{
/*
* 使用一个辅助栈模拟压入弹出过程
* 如果下一个弹出的数字刚好是栈顶数字 那么直接弹出
* 如果下一个弹出的数字不在栈顶 则把压栈序列中还没有入栈的数字压入辅助栈
* 直到把下一个需要弹出的数字压入栈顶为止
* 如果所有数字都压入栈后依然没有找到下一个弹出的数字 则该序列不可能是一个弹出序列
*/
public boolean IsPopOrder(int [] pushA,int [] popA)
{
if(pushA.length==0||popA.length==0)
return false;
Stack<Integer> stack=new Stack<>();
stack.push(pushA[0]);
int index1=1;
int index2=0;
boolean flag=false;
while(!flag)
{
if(stack.peek()==popA[index2])
{
stack.pop();
index2++;
}
else
{
while(index1<pushA.length)
{
stack.push(pushA[index1]);
index1++;
if(stack.peek()==popA[index2])
break;
}
}
if(index1==pushA.length)
flag=true;
}
while(!stack.isEmpty()&&stack.peek()==popA[index2])
{
{
stack.pop();
index2++;
}
}
return stack.isEmpty();
}
}
面试题32.1:从上到下打印二叉树
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/*
* P171面试题32.1 从上到下打印二叉树
* 题目一:不分行从上到下打印二叉树
* 从上到下打印出二叉树的每个节点 同一层的节点按照从左到右的顺序打印
*/
public class T32_1
{
/*
* 借助队列来实现
* 每次打印一个节点的时候 如果该节点有子节点 则把该节点的子节点放到队列末尾
* 接下来从队列头取出最早进入队列的节点 直到队列中所有的节点都被打印
*/
public ArrayList<Integer> PrintFromTopToBottom(TreeNode root)
{
ArrayList<Integer> res=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(root==null)
return res;
queue.add(root);
while(!queue.isEmpty())
{
TreeNode temp=queue.poll();
res.add(temp.val);
if(temp.left!=null)
queue.add(temp.left);
if(temp.right!=null)
queue.add(temp.right);
}
return res;
}
}
面试题32.2:分行从上到下打印二叉树
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
/*
* P174题目二:分行从上到下打印二叉树
* 从上到下按层打印二叉树 同一层的节点按从左到右的顺序打印 每一层打印到一行
*/
public class T32_2
{
/*
* 和上一题类似 但是加入一个变量num记录当前层的节点数
* 例如
* 8
* 6 10
* 5 7 9 11
* 这棵树 当8进入队列时 队列中有1个元素 记录num为1
* 每当打印一个元素时 num-1 直到num=0
* 此时再次统计队列中元素个数 为2 那么再打印2个元素后再次统计
* 每次统计时换行即可
*/
ArrayList<ArrayList<Integer> > Print(TreeNode pRoot)
{
ArrayList<ArrayList<Integer>> res=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(pRoot==null)
return res;
queue.add(pRoot);
int num=queue.size();
while(!queue.isEmpty())
{
ArrayList<Integer> row=new ArrayList<>();
while(num!=0)
{
TreeNode temp=queue.poll();
num--;
row.add(temp.val);
if(temp.left!=null)
queue.add(temp.left);
if(temp.right!=null)
queue.add(temp.right);
}
res.add(row);
num=queue.size();
}
return res;
}
}
面试题32.3:之字形打印二叉树
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedList;
import java.util.Queue;
/*
* P176题目三:之字形打印二叉树
* 请实现一个函数按照之字形打印二叉树
* 即 第一行按照从左到右的顺序打印 第二行按照从右到左的顺序打印 第三行再按照从左到右的顺序打印
* 其他行以此类推
*/
public class T32_3
{
/*
* 按照上一题的方法实现
* 只需要设置一个值记录当前行数 若当前行为偶数 则颠倒当前行的顺序
* 使用Collections.reverse函数 也可手动实现
*/
public ArrayList<ArrayList<Integer> > Print(TreeNode pRoot)
{
ArrayList<ArrayList<Integer>> res=new ArrayList<>();
Queue<TreeNode> queue=new LinkedList<>();
if(pRoot==null)
return res;
queue.add(pRoot);
int num=queue.size();
int rownum=1;
while(!queue.isEmpty())
{
ArrayList<Integer> row=new ArrayList<>();
while(num!=0)
{
TreeNode temp=queue.poll();
num--;
row.add(temp.val);
if(temp.left!=null)
queue.add(temp.left);
if(temp.right!=null)
queue.add(temp.right);
}
if(rownum%2==0)
{
Collections.reverse(row);
}
res.add(row);
rownum++;
num=queue.size();
}
return res;
}
}
面试题33:二叉搜索树的后序遍历序列
/*
* P179面试题33:二叉搜索树的后序遍历序列
* 题目:输入一个整数数组 判断该数组是不是某二叉搜索树的后序遍历结果
* 假设输入的数组的任意两个数字都互不相同
*/
public class T33
{
/*
* 思路:后序遍历的最后一个数就是根节点
* 根据根节点的值可以确认左右子树 值小于根节点的就是左子树 大于的就是右子树
* 通过递归的方式处理 确定每一部分对应的子树的结构
*/
public boolean VerifySquenceOfBST(int [] sequence)
{
if(sequence.length==0)
return false;
return Core(sequence,0,sequence.length-1);
}
private boolean Core(int sequence[],int first,int last)
{
//当递归进行到某个叶子节点时 递归终止 叶子节点没有子节点 所以可以用下标间的距离判断
if(last-first<2)
return true;
int rootnow=sequence[last];
int left=first;
while(left<last&&sequence[left]<rootnow)
left++;//找到当前根节点的左子树的所有节点 这些节点都会小于根节点
for(int i=left;i<last;i++)
if(sequence[i]<rootnow)
return false;//检查当前根节点的右子树的所有节点 若这些节点有小于根节点的值 那么不可能是一个二叉搜索树的后序遍历
return Core(sequence,first,left-1)&&Core(sequence,left,last-1);//再去递归的判断左子树和右子树
}
}
面试题34:二叉树中和为某一值的路径
import java.util.ArrayList;
/*
* P182面试题34:二叉树中和为某一值的路径
* 题目:输入一棵二叉树和一个整数 打印出二叉树中节点值的和为输入整数的所有路径
* 从树的根节点开始往下一直到叶节点所经过的节点形成一条路径
*/
public class T34
{
private ArrayList<ArrayList<Integer>> ret=new ArrayList<>();
public ArrayList<ArrayList<Integer>> FindPath(TreeNode root,int target)
{
Core(root,target,new ArrayList<>());
return ret;
}
private void Core(TreeNode root,int target,ArrayList<Integer> res)
{
if(root==null)
return;
res.add(root.val);//先假设当前节点在路径中
target=target-root.val;
if(target==0&&root.left==null&&root.right==null)//一直到叶子节点
ret.add(new ArrayList<>(res));
else
{
Core(root.left,target,res);
Core(root.right,target,res);
}
res.remove(res.size()-1);//如果到达这一步时没有完成条件 那么就说明这个之前假设在路径中的节点实际上不应在路径中
//那么将它去掉 类似于回溯法的思想
}
}
面试题35:复杂链表的复制
/*
* P187面试题35:复杂链表的复制
* 输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点)
* 返回结果为复制后复杂链表的head(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
*/
/*
public class RandomListNode {
int label;
RandomListNode next = null;
RandomListNode random = null;
RandomListNode(int label) {
this.label = label;
}
}
*/
/*
* 思路:可以先将链表复制一遍 这一遍复制中将next域连接完成 而对于random域 由于对于某个节点而言 他的random可能指向
* 它前面的节点 也可能指向它后面的节点 所以只能通过再遍历一遍链表的方式找到这个节点并连接上
* 对每个节点都要遍历一遍链表 时间复杂度为O(n^2)
*
* 上述方法的时间主要花费在定位random指向的节点上 所以在第一步复制时 将节点N与它的复制N'的配对信息存储在哈希表中
* 这样如果在原始链表中N的random指向S 那么对应的N'应指向S' 通过S与S'的配对信息 就可以通过S找到S'
* 时间复杂度O(n) 但需要额外的空间(n)
*
* 使用的方法是 在复制链表时 直接将新节点连接在老节点后
* 即:1--1'--2--2'--3--3'--4--4'..........
* 这样如果1的random指向3
* 那么1'指向3'的设置非常简单 令1.next.rondom指向1.rondom.next即可
* 最后再进行拆分 将偶数位的节点连接在一起即可
*/
public class T35
{
public RandomListNode Clone(RandomListNode pHead)
{
if(pHead==null)
return null;
//复制
RandomListNode cur=pHead;
while(cur!=null)
{
RandomListNode temp=new RandomListNode(cur.label);
temp.next=cur.next;
cur.next=temp;
cur=cur.next.next;
}
//连接random
cur=pHead;
while(cur!=null)
{
if(cur.random!=null)
cur.next.random=cur.random.next;
cur=cur.next.next;
}
//拆分
cur=pHead;
RandomListNode res=pHead.next;
while(cur.next!=null)
{
RandomListNode next = cur.next;
cur.next = next.next;
cur = next;
}
return res;
}
}
面试题36:二叉搜索树与双向链表
/*
* P191面试题36:二叉搜索树与双向链表
* 题目:输入一棵二叉搜索树 将该二叉搜索树转换成一个排序的双向链表
* 要求不能创建任何新的节点 只能调整数中节点指针的指向
*/
public class T36
{
/*
* 递归解决 对根节点而言 他的左子树都小于他 右子树都大于他 即是一个排序的序列 将这个根节点的next域与pre域指向左、右子树
* 本题用TreeNode代替双向链表 对于根节点的pre 它应该指向左子树中的最大节点 如果这棵左子树已经是处理好的双向链表
* 那么它的最后一个元素就是想要的 对于根节点的next 它应该指向右子树中的最小节点 同样如果已经处理好 那么它的第一个元素就是想要的
*
* 用原题中给的例子进行分析 先分析根节点10的左子树 6 4 8
* 形成的双向链表应该为4===6===8 目前6连向4和6连向8的连接已经存在
* 所以需要补全4指向6的 和8指向6的
* 即:对某个左 右子节点均为叶子节点的根节点而言 需要让他的左节点的next(即right)和右节点的pre(即left)指向自身
*
* 而对于节点10这样的节点而言 他的pre应该指向8 而8是他的左子树构建成双向链表后的最后一个节点
* 他的next应该指向12 而12是他的右子树构建成双向链表后的第一个节点
* 所以 左 右子节点均为叶子节点的根节点就是递归处理的最底层
*/
public TreeNode Convert(TreeNode pRootOfTree)
{
if (pRootOfTree == null)
return null;
if (pRootOfTree.left != null)
{
if (pRootOfTree.left.left == null && pRootOfTree.left.right == null)
pRootOfTree.left.right = pRootOfTree;
else
{
Convert(pRootOfTree.left);
if (pRootOfTree.left.right != null)
pRootOfTree.left = pRootOfTree.left.right;
pRootOfTree.left.right = pRootOfTree;
}
}
if (pRootOfTree.right != null)
{
if (pRootOfTree.right.left == null && pRootOfTree.right.right == null)
pRootOfTree.right.left = pRootOfTree;
else
{
Convert(pRootOfTree.right);
if (pRootOfTree.right.left != null)
pRootOfTree.right = pRootOfTree.right.left;
pRootOfTree.right.left = pRootOfTree;
}
}
return findLeft(pRootOfTree);
}
private TreeNode findLeft(TreeNode root)
{
while (root.left != null)
root = root.left;
return root;
}
}
面试题37:序列化二叉树
/*
* P194面试题37:序列化二叉树
* 请实现两个函数 分别用来序列化和反序列化二叉树
*/
public class T37
{
/*
* 1. 对于序列化:使用前序遍历,递归的将二叉树的值转化为字符,并且在每次二叉树的结点
不为空时,在转化val所得的字符之后添加一个' '作为分割。对于空节点则以 '#' 代替。
2. 对于反序列化:按照前序顺序,递归的使用字符串中的字符创建一个二叉树
对于这样的一棵二叉树:
1
2 3
4 5 6
他将被序列化成字符串
"1 2 4 # # # 3 5 # # 6 # #"
他的还原过程为:
依次读出每一个数字 用递归的方式还原每一个子节点 每使用一个元素 使字符串下标+1
*/
String Serialize(TreeNode root)
{
if(root==null)
return "#";
return root.val+" "+Serialize(root.left)+" "+Serialize(root.right);
}
private int index=-1;//记录当前下标位置 每次反序列化一个节点 就使字符串前进一个位置
TreeNode Deserialize(String str)
{
if(str.length() == 0)
return null;
String[] strs = str.split(" ");//由于节点值可能出现多位数 所以不能简单的使index++ 用字符串数组来得到每一个节点的值
return Deserialize2(strs);
}
TreeNode Deserialize2(String[] strs)
{
index++;
if (!strs[index].equals("#"))
{
TreeNode root = new TreeNode(0);
root.val = Integer.parseInt(strs[index]);
root.left = Deserialize2(strs);
root.right = Deserialize2(strs);
return root;
}
return null;
}
}
面试题38:字符串的排列
import java.util.ArrayList;
/*
* P197面试题38:字符串的排列
* 题目:输入一个字符串 打印出该字符串中字符的所有排列
* 例如 输入字符串abc
* 那么打印出 abc acb bac bca cab和cba
*/
public class T38
{
public static void main(String[] args)
{
ArrayList<String> res=new ArrayList<>();
res=Permutation("aab");
for(String temp:res)
System.out.println(temp);
}
static ArrayList<String> res=new ArrayList<>();
public static ArrayList<String> Permutation(String str)
{
char arr[]=str.toCharArray();
fullSort(arr,0,arr.length-1);
return res;
}
private static void fullSort(char arr[],int start,int end)
{
if(start==end)
{
String temp=new String(arr);
if(!res.contains(temp))
{
res.add(new String(arr));
return;
}
}
for(int i=start;i<=end;i++)
{
char temp=arr[i];
arr[i]=arr[start];
arr[start]=temp;
fullSort(arr,start+1,end);
temp=arr[i];
arr[i]=arr[start];
arr[start]=temp;
}
}
}
面试题39:数组中出现次数超过一半的数字
/*
* P205面试题39:数组中出现次数超过一半的数字
* 题目:数组中有一个数字出现的次数超过数组长度的一般 请找出这个数字
*/
public class T39
{
/*
* 排序数组之后下标为数组长度的一半处的元素为目标元素 该方法时间复杂度为O(nlogn)
* 哈希思想统计每个数字出现的次数 时间复杂度为O(n) 但是需要额外O(n)的空间
* 用二进制的方法也是一种思路 统计每一位的0/1出现的次数 (用左移之后与运算的方式) 0/1哪个超过一半 那么这位就是0/1中超过一半的数
* 每一位都这样计算 最后的数字转换为10进制就是目标数字
*
* 本题使用的是摩尔投票法:每次删除一对(两个)不同的元素 直到数组中只剩下相同的元素
* 若目标元素存在 则一定可以删除完成所有非目标元素的元素
* 具体的实现方式有多种 使用的思路如下:
* 使用一个计数器num 设置一个候选数字res 遍历数组
* 若res与当前元素相同 则num++ 不同则num--
* 当num为0时 令当前元素成为候选数字 直到遍历整个数组
* 之后还需要再遍历一次数组 防止出现不存在目标元素的情况
*/
public int MoreThanHalfNum_Solution(int[] array)
{
int res = 0;
int num = 0;
for (int i = 0; i < array.length; i++)
{
if (num == 0)
res = array[i];
if (res == array[i])
num++;
else
num--;
}
num = 0;
for (int i = 0; i < array.length; i++)
{
if (res == array[i])
num++;
}
if (num > array.length / 2)
return res;
else
return 0;
}
}
面试题40:最小的k个数
import java.util.ArrayList;
import java.util.Arrays;
import java.util.PriorityQueue;
/*
* P209面试题40:最小的k个数
* 题目:输入n个整数 找出其中最小的k个数
*/
public class T40
{
//方法一:排序数组后直接得到 复杂度为O(nlogn)
public ArrayList<Integer> GetLeastNumbers_Solution(int[] input, int k)
{
ArrayList<Integer> ret = new ArrayList<>();
if (k > input.length)
return ret;
Arrays.parallelSort(input);
for (int i = 0; i < k; i++)
ret.add(input[i]);
return ret;
}
public static void main(String[] args)
{
int input[]={4,5,1,6,2,7,3,8};
ArrayList<Integer> ret=GetLeastNumbers_Solution2(input,4);
}
/*
* 方法二:使用Partition函数实现
* Partition函数是实现快速排序的核心函数
* 其功能是返回一个整数 j 使得 a[0..j-1] 小于等于 a[j] 且 a[j+1..最后一个元素] 大于等于 a[j]
* 此时 a[j] 就是数组的第 j 大元素 可以利用这个特性找出数组的第 K 个元素
* 而由于基准数的选取是随机的(本题使用第一个元素作为基准数)
* 并不能保证直接得到第k大的数字 比如若返回的下标j是10 那么只能得到前9个小于data[10]的数 而这些元素的排列是无序的
* 所以需要一个额外的函数来保证Partition函数返回的j刚好为k
*/
public static ArrayList<Integer> GetLeastNumbers_Solution2(int[] input, int k)
{
ArrayList<Integer> ret = new ArrayList<>();
if (k > input.length)
return ret;
findKthNum(input,k-1);
for(int i=0;i<k;i++)
ret.add(input[i]);
return ret;
}
private static void findKthNum(int nums[],int k)
{
int start=0;
int end=nums.length-1;
while(start<end)
{
int j=Partition(nums,start,end);
if(j==k)
break;
if(j>k)//若j大于k 则在这前j个元素中查找即可
end=j-1;
if(j<k)//若j小于k 则在j之后的元素中查找
start=j+1;
}
}
private static int Partition(int data[],int start,int end)
{
int base=data[start];//选取第一个数为基准数
int i=start+1,j=end;//定义两个"哨兵" (《啊哈,算法》图解快速排序)
while(true)
{
while(j!=start&&data[j]>base)
j--;
while(i!=end&&data[i]<base)
i++;
//i向右找到第一个大于基准数的数 j向左找到第一个小于基准数的数
if(j>i)
swap(data,i,j);//若i、j尚未重合 则交换两元素
else
break;
}
swap(data,start, j);//重合之后交换基准数与j的位置
return j;
}
private static void swap(int[] nums, int i, int j)
{
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
/*
* 方法三:大小为 K 的最小堆
* 复杂度:O(NlogK) + O(K) 适合处理海量数据
* 应该使用大顶堆来维护最小堆,而不能直接创建一个小顶堆并设置一个大小,企图让小顶堆中的元素都是最小元素。
* 维护一个大小为 K 的最小堆过程如下:在添加一个元素之后,如果大顶堆的大小大于 K,那么需要将大顶堆的堆顶元素去除。
*/
public ArrayList<Integer> GetLeastNumbers_Solution3(int[] nums, int k)
{
if (k > nums.length || k <= 0)
return new ArrayList<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((o1, o2) -> o2 - o1);
for (int num : nums) {
maxHeap.add(num);
if (maxHeap.size() > k)
maxHeap.poll();
}
return new ArrayList<>(maxHeap);
}
}
面试题41:数据流中的中位数
import java.util.LinkedList;
/*
* P214面试题41:数据流中的中位数
* 题目:如何得到一个数据流中的中位数?
* 如果从数据流中读出奇数个数值 那么中位数就是所有数值排序之后位于中间的数值
* 如果从数据流中读出偶数个数值 那么中位数就是所有数值排序之后中间两个数的平均值
* 我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数
*/
public class T41
{
//使用List存储数据 维持List中数据有序
LinkedList<Integer> list = new LinkedList<Integer>();
public void Insert(Integer num)
{
if(list.size()==0||num<list.getFirst())
list.addFirst(num);
else
{
boolean flag=false;
for(Integer temp:list)
{
if(temp>num)
{
int index=list.indexOf(temp);
list.add(index, num);
flag=true;
break;
}
}
if(!flag)
list.addLast(num);
}
}
public Double GetMedian()
{
if(list.size()==0)
return null;
if(list.size()%2==0)
{
Double temp=Double.valueOf(list.get(list.size()/2-1)+list.get(list.size()/2));
return temp/2;
}
return Double.valueOf(list.get((list.size() + 1) / 2 - 1));
}
}
面试题42:连续子数组的最大和
/*
* P218面试题42:连续子数组的最大和
* 题目:输入一个整形数组,数字里有正数也有负数 数组中的一个或连续多个整数组成一个子数组
* 求所有子数组的和的最大值 要求时间复杂度为O(n)
* 例如:{6, -3, -2, 7, -15, 1, 2, 2},连续子数组的最大和为 8(从第 0 个开始,到第 3 个为止)
*/
public class T42
{
/*
* 动态规划
* 转移方程为dp[i]=Max(dp[i-1]+array[i],array[i]
* 意思是 若array[i]的值大于dp[i-1]+array[i] 就舍弃前面的所有数字
* 由于只需要用到dp[i]和dp[i-1] 所以不需要用数组来储存 用一个临时变量即可
*/
public int FindGreatestSumOfSubArray(int[] array)
{
if(array.length==0)
return 0;
int dp=array[0];
int res=array[0];
for(int i=1;i<array.length;i++)
{
dp=Math.max(dp+array[i], array[i]);
res=Math.max(res, dp);
}
return res;
}
}
面试题43:1~n整数中1出现的次数
/*
* P221面试题43:1~n整数中1出现的次数
* 题目:输入一个整数n 求1~n这n个整数的十进制表示中1出现的次数
* 例如 输入12,1~12这些证书中包含1的数字有1、10、11和12
* 1一共出现了5次
*/
/*
* 分析1出现的次数分为3种情况
* 以数字abcde中的c位(百位)为例
* 情况一:百位若为0 则看高位 以数字54021为例 本位可能为1的情况有:100~199 1100~1199 2100~2199......53100~53199
* 共5400个 取决于高位数(54)*本位数(100)
* 情况二:百位为1 则不仅要计算情况一的数目 还要看低位 如54121 除5400个情况一种的1外 还要加上54100~54121共22个数 即低位+1
* 情况三:百位大于1(2~9)如54321 看高位 在情况一的基础上再加上54100~54199 即(高位数+1)*当前位数=5500个
*/
public class T43
{
public int NumberOf1Between1AndN_Solution(int n)
{
int res=0;
int i=1;//当前位
while((n/i)!=0)
{
int high=n/(i*10);//高位数字
int cur=(n/i)%10;//本位数字
int low=n%i;//低位数字
if(cur==0)
res+=high*i;
else if(cur==1)
res=res+high*i+low+1;
else
res+=(high+1)*i;
i=i*10;
}
return res;
}
}
面试题44:数字序列中某一位的数字
/*
* P225面试题44:数字序列中某一位的数字
* 题目:数字以0123456789101112131415....的格式序列化到每一个字符序列中
* 在这个序列中(从0开始计数)第5位是5 第13位是1 第19位是4
* 请写一个函数 求任意第n位对应的数字
*/
public class T44
{
public static void main(String[] args)
{
System.out.println(digitAtIndex(19));
}
public static int digitAtIndex(int index)
{
if(index<0)
return -1;
if(index==0)
return 0;
boolean flag=false;
int digits=1;
while(!flag)
{
int count=digits*countOfIntegers(digits);
if(index>=count)
{
index-=count;
digits++;
}
else
flag=true;
}
int res=countOfIntegers(index,digits);
return res;
}
/*
* 这个函数用来得到m位的数字总共有多少个
* 例如 输入2 则返回两位数的个数(10~99)90
* 输入3 则返回三位数(100~999)的个数900
*/
private static int countOfIntegers(int digits)
{
if(digits==1)
return 10;
int count=(int) (Math.pow(10, digits)-Math.pow(10, digits-1));
return count;
}
/*
* 当知道要找的那一位数字位于某m位数之后 需要知道这个m位数字的第一个数字
* 例如 第一个两位数是10 第一个三位数是100
*/
private static int beginNumber(int digits)
{
if(digits==1)
return 0;
int res=(int) Math.pow(10, digits-1);
return res;
}
/*
* 利用下面这个函数找出结果数字
*/
private static int countOfIntegers(int index,int digits)
{
int begin=beginNumber(digits);
begin=begin+(index/digits);
int count=index%digits;
String num=begin+"";
int res=num.charAt(count)-'0';
return res;
}
}
面试题45:把数组排成最小的数
import java.util.Arrays;
import java.util.Comparator;
/*
* P227面试题45:把数组排成最小的数
* 题目:输入一个正整数数组 把数组里所有数字拼接起来排成一个数
* 打印能拼接出的所有数字中最小的一个
* 例如 输入数组{3,32,321} 则打印出这3个数字能排成的最小数字321323(321+32+3)
*/
public class T45
{
/*
* 制定专门的排序规则 如3 与 32
* 332>323 所以32应该放在3前面 ab>ba 则b<a
* 将int形数组转换为String形数组 可以实现拼接
*/
public String PrintMinNumber(int [] numbers)
{
if(numbers.length==0)
return "";
String nums[]=new String[numbers.length];
for(int i=0;i<numbers.length;i++)
nums[i]=numbers[i]+"";
Arrays.parallelSort(nums, new Comparator<String>()
{
@Override
public int compare(String o1, String o2)
{
String s1=o1+o2;
String s2=o2+o1;
return s1.compareTo(s2);
}
});
String res="";
for(int i=0;i<numbers.length;i++)
res+=nums[i];
return res;
}
}
面试题46:把数字翻译成字符串
/*
* P231面试题46:把数字翻译成字符串
* 题目:给定一个数字 我们按照如下规则把它翻译成字符串:1 翻译成“a”,2 翻译成“b”... 26 翻译成“z”
* 一个数字有多种翻译可能
* 例如 12258 一共有 5 种,分别是 abbeh,lbeh,aveh,abyh,lyh
* 实现一个函数,用来计算一个数字有多少种不同的翻译方法
*/
public class T46
{
/*
* 与上台阶问题非常相似 以 12258进行分析 同样是考虑一步使用1个数字还是2个数字
* 可以用动态规划解决 dp[i]表示以第i个数字结尾(下标从1开始)时共有多少种方法
* 首先设置dp[0]=1 其他的dp[i]初始化保持0
* 当i=1时 dp[1]=1
* 当i=2时 数字有1和2 可以分别翻译也可以组合成12 dp[2]=2
* 当i=3时 分别有1 2 2、1 22、12 2三种翻译方式 dp[3]=3
* 当i=4时 新添加的数字为5 他可以独立成为一个字母 所以dp[4]=dp[4]+dp[3]
* 也可以和他前面的2组成25 所以需要再加上只有最开始的前两个数字的方法数dp[4]=dp[4]+dp[2]
* 即 若本位合法 则dp[i]+=dp[i-1] 若本位与上一位组合合法 则dp[i]+=dp[i-2]
* 本位合法只需本位不等于0 组合合法需要第一位不为0且组合小于等于26
* 由于只需要用到dp[i-1]和dp[i-2]两个数 所以也可以用临时变量来存储他们 不需要使用长度为n的数组
*/
public static void main(String[] args)
{
System.out.println(numDecodings("12258"));
}
public static int numDecodings(String s)
{
if(s==null||s.length()==0)
return 0;
int temp1=1;//初始化dp[0]
int temp2=1;//初始化dp[1]
if(s.charAt(0)=='0')
temp2=0;
for(int i=2;i<=s.length();i++)
{
int temp=0;//初始化dp[i]
if(s.charAt(i-1)!='0')
temp+=temp2;//dp[i]+=dp[i-1]
if(s.charAt(i-2)=='0')
{
temp1=temp2;
temp2=temp;
continue;
}
int twonum=Integer.valueOf(s.substring(i-2, i));
if(twonum<=26)
temp+=temp1;//dp[i]+=dp[i-2]
temp1=temp2;
temp2=temp;
}
return temp2;
}
}
面试题47:礼物的最大价值
/*
* P233面试题47:礼物的最大价值
* 题目:在一个m*n的棋盘的每一格都放有一个礼物 每个礼物都有一定的价值(价值大于0)
* 你可以从棋盘的左上角开始拿格子里的礼物 并每次向右或向下移动一格 直到到达棋盘的右下角
* 给定一个棋盘及其上面的礼物 请计算你最多能拿到多少价值的礼物
*/
public class T47
{
/*
* 若要到达i,j格子 只有两种方法 一种是从他的左边来 一种是从他上边来
* 转移方程f(i,j)=Max(f(i-1),j),(f(i,j-1))+gift(i,j)
* 另外若i=0 即第一行的格子 只能从左边来 j=0即第一列的格子 只能从上面来
*/
public static void main(String[] args)
{
int values[][]={{1,10,3,8},{12,2,9,6},{5,7,4,11},{3,7,16,5}};
System.out.println(getMost(values));
}
public static int getMost(int[][] values)
{
int rows=values.length;
int cols=values[0].length;
int dp[][]=new int[rows][cols];
dp[0][0]=values[0][0];
for(int i=0;i<rows;i++)
{
for(int j=0;j<cols;j++)
{
if(i==0&j==0)
continue;
if(i==0)
dp[i][j]=dp[i][j-1]+values[i][j];
else if(j==0)
dp[i][j]=dp[i-1][j]+values[i][j];
else
dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1])+values[i][j];
}
}
return dp[rows-1][cols-1];
}
}
面试题48:最长不含重复字符的子字符串
import java.util.Arrays;
/*
* P236面试题48:最长不含重复字符的子字符串
* 题目:请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
* 假设字符串中只包含 'a'~'z'的字符
* 例如 在字符串 "arabcacfr"中 最长的不含重复字符的子字符串是"acfr" 长度为4
*/
public class T48
{
public static void main(String[] args)
{
System.out.println(longestSubstringWithoutDuplication("arabcacfr"));
}
/*
* 函数f(i)表示以第i个字符结尾的字符串中的最长不含重复字符的子字符串长度 如果第i个字符之前没有出现过 那么f(i)=f(i-1)+1
* 如果出现过 那么分两种情况 记录这个字符与它上一次出现的位置间的距离为d 1.若d<=f(i-1) 则f(i)=d 2.若d>f(i-1)
* 则f(i)=f(i-1)+1
*/
public static int longestSubstringWithoutDuplication(String str)
{
int curLen = 0;
int maxLen = 0;
int[] preIndexs = new int[26];
Arrays.fill(preIndexs, -1);
for (int curI = 0; curI < str.length(); curI++)
{
int c = str.charAt(curI) - 'a';
int preI = preIndexs[c];
if (preI == -1 || curI - preI > curLen)
{
curLen++;
}
else
{
maxLen = Math.max(maxLen, curLen);
curLen = curI - preI;
}
preIndexs[c] = curI;
}
maxLen = Math.max(maxLen, curLen);
return maxLen;
}
}
面试题49:丑数
/*
* P240面试题49:丑数
* 题目:我们把只包含因子2、3、5的数称为丑数
* 求按从小到大的顺序的第N个丑数
* 例如 6、8都是丑数,但14不是 因为它的因子包含7
* 把1当作第1个丑数
*/
public class T49
{
/*
* 由于丑数的性质 所以对任意一个丑数
* 他一定是由另一个丑数*2/*3/*5所得到的
* 所以可以依次求得下一个丑数
* 用指针的形式记录三个队列的当前最小元素
* 维护总的结果序列
* 举例说明:从上到下四行分别为:结果序列 *2序列 *3序列 *5序列 |代表指针位置
* 第一步:1
* |2
* |3
* |5
* 第二步: 1 2(此时2为三队列中最小值 2加入结果序列 *2队列指针移动 同时将2*2 2*3 2*5的结果分别放入三队列)
* 2 | 4
* |3 6
* |5 10
* 第三步:1 2 3(此时3为最小值)
* 2 | 4 6
* 3 |6 9
* |5 10 15
*/
public int GetUglyNumber_Solution(int index)
{
if(index<=6)
return index;
int ret[]=new int[index];
ret[0]=1;
int c2=0;
int c3=0;
int c5=0;
for(int i=1;i<index;i++)
{
ret[i]=Math.min(ret[c2]*2,Math.min(ret[c3]*3,ret[c5]*5));
if(ret[i]==ret[c2]*2)
c2++;
if(ret[i]==ret[c3]*3)
c3++;
if(ret[i]==ret[c5]*5)
c5++;
}
return ret[index-1];
}
}
面试题50:第一个只出现一次的字符
/*
* P243面试题50:第一个只出现一次的字符
* 在一个字符串(0<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符
* 并返回它的位置, 如果没有则返回 -1(需要区分大小写).
*/
public class T50
{
/*
* 关于为什么开辟一个长度为58的数组
* 由于只存在字母 A-Z是65-90 a-z是97-122
* 他们一共是26+26=52个字符
* 而90-96之间有6个字符不会出现 但是为了统一利用-65来计算
* 所以只能额外加上这6个字符的长度
*/
public int FirstNotRepeatingChar(String str)
{
int[] cnts = new int[58];
for (int i = 0; i < str.length(); i++)
cnts[str.charAt(i)-65]++;
for (int i = 0; i < str.length(); i++)
if (cnts[str.charAt(i)-65] == 1)
return i;
return -1;
}
}
面试题51:数组中的逆序对
/*
* P249面试题51:数组中的逆序对
*/
public class T51
{
/*
* 归并排序的改进,在合并时以倒序合并,指针同时指向两子数组的末尾 这样对于a数组的结尾元素 若b数组的结尾元素小于它
* 则整个b数组的所有元素小于它 那么有b.lenth个逆序对
*/
private long cnt = 0;
private int[] tmp; // 若在递归内声明辅助数组 会导致超时
public int InversePairs(int[] nums)
{
tmp = new int[nums.length];
mergeSort(nums, 0, nums.length - 1);
return (int) (cnt % 1000000007);
}
private void mergeSort(int[] nums, int l, int h)
{
if (h - l < 1)
return;
int m = l + (h - l) / 2;
mergeSort(nums, l, m);
mergeSort(nums, m + 1, h);
merge(nums, l, m, h);
}
private void merge(int[] nums, int l, int m, int h)
{
int i = l, j = m + 1, k = l;
while (i <= m || j <= h)
{
if (i > m)
tmp[k] = nums[j++];
else if (j > h)
tmp[k] = nums[i++];
else if (nums[i] < nums[j])
tmp[k] = nums[i++];
else
{
tmp[k] = nums[j++];
this.cnt += m - i + 1; // nums[i] >= nums[j],说明 nums[i...mid]
// 都大于 nums[j]
}
k++;
}
for (k = l; k <= h; k++)
nums[k] = tmp[k];
}
}
面试题52:两个链表的第一个公共节点
/*
* P253 两个链表的第一个公共节点
* 题目:输入两个链表 找出它们的第一个公共节点
*/
/*
* 思路一:同时从两个链表的尾部出发 找到共同节点
* 利用栈实现从尾部遍历
* 时间复杂度为O(m+n) 空间复杂度为O(m+n)
*
* 思路二:分别遍历两链表 记录长度为a和b (假设a>b) 令a-b=n
* 长的链表先走n步 两链表再同时前进 直到找到公共节点
*
* 思路三:两链表从头同时前进,a链表走完后从b链表的起点再次前进 b链表走完后从a链表的起点再次前进
* 这样一定会在公共节点汇合
* 因为假设a链表的独立段长度为a b链表的独立长度为b 两链表的公共段长度为c
* 有a+c+b=b+c+a
*/
public class T52
{
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2)
{
ListNode p1=pHead1;
ListNode p2=pHead2;
while(p1!=p2)
{
if(p1==null)
p1=pHead2;
else
p1=p1.next;
if(p2==null)
p2=pHead1;
else
p2=p2.next;
}
return p1;
}
}
面试题53:在排序数组中查找数字
/*
* P263面试题53:在排序数组中查找数字
* 题目:统计一个数字在排序数组中出现的次数
*/
public class T53
{
/*
* 利用二分查找找到第一个k和最后一个k的位置 相减即可
* 需要对二分查找稍作改进 在找第一个k时 若nums[index]==k 此时因为要去寻找第一个k 所以仍然去它的前半段寻找
* 而找最后一个k时 若nums[index]==k 则要去他的后半段寻找
* 那么需要两种不同的实现 不过在第一种实现的基础上 我们可以寻找第一个(k+1)出现的位置来代替最后一个k出现的位置
*/
public int GetNumberOfK(int [] array , int k)
{
int first = binarySearch(array, k);
int last = binarySearch(array, k + 1);
if(first==array.length||array[first]!=k)
return 0;
else
return last-first;
}
private int binarySearch(int nums[],int k)
{
int l=0,h=nums.length-1;
int mid=(l+h)/2;
while(l<=h)
{
if(nums[mid]>=k)
h=mid-1;
else
l=mid+1;
mid = (l + h)/2;
}
return l;
}
}
面试题54:二叉搜索树的第k大节点
/*
* P269面试题54:二叉搜索树的第k大节点
* 题目:给定一棵二叉搜索树,请找出其中第k大的节点
*/
public class T54
{
private TreeNode res;
private int count=0;
TreeNode KthNode(TreeNode pRoot, int k)
{
inOrder(pRoot,k);
return res;
}
private void inOrder(TreeNode node,int k)
{
if(node==null||count>=k)
return;
inOrder(node.left,k);
count++;
if(count==k)
res=node;
inOrder(node.right,k);
}
}
面试题55.1:二叉树的深度
/*
* P271面试题55:二叉树的深度
* 题目:输入一棵二叉树的根节点,求该树的深度
*/
public class T55_1
{
/*
* 如果一棵树只有一个节点 那么它的深度为1
* 如果根节点只有左子树 那么树的深度是其左子树的深度+1
* 如果根节点有左右子树,那么树的深度是左右子树中深度的较大值+1
* 递归处理
*/
public int TreeDepth(TreeNode root)
{
if(root==null)
return 0;
int Tleft=TreeDepth(root.left);
int Tright=TreeDepth(root.right);
return Math.max(Tleft+1,Tright+1);
}
}
面试题55.2平衡二叉树
/*
* P273面试题55.2 平衡二叉树
* 题目:输入一棵二叉树的根节点 判断该数是不是平衡二叉树 即任意左右子树的深度相差不超过1
*/
public class T55_2
{
/*
* 调用上一题的TreeDepth函数 可以简单的完成
* 但是会重复计算多次下层节点
* 如果改为从下往上遍历,借助后序遍历算法,在遍历到一个节点时,已经遍历了他的左右子树
* 那么在遍历每个节点时记录他的深度,就可以在遍历的同时判断是否是平衡的
*/
private boolean flag=true;
public boolean IsBalanced_Solution(TreeNode root)
{
height(root);
return flag;
}
private int height(TreeNode node)
{
if(node==null)
return 0;
int left=height(node.left);
int right=height(node.right);
if(Math.abs(left-right)>1)
flag=false;
return Math.max(left+1, right+1);
}
}
面试题56:数组中只出现一次的两个数字
/*
* P275面试题56 数组中只出现一次的数字
* 题目:一个整形数组里除两个数字之外,其他数字都出现了两次(或偶数次)
*/
public class T56
{
//num1,num2分别为长度为1的数组。传出参数
//将num1[0],num2[0]设置为返回结果
/*
* 用异或来使出现偶数次的数字抵消
* 异或的结果中必定存在1
* 以第一个1出现的位置为分界线将所有数据分为两组
* 一组为该位为1 另一组为该位为0
* 分别异或这两组 因为相同的数字必定在同一组
* 而不同的数字由于这一位不相同 所以在不同的组
* 这样两组异或所剩下的元素为结果
*/
public void FindNumsAppearOnce(int [] array,int num1[] , int num2[])
{
int num=0;
for(int i=0;i<array.length;i++)
{
num=num^array[i];
}
int count=1;//标志位 记录第一个1出现的位置
while((num&count)==0)
{
count=count<<1;
}
int x1=0;
int x2=0;
for(int i=0;i<array.length;i++)
{
if((array[i]&count)==0)
x1=x1^array[i];
else
x2=x2^array[i];
}
num1[0]=x1;
num2[0]=x2;
}
}
面试题57.1:和为s的数字
import java.util.ArrayList;
/*
* P280面试题57
* 题目一:和为s的数字
* 输入一个递增排序的数组和一个数字s 在数组中查找两个数 使得他们的和正好是s
* 如果有多对数字的和等于s 则输出任意一对即可
*/
public class T57
{
/*
* 从两头开始搜索 若和>目标 则右指针左移 反之左指针右移
*/
public ArrayList<Integer> FindNumbersWithSum(int [] array,int sum)
{
ArrayList<Integer> ret=new ArrayList<>();
if(array.length==0)
return ret;
int left=0;
int right=array.length-1;
while(left<right)
{
int temp=array[left]+array[right];
if(temp==sum)
{
ret.add(array[left]);
ret.add(array[right]);
return ret;
}
if(temp>sum)
{
right--;
}
else
left++;
}
return ret;
}
}
面试题57.2 和为s的连续正数序列
import java.util.ArrayList;
/*
* P282
* 输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序
* 例如 输入15 输出1~5 4~6和7~8
*/
public class T57_2
{
/*
* 使用两个指针 初始left指向1 right指向2
* 计算left与right之间的序列和 若大于目标值 则left++ 若小于目标值 则right++
* 若找到目标值 则right++ 重复上述过程
* 直到right>sum 因为至少要2个数
*/
public ArrayList<ArrayList<Integer> > FindContinuousSequence(int sum)
{
ArrayList<ArrayList<Integer> > ret=new ArrayList<>();
int left=1;
int right=2;
int temp=3;//1+2=3
while(sum>right)
{
if(temp>sum)
{
temp-=left;
left++;
}
else if(temp<sum)
{
right++;
temp+=right;
}
else
{
ArrayList<Integer> list=new ArrayList<>();
for(int i=left;i<=right;i++)
list.add(i);
ret.add(list);
right++;
temp+=right;
}
}
return ret;
}
}
面试题58.1翻转字符串
import java.util.Stack;
/*
* P284
* 翻转单词的顺序 但是字符的顺序不变 标点符号和普通字母一样处理
* 例如 输入I am a student.
* 输出 student. a am I
*/
public class T58
{
/*
* 若允许使用额外空间 可以使用栈来解决
* 建立String类型的栈 依次入栈后栈顶到栈底为 I/am/a/student.
* 之后依次出栈即可 对于空格可以在入栈时不入 出栈时若栈不为空 则加一个空格
*/
public String ReverseSentence(String str)
{
if(str==null||str.length()==0||str.trim().length()==0)
return str;
Stack<String> reverse=new Stack<String>();
String strings[]=str.split(" ");
for(int i=0;i<strings.length;i++)
reverse.push(strings[i]);
String res=reverse.pop().toString();
while(!reverse.isEmpty())
{
res=res+" "+reverse.pop();
}
return res;
}
/*
* 若输入为数中的字符数组 且要求原地翻转
* 则用书上的解法:先翻转所有字符 再翻转每个单词
*/
public static String ReverseSentence1(String str)
{
char c[]=str.toCharArray();
reverse(c,0,c.length-1);
int left=0,right=0;
while(true)
{
if(right==c.length)
{
reverse(c,left,right-1);
break;
}
if(c[right]!=' ')
right++;
else
{
reverse(c,left,right-1);
right++;
left=right;
}
}
System.out.println(new String(c));
return null;
}
private static void reverse(char c[],int i,int j)
{
for(;i<j;i++)
{
swap(c,i,j);
j--;
}
}
private static void swap(char c[],int i,int j)
{
char temp=c[i];
c[i]=c[j];
c[j]=temp;
}
public static void main(String[] args)
{
ReverseSentence1("Wonderful") ;
}
}
面试题58.2 左旋转字符串
/*
* P286 左旋转字符串
* 把字符串前面的若干个字符转移到字符串的尾部
* 比如 输入字符串“abcdefg”和数字2
* 返回cdefgab
*/
public class T58_2
{
/*
* 将ab看作一部分 cdefg看作另一部分
* 使用上一题的reverse函数分别对两部分进行翻转 结果为bagfedc
* 再对整个字符串进行翻转 cdefgab
*/
public static String LeftRotateString(String str,int n)
{
if (n >= str.length())
return str;
char c[]=str.toCharArray();
reverse(c,0,n-1);
reverse(c,n,c.length-1);
reverse(c,0,c.length-1);
return new String(c);
}
private static void reverse(char c[],int i,int j)
{
for(;i<j;i++)
{
swap(c,i,j);
j--;
}
}
private static void swap(char c[],int i,int j)
{
char temp=c[i];
c[i]=c[j];
c[j]=temp;
}
public static void main(String[] args)
{
System.out.println(LeftRotateString("abcdefg",2));
}
}
面试题59 滑动窗口的最大值
import java.util.ArrayList;
import java.util.Deque;
import java.util.LinkedList;
/*
* P288 滑动窗口的最大值
* 给定一个数组和滑动窗口的大小 请找出所有滑动窗口里的最大值
* 例如 如果输入数组 {2,3,4,2,6,2,5,1}及滑动窗口的大小3
* 那么一共存在6个滑动窗口 它们的最大值分别为{4,4,6,6,6,5}
*/
public class T59
{
/*
* 使用双端队列Deque实现 滑动窗口的最大值总是保存在队列头部 队列里的数据总是从大到小排列
* 在这个队列中存入的并非是实际的数字 而是它在数组中的下标 这样就可以判断某个数是否已经脱离滑动窗口
* 如果当前数字大于队列尾,则删除队列尾,直到当前数字小于等于队列尾,或者队列空
*/
public ArrayList<Integer> maxInWindows(int[] num, int size)
{
ArrayList<Integer> res = new ArrayList<>();
if (num == null || num.length == 0 || size == 0 || size > num.length)
{
return res;
}
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < num.length; i++)
{
if (!deque.isEmpty())
{
if (i >= deque.peek() + size)
{
deque.pop();
}
while (!deque.isEmpty() && num[i] >= num[deque.getLast()])
{
deque.removeLast();
}
}
deque.offer(i);
if (i + 1 >= size)
{
res.add(num[deque.peek()]);
}
}
return res;
}
}
面试题60 n个骰子的点数