课程说明:程序员找工作必备——必须掌握的算法面试技巧精讲课,适合所有技术求职人员,尤其是算法、数据结构较为薄弱的同学。课程精选十二章算法常考知识点,每章均配套近年名企考题练习,知识点分门别类,讲解深入浅出,使学习更系统、理解更容易、掌握更牢固
第一章 字符串和二叉树问题
课程学习
1.1二叉树打印
二叉树的层次遍历
案例1:给定一棵二叉树的头结点head,按照下图所示的格式打印出来。
要求打印格式:
1
2 3
4 5 6
7 8
解决方案:定义两个变量last和nlast,last表示当前正在打印的行的最右节点,nlast表示下一行的最右节点,关键的问题是如何更新last和nlast的值
二叉树序列化与反序列化
1.二叉树->字符串(序列化)
2.字符串->二叉树(反序列化)
序列化的方式:
1.根据先序遍历序列化
2.根据中序遍历序列化
3.根据后序遍历序列化
4.根据层次遍历序列化
案例2:(3!或者#!代表节点,什么方式序列化,就用什么方式反序列话)
随堂习题:
1.2二叉树打印练习题
有一棵二叉树,请设计一个算法,按照层次打印这棵二叉树。
给定二叉树的根结点root,请返回打印结果,结果按照每一层一个数组进行储存,所有数组的顺序按照层数从上往下,且每一层的数组内元素按照从左往右排列。保证结点数小于等于500。
算法设计代码实现:
1 import java.util.LinkedList; 2 3 class TreeNode { 4 int val = 0; 5 TreeNode left = null; 6 TreeNode right = null; 7 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 } 12 13 public class TreePrinter { 14 public int[][] printTree(TreeNode root) { 15 // write code here 16 // 辅助队列queue 17 LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); 18 // 当前正在访问的节点 19 TreeNode cur = root; 20 // 当前正在打印的行的最右节点 21 TreeNode last = root; 22 // 下一行要打印的最右节点 23 TreeNode nlast = root; 24 if (cur == null) { 25 return null; 26 } 27 // result用于存放已经遍历过得节点,每一层对应一个集合 28 LinkedList<LinkedList<TreeNode>> result = new LinkedList<LinkedList<TreeNode>>(); 29 // curReslut表示当前遍历的节点的集合,用来存放一层的节点 30 LinkedList<TreeNode> curResult = new LinkedList<TreeNode>(); 31 queue.add(root); 32 while (queue.size() != 0) { 33 cur = queue.poll(); 34 // 存放每一层的节点 35 curResult.add(cur); 36 if (cur.left != null) { 37 queue.add(cur.left); 38 nlast = cur.left; 39 } 40 if (cur.right != null) { 41 queue.add(cur.right); 42 nlast = cur.right; 43 } 44 // 一层遍历结束 45 if (cur == last) { 46 result.add(curResult); 47 // 更新last的值 48 last = nlast; 49 curResult = new LinkedList<TreeNode>(); 50 } 51 } 52 // 用二维数组保存每一层节点 53 int[][] resultArr = new int[result.size()][]; 54 for (int i = 0; i < result.size(); i++) { 55 resultArr[i] = new int[result.get(i).size()]; 56 for (int j = 0; j < resultArr[i].length; j++) { 57 resultArr[i][j] = result.get(i).get(j).val; 58 } 59 } 60 return resultArr; 61 } 62 63 }
1.3字符串
三个知识点:
1.字符串局部旋转函数的思路(SwardForOffer上有相应的练习题目-旋转字符串)
2.拼接两个字符串,选出两个字符串拼接之后字典序最小的拼接方式
3.KMP算法
随堂练习
1.4两串旋转练习题
如果对于一个字符串A,将A的前面任意一部分挪到后边去形成的字符串称为A的旋转词。比如A="12345",A的旋转词有"12345","23451","34512","45123"和"51234"。对于两个字符串A和B,请判断A和B是否互为旋转词。
给定两个字符串A和B及他们的长度lena,lenb,请返回一个bool值,代表他们是否互为旋转词。
返回:true
算法设计代码实现:
1 import java.util.*; 2 3 public class Rotation { 4 public boolean chkRotation(String A, int lena, String B, int lenb) { 5 // write code here 6 if(lena!=lenb){ 7 return false; 8 } 9 String AA=A+A; 10 if(AA.contains(B)){ 11 return true; 12 }else{ 13 return false; 14 } 15 } 16 }
第二章 排序
详细介绍常见的排序算法过程,以及各个排序算法稳定性、时间和空间复杂度,当然还有常见面试题目的讲解
2.1排序(1)
时间复杂度为O(n2)的排序算法:冒泡排序,选择排序,插入排序
时间复杂度为O(nlogn)的排序算法:归并排序,快速排序,堆排序,希尔排序
空间复杂度为O(1)的排序算法:选择排序,插入排序,冒泡排序,希尔排序,堆排序
空间复杂度为O(logN)~O(n)的排序算法:快速排序
空间复杂度为O(n)的排序算法:归并排序
空间复杂度为O(M)的排序算法:基数排序和计数排序(M为选择的桶的数量)
排序算法的稳定性:(有重复元素的情况i容易出现不稳定),
稳定的排序:其他的
不稳定的排序:选择排序(2,2,2,1),堆排序(5,5,5),快速排序(4,3,3,3,5),希尔排序(5,1,1,5))
2.2节 冒泡排序练习题
对于一个int数组,请编写一个冒泡排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class BubbleSort { 4 public int[] bubbleSort(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 for(int i=0;i<n-1;i++){ 10 for(int j=0;j<n-1;j++){ 11 if(A[j]>A[j+1]){ 12 int temp=A[j]; 13 A[j]=A[j+1]; 14 A[j+1]=temp; 15 } 16 } 17 } 18 return A; 19 } 20 }
2.3节 选择排序练习题
对于一个int数组,请编写一个选择排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class SelectionSort { 4 public int[] selectionSort(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 for(int i=0;i<n-1;i++){ 10 for(int j=i+1;j<n;j++){ 11 if(A[i]>A[j]){ 12 int temp=A[i]; 13 A[i]=A[j]; 14 A[j]=temp; 15 } 16 } 17 } 18 return A; 19 } 20 }
2.4节 插入排序练习题
对于一个int数组,请编写一个插入排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class InsertionSort { 4 public int[] insertionSort(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 //我这里使用的无哨兵的插入排序 10 for(int i=1;i<n;i++){ 11 //当不满足if条件时,直接忽略判断,可以直接到数组中的i位置,相当于continue 12 if(A[i]<A[i-1]){ 13 int tempValue=A[i]; 14 int j; 15 for(j=i-1;j>=0&&tempValue<A[j];j--){ 16 A[j+1]=A[j]; 17 } 18 A[j+1]=tempValue; 19 } 20 } 21 return A; 22 } 23 }
2.5节 归并排序练习题
对于一个int数组,请编写一个归并排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class MergeSort { 4 public int[] mergeSort(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 10 sort(A,0,n-1); 11 return A; 12 } 13 public void sort(int[] A,int left,int right){ 14 if(left>=right){ 15 return ; 16 } 17 int mid=(left+right)/2; 18 sort(A,left,mid); 19 sort(A,mid+1,right); 20 merge(A,left,mid,right); 21 22 } 23 public void merge(int[] A,int left,int mid,int right){ 24 //定义一个辅助数组用来存放归并两个有序子数组的结果 25 int[] tempArr=new int[A.length]; 26 int leftFirst=left; 27 int leftEnd=mid; 28 int rightFirst=mid+1; 29 int rightEnd=right; 30 int record=left; 31 //指向辅助数组 32 int cur=left; 33 while(leftFirst<=leftEnd&&rightFirst<=rightEnd){ 34 if(A[leftFirst]<=A[rightFirst]){ 35 tempArr[cur++]=A[leftFirst++]; 36 }else{ 37 tempArr[cur++]=A[rightFirst++]; 38 } 39 } 40 while(leftFirst<=leftEnd){ 41 tempArr[cur++]=A[leftFirst++]; 42 } 43 while(rightFirst<=rightEnd){ 44 tempArr[cur++]=A[rightFirst++]; 45 } 46 while(left<=right){ 47 A[left]=tempArr[left++]; 48 } 49 50 } 51 }
2.6节 快速排序练习题
对于一个int数组,请编写一个快速排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class QuickSort { 4 public int[] quickSort(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 quick(A,0,n-1); 10 11 return A; 12 } 13 public void quick(int[] A,int low,int high){ 14 if(low<high){ 15 16 int pivot=partition(A,low,high); 17 quick(A,low,pivot-1); 18 quick(A,pivot+1,high); 19 } 20 } 21 public int partition(int[] A,int low,int high){ 22 //假设第一个基准值为A[0] 23 int key=A[low]; 24 while(low<high){ 25 while(low<high&&key<=A[high]){ 26 high--; 27 } 28 if(low<high){ 29 A[low]=A[high]; 30 low++; 31 } 32 while(low<high&&key>=A[low]){ 33 low++; 34 } 35 if(low<high){ 36 A[high]=A[low]; 37 high--; 38 } 39 } 40 A[low]=key; 41 return low; 42 } 43 }
2.7节 堆排序练习题
对于一个int数组,请编写一个堆排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class HeapSort { 4 public int[] heapSort(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 //建立初始大根堆 10 A=buildHeap(A); 11 for(int i=n-1;i>0;i--){ 12 //交换堆顶元素于堆中的最后一个节点 13 int temp=A[0]; 14 A[0]=A[i]; 15 A[i]=temp; 16 //将剩余的元素调整成堆 17 adjustHeap(A,0,i); 18 } 19 return A; 20 } 21 private static int[] buildHeap(int A[]){ 22 for(int i=(A.length-1)/2;i>=0;i--){ 23 //从最后一个非叶子节点开始调整堆成为大根堆 24 adjustHeap(A,i,A.length); 25 } 26 return A; 27 } 28 private static void adjustHeap(int[] A,int cur,int len){ 29 //保存正在被调整的节点 30 int tempValue=A[cur]; 31 for(int i=2*cur+1;i<len;i=i*2+1){ 32 if(i!=len-1&&A[i]<A[i+1]){ 33 i++; 34 } 35 if(tempValue>A[i]){ 36 break; 37 }else{ 38 A[cur]=A[i]; 39 //改变索引,下一个要调整的节点 40 cur=i; 41 } 42 } 43 A[cur]=tempValue; 44 } 45 }
2.8节 希尔排序练习题
对于一个int数组,请编写一个堆排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class ShellSort { 4 public int[] shellSort(int[] A, int n) { 5 shellSort(A); 6 return A; 7 } 8 public void shellSort(int arr[]) { 9 if (arr == null || arr.length == 0) { 10 return; 11 } 12 // 增量序列 13 int incrementValue = arr.length / 2; 14 while (incrementValue >= 1) { 15 for (int i = 0; i < arr.length; i++) { 16 for (int j = i + incrementValue; j < arr.length; j += incrementValue) { 17 int temp = arr[j]; 18 int k; 19 for (k = j - incrementValue; k >= 0 && arr[k] > temp; k = k 20 - incrementValue) { 21 arr[k + incrementValue] = arr[k]; 22 } 23 arr[k + incrementValue] = temp; 24 } 25 } 26 // 改变递增序列的值 27 incrementValue /= 2; 28 } 29 } 30 }
2.9排序(2)
时间复杂度为O(n)的排序算法,不是基于比较的排序算法,它们的函数原型来自于桶排序,常见的分为计数排序和基数排序
【关于排序算法的补充说明】
(1)排序算法无绝对优劣
例如对人的年龄或者身高排序,数据范围通常都比较小,可以采用计数排序,
但是对于均与分布的整数,计数排序就不适合了。
除非面试题特别说明,否则认为要排序的数据范围是均匀分布的。
(2)为什么叫快速排序
并不代表它比堆排序和归并排序优良,在最好情况下,它的渐进复杂度和两者是一样的,
只不过快速排序的常量系数(1.38)比较小而已。
(3)工程上的排序
综合了多种排序算法,当元素个数比较少的情况下,会选择插入排序等,
元素个数比较多的情况下,会选择时间复杂度为O(N*logN)的快速排序等算法。
2.10节 计数排序练习题
对于一个int数组,请编写一个堆排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 //计数排序:函数原型:桶排 4 public class CountingSort { 5 public int[] countingSort(int[] A, int n) { 6 // write code here 7 if(A==null||n==0){ 8 return null; 9 } 10 int max=A[0]; 11 int min=A[0]; 12 for(int i=1;i<n;i++){ 13 if(max<A[i]){ 14 max=A[i]; 15 } 16 if(min>A[i]){ 17 min=A[i]; 18 } 19 } 20 //需要的桶的数量为max-min+1; 21 int CountArr[]=new int[max-min+1]; 22 //将需要排列数组元素放入桶中 23 for(int i=0;i<n;i++){ 24 CountArr[A[i]-min]++; 25 } 26 int index=0; 27 for(int i=0;i<CountArr.length;i++){ 28 while(CountArr[i]-->0){ 29 A[index++]=min+i; 30 } 31 } 32 return A; 33 } 34 }
2.11节 基数排序练习题
对于一个int数组,请编写一个堆排序算法,对数组元素排序。
给定一个int数组A及数组的大小n,请返回排序后的数组。
[1,2,3,5,2,3],6
[1,2,2,3,3,5]
算法设计代码实现:
1 import java.util.*; 2 3 public class RadixSort { 4 public int[] radixSort(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 //指向数字的第几位:范围为1-4 10 int bitNum=1; 11 //数组countArr存放每一次排序之后的结果,比如说比较个位时,数字为3出现的次数 12 int countArr[]=new int[10]; 13 //求每一位的数字时用到的除数,div按照10*div的规律增长,即1,10,100,1000 14 int div=1; 15 //高维表示0-9号桶,低维表示桶中对应关键子出现的次数(最大为n) 16 //给出每一个数组元素经过一趟排序在哪一个桶中 17 int number[][]=new int[10][n]; 18 while(bitNum<=4){ 19 for(int i=0;i<n;i++){ 20 //分离关键字 21 int lsd=(A[i]/div)%10; 22 number[lsd][countArr[lsd]++]=A[i]; 23 } 24 int index=0; 25 //将数据从桶中拿出来,进行下一位关键字的比较 26 for(int i=0;i<10;i++){ 27 28 if(countArr[i]>0){ 29 for(int j=0;j<countArr[i];j++){ 30 A[index++]=number[i][j]; 31 } 32 } 33 countArr[i]=0; 34 } 35 bitNum++; 36 div=div*10; 37 } 38 return A; 39 40 } 41 }
2.12排序(3)
1、小范围排序
2、重复值判断
3、有序数组合并
2.13小范围排序练习题
已知一个几乎有序的数组,几乎有序是指,如果把数组排好顺序的话,每个元素移动的距离可以不超过k,并且k相对于数组来说比较小。请选择一个合适的排序算法针对这个数据进行排序。
给定一个int数组A,同时给定A的大小n和题意中的k,请返回排序后的数组。
[2,1,4,3,6,5,8,7,10,9],10,2
返回:[1,2,3,4,5,6,7,8,9,10]
算法设计代码实现(使用优化后的堆排序):
---------------------------------------------------------------------------------------------------------------------------------------------------------
2.14重复值判断练习题
请设计一个高效算法,判断数组中是否有重复值。必须保证额外空间复杂度为O(1)。
给定一个int数组A及它的大小n,请返回它是否有重复值。
[1,2,3,4,5,5,6],7
返回:true
2.15有序数组合并练习题
有两个从小到大排序以后的数组A和B,其中A的末端有足够的缓冲空容纳B。请编写一个方法,将B合并入A并排序
给定两个有序int数组A和B,A中的缓冲空用0填充,同时给定A和B的真实大小int n和int m,请返回合并后的数组。
算法设计代码实现:A数组和B数组从后向前遍历
1 import java.util.*; 2 3 public class Merge { 4 public int[] mergeAB(int[] A, int[] B, int n, int m) { 5 // write code here 6 int indexCombine=n+m-1; 7 int indexA=n-1; 8 int indexB=m-1; 9 while(indexA>=0&&indexB>=0){ 10 if(A[indexA]>B[indexB]){ 11 A[indexCombine--]=A[indexA--]; 12 }else if(A[indexA]==B[indexB]){ 13 A[indexCombine--]=A[indexA--]; 14 indexB--; 15 }else{ 16 A[indexCombine--]=B[indexB--]; 17 } 18 } 19 //把B数组中剩余的部分添加到A数组中 20 while(indexB>=0){ 21 A[indexCombine--]=B[indexB--]; 22 } 23 return A; 24 } 25 }
2.16排序(4)
四个案例:
1)荷兰国旗问题:三色排序
2)有序矩阵查找
3)最短子数组
4)相邻两数最大差值
2.17三色排序练习题
有一个只由0,1,2三种元素构成的整数数组,请使用交换、原地排序而不是使用计数进行排序。
给定一个只含0,1,2的整数数组A及它的大小,请返回排序后的数组。保证数组大小小于等于500。
[0,1,1,0,2,2],6
返回:[0,0,1,1,2,2]
1 import java.util.*; 2 3 public class ThreeColor { 4 public int[] sortThreeColor(int[] A, int n) { 5 // write code here 6 //基于快速排序的思想 7 if(A==null||n==0){ 8 return null; 9 } 10 int start=-1; 11 int end=n; 12 int index=0; 13 while(index<end){ 14 if(A[index]==0){ 15 swap(A,index++,++start); 16 }else if(A[index]==2){ 17 swap(A,index,--end); 18 }else{ 19 index++; 20 } 21 } 22 return A; 23 } 24 private void swap(int arr[],int i,int j){ 25 int temp=arr[i]; 26 arr[i]=arr[j]; 27 arr[j]=temp; 28 } 29 30 }
2.18有序矩阵查找练习题
现在有一个行和列都排好序的矩阵,请设计一个高效算法,快速查找矩阵中是否含有值x。
给定一个int矩阵mat,同时给定矩阵大小nxm及待查找的数x,请返回一个bool值,代表矩阵中是否存在x。所有矩阵中数字及x均为int范围内整数。保证n和m均小于等于1000。
[[1,2,3],[4,5,6],[7,8,9]],3,3,10
返回:false
算法设计:
1 import java.util.*; 2 3 public class Finder { 4 public boolean findX(int[][] mat, int n, int m, int x) { 5 // write code here 6 //使用矩阵的右上角元素作为比较的关键字 7 boolean found=false; 8 if(mat==null||mat.length==0||n<0||m<0){ 9 return found; 10 } 11 int row=0; 12 int column=m-1; 13 while(row<n&&column>=0){ 14 if(mat[row][column]==x){ 15 found=true; 16 break; 17 }else if(mat[row][column]>x){ 18 column--; 19 }else{ 20 row++; 21 } 22 } 23 return found; 24 } 25 }
2.19最短子数组查找练习题
对于一个数组,请设计一个高效算法计算需要排序的最短子数组的长度。
给定一个int数组A和数组的大小n,请返回一个二元组,代表所求序列的长度。(原序列位置从0开始标号,若原序列有序,返回0)。保证A中元素均为正整数。
[1,4,6,5,9,10],6
返回:2
思路分析:
从左开始遍历数组,记录下已经遍历部分的最大值max,如果遍历的数值小于max时,记录这种情况下最右的位置right。
从左开始遍历数组,记录下已经遍历部分的最大值min,如果遍历的数值大于min时,记录这种情况下最右的位置left。
算法设计:
1 import java.util.*; 2 3 public class Subsequence { 4 public int shortestSubsequence(int[] A, int n) { 5 // write code here 6 if(A==null||n<2){ 7 return 0; 8 } 9 //从左向右遍历数组,寻找最大值,只考虑比当前值大的情况,找出这种情况最右边的位置 10 //从右向左遍历数组,寻找最小值,只考虑比当前值小的情况,找出这种情况最左边的位置 11 //遍历结束,最左值和最右值中间的数就是需要排序的最小的子数组 12 int max=A[0]; 13 int min=A[n-1]; 14 int maxIndex=0; 15 int minIndex=0; 16 //先从左向右遍历 17 for(int i=1;i<n;i++){ 18 if(max>A[i]){ 19 maxIndex=i; 20 } 21 if(max<A[i]){ 22 max=A[i]; 23 } 24 } 25 //从右向左遍历 26 for(int i=n-2;i>=0;i--){ 27 if(min>A[i]){ 28 min=A[i]; 29 } 30 if(min<A[i]){ 31 minIndex=i; 32 } 33 } 34 if(maxIndex==0||minIndex==0){ 35 return 0; 36 } 37 return maxIndex-minIndex+1; 38 } 39 }
2.20相邻两数最大差值
有一个整形数组A,请设计一个复杂度为O(n)的算法,算出排序后相邻两数的最大差值。
给定一个int数组A和A的大小n,请返回最大的差值。保证数组元素多于1个。
对于两棵彼此独立的二叉树A和B,请编写一个高效算法,检查A中是否存在一棵子树与B树的拓扑结构完全相同。
给定两棵二叉树的头结点A和B,请返回一个bool值,代表A中是否存在一棵同构于B的子树。
1 import java.util.*; 2 3 class TreeNode { 4 int val = 0; 5 TreeNode left = null; 6 TreeNode right = null; 7 public TreeNode(int val) { 8 this.val = val; 9 } 10 } 11 public class IdenticalTree { 12 public boolean chkIdentical(TreeNode A, TreeNode B) { 13 // write code here 14 String strA = serialize(A); 15 String strB = serialize(B); 16 return strA.contains(strB); 17 } 18 19 public String serialize(TreeNode root) { 20 StringBuilder builder=new StringBuilder(); 21 if (root == null) { 22 return "# "; 23 } 24 return builder.append(root.val).append(" ").append(serialize(root.left)).append(serialize(root.right)).toString(); 25 //return root.val + " " + serialize(root.left) + serialize(root.right); 26 } 27 28 }
3.3词语变形练习题
对于两个字符串A和B,如果A和B中出现的字符种类相同且每种字符出现的次数相同,则A和B互为变形词,请设计一个高效算法,检查两给定串是否互为变形词。
给定两个字符串A和B及他们的长度,请返回一个bool值,代表他们是否互为变形词。
"abc",3,"bca",3
返回:true
1 import java.util.*; 2 3 public class Transform { 4 public boolean chkTransform(String A, int lena, String B, int lenb) { 5 // write code here 6 if(A==null||B==null||lena==0||lenb==0){ 7 return false; 8 } 9 //字符串转换为对应的字符数组 10 char[] chas1=A.toCharArray(); 11 char[] chas2=B.toCharArray(); 12 //建立哈希表,字符作为键值,字符出现的次数为哈希表的值 13 int map[]=new int[256]; 14 for(int i=0;i<lena;i++){ 15 map[chas1[i]]++; 16 } 17 for(int i=0;i<lenb;i++){ 18 //这种情况说明B中有的A中没有,或者A中对应的字符不够减,即对应字符的个数不相同 19 if(map[chas2[i]]--==0){ 20 return false; 21 } 22 } 23 return true; 24 } 25 }
3.4字符串(2)
需要掌握的题目技巧:局部逆序
1)两串旋转
2)句子的逆序
3)字符串移位
4)拼接最小字典序
3.5两串旋转练习题
如果对于一个字符串A,将A的前面任意一部分挪到后边去形成的字符串称为A的旋转词。比如A="12345",A的旋转词有"12345","23451","34512","45123"和"51234"。对于两个字符串A和B,请判断A和B是否互为旋转词。
给定两个字符串A和B及他们的长度lena,lenb,请返回一个bool值,代表他们是否互为旋转词。
"cdab",4,"abcd",4
返回:true
1 import java.util.*; 2 3 public class Rotation { 4 public boolean chkRotation(String A, int lena, String B, int lenb) { 5 // write code here 6 if(lena!=lenb){ 7 return false; 8 } 9 String AA=A+A; 10 if(AA.contains(B)){ 11 return true; 12 }else{ 13 return false; 14 } 15 } 16 }
3.6句子的逆序练习题
对于一个字符串,请设计一个算法,只在字符串的单词间做逆序调整,也就是说,字符串由一些由空格分隔的部分组成,你需要将这些部分逆序。
给定一个原字符串A和他的长度,请返回逆序后的字符串。
"dog loves pig",13
返回:"pig loves dog"
1 import java.util.*; 2 3 public class Reverse { 4 public String reverseSentence(String A, int n) { 5 if (A == null || n == 0) { 6 return A; 7 } 8 char[] s = A.toCharArray(); 9 rotateWord(s); 10 //返回字符数组的字符串形式 11 return String.valueOf(s); 12 } 13 14 15 public void rotateWord(char[] chas) { 16 if (chas == null || chas.length == 0) { 17 return; 18 } 19 reverse(chas, 0, chas.length - 1); 20 int start = 0; 21 int end=0; 22 int i=0; 23 while(i < chas.length) { 24 //找一个单词开始的位置 25 while(i<chas.length&&chas[i]==' '){ 26 i++; 27 start=i; 28 } 29 //找一个单词结束的位置 30 while(i<chas.length&&chas[i]!=' '){ 31 i++; 32 end=i; 33 } 34 reverse(chas,start,end-1); 35 } 36 } 37 38 //真正执行翻转的代码 39 public void reverse(char[] chas, int start, int end) { 40 char tmp = 0; 41 while (start < end) { 42 tmp = chas[start]; 43 chas[start] = chas[end]; 44 chas[end] = tmp; 45 start++; 46 end--; 47 } 48 } 49 }
3.7字符串移位练习题
对于一个字符串,请设计一个算法,将字符串的长度为len的前缀平移到字符串的最后。
给定一个字符串A和它的长度,同时给定len,请返回平移后的字符串。
"ABCDE",5,3
返回:"DEABC"
1 import java.util.*; 2 3 public class Translation { 4 public String stringTranslation(String A, int n, int len) { 5 // write code here 6 if(A==null||n==0){ 7 return A; 8 } 9 if(len<0||len>n){ 10 return null; 11 } 12 char[] chas=A.toCharArray(); 13 rolateString(chas,len); 14 return String.valueOf(chas); 15 } 16 private void rolateString(char chas[],int len){ 17 if(chas==null||len<=0||len>=chas.length){ 18 return; 19 } 20 //先局部翻转 21 reverse(chas,0,len-1); 22 reverse(chas,len,chas.length-1); 23 //翻转整个字符串 24 reverse(chas,0,chas.length-1); 25 } 26 private void reverse(char chas[],int left,int right){ 27 while(left<right){ 28 char temp=chas[left]; 29 chas[left]=chas[right]; 30 chas[right]=temp; 31 left++; 32 right--; 33 } 34 } 35 }
3.8拼接最小字典序练习题
对于一个给定的字符串数组,请找到一种拼接顺序,使所有小字符串拼接成的大字符串是所有可能的拼接中字典序最小的。
给定一个字符串数组strs,同时给定它的大小,请返回拼接成的串。
["abc","de"],2
"abcde"
1 import java.util.*; 2 3 4 public class Prior { 5 //定义比较器:要学会使用比较器 6 //这里需要指定需要比较的对象类型,比如这里的String 7 public class MyComparator implements Comparator<String>{ 8 @Override 9 public int compare(String A,String B){ 10 return (A+B).compareTo(B+A); 11 } 12 } 13 public String findSmallest(String[] strs, int n) { 14 // write code here 15 if(strs==null||n==0){ 16 return null; 17 } 18 StringBuilder builder=new StringBuilder(); 19 Arrays.sort(strs,new MyComparator()); 20 for(int i=0;i<strs.length;i++){ 21 builder.append(strs[i]); 22 } 23 return builder.toString(); 24 } 25 }
3.9字符串(3)
1)替换空格(先统计空格数量,从后向前进行替换);时间复杂度O(n),空间复杂度O(n)
2)合法括号序列判断(左括号,num++,右括号,num--);时间复杂度O(n),空间复杂度O(1)
3)最长无重复子串:时间复杂度O(n),空间复杂度O(n);
3.10 替换空格练习题
请编写一个方法,将字符串中的空格全部替换为“%20”。假定该字符串有足够的空间存放新增的字符,并且知道字符串的真实长度(小于等于1000),同时保证字符串由大小写的英文字母组成。
给定一个string iniString 为原始的串,以及串的长度 int len, 返回替换后的string。
"Mr John Smith”,13
返回:"Mr%20John%20Smith"
”Hello World”,12
返回:”Hello%20%20World”
解法1:
1 import java.util.*; 2 3 public class Replacement { 4 public String replaceSpace(String iniString, int length) { 5 // write code here 6 if(iniString==null||length==0){ 7 return null; 8 } 9 if(iniString==""){ 10 return iniString; 11 } 12 StringBuilder builder=new StringBuilder(); 13 for(int i=0;i<length;i++){ 14 if(iniString.charAt(i)!=' '){ 15 builder.append(iniString.charAt(i)); 16 }else{ 17 builder.append('%'); 18 builder.append('2'); 19 builder.append('0'); 20 } 21 } 22 return builder.toString(); 23 } 24 25 }
解法二:先统计空格数量,再从后向前进行替换
1 import java.util.*; 2 3 public class Replacement { 4 5 public String replaceSpace(String iniString, int length) { 6 // write code here 7 if(iniString==null||length<=0){ 8 return iniString; 9 } 10 char[] chas=iniString.toCharArray(); 11 //统计空格的数量 12 int numberOfBlank=0; 13 //原始字符串的长度 14 int originalLength=length; 15 //先遍历一遍字符串统计其中的空格数目 16 for(int i=0;i<chas.length;i++){ 17 if(chas[i]==' '){ 18 numberOfBlank++; 19 } 20 } 21 if(numberOfBlank==0){ 22 return iniString; 23 } 24 //替换后字符串的总长度 25 int newLength=originalLength+2*numberOfBlank; 26 char chasCopy[]=new char[newLength]; 27 28 //定义两个变量,分别指向替换前后的两个字符串的最后位置 29 int originalIndex=originalLength-1; 30 31 int newIndex=newLength-1; 32 for(int i=originalIndex;i>=0;i--){ 33 if(chas[i]==' '){ 34 chasCopy[newIndex--]='0'; 35 chasCopy[newIndex--]='2'; 36 chasCopy[newIndex--]='%'; 37 }else{ 38 chasCopy[newIndex--]=chas[i]; 39 } 40 } 41 return String.valueOf(chasCopy); 42 } 43 44 }
3.11 合法括号序列判断练习题
对于一个字符串,请设计一个算法,判断其是否为一个合法的括号串。
给定一个字符串A和它的长度n,请返回一个bool值代表它是否为一个合法的括号串。
"(()())",6
返回:true
"()a()()",7
返回:false
"()(()()",7
返回:false
1 import java.util.*; 2 3 public class Parenthesis { 4 public boolean chkParenthesis(String A, int n) { 5 6 // write code here 7 if(A==null||n==0){ 8 return true; 9 } 10 //从左到右遍历字符串 11 int num=0; 12 char chas[]=A.toCharArray(); 13 for(int i=0;i<n;i++){ 14 15 if(chas[i]=='('){ 16 num++; 17 }else if(chas[i]==')'){ 18 num--; 19 }else{ 20 continue; 21 } 22 } 23 if(num==0){ 24 return true; 25 }else{ 26 return false; 27 } 28 } 29 }
3.12 最长无重复子串练习题
对于一个字符串,请设计一个高效算法,找到字符串的最长无重复字符的子串长度。
给定一个字符串A及它的长度n,请返回它的最长无重复字符子串长度。保证A中字符全部为小写英文字符,且长度小于等于500。
"aabcb",5
返回:3
1 import java.util.*; 2 3 public class DistinctSubstring { 4 public int longestSubstring(String A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return 0; 8 } 9 //可以实现时间复杂度O(n),空间复杂度为O(n) 10 char chas[]=A.toCharArray(); 11 //模拟一个哈希表,记录每种字符之前出现的位置 12 int map[]=new int[256]; 13 //初始化map数组 14 for(int i=0;i<256;i++){ 15 map[i]=-1; 16 } 17 //当前找到的最长无重复子串的长度 18 int len=0; 19 //pre表示当前字符出现的最靠右的位置 20 int pre=-1; 21 int cur=0; 22 for(int i=0;i<n;i++){ 23 pre=Math.max(pre,map[chas[i]]); 24 //当前找到的无重复字符序列的长度 25 cur=i-pre; 26 //比较cur与之前找到的最长长度子串的大小 27 len=Math.max(len,cur); 28 //更新chas[i]最新出现的位置 29 map[chas[i]]=i; 30 } 31 return len; 32 } 33 34 }
第4章 队列和栈
队列队列、栈和双端队列结构的介绍,宽度优先遍历与深度优先遍历的介绍,常见的代码面试题目。、栈和双端队列结构的介绍,宽度优先遍历与深度优先遍历的介绍,常见的代码面试题目。
4.1 队列和栈(1)
栈和队列的基本性质:
栈:后进先出
队列:先进先出
栈结构的基本操作:
1)pop操作
2)peek或者top操作
3)push操作
4)size操作
与栈相关的图遍历方法:深度优先遍历(DFS)
与队列相关的图遍历方法:宽度优先遍历(BFS)
4.2可查询最值的栈练习题
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
思路:使用一个辅助栈StackMin保存每一步的最小值
1 import java.util.Stack; 2 3 4 public class Solution { 5 6 //数据栈 7 private Stack<Integer> stackData; 8 //辅助栈 9 private Stack<Integer> stackMin; 10 11 public Solution(){ 12 stackData=new Stack<Integer>(); 13 stackMin=new Stack<Integer>(); 14 } 15 16 //入栈操作 17 public void push(int node) { 18 stackData.push(node); 19 if(stackMin.empty()){ 20 stackMin.push(node); 21 }else{ 22 if(node<=stackMin.peek()){ 23 stackMin.push(node); 24 } 25 } 26 } 27 //出栈操作 28 public void pop() { 29 int data=stackData.pop(); 30 if(data==stackMin.peek()){ 31 stackMin.pop(); 32 } 33 34 } 35 //返回栈顶的元素 36 public int top() { 37 return stackData.peek(); 38 } 39 //得到栈的最小元素 40 public int min() { 41 return stackMin.peek(); 42 } 43 }
4.3 队列和栈(2)
1)双栈队列:需要注意两个条件,stackPush中的数据需要一次性导入stackPop()中,stackPop()中不为空时,不能向其中压入数据,否则会导致顺序错误
2)栈的反转
3)双栈排序
4.4双栈队列练习题
编写一个类,只能用两个栈结构实现队列,支持队列的基本操作(push,pop)。
给定一个操作序列ope及它的长度n,其中元素为正数代表push操作,为0代表pop操作,保证操作序列合法且一定含pop操作,请返回pop的结果序列。
[1,2,3,0,4,0],6
返回:[1,2]
1 import java.util.Stack; 2 3 public class TwoStack { 4 public int[] twoStack(int[] ope, int n) { 5 /* 6 需要保证的两个前提:help不为空,不能向其中压入元素 7 stackData中的数据需要一次性压入help栈中 8 9 */ 10 // write code here 11 //压入数据的栈 12 Stack<Integer> stackData=new Stack<Integer>(); 13 //定义一个辅助栈 14 Stack<Integer> help=new Stack<Integer>(); 15 //返回ope中pop操作的次数 16 int popNum=0; 17 for(int i=0;i<n;i++){ 18 if(ope[i]==0){ 19 popNum++; 20 }else{ 21 stackData.push(ope[i]); 22 } 23 } 24 //定义一个数组,存储pop的结果序列,大小为popNum 25 int result[]=new int[popNum]; 26 while(!stackData.empty()){ 27 help.push(stackData.pop()); 28 } 29 for(int i=0;i<popNum;i++){ 30 result[i]=help.pop(); 31 } 32 return result; 33 } 34 }
4.5栈的反转练习题
实现一个栈的逆序,但是只能用递归函数和这个栈本身的pop操作来实现,而不能自己申请另外的数据结构。
给定一个整数数组A即为给定的栈,同时给定它的大小n,请返回逆序后的栈。
[4,3,2,1],4
返回:[1,2,3,4]
1 import java.util.*; 2 3 public class StackReverse { 4 public int[] reverseStack(int[] A, int n) { 5 // write code here 6 if(A==null||n==0){ 7 return null; 8 } 9 Stack<Integer> stack=new Stack<Integer>(); 10 for(int i=0;i<n;i++){ 11 stack.push(A[i]); 12 } 13 reverse(stack); 14 for(int i=n-1;i>=0;i--){ 15 A[i]=stack.pop(); 16 } 17 return A; 18 } 19 //定义getStack()方法,返回并删除栈底的元素 20 21 22 private int getStack(Stack<Integer> stack){ 23 24 int result=stack.pop(); 25 if(stack.empty()){ 26 return result; 27 }else{ 28 int last=getStack(stack); 29 stack.push(result); 30 return last; 31 } 32 } 33 private void reverse(Stack<Integer> stack){ 34 if(stack.empty()){ 35 return; 36 } 37 int i=getStack(stack); 38 reverse(stack); 39 stack.push(i); 40 } 41 42 43 }
4.6双栈排序练习题
请编写一个程序,按升序对栈进行排序(即最大元素位于栈顶),要求最多只能使用一个额外的栈存放临时数据,但不得将元素复制到别的数据结构中。
给定一个int[] numbers(C++中为vector<int>),其中第一个元素为栈顶,请返回排序后的栈。请注意这是一个栈,意味着排序过程中你只能访问到第一个元素。
[1,2,3,4,5]
返回:[5,4,3,2,1]
1 import java.util.Stack; 2 import java.util.ArrayList; 3 public class TwoStacks { 4 public ArrayList<Integer> twoStacksSort(int[] numbers) { 5 6 if(numbers==null||numbers.length==0){ 7 return null; 8 } 9 10 // write code here 11 Stack<Integer> stackData=new Stack<Integer>(); 12 Stack<Integer> helper=new Stack<Integer>(); 13 for(int i=0;i<numbers.length;i++){ 14 stackData.push(numbers[i]); 15 } 16 17 while(!stackData.empty()){ 18 19 int cur=stackData.pop(); 20 if(helper.empty()){ 21 helper.push(cur); 22 }else{ 23 if(cur<=helper.peek()){ 24 helper.push(cur); 25 }else{ 26 while(!helper.empty()&&cur>helper.peek()){ 27 stackData.push(helper.pop()); 28 } 29 helper.push(cur); 30 } 31 } 32 } 33 34 while(!helper.empty()){ 35 stackData.push(helper.pop()); 36 } 37 ArrayList<Integer> aList=new ArrayList<Integer>(); 38 while(!stackData.empty()){ 39 aList.add(stackData.pop()); 40 } 41 return aList; 42 43 } 44 }
4.7队列和栈
滑动窗口和数组变树
4.8滑动窗口练习题
常规方法时间复杂度O(N*W):N为数组元素个数,W为窗口大小,即从数组左部开始依次遍历窗口大小的元素,找出局部的窗口最大值
1 import java.util.*; 2 3 public class SlideWindow { 4 public int[] slide(int[] arr, int n, int w) { 5 // write code here 6 int[] res=new int[n-w+1]; 7 int index=0; 8 for(int i=0;i<=n-w;i++){ 9 res[index++]=getMax(arr,i,i+w); 10 } 11 return res; 12 } 13 14 //求数组最大值并返回 15 public int getMax(int[] arr,int start,int end){ 16 int max=arr[start]; 17 for(int i=start;i<end;i++){ 18 if(arr[i]>max){ 19 max=arr[i]; 20 } 21 } 22 return max; 23 } 24 }
最优解:时间复杂度为O(N);使用双端队列来更新窗口最大值
题目描述:
有一个整型数组 arr 和一个大小为 w 的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。 返回一个长度为n-w+1的数组res,res[i]表示每一种窗口状态下的最大值。 以数组为[4,3,5,4,3,3,6,7],w=3为例。因为第一个窗口[4,3,5]的最大值为5,第二个窗口[3,5,4]的最大值为5,第三个窗口[5,4,3]的最大值为5。第四个窗口[4,3,3]的最大值为4。第五个窗口[3,3,6]的最大值为6。第六个窗口[3,6,7]的最大值为7。所以最终返回[5,5,5,4,6,7]。
给定整形数组arr及它的大小n,同时给定w,请返回res数组。保证w小于等于n,同时保证数组大小小于等于500。
[4,3,5,4,3,3,6,7],8,3
返回:[5,5,5,4,6,7]
1 import java.util.*; 2 public class SlideWindow { 3 public int[] slide(int[] arr, int n, int w) { 4 //非法输入判断 5 if(arr==null||w<1||n<w){ 6 return null; 7 } 8 int res[]=new int[n-w+1]; 9 int index=0; 10 //需要的双端队列 11 LinkedList<Integer> qmax=new LinkedList<Integer>(); 12 for(int i=0;i<n;i++){ 13 while(!qmax.isEmpty()&&arr[qmax.peekLast()]<=arr[i]) 14 { 15 qmax.pollLast(); 16 } 17 //队列qmax中存放的是元素的索引 18 qmax.addLast(i); 19 //队头的最大值已经过期 20 if(qmax.peekFirst()<=i-w){ 21 qmax.pollFirst(); 22 } 23 if(i>=w-1){ 24 res[index++]=arr[qmax.peekFirst()]; 25 } 26 } 27 return res; 28 } 29 }
4.9数组变树练习题
第五章 链表
链表问题的高分的关键点,链表有关的常见面试题。
5.1 链表(1)
1)链表问题算法难度不高,主要考查代码实现能力
2)链表和数组都是一种线性结构:数组是一段连续的存储空间,链表空间不一定连续,是临时分配的
链表的分类:
按连接方向分类:单链表(next)和双链表(next和pre)
按照有环无环分类:普通链表和循环链表
链表问题代码实现的关键点:
1)链表调整函数的返回值类型,根据要求往往是节点类型
2)处理链表的过程中,使用画图处理清楚逻辑
3)链表问题对于边界问题处理比较严苛
关于链表插入和删除的注意事项:
1)特殊处理链表为空,或者链表长度为1的情况
2)注意插入操作的调整过程
3)注意删除操作的调整过程(头尾节点以及空节点需要特殊考虑);
5.2 环形链表插值练习题
有一个整数val,如何在节点值有序的环形链表中插入一个节点值为val的节点,并且保证这个环形单链表依然有序。
给定链表的信息,及元素的值A及对应的nxt指向的元素编号同时给定val,请构造出这个环形链表,并插入该值。
[1,3,4,5,7],[1,2,3,4,0],2
返回:{1,2,3,4,5,7}
1 import java.util.*; 2 3 4 class ListNode { 5 int val; 6 ListNode next = null; 7 8 ListNode(int val) { 9 this.val = val; 10 } 11 } 12 public class InsertValue { 13 public ListNode insert(int[] A, int[] nxt, int val) { 14 // write code here 15 ListNode node = new ListNode(val); 16 if(A==null||A.length<=0){ 17 // node.next=node; 18 return node; 19 } 20 ListNode head=new ListNode(A[0]); 21 ListNode tmp=head; 22 //构建这个链表 23 for(int i=0;i<A.length-1;i++){ 24 ListNode newNode= new ListNode(A[nxt[i]]); 25 tmp.next=newNode; 26 tmp=newNode; 27 } 28 //测试用例存在问题;此处应该是构建链表的最后一个节点指向head节点 29 // tmp.next=head; 30 31 32 ListNode pre=head; 33 ListNode cur=pre.next; 34 //正常情况应该是这样:while(cur!=head) 35 while(cur!=null){ 36 if(pre.val<=val&&val<=cur.val) 37 break; 38 pre=cur; 39 cur=cur.next; 40 } 41 42 node.next=cur; 43 pre.next=node; 44 45 if(val<head.val){ 46 return node; 47 }else{ 48 return head; 49 } 50 } 51 }
5.3访问单个节点的删除练习题
题目:实现一个算法,删除单向链表中间的某个结点,假定你只能访问该结点。给定带删除的节点,请执行删除操作,若该节点为尾节点,返回false,否则返回true
1 import java.util.*; 2 3 /* 4 public class ListNode { 5 int val; 6 ListNode next = null; 7 8 ListNode(int val) { 9 this.val = val; 10 } 11 }*/ 12 public class Remove { 13 public boolean removeNode(ListNode pNode) { 14 if (pNode == null) { 15 return false; 16 } 17 ListNode next = pNode.next; 18 if (next == null) { 19 return false; 20 } 21 pNode.val = next.val; 22 pNode.next = next.next; 23 return true; 24 } 25 }
5.4 链表(2)
5.5链表的分化练习题
对于一个链表,我们需要用一个特定阈值完成对它的分化,使得小于等于这个值的结点移到前面,大于该值的结点在后面,同时保证两类结点内部的位置关系不变。
给定一个链表的头结点head,同时给定阈值val,请返回一个链表,使小于等于它的结点在前,大于等于它的在后,保证结点值不重复。
{1,4,2,5},3
{1,2,4,5}
5.6打印两个链表的公共值练习题
现有两个升序链表,且链表中均无重复元素。请设计一个高效的算法,打印两个链表的公共值部分。
给定两个链表的头指针headA和headB,请返回一个vector,元素为两个链表的公共部分。请保证返回数组的升序。两个链表的元素个数均小于等于500。保证一定有公共值
{1,2,3,4,5,6,7},{2,4,6,8,10}
返回:[2.4.6]
5.7链表的k逆序练习题
有一个单链表,请设计一个算法,使得每K个节点之间逆序,如果最后不够K个节点一组,则不调整最后几个节点。例如链表1->2->3->4->5->6->7->8->null,K=3这个例子。调整后为,3->2->1->6->5->4->7->8->null。因为K==3,所以每三个节点之间逆序,但其中的7,8不调整,因为只有两个节点不够一组。
给定一个单链表的头指针head,同时给定K值,返回逆序后的链表的头指针。
5.8链表指定值清除练习题
现在有一个单链表。链表中每个节点保存一个整数,再给定一个值val,把所有等于val的节点删掉。
给定一个单链表的头结点head,同时给定一个值val,请返回清除后的链表的头结点,保证链表中有不等于该值的其它值。请保证其他元素的相对顺序。
{1,2,3,4,3,2,1},2
{1,3,4,3,1}
5.9链表的回文结构练习题
请编写一个函数,检查链表是否为回文。
给定一个链表ListNode* pHead,请返回一个bool,代表链表是否为回文。
{1,2,3,2,1}
返回:true
{1,2,3,2,3}
返回:false
第六章 二分搜索
6.1二分搜索(1)
二分搜索常见的应用场景
1)在有序序列中查找一个数
2)并不一定非要在有序序列中才能应用,只要可以缩小范围,都可以使用二分搜索的思想
二分搜索的常见考察点:
1)对于边界条件的考察以及代码的实现能力
二分搜索的重要提醒:mid=(left+right)/2,此时mid在left和right是很大的值的时候可能会产生溢出,更安全的方式mid=left+(right-left)/2
6.2局部最小值位置练习题
定义局部最小的概念。arr长度为1时,arr[0]是局部最小。arr的长度为N(N>1)时,如果arr[0]<arr[1],那么arr[0]是局部最小;如果arr[N-1]<arr[N-2],那么arr[N-1]是局部最小;如果0<i<N-1,既有arr[i]<arr[i-1]又有arr[i]<arr[i+1],那么arr[i]是局部最小。 给定无序数组arr,已知arr中任意两个相邻的数都不相等,写一个函数,只需返回arr中任意一个局部最小出现的位置即可。
1 public class Solution { 2 public int getLessIndex(int[] arr) { 3 //数组的长度len 4 int len=arr.length; 5 if(arr==null||arr.length==0){ 6 //不存在局部最小值 7 return -1; 8 } 9 //只有一个元素或者arr[0]<arr[1]满足局部最小的定义 10 if(arr.length==1||arr[0]<arr[1]){ 11 return 0; 12 } 13 //arr[n-1]<arr[n-1]满足局部最小的定义 14 if(arr[len-1]<arr[len-2]){ 15 return len-1; 16 } 17 int left=1; 18 int right=len-2; 19 int mid=0; 20 while(left<right){ 21 mid=(left+right)/2; 22 if(arr[mid]>arr[mid-1]){ 23 right=mid-1; 24 }else if(arr[mid]>arr[mid+1]){ 25 left=mid+1; 26 }else{ 27 return mid; 28 } 29 } 30 return left; 31 } 32 }
6.3元素最左出现练习题
对于一个有序数组arr,再给定一个整数num,请在arr中找到num这个数出现的最左边的位置。
给定一个数组arr及它的大小n,同时给定num。请返回所求位置。若该元素在数组中未出现,请返回-1。
[1,2,3,3,4],5,3
返回:2
1 import java.util.*; 2 3 public class LeftMostAppearance { 4 public int findPos(int[] arr, int n, int num) { 5 // write code here 6 //表示num出现的最左的位置,如果数组中没有值为num的元素,则返回-1; 7 int res=-1; 8 if(arr==null||n==0){ 9 return res; 10 } 11 //定义两个变量,分别从数组的两端开始遍历操作 12 int left=0; 13 int right=n-1; 14 int mid=0; 15 while(left<=right){ 16 mid=(left+right)/2; 17 if(arr[mid]<num){ 18 left=mid+1; 19 }else if(arr[mid]>num){ 20 right=mid-1; 21 }else{ 22 res=mid; 23 right=mid-1; 24 } 25 } 26 return res; 27 } 28 }
6.4循环有序数组最小值练习题
对于一个有序循环数组arr,返回arr中的最小值。有序循环数组是指,有序数组左边任意长度的部分放到右边去,右边的部分拿到左边来。比如数组[1,2,3,3,4],是有序循环数组,[4,1,2,3,3]也是。
给定数组arr及它的大小n,请返回最小值。
[4,1,2,3,3],5
返回:1
1 import java.util.*; 2 3 public class MinValue { 4 public int getMin(int[] arr, int n) { 5 // write code here 6 //定义两个变量,index1代表第一个子数组的第一个,index2代表第二个子数组的最后一个元素 7 int index1=0; 8 int index2=n-1; 9 int indexMid; 10 while(arr[index1]>=arr[index2]){ 11 //当前后两个指针相邻时说明index2指向的就是最小值 12 if(index2-index1==1){ 13 return arr[index2]; 14 15 } 16 indexMid=(index1+index2)/2; 17 //中间元素无法确定是属于前后哪一个子数组,此时需要遍历数组寻找最小值 18 if(arr[indexMid]==arr[index1]&&arr[index1]==arr[index2]&&arr[index2]==arr[indexMid]){ 19 return searchMin(arr,index1,index2); 20 } 21 if(arr[indexMid]>=arr[index1]){ 22 index1=indexMid; 23 }else if(arr[indexMid]<=arr[index2]){ 24 index2=indexMid; 25 } 26 } 27 //当不满足while循环的条件时,说明数组原本就有序,即旋转数组是其原数组 28 return arr[index1]; 29 } 30 private int searchMin(int array[],int index1,int index2){ 31 int result=array[index1]; 32 for(int i=index1+1;i<=index2;i++){ 33 if(result>array[i]){ 34 result=array[i]; 35 } 36 } 37 return result; 38 39 } 40 }
6.5二分搜索(2)
分析问题时注意边界条件的考察,比如数组的第一个元素,数组的最后一个元素
6.6最左原位
有一个有序数组arr,其中不含有重复元素,请找到满足arr[i]==i条件的最左的位置。如果所有位置上的数都不满足条件,返回-1。
给定有序数组arr及它的大小n,请返回所求值。
[-1,0,2,3],4
返回:2
1 import java.util.*; 2 3 public class Find { 4 public int findPos(int[] arr, int n) { 5 // write code here 6 //定位出现的位置 7 int res=-1; 8 if(arr==null||n==0){ 9 return res; 10 } 11 //有序没有指明数组是升序或者降序 12 int left=0; 13 int right=n-1; 14 int mid=0; 15 while(left<=right){ 16 if((arr[left]>left)||(arr[right]<right)){ 17 break; 18 } 19 mid=(left+right)/2; 20 if(arr[mid]>mid){ 21 right=mid-1; 22 }else if(arr[mid]<mid){ 23 left=mid+1; 24 }else{ 25 res=mid; 26 right=mid-1; 27 } 28 } 29 return res; 30 } 31 }
6.7 完全二叉树计数练习题
给定一棵完全二叉树的根节点root,返回这棵树的节点个数。如果完全二叉树的节点数为N,请实现时间复杂度低于O(N)的解法。
给定树的根结点root,请返回树的大小。
6.8快速N次方练习题
如果更快的求一个整数k的n次方。如果两个整数相乘并得到结果的时间复杂度为O(1),得到整数k的N次方的过程请实现时间复杂度为O(logN)的方法。
给定k和n,请返回k的n次方,为了防止溢出,请返回结果Mod 1000000007的值。
2,3
返回:8
1 import java.util.*; 2 import java.math.BigInteger; 3 4 public class QuickPower { 5 public int getPower(int a, int n) { 6 BigInteger res = BigInteger.valueOf(1); 7 BigInteger tmp = BigInteger.valueOf(a); 8 for (; n != 0; n >>= 1) { 9 if ((n & 1) != 0) { 10 res = res.multiply(tmp); 11 } 12 tmp = tmp.multiply(tmp); 13 res = res.mod(BigInteger.valueOf(1000000007)); 14 tmp = tmp.mod(BigInteger.valueOf(1000000007)); 15 } 16 return res.mod(BigInteger.valueOf(1000000007)).intValue(); 17 } 18 }
第7章 二叉树
7.1二叉树(1)
二叉树类型的题目是常见的面试考查题目
1)能够结合队列、栈、链表字符串等数据结构
2)需要掌握图的基本遍历方式,比如BFS和DFS
3)需要掌握递归函数的使用,并自己设计出递归过程
4)与实际工作紧密结合
案例一:二叉树的先序、中序、后序遍历(递归方式和非递归方式(借助栈,先序和中序一个栈,后续两个栈(将弹出的节点压入栈S2中)))
7.2递归二叉树的序列打印练习题
请用递归方式实现二叉树的先序、中序和后序的遍历打印。
给定一个二叉树的根结点root,请依次返回二叉树的先序,中序和后续遍历(二维数组的形式)。
1 import java.util.*; 2 3 /* 4 public class TreeNode { 5 int val = 0; 6 TreeNode left = null; 7 TreeNode right = null; 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 }*/ 12 public class TreeToSequence { 13 public int[][] convert(TreeNode root) { 14 // write code here 15 //定义集合容器存储遍历过程中的节点 16 List<Integer> preList=new ArrayList<Integer>(); 17 List<Integer> inList=new ArrayList<Integer>(); 18 List<Integer> lastList=new ArrayList<Integer>(); 19 preOrder(root,preList); 20 inOrder(root,inList); 21 lastOrder(root,lastList); 22 int res[][]=new int[3][preList.size()]; 23 for(int i=0;i<preList.size();i++){ 24 res[0][i]=preList.get(i); 25 res[1][i]=inList.get(i); 26 res[2][i]=lastList.get(i); 27 } 28 return res; 29 30 } 31 //先序遍历 32 private void preOrder(TreeNode root,List <Integer> preList){ 33 if(root==null){ 34 return; 35 } 36 preList.add(root.val); 37 preOrder(root.left,preList); 38 preOrder(root.right,preList); 39 40 } 41 //中序遍历 42 private void inOrder(TreeNode root,List<Integer> inList){ 43 if(root==null){ 44 return; 45 } 46 inOrder(root.left,inList); 47 inList.add(root.val); 48 inOrder(root.right,inList); 49 } 50 //后序遍历 51 private void lastOrder(TreeNode root,List<Integer> lastList){ 52 if(root==null){ 53 return; 54 } 55 lastOrder(root.left,lastList); 56 lastOrder(root.right,lastList); 57 lastList.add(root.val); 58 59 } 60 }
7.3非递归二叉树的序列打印练习题
请用非递归方式实现二叉树的先序、中序和后序的遍历打印。
给定一个二叉树的根结点root,请依次返回二叉树的先序,中序和后续遍历(二维数组的形式)。
1 import java.util.*; 2 3 /* 4 public class TreeNode { 5 int val = 0; 6 TreeNode left = null; 7 TreeNode right = null; 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 }*/ 12 public class TreeToSequence { 13 public int[][] convert(TreeNode root) { 14 // write code here 15 //定义先序遍历需要的存储空间 16 List<Integer> stackPre=new ArrayList<Integer>(); 17 //定义中序遍历需要的存储空间 18 List<Integer> stackIn=new ArrayList<Integer>(); 19 //定义后序遍历需要的存储空间 20 List<Integer> stackPost=new ArrayList<Integer>(); 21 22 //先序遍历 23 preTraverse(root,stackPre); 24 //中序遍历 25 inTraverse(root,stackIn); 26 //后序遍历 27 lastTraverse(root,stackPost); 28 //定义储存最后结果的数组 29 int res[][]=new int[3][stackPre.size()]; 30 for(int i=0;i<stackPre.size();i++){ 31 res[0][i]=stackPre.get(i); 32 res[1][i]=stackIn.get(i); 33 res[2][i]=stackPost.get(i); 34 } 35 return res; 36 } 37 //非递归先序遍历 38 private void preTraverse(TreeNode root,List stackPre){ 39 if(root==null){ 40 return; 41 } 42 //申请辅助栈 43 Stack<TreeNode> stack = new Stack<>(); 44 stack.push(root); 45 while(!stack.empty()){ 46 TreeNode cur=stack.pop(); 47 stackPre.add(cur.val); 48 if(cur.right!=null){ 49 stack.push(cur.right); 50 } 51 if(cur.left!=null){ 52 stack.push(cur.left); 53 } 54 } 55 } 56 //非递归中序遍历 57 private void inTraverse(TreeNode root,List stackIn){ 58 if(root==null){ 59 return; 60 } 61 //申请辅助栈 62 Stack<TreeNode> stack = new Stack<>(); 63 TreeNode cur=root; 64 while(!stack.empty()||cur!=null){ 65 //一直找到最左下方的节点,将左面这一路都加入到栈中 66 while(cur!=null){ 67 stack.push(cur); 68 cur=cur.left; 69 } 70 //弹出一个节点,将其加入结果集合stackIn中(相当于打印输出),然后转换到该节点的右子树,继续找最左子节点的操作 71 TreeNode popNode=stack.pop(); 72 stackIn.add(popNode.val); 73 cur=popNode.right; 74 } 75 } 76 //非递归后序遍历 77 private void lastTraverse(TreeNode root,List stackPost){ 78 if(root == null) 79 return; 80 Stack<TreeNode> st1 = new Stack<TreeNode>(); 81 Stack<TreeNode> st2 = new Stack<TreeNode>(); 82 st1.push(root); 83 TreeNode cur = root; 84 //先将根结点压入辅助栈1,弹出,并且将左右子节点分别压入栈中,将弹出的节点重新压入辅助栈2 85 //操作完毕,辅助栈二的弹出序列就是后序遍历的结果序列 86 while(!st1.empty()){ 87 cur = st1.pop(); 88 if(cur.left != null){ 89 st1.push(cur.left); 90 } 91 if(cur.right != null){ 92 st1.push(cur.right); 93 } 94 st2.push(cur); 95 } 96 while(!st2.empty()){ 97 stackPost.add(st2.pop().val); 98 } 99 } 100 }
7.4二叉树(2)
7.5二叉树的打印练习题
有一棵二叉树,请设计一个算法,按照层次打印这棵二叉树。
给定二叉树的根结点root,请返回打印结果,结果按照每一层一个数组进行储存,所有数组的顺序按照层数从上往下,且每一层的数组内元素按照从左往右排列。保证结点数小于等于500。
1 import java.util.LinkedList; 2 3 class TreeNode { 4 int val = 0; 5 TreeNode left = null; 6 TreeNode right = null; 7 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 } 12 13 public class TreePrinter { 14 public int[][] printTree(TreeNode root) { 15 // write code here 16 // 辅助队列queue 17 LinkedList<TreeNode> queue = new LinkedList<TreeNode>(); 18 // 当前正在访问的节点 19 TreeNode cur = root; 20 // 当前正在打印的行的最右节点 21 TreeNode last = root; 22 // 下一行要打印的最右节点 23 TreeNode nlast = root; 24 if (cur == null) { 25 return null; 26 } 27 // result用于存放已经遍历过得节点,每一层对应一个集合 28 LinkedList<LinkedList<TreeNode>> result = new LinkedList<LinkedList<TreeNode>>(); 29 // curReslut表示当前遍历的节点的集合,用来存放一层的节点 30 LinkedList<TreeNode> curResult = new LinkedList<TreeNode>(); 31 queue.add(root); 32 while (queue.size() != 0) { 33 cur = queue.poll(); 34 // 存放每一层的节点 35 curResult.add(cur); 36 if (cur.left != null) { 37 queue.add(cur.left); 38 nlast = cur.left; 39 } 40 if (cur.right != null) { 41 queue.add(cur.right); 42 nlast = cur.right; 43 } 44 // 一层遍历结束 45 if (cur == last) { 46 result.add(curResult); 47 // 更新last的值 48 last = nlast; 49 curResult = new LinkedList<TreeNode>(); 50 } 51 } 52 // 用二维数组保存每一层节点 53 int[][] resultArr = new int[result.size()][]; 54 for (int i = 0; i < result.size(); i++) { 55 resultArr[i] = new int[result.get(i).size()]; 56 for (int j = 0; j < resultArr[i].length; j++) { 57 resultArr[i][j] = result.get(i).get(j).val; 58 } 59 } 60 return resultArr; 61 } 62 63 }
7.6二叉树的序列化练习题
首先我们介绍二叉树先序序列化的方式,假设序列化的结果字符串为str,初始时str等于空字符串。先序遍历二叉树,如果遇到空节点,就在str的末尾加上“#!”,“#”表示这个节点为空,节点值不存在,当然你也可以用其他的特殊字符,“!”表示一个值的结束。如果遇到不为空的节点,假设节点值为3,就在str的末尾加上“3!”。现在请你实现树的先序序列化。
给定树的根结点root,请返回二叉树序列化后的字符串。
1 import java.util.*; 2 3 /* 4 public class TreeNode { 5 int val = 0; 6 TreeNode left = null; 7 TreeNode right = null; 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 }*/ 12 public class TreeToString { 13 public String toString(TreeNode root) { 14 // write code here 15 StringBuilder builder=new StringBuilder(); 16 17 preSerialize(root,builder); 18 return builder.toString(); 19 } 20 private void preSerialize(TreeNode root,StringBuilder builder){ 21 if(root==null){ 22 builder.append("#!"); 23 return; 24 } 25 builder.append(root.val).append("!"); 26 preSerialize(root.left,builder); 27 preSerialize(root.right,builder); 28 29 } 30 }
7.7二叉树(3)
二叉树的子树:在二叉树中以任意一个节点为头的整棵树称为二叉树的子树;
平衡二叉树(AVL树):
1)空树是一棵平衡二叉树
2)如果一棵树不为空,并且其中所有的子树都满足左子树和右子树的高度差都不超过1
搜索二叉树:每棵子树的头结点的值都比各自左子树上的所有节点的值要大,比各自右子树上的所有节点的值都要小;搜索二叉树按照中序遍历得到的结果一定是按照从小到大排列的;红黑树和AVL树都是搜索二叉树的不同实现(为了是搜索二叉树搜索效率更好或者调整的代价更小)
满二叉树:满二叉树是除了最后一层的节点无任何子节点以外,剩下的每一层上的节点都有两个子节点;满二叉树的层数为L,节点树为N,则N=2^L-1;L=log2(N+1);
完全二叉树:除了最后一层之外,其他每一层的节点数都是满的,最后一层如果也满了,是一棵满二叉树,也是完全二叉树,最后一层如果不满,则缺少的节点全部集中在右面,即最后一层的叶子节点全部集中在左面;
后继结点:一个节点的后继结点是指,这个节点在中序遍历序列中的下一个节点
前驱节点:一个节点的前驱结点是指,这个节点在中序遍历序列中的上一个节点
7.8平衡二叉树练习题
有一棵二叉树,请设计一个算法判断这棵二叉树是否为平衡二叉树。
给定二叉树的根结点root,请返回一个bool值,代表这棵树是否为平衡二叉树。
解法一:存在节点的重复遍历
1 import java.util.*; 2 3 /* 4 public class TreeNode { 5 int val = 0; 6 TreeNode left = null; 7 TreeNode right = null; 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 }*/ 12 public class CheckBalance { 13 public boolean check(TreeNode root) { 14 // write code here 15 if(root==null){ 16 return true; 17 } 18 int lh=getDepth(root.left); 19 int rh=getDepth(root.right); 20 int diff=lh-rh; 21 if(diff>1||diff<-1){ 22 return false; 23 } 24 return check(root.left)&&check(root.right); 25 } 26 //返回一个节点的深度,即该节点到叶子节点的路径最大长度 27 private int getDepth(TreeNode root){ 28 if(root==null){ 29 return 0; 30 } 31 int lh=getDepth(root.left); 32 int rh=getDepth(root.right); 33 34 return lh>rh?(lh+1):rh+1; 35 } 36 }
解法二:每个节点只遍历一次
1 import java.util.*; 2 3 /* 4 public class TreeNode { 5 int val = 0; 6 TreeNode left = null; 7 TreeNode right = null; 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 }*/ 12 public class CheckBalance { 13 public boolean check(TreeNode root) { 14 //定义一个引用类型的数据作为平衡标记,通过传引用的方式在递归左右子树时修改平衡标记 15 boolean[] res=new boolean[1]; 16 //从根节点开始遍历树,遍历过程中修改平衡标记 17 res[0]=true; 18 postCheck(root,1,res); 19 20 return res[0]; 21 } 22 public int postCheck(TreeNode root,int depth,boolean[] res){ 23 if(root==null){ 24 return depth; 25 } 26 //遍历一次左子树,获取深度(深度已经在参数改变了,目的是为了检查左子树是否平衡) 27 //若遍历左子树过程中修改了平衡标记为false,则子树非平衡,所以当前结点为根的子树非平衡,不再递归,直接返回 28 29 int left_depth=postCheck(root.left,depth+1,res); 30 if(res[0]==false){ 31 return depth; 32 } 33 //若左子树是平衡的,则遍历右子树并获取深度 34 //若遍历右子树过程中修改了平衡标记为false,则子树非平衡,所以当前结点为根的子树非平衡,不再递归,直接返回 35 int right_depth=postCheck(root.right,depth+1,res); 36 if(res[0]==false){ 37 return depth; 38 } 39 40 //若左右子树都是平衡的,则对左右子树深度进行比较,判断当前结点为根的子树是否平衡 41 if(Math.abs(left_depth-right_depth)>1){//高度差大于1,当前子树不平衡,修改平衡标记 42 res[0]=false; 43 } 44 //用左右子树深度最大者作为自己的高度 45 return Math.max(left_depth,right_depth); 46 } 47 }
7.9完全二叉树判断练习题
有一棵二叉树,请设计一个算法判断它是否是完全二叉树。
给定二叉树的根结点root,请返回一个bool值代表它是否为完全二叉树。树的结点个数小于等于500。
思路:
非递归方法,基本是层次遍历二叉树 依次检查每一个节点:
1.当发现有一个节点的左子树为空,右子树不为空时 直接返回false.
2.当发现有一个节点的左子树不为空,右子树为空时,置标志位为1。
3.当发现有一个节点的左右子树均为空时,置标志位为1。
标志位为1的作用是,标记此节点以下的节点均应为叶子节点(没有左右孩子),否则此树为一棵非完全二叉树。
1 import java.util.*; 2 3 /* 4 public class TreeNode { 5 int val = 0; 6 TreeNode left = null; 7 TreeNode right = null; 8 public TreeNode(int val) { 9 this.val = val; 10 } 11 }*/ 12 public class CheckCompletion { 13 public boolean chk(TreeNode root) { 14 // write code here 15 Queue<TreeNode> queue = new LinkedList<TreeNode>(); 16 boolean leaf = false; // 叶子结点 17 TreeNode left; 18 TreeNode right; 19 queue.add(root); 20 while (!queue.isEmpty()) { 21 root = queue.poll(); 22 left = root.left; 23 right = root.right; 24 if ((leaf&&(left!=null||right!=null)) || (left==null&&right!=null)) { 25 // 如果之前层遍历的结点没有右孩子,且当前的结点有左或右孩子,直接返回false 26 // 如果当前结点有右孩子却没有左孩子,直接返回false 27 return false; 28 } 29 if (left != null) { 30 queue.offer(root.left); 31 } 32 if (right != null) { 33 queue.offer(root.right); 34 }else { 35 leaf = false; // 如果当前结点没有右孩子,那么之后层遍历到的结点必须为叶子结点 36 } 37 } 38 return true; 39 } 40 }
7.11折纸练习题
请把纸条竖着放在桌⼦上,然后从纸条的下边向上⽅对折,压出折痕后再展 开。此时有1条折痕,突起的⽅向指向纸条的背⾯,这条折痕叫做“下”折痕 ;突起的⽅向指向纸条正⾯的折痕叫做“上”折痕。如果每次都从下边向上⽅ 对折,对折N次。请从上到下计算出所有折痕的⽅向。
给定折的次数n,请返回从上到下的折痕的数组,若为下折痕则对应元素为"down",若为上折痕则为"up".
1
返回:["down"]
7.12寻找错误结点练习题
一棵二叉树原本是搜索二叉树,但是其中有两个节点调换了位置,使得这棵二叉树不再是搜索二叉树,请找到这两个错误节点并返回他们的值。保证二叉树中结点的值各不相同。
给定一棵树的根结点,请返回两个调换了位置的值,其中小的值在前。
7.13树上最远距离练习题
从二叉树的节点A出发,可以向上或者向下走,但沿途的节点只能经过一次,当到达节点B时,路径上的节点数叫作A到B的距离。对于给定的一棵二叉树,求整棵树上节点间的最大距离。
给定一个二叉树的头结点root,请返回最大距离。保证点数大于等于2小于等于500.
7.14最大二叉搜索子树练习题
有一棵二叉树,其中所有节点的值都不一样,找到含有节点最多 的搜索二叉子树,并返回这棵子树的头节点.
给定二叉树的头结点root,请返回所求的头结点,若出现多个节点最多的子树,返回头结点权值最大的。
第八章 位运算
介绍布隆过滤器的基本内容、常见位运算题目的分析
8.1位运算(1)
常见算数运算符:+、-、*、/、%
常见位运算操作符:&、|、~、^、<<(左移右侧补0)、>>(右移,左侧补符号位)、>>>(右移,左侧补0)
Url黑名单存入哈希表或者数据库,就可以针对每一个url,查询数据库,看其是否在黑名单中,但是需要的存储空间过大
小知识:这也就是我们8G的优盘,其实真是空间只有7.2G左右的原因
B=字节 KB=千字节 MB=兆字节 GB=千兆字节 操作系统和我们日常应用中 1KB=1024B 1MB=1024KB 1GB=1024MB 硬盘制造厂商的计算方法和我们日常应用与操作系统中的计算方法不同 1KB=1000B 1MB=1000KB 1GB=1000MB 这就造成了硬盘标称的容量和操作系统中显示的实际容量不同。
布隆过滤器应用的常见场景:
1)网页黑名单系统
2)垃圾邮件过滤系统
3)爬虫的网址判断重复系统
4)容忍一定程度的失误率
5)对空间要求比较严格
布隆过滤器:布隆过滤器可以精确的表示一个集合,可以精确判断某一个元素是否在该集合中,精确程度由用户的具体设计决定,布隆过滤器的优势在于使用很小的空间就可以保证高的精确率。
布隆过滤器的生成过程:
8.2位运算(2)
8.3交换练习题
请编写一个算法,不用任何额外变量交换两个整数的值。
给定一个数组num,其中包含两个值,请不用任何额外变量交换这两个值,并将交换后的数组返回。
[1,2]
返回:[2,1]
1 import java.util.*; 2 3 public class Swap { 4 public int[] getSwap(int[] num) { 5 // write code here 6 num[0]=num[0]+num[1]; 7 num[1]=num[0]-num[1]; 8 num[0]=num[0]-num[1]; 9 return num; 10 } 11 }
8.4比较练习题
对于两个32位整数a和b,请设计一个算法返回a和b中较大的。但是不能用任何比较判断。若两数相同,返回任意一个。
给定两个整数a和b,请返回较大的数。
1,2
返回:2
8.5寻找奇数出现练习题
有一个整型数组A,其中只有一个数出现了奇数次,其他的数都出现了偶数次,请打印这个数。要求时间复杂度为O(N),额外空间复杂度为O(1)。
给定整形数组A及它的大小n,请返回题目所求数字。
[1,2,3,2,1],5
返回:3
1 import java.util.*; 2 3 public class OddAppearance { 4 public int findOdd(int[] A, int n) { 5 // write code here 6 //利用n^0=n;n^n=1;来实现寻找操作 7 if(A==null||n<2){ 8 return -1; 9 } 10 int res=A[0]; 11 for(int i=1;i<n;i++){ 12 res=res^A[i]; 13 } 14 return res; 15 } 16 }
8.6寻找奇数出现II练习题
给定一个整型数组arr,其中有两个数出现了奇数次,其他的数都出现了偶数次,找到这两个数。要求时间复杂度为O(N),额外空间复杂度为O(1)。
给定一个整形数组arr及它的大小n,请返回一个数组,其中两个元素为两个出现了奇数次的元素,请将他们按从小到大排列。
[1,2,4,4,2,1,3,5],8
返回:[3,5]
第9章 排列组合
介绍了笔试和面试中有关排列组合问题常见的考察点
9.1排列组合
高中概率的相关知识:
排列组合的公式:
排列计算公式:
二项式定理:
9.2方格移动练习题
在XxY的方格中,以左上角格子为起点,右下角格子为终点,每次只能向下走或者向右走,请问一共有多少种不同的走法
给定两个正整数int x,int y,请返回走法数目。保证x+y小于等于12。
2,2
返回:2
1 import java.util.*; 2 3 public class Robot { 4 public int countWays(int x, int y) { 5 // write code here 6 //总的步数 7 int sum=x+y-2; 8 //向下的步数,这里也可以是right=x-1 9 int down=y-1; 10 return fac(sum)/(fac(down)*fac(sum-down)); 11 } 12 private int fac(int num){ 13 int sum=1; 14 for(int i=1;i<=num;i++){ 15 sum*=i; 16 } 17 return sum; 18 } 19 }
9.3站队问题练习题
n个人站队,他们的编号依次从1到n,要求编号为a的人必须在编号为b的人的左边,但不要求一定相邻,请问共有多少种排法?第二问如果要求a必须在b的左边,并且一定要相邻,请问一共有多少种排法?
给定人数n及两个人的编号a和b,请返回一个两个元素的数组,其中两个元素依次为两个问题的答案。保证人数小于等于10。
7,1,2
返回:[2520,720]
1 import java.util.*; 2 3 public class StandInLine { 4 public int[] getWays(int n, int a, int b) { 5 // write code here 6 int array[]=new int[2]; 7 //A在B的左面+B在A的左面=fac(n),因此各占一半 8 array[0]=fac(n)/2; 9 array[1]=fac(n-1); 10 return array; 11 } 12 private int fac(int num){ 13 int sum=1; 14 for(int i=1;i<=num;i++){ 15 sum*=i; 16 } 17 return sum; 18 } 19 }
9.4孤傲的A练习题
A(A也是他的编号)是一个孤傲的人,在一个n个人(其中编号依次为1到n)的队列中,他于其中的标号为b和标号c的人都有矛盾,所以他不会和他们站在相邻的位置。现在问你满足A的要求的对列有多少种?
给定人数n和三个人的标号A,b和c,请返回所求答案,保证人数小于等于11且大于等于3。
6,1,2,3
288
计算公式:n!-4*(n-1)!+2*(n-2)!
1 import java.util.*; 2 3 public class LonelyA { 4 public int getWays(int n, int A, int b, int c) { 5 // write code here 6 return fac(n)-4*fac(n-1)+2*fac(n-2); 7 } 8 private int fac(int num){ 9 int sum=1; 10 for(int i=1;i<=num;i++){ 11 sum*=i; 12 } 13 return sum; 14 } 15 }
9.5分糖果练习题
n颗相同的糖果,分给m个人,每人至少一颗,问有多少种分法。
给定n和m,请返回方案数,保证n小于等于12,且m小于等于n。
思路:n-1个空挡中寻找m-1个挡板,把m个人分开
10,3
返回:36
1 import java.util.*; 2 3 public class Distribution { 4 public int getWays(int n, int m) { 5 // write code here 6 if(m==n){ 7 return 1; 8 } 9 return fac(n-1)/(fac(m-1)*fac(n-m)); 10 } 11 private int fac(int num){ 12 int sum=1; 13 for(int i=1;i<=num;i++){ 14 sum*=i; 15 } 16 return sum; 17 } 18 }
9.6排列组合(2)
卡特兰重要公式1:f(n)=C(n,2*n)/(n+1),括号匹配,出栈入栈操作序列等问题
卡特兰重要公式2:f(0)=1,f(1)=1,f(2)=2,f(3)=5.....时,f(n)=C(n,2*n)/(n+1)
9.7括号序列练习题
假设有n对左右括号,请求出合法的排列有多少个?合法是指每一个括号都可以找到与之配对的括号,比如n=1时,()是合法的,但是)(为不合法。
给定一个整数n,请返回所求的合法排列数。保证结果在int范围内。
1
返回:1
1 import java.util.*; 2 3 public class Parenthesis { 4 public int countLegalWays(int n) { 5 return catalan(n); 6 } 7 8 public int catalan(int n){ 9 int up = 1; 10 int down = 1; 11 for(int i=1;i<=n;i++){ 12 up *= (n*2-i+1); 13 down *= i; 14 } 15 16 return up/(down*(n+1)); 17 18 19 } 20 }
9.8进出栈练习题
n个数进出栈的顺序有多少种?假设栈的容量无限大。
给定一个整数n,请返回所求的进出栈顺序个数。保证结果在int范围内。
1
返回:1
1 import java.util.*; 2 3 public class Stack { 4 public int countWays(int n) { 5 6 return catalan(n); 7 } 8 9 public int catalan(int n){ 10 int up = 1; 11 int down = 1; 12 for(int i=1;i<=n;i++){ 13 up *= (n*2-i+1); 14 down *= i; 15 } 16 17 return up/(down*(n+1)); 18 19 20 } 21 }
9.9排队买票练习题
2n个人排队买票,n个人拿5块钱,n个人拿10块钱,票价是5块钱1张,每个人买一张票,售票员手里没有零钱,问有多少种排队方法让售票员可以顺利卖票。
给定一个整数n,请返回所求的排队方案个数。保证结果在int范围内。
1
返回:1
1 import java.util.*; 2 3 public class BuyTickets { 4 public int countWays(int n) { 5 6 return catalan(n); 7 } 8 9 public int catalan(int n){ 10 int up = 1; 11 int down = 1; 12 for(int i=1;i<=n;i++){ 13 up *= (n*2-i+1); 14 down *= i; 15 } 16 return up/(down*(n+1)); 17 18 } 19 }
9.10二叉树统计练习题
求n个无差别的节点构成的二叉树有多少种不同的结构?
给定一个整数n,请返回不同结构的二叉树的个数。保证结果在int范围内。
1
返回:1
思路:卡特兰书数的应用二:f(0)=1,f(1)=1,f(2)=2,f(3)=5.....时,f(n)=C(n,2*n)/(n+1)
1 import java.util.*; 2 3 public class TreeCount { 4 public int countWays(int n) { 5 6 return catalan(n); 7 } 8 9 public int catalan(int n){ 10 int up = 1; 11 int down = 1; 12 for(int i=1;i<=n;i++){ 13 up *= (n*2-i+1); 14 down *= i; 15 } 16 return up/(down*(n+1)); 17 18 } 19 }
9.11高矮排列练习题
12个高矮不同的人,排成两排,每排必须是从矮到高排列,而且第二排比对应的第一排的人高,问排列方式有多少种?
给定一个偶数n,请返回所求的排列方式个数。保证结果在int范围内。
1
返回:1
1 import java.util.*; 2 3 public class HighAndShort { 4 public int countWays(int n) { 5 return count(n/2); 6 } 7 8 //卡特兰数重要公式 9 public int count(int n){ 10 int up = 1; 11 int down = 1; 12 for(int i=1;i<=n;i++){ 13 up *= (n*2-i+1); 14 down *= i; 15 } 16 17 return up/(down*(n+1)); 18 19 20 } 21 }
9.12错装信封练习题
有n个信封,包含n封信,现在把信拿出来,再装回去,要求每封信不能装回它原来的信封,问有多少种装法?
给定一个整数n,请返回装发个数,为了防止溢出,请返回结果Mod 1000000007的值。保证n的大小小于等于300。
2
返回:1
思路分析:
分析:我们假设第k封信装入了第i个信封中,此时有以下两种情况:
* 1.第i封信刚好也装入了第k个信封中,即互换了,此时剩余的n-2封信有f(n-2)种装法。
* 2.第i封信没有装入第k个信封中,此时剩余的n-1封信有f(n-1)种装法。
* i的选择有(n-1)种
* 所以,f(n)=(n-1)*(f(n-1)+f(n-2));
1 import java.util.*; 2 3 public class CombineByMistake { 4 public int countWays(int n) { 5 if(n == 1){ 6 return 0; 7 } 8 long[] res = new long[n+1]; 9 res[2] = 1; 10 for(int i=3;i<=n;i++){ 11 res[i] = (i-1)*(res[i-1] + res[i-2])%1000000007; 12 } 13 return (int)res[n]; 14 } 15 }
第10章 概率
概率相关题目介绍
10.1概率(1)
1.在笔试面试中常作为客观问题出现(选择题)
2.在笔试中往往出现概率、期望的计算
3.往往利用古典概率进行计算(组合数学)
概率的应用:
1.利用随机来改进著名算法(快速排序:打乱输入,避免最坏情况的出现)
2.随机数发生器(用给定的随机数发生器去构造另一个,工程中用于采样)
10.2足球比赛练习题
有2k只球队,有k-1个强队,其余都是弱队,随机把它们分成k组比赛,每组两个队,问两强相遇的概率是多大?
给定一个数k,请返回一个数组,其中有两个元素,分别为最终结果的分子和分母,请化成最简分数
4
返回:[3,7]
1 import java.util.*; 2 3 public class Championship { 4 public int[] calc(int k) { 5 // write code here 6 int[] res=new int[2]; 7 int total=1; 8 int notmeet=1; 9 for(int i=1;i<2*k;i+=2){ 10 total=total*i; 11 } 12 for(int i=k-1;i>0;i--){ 13 notmeet=notmeet*(i+2); 14 } 15 int meet=total-notmeet; 16 17 int gongyue=gcd(total,meet); 18 res[0]=meet/gongyue; 19 res[1]=total/gongyue; 20 return res; 21 } 22 23 private int gcd(int total, int meet) { 24 while(meet!=0){ 25 int r=total%meet; 26 if(r==0){ 27 return meet; 28 }else{ 29 total=meet; 30 meet=r; 31 } 32 } 33 return total; 34 35 } 36 }
10.3蚂蚁习题
n只蚂蚁从正n边形的n个定点沿着边移动,速度是相同的,问它们碰头的概率是多少?
给定一个正整数n,请返回一个数组,其中两个元素分别为结果的分子和分母,请化为最简分数。
3
返回:[3,4]
1 import java.util.*; 2 3 public class Ants { 4 public int[] collision(int n) { 5 // write code here 6 int total=1; 7 for(int i=1;i<=n;i++){ 8 total*=2; 9 } 10 //所有蚂蚁都按照顺时针或者逆时针运动则不会相遇 11 int notMeet=2; 12 int meet=total-notMeet; 13 //求total和meet的公约数 14 int gys=gcd(total,meet); 15 int res[]={meet/gys,total/gys}; 16 return res; 17 } 18 private int gcd(int total,int meet){ 19 while(meet!=0){ 20 int r=total%meet; 21 if(r==0){ 22 return meet; 23 }else{ 24 total=meet; 25 meet=r; 26 } 27 } 28 return total; 29 } 30 }
10.4随机函数练习题
给定一个等概率随机产生1~5的随机函数,除此之外,不能使用任何额外的随机机制,请实现等概率随机产生1~7的随机函数。(给定一个可调用的Random5::random()方法,可以等概率地随机产生1~5的随机函数)
1 import java.util.*; 2 3 public class Random7 { 4 private static Random rand = new Random(123456); 5 // 随机产生[1,5] 6 private int rand5() { 7 return 1 + rand.nextInt(5); 8 } 9 10 // 通过rand5实现rand7 11 public int randomNumber() { 12 int num=5*rand5()+rand5(); 13 while(num > 20 || num < 7){ 14 num = 5 * rand5()+rand5(); 15 } 16 return num % 7 + 1; 17 } 18 }
10.5随机01练习题
给定一个以p概率产生0,以1-p概率产生1的随机函数RandomP::f(),p是固定的值,但你并不知道是多少。除此之外也不能使用任何额外的随机机制,请用RandomP::f()实现等概率随机产生0和1的随机函数。
1 import java.util.*; 2 3 public class Random01 { 4 private static double p = new Random().nextFloat(); 5 // 随机概率p 6 public static int f() { 7 return new Random().nextFloat() < p ? 0 : 1; 8 } 9 10 //连续两次调用f则产生01和10序列的概率都是p*(p-1) 11 public int random01() { 12 // 通过f函数实现01等概率返回 13 14 while(true){ 15 int m=Random01.f(); 16 int n=Random01.f(); 17 //mn为10返回1,mn为01返回0 18 if(m!=n){ 19 return m>n?1:0; 20 } 21 } 22 } 23 }
在代码中如果mn产生的是11或者00这种序列,再次调用随机函数f;
10.6概率(2)
10.7随机区间函数练习题
假设函数f()等概率随机返回一个在[0,1)范围上的浮点数,那么我们知道,在[0,x)区间上的数出现的概率为x(0<x≤1)。给定一个大于0的整数k,并且可以使用f()函数,请实现一个函数依然返回在[0,1)范围上的数,但是在[0,x)区间上的数出现的概率为x的k次方。
1 import java.util.*; 2 3 public class RandomSeg { 4 private Random rand = new Random(12345); 5 public double f() { 6 return rand.nextFloat(); 7 } 8 // 请调用f()函数实现 9 public double random(int k, double x) { 10 double max = 0; 11 for(int i = 0; i < k ; i++) 12 { 13 max = Math.max(max,f()); 14 } 15 return max; 16 } 17 }
10.8随机数组打印练习题
给定一个长度为N且没有重复元素的数组arr和一个整数M,实现函数等概率随机打印arr中的M个数。
技巧:将已经打印过的数组元素移动到数组的n-1,n-2,n-2...的位置,知道被打印的整数的个数为M为止(要理解和掌握这种建立局部无效区的运用)
1 import java.util.*; 2 3 public class RandomPrint { 4 public int[] print(int[] arr, int N, int M) { 5 Random rand=new Random(); 6 int point; 7 int res[]=new int[M]; 8 for(int i=0;i<M;i++){ 9 point=rand.nextInt()%(N-i); 10 res[i]=arr[point]; 11 swap(arr,point,N-i-1); 12 } 13 return res; 14 } 15 private void swap(int arr[],int i,int j){ 16 int temp=arr[i]; 17 arr[i]=arr[j]; 18 arr[j]=temp; 19 } 20 }
第11章 大数据
介绍哈希函数的内容、map-reduce的基本知识、常见海量数据题目分析、大数据题目的常规技巧
11.1大数据(1)
MapReduce和hadoop逐渐成为面试热门
介绍哈希函数:
哈希函数又叫散列函数,哈希函数的输入域可以是非常大的范围,但是输出域是一个固定的范围,假设为S;
哈希函数的性质:
1)典型的哈希函数有拥有无限的输入值域
2)相同的输入值,返回值(哈希值)相同
3)输入值不同时,输出可能相同,也有可能不同
4)不同输入值得到的哈希值,在输出域S上是均匀分布的(重要性质)
1—3性质是哈希函数的基础,4是评价哈希函数优劣的关键。
不同输入值得到的哈希值越均匀的分布在S上,则哈希函数越优秀,输出域S堆m取余,则会均匀分布在(0,m-1)的位置上,这是显而易见的。
经典的哈希函数算法实现:MD5算法和SHA1算法(了解即可,不要求掌握)
介绍Map-Reduce:
1.Map阶段:通过哈希函数,哈希函数可以使用系统默认的,也可以使用用户自己定义的,把大任务分成子任务
2. Reduce阶段:子任务按照用户的要求并发处理,然后合并结果
难点:工程上的处理
【注意点】:
1)备份的考虑:分布式存储的设计细节,以及容灾策略
2)任务分配策略与任务进度跟踪的细节设计,节点状态的呈现
3)多用户权限的控制
举例:
用Map-Reduce统计一篇文章中,每个单词出现的个数:
首先:文章预处理
1)去除文章中的标点符号;
2)对连字符"--"的处理,比如pencil-box,此时需要之一单词在一行中没有写完,用连字符连接的情况
3)对于缩写的处理:如I'm don't I'd like等等
4)大小写的处理
总之,预处理的结果是得到只包含单词之后的文本,也就是将单词抓取出来;
然后:针对只包含单词的文本
map阶段:对每个单词生成词频为1的记录,比如{dog,1),{pig,1},一个单词可能有多个词频为1的记录,此刻还没有进行合并,对于单个单词的单一词频记录,通过哈希函数,得到每个单词的哈希值,并根据该值分成若干组任务,此时单词的重复词频记录通过hash,会被映射到相同的位置;
reduce阶段:子任务(每个子任务包含若干种单词,但是同一种单词不会被分配进不同的子任务),对单个子任务中同一个单词的词频进行合并,得到同种单词合并后的组,最后所有同种单词合并的组进行最后的合并,就得到每种单词的词频合并。
因为每个子任务都是并行处理的,所以会节省时间,这也是map-reduce的优势之一;
常见海量处理数据题目关键:
1.本质上在于分而治之,通过哈希函数将大任务分流到机器,或者将大文件分流成小文件,在每一个小部分上进行处理,然后合并处理的结果
2.常用到hashmap或者bitmap等数据结构,难点在于通讯、时间和空间的的估算
举例:
案例一:请对10亿个IPV4的Ip地址进行排序,每个Ip只会出现一次
因为IpV4的IP数量约等于42亿,因此普通的方法可以将IP转换为32位无符号整数,把这些无符号整数排序之后转会IP地址即可
10亿个IP地址转换为10亿个整数(每个整数4个字节),因此需要4G空间;
【更好的方法】:申请一个2^32的bit类型的数组,数组中每个位置都是一个bit,只能有0或者1这两种状态。因此长度为2^32bit的数组bitmap,需要空间大小为:
2^32/(8^2^20),因为1B=8bit;1M=2^10B(我们的日常生活中是这样换算,操作系统厂商是1MB=10^3)
当出现数字1,则将bitmap中对应的0位置描黑,出现数字k,则将bitmap中对应的k-1位置描黑,因此10亿个IP地址描黑bitmap中的10个位置,
如果整数1出现,就把bitmap对应的位置从0变到1,这个数组可以表示任意一个(注意是一个)32位无符号整数是否出现过,
然后把10亿个ip地址全部转换成整数,相应的在bitmap中相应的位置描黑即可,
最后只要从bitmap的零位一直遍历到最后,然后提取出对应不为0位的整数,再转换成ip地址,就完成了从小到大的排序。
案例二:
【题目】请对10亿个年龄进行排序
【关键】年龄是有范围的,一般为0~200.所以我们很容易联想到桶排序!通过桶排序的进化版计数排序实现最终排序。
11.2大数据(2)
哈希函数分流加哈希表解决Top K问题
案例一:
有一个包含20亿个全是32位整数的大文件,在其中找到出现次数最多的数。内存限制为2G。
(1)通常的方法
使用哈希表做词频统计,key记录具体某一个整数,value记录这个整数出现的次数,最后统计即可。
首先看一下哈希表结构如何设计,key就是32位整数,4字节
4字节,32位<——整型<——key——>具体某一个整数
假设极端情况一个整数出现了20亿次,那么value可能的最大值是20亿,
即2^31,也需要用4字节保存
4字节,31位<<——value——>这个整数出现的次数
所以哈希表每条记录占据8字节,20亿数据需要占据16G空间,一次性可能会超出内存限制,现在对这个方法进行改进。
(2)分而治之的方法
把包含20亿个数的大文件中的每一个数使用哈希函数分流,把大文件分成若干个小文件,假设有16个小文件。
同一种数不会被分流到不同文件,这是哈希函数的性质决定的,对不不同的数,每个文件中含有整数的种数几乎一样,这也是哈希函数的性质决定的。
然后用哈希表统计每个小文件中每种数出现的次数,此时肯定不会出现溢出,现在就得到了每个小文件中出现次数最多的数,然后把这每个小文件的出现最多的数进行比较,就可以得到结果。
大数据题目的一个特点是,通过哈希函数不断分流,来解决内存限制或者其他限制的问题。
分治+桶思想+bitmap解决大数据中元素是否出现问题
32 位无符号整数的范围是0~4294967295 。现在有一个正好包含40亿个无符号整数的文件,所以在整个范围中必然有没有出现过的数,可以使用最多10M的内存,只用找到一个没出现过的数即可,该如何找?
(1)使用哈希表
key——>具体某一个整数(32bit),value——>这个整数是否出现(1bit),
每条记录至少需要4字节,假设40亿条数据全部不相同,40亿条记录占据160亿字节,即需要16G内存,肯定不可以。
(2)使用bitmap
申请一个长度为2^32的bit类型的数组,这个数组需要2^29Byte,即512Mb空间,依然不符合要求
(3)桶排序思想,数字范围分成若干个桶
0~2^32-1范围分成64个区间,每个区间应该装下2^32/64的数,总共的范围为42亿,一共有40亿个数字,肯定有区间计数不足2^32/64,只要找到一个区间,在这个区间肯定有没出现过的数。假设这个区间为A,接下来在再对这个区间的范围构造bitmap,需要占用空间为512/64Mb,然后再遍历这40亿个数字,最后考察bitmap上哪个没出现即可。
首先根据内存限制,确定分成多少个空间,然后利用区间计数的方式,找到哪个区间计数不足,
哈希分流+哈希表词频统计+小根堆和外排序解决Top K问题
某搜索公司一天的用户搜索词汇是海量的,假设有百亿的数据量,请设计一种求出每天最热100词的可行办法。
一致性哈希,解决节点数量改变导致哈希函数分流手段失效的问题
潜在的问题:增加、删除机器时,数据迁移的代价会很高
对这个区间上的数利用bitmap即可。
新增机器:
删除机器:
第12章 动态规划
12.1动态规划(1)
动态规划问题优化轨迹:在面试中出现类似题目,优化轨迹高度一致
暴力搜索方法->记忆搜索方法->动态规划方法->状态继续化简后的动态规划方法
什么是动态规划?
1.其本质是利用申请的空间来记录每一个暴力搜索的计算结果,下次要用结果的时候直接使用,而不再进行重复的递归过程
2.动态规划规定每一种递归状态的计算顺序,依次进行计算
12.2找零钱练习题
有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。
给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。
[1,2,4],3,3
返回:2
方法1:暴力搜索
1 import java.util.*; 2 3 public class Exchange { 4 public int countWays(int[] penny, int n, int aim) { 5 // write code here 6 if(penny==null||penny.length==0||aim<0){ 7 return 0; 8 } 9 //使用penny[0....N-1]这些面值得到aim的方法总数 10 return process(penny,0,aim); 11 } 12 private int process(int penny[],int index,int aim){ 13 int res=0; 14 if(index==penny.length){ 15 res=aim==0?1:0; 16 }else{ 17 for(int i=0;penny[index]*i<=aim;i++){ 18 res+=process(penny,index+1,aim-penny[index]*i); 19 } 20 } 21 return res; 22 } 23 }
方法2:动态规划方法
1 import java.util.*; 2 3 public class Exchange { 4 public int countWays(int[] penny, int n, int aim) { 5 if (penny == null || penny.length == 0 || aim < 0) { 6 return 0; 7 } 8 int[][] dp = new int[penny.length][aim + 1]; 9 //矩阵的第一列,使用arr[i]组成钱数为0的方法总数为1,先求简单的 10 for (int i = 0; i < n; i++) { 11 dp[i][0] = 1; 12 } 13 //矩阵的第一行 14 for(int i=0;i<aim+1;i++){ 15 if(i%penny[0]==0) 16 dp[0][i]=1; 17 } 18 for (int i = 1; i < penny.length; i++) { 19 for (int j = 1; j <= aim; j++) { 20 //如果面值为arr[i[>j,根部则构成钱数目为J时无法用到面值为arr[i]的]钱币 21 if(penny[i]>j) 22 dp[i][j]=dp[i-1][j]; 23 else 24 25 dp[i][j]=dp[i-1][j]+dp[i][j-penny[i]]; 26 } 27 } 28 return dp[n-1][aim]; 29 } 30 }
第十三章 智力题
一些博弈性质的智力题
13.1涂色I练习题
你要在一个nxm的格子图上涂色,你每次可以选择一个未涂色的格子涂上你开始选定的那种颜色。同时为了美观,我们要求你涂色的格子不能相邻,也就是说,不能有公共边,现在问你,在采取最优策略的情况下,你最多能涂多少个格子?
给定格子图的长n和宽m。请返回最多能涂的格子数目。
1,2
返回:1
1 import java.util.*; 2 3 public class Paint { 4 public int getMost(int n, int m) { 5 // write code here 6 if(n*m%2==0){ 7 return m*n/2; 8 }else{ 9 return m*n/2+1; 10 } 11 } 12 }
13.2赛马练习题
作为一个马场的主人,你要安排你的n匹赛马和另一个马场的n匹马比赛。你已经知道了对方马场的出战表,即参加每一场的马的强壮程度。当然你也知道你自己的所有马的强壮程度。我们假定比赛的结果直接由马的强壮程度决定,即更壮的马获胜(若相同则双方均不算获胜),请你设计一个策略,使你能获得尽量多的场次的胜利。
给定对方每场比赛的马的强壮程度oppo及你的所有马的强壮程度horses(强壮程度为整数,且数字越大越强壮)同时给定n,请返回最多能获胜的场次。
[1,2,3],[1,2,3],3
返回:2
1 import java.util.*; 2 3 public class HorseRace { 4 public int winMost(int[] oppo, int[] horses, int n) { 5 // write code here 6 //先对两个数组元素进行从小到大的排序,然后从后向前依次比较就好 7 sort(oppo,n); 8 sort(horses,n); 9 //记录可以赢的局数 10 int count=0; 11 //j标记自己马的健壮程度数组horse。从后向前进行遍历 12 int j=n-1; 13 for(int i=n-1;i>=0;i--){ 14 if(oppo[i]<horses[j]){ 15 count++; 16 j--; 17 } 18 } 19 return count; 20 } 21 private void sort(int a[],int len){ 22 23 for(int i=0;i<len;i++){ 24 for(int j=0;j<len-1;j++){ 25 if(a[j]>a[j+1]){ 26 swap(a,j,j+1); 27 } 28 } 29 } 30 } 31 private void swap(int a[],int i,int j){ 32 int temp=a[i]; 33 a[i]=a[j]; 34 a[j]=temp; 35 } 36 }
13.3跳格子练习题
你和你的朋友正在玩棋子跳格子的游戏,而棋盘是一个由n个格子组成的长条,你们两人轮流移动一颗棋子,每次可以选择让棋子跳1-3格,先将棋子移出棋盘的人获得胜利。我们知道你们两人都会采取最优策略,现在已知格子数目,并且初始时棋子在第一格由你操作。请你计算你是否能获胜。
给定格子的数目n(n为不超过300的正整数)。返回一个整数,1代表能获胜,0代表不能获胜。
3
返回:1
1 import java.util.*; 2 3 public class Jump { 4 public int checkWin(int n) { 5 // write code here 6 if((n-1)%4==0){ 7 return 0; 8 } 9 else{ 10 return 1; 11 } 12 } 13 }
13.4游戏练习
A与B做游戏。 在一个n*m的矩阵中的出发点是(1,m),终点是(n,1),规则是只能向左移动一格,向下一格或向左下移动一格,先走到终点的为winner。 A先走。
给定两个整数n和m,请返回最后的获胜者的名字(A或B)。
5 3
返回:B
1 import java.util.*; 2 3 public class Game { 4 public char getWinner(int n, int m) { 5 // write code here 6 //// 最优策略是两个人每次共走2格, 7 // 如果A向左下2移动一格,相当于移动2格,此时B也应该向左下移动,总共是4格 8 if(m%2!=0 && n%2!=0) 9 return 'B';// // 此时B距离终点只有1格 10 else 11 return 'A'; 12 } 13 }
13.5数组清空练习题
现在有一个整数数组,其元素值均为1-n范围内的某个整数,现在你和你的朋友在玩一个游戏,游戏的目的是把数组清空,你们轮流操作,你是先手,每次操作你可以删除数组中值为某个数的元素任意多个(当然数组中值为这个数的元素个数应大于等于你删除的个数,且你至少要删除一个数)。最先把数组清空的人获得胜利。假设你们都采取最优策略,请你计算你能否获得胜利。
给定一个整数数组A和元素个数n。请返回一个整数,1代表你能获胜,0代表你不能获胜。
[1,1,1]
返回:1
1 import java.util.*; 2 3 public class Clear { 4 public int getWinner(int[] A, int n) { 5 // write code here 6 if(A == null || n == 0){ 7 throw new RuntimeException("数组为空!"); 8 } 9 int result = 0; 10 int num; 11 Arrays.sort(A); 12 for(int i = 0;i < n;++ i){ 13 int j = i; 14 num = 0; 15 while(j < n && A[i] == A[j]){ 16 ++ num; 17 ++ j; 18 } 19 result ^= num; 20 } 21 return result == 0 ? 0 : 1; 22 } 23 }