给定一数组,其大小为8个元素,数组内的数据无序。
6 3 5 7 0 4 1 2 冒泡排序:两两比较,将两者较少的升上去,第一次比较空间为0-(N-1)直到最后一轮比较空间为0-1 public class bubbleSort { public static void main(String[] args) { int[] test = { 6, 3, 5, 7, 0, 4, 1, 2 }; for (int i = 0; i < test.length - 1; i++) { for (int j = 0; j < test.length - i - 1; j++) { if (test[j] > test[j + 1]) { int temp = test[j]; test[j] = test[j + 1]; test[j + 1] = temp; } } } for (int k = 0; k < test.length; k++) { System.out.println(test[k]); } } } 选择排序:在第一趟遍历N个数据,找出其中最小的数值与第一个元素交换,第二趟遍历剩下的N-1个数据,找出其中最小的数值与第二个元素交换……第N-1趟遍历剩下的2个数据,找出其中最小的数值与第N-1个元素交换,至此选择排序完成。 public class selectSort { public static void main(String[] args) { int[] test = { 6, 3, 5, 7, 0, 4, 1, 2 }; for (int i = 0; i < test.length; i++) { int min = i; for (int j = i + 1; j < test.length; j++) { if (test[min] > test[j]) { min = j; } } if (min != i) { int temp = test[i]; test[i] = test[min]; test[min] = temp; } } for (int k = 0; k < test.length; k++) { System.out.println(test[k]); } } } 插入排序:对于未排序数据,在已排序序列中从后向前扫描,找到相应的位置并插入。 public class insertSort { public static void main(String[] args) { int[] test = { 6, 3, 5, 7, 0, 4, 1, 2 }; for (int i = 0; i < test.length; i++) { for (int j = i; j > 0; j--) { if (test[j] < test[j - 1]) { int temp = test[j]; test[j] = test[j - 1]; test[j - 1] = temp; } else { break; } } } for (int k = 0; k < test.length; k++) { System.out.println(test[k]); } } }思想:不是基于比较,而是来自于桶排序,桶排序的基本思想则是把数则是arr划分为n个大小相同子区间(桶),每个子区间各自排序,最后合并。
计数排序:需要占用大量空间,它仅适用于数据比较集中的情况。比如[0~100],[10000~19999]这样的数据,对每一个输入的元素arr[i],确定小于 arr[i] 的元素个数,假设有5个数小于 arr[i],所以 arr[i] 应该放在数组的第6个位置上。基数排序:实质为多关键字排序,思路是将待排数据里排序关键字拆分成多个排序关键字;第1个排序关键字,第2个排序关键字,第3个排序关键字等,然后,根据子关键字对待排序数据进行排序,如个位数,十位数,百位数等。经典排序算法的空间复杂度
O(1):插入排序、选择排序、冒泡排序、堆排序、希尔排序;O(logN)~O(N):快速排序;O(N):归并排序;O(M): 计数排序、基数排序(和选择桶的数量有关)。经典排序算法的稳定性 稳定性:假定待排序的记录序列中,存在多个具有相同的关键字的记录,若经过排序,这些记录的相对次序保持不变,称这种排序算法是稳定的,否则称为不稳定的。
稳定的排序算法:冒泡排序、插入排序、归并排序、计数排序、基数排序、桶排序不稳定的排序算法:选择排序、快速排序、希尔排序、堆排序(1) 网页黑名单系统,垃圾邮件过滤系统,爬虫的网址判断重复系统,容忍一定程度的失误率,但对空间要求较严格 。 布隆过滤器:可精确地代表一个集合;可精确判断某一元素是否在此集合中;精确程度由用户的具体设计决定;做到100%的精确即正确是不可能的。 布隆过滤器的优势在于,利用很少的空间可以做到精确率较高。
布隆过滤器的bitarray大小如何确定? 大小为m(过小),样本数量为n(相较于m过大),失误率为p(过大)。 举例输入:n = 100亿,p = 0.01% 1. m = - n x lnp / (ln2) 2 得到m = 19.19n 向上取整为20n,2000亿bit,约为25G。 2. k = ln2 x m/n = 0.7 x m/n = 14 因此需要14个彼此独立的哈希函数。 3. 此时失误率为(1 - e -nk/m) k = 0.006%,其中m = 20n, k = 14。(2) 不用任何额外变量交换两个整数的值
给定整数a和b a = a0, b = b0 a = a ^ b --> a = a0 ^ b0, b = b0; b = a ^ b --> a = a0 ^ b0, b = a0 ^ b0 ^ b0 = a0; a = a ^ b --> a = a0 ^ b0 ^ a0 = b0, b = a0;(3) 给定两个32位整数a和b,返回a和b中较大的,但是不能用任何比较判断。
方法1:得到a - b的符号,根据该符号决定返回a或b。 public static int flip(int n){ return n ^ 1; } public static int sign(int n){ return flip((n >> 31) & 1); } public static int getMax(int a, int b){ int c = a - b; int scA = sign(c); int scB = flip(scA); return a = a * scA + b * scB; }方法一可能会有问题,当a = b溢出时,会发生错误。
方法2 public static int getMax(int a, int b){ int c = a - b; int as = sign(a); //a的符号,as == 1表示a为非负,as == 0表示a为负 int bs = sign(b); //b的符号,bs == 1表示a为非负,bs == 0表示b为负 int cs = sign(c); //a - b的符号 int difab = as ^ bs; //表示a和b是否符号不相同,不相同为1,相同为0 int sameab = flip(difab); //表示a和b是否符号相同,相同为1,不相同为0 int returnA = difab + as + sameab + cs; int returnB = flip(returnA) return a * returnA + b * returnB; }(3) 给定一个整型数组arr,其中只有一个数出现了奇数次,其他数都出现了偶数次,请打印这个数,要求时间复杂度为O(n),额外空间复杂度为O(1)。
注意点:n与0异或结果为n,n与n异或结果为0。 异或运算满足交换律,结合律。(4) 给定一个整型数组arr,其中有两个数出现了奇数次,其他数都出现了偶数次,请打印这个数,要求时间复杂度为O(n),额外空间复杂度为O(1)。
(5) 请设置一种加密过程,完成对明文text的加密和解密工作。
明文text,用户给定密码pw,假设密文为cipher。 cipher = text ^ pw text = cipher ^ pw = text ^ pw ^ pw = text 如果text长度大于pw,循环使用pw与text进行按位异或。博主:常敲代码手不抖 1. 教你彻底学会动态规划——入门篇 2. 教你彻底学会动态规划——进阶篇
给定数组arr,arr中所有的值都为正数且不重复,每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim代表要找的钱数,求换钱有多少种方法。
arr = [5、10、25、1], aim = 1000.
暴力搜索方法–>记忆搜索方法–>动态规划方法–>状态继续化简后的动态规划方法
暴力搜索 1. 用0张5元的货币,让[10,25,1]组成剩下的1000,最终方法数记为---------------------------res1 2. 用1张5元的货币,让让[10,25,1]组成剩下的995,最终方法数记为---------------------------res2 3. 用2张5元的货币,让让[10,25,1]组成剩下的990,最终方法数记为---------------------------res3 ........................................................................................... 201. 用200张5元的货币,让让[10,25,1]组成剩下的0,最终方法数记为-------------------------res201 定义递归函数:int p1(arr,index,aim),它的含义是如果用arr[index...N-1]这些面值的钱组成aim,返回总的方法数。 记忆搜索 arr = [5、10、25、1], aim = 1000. p(index,aim) 结果表map 1. 每计算完一个p(index,aim),都将结果放入到map中,index和aim组成共同key,返回结果为value; 2. 要进入一个递归过程p(index,aim),先以index和aim注册的key在map中查询是否已经存在value,如果存在,则直接取值,如果不存在,才进行递归运算。 动态规划 如果arr长度为N,生成行数为N,列数为aim + 1的矩阵dp.dp[i][j]的含义是在使用arr[0...i]货币的情况下,组成钱数j有多少种方法。 记忆搜索方法与动态规划方法的联系 1. 记忆化搜索方法就是某种形态的动态规划方法; 2. 记忆化搜索方法不关心到达某一个递归过程的路径,只是单纯地对计算过的递归过程进行记录,避免重复的递归过程; 3. 动态规划的方法则是规定好每一个递归过程的计算顺序,依次进行计算,后面的计算过程严格依赖前面的计算过程; 4. 两者都是空间换时间的方法,也都有枚举的过程,区别就在于动态规划规定计算顺序,而记忆搜索不用规定。 什么是动态规划方法? 1. 其本质是利用申请的空间来记录每一个暴力搜索的计算结果,下次要用结果的时候直接使用,而不再进行重复的递归过程; 2. 动态规划规定每一种递归状态的计算顺序,依次进行计算。 状态继续化简后动态规划方法 动态规划方法中dp[i][j]等于如下值的累加: dp[i-1][j] dp[i-1][j-1*arr[i]] dp[i-1][j-2*arr[i]] dp[i-1][j-3*arr[i]] 以上可以化简为:dp[i][j] = dp[i-1][j-arr[i]] + dp[i-1][j] 暴力递归题目可以优化成动态规划方法的大体过程: 1. 实现暴力递归方法; 2. 在暴力搜索方法的函数中看看哪些参数可以代表递归过程; 3. 找到代表递归过程的参数之后,记忆化搜索的方法非常容易实现,利用hashmap将部分递归值进行存储; 4. 通过分析记忆化搜索的依赖路径,进而实现动态规划; 5. 根据记忆化搜索方法该出动态规划方法,进而看看是否能化简,如果能化简,还能实现时间复杂度更低的动态规划方法。