脚本之家(jb51.cc)算法栏目主要收集统计算法、大数据算法等资料。
斐波那契数列问题是程序员学习和笔试中都会面对的问题,绝大多数的童鞋(包括笔者)都会采用递归的方式实现。这种方式简洁而且优雅,但其实写出的代码是垃圾代码,当n稍微大一点的时候,执行效率是非常低下的。本文
public int find(long searchKey){ int i; int begin = 0; int end = nElems - 1; while(true){ ...
/** * 选择排序 * * 原理:将最小值与数组第1个即array[0]交换,第二次则忽略array[0],直接从array[1]至array[array.length-1]中 * 选择出最小值与a
冒泡排序就是依次取出最大数,然后依次交换放到数组最后边。直观写法:public long[] sort(long[] a){ int n = a.length - 1; // Step:1 选出最大数
原文链接:http://www.orlion.ga/193/ 由上图可看到希尔排序先约定一个间隔(图中是4),然后对0、4、8这个三个位置的数据进行插入排序,然后向右移一位对位置1、5、9进行插入排序
/** * 插入排序 * * 原理:从数组中取出一个值插入到一个左边已经排好序的数组片段中 * * @param a * @return */ public long[] InsertSort(lon
原文链接:http://www.orlion.ga/201/ 一、划分算法 1、划分 划分是快速排序的根本机制。划分数据就是把数据分成两组,所有大于特定值的数据在一组,所有小于特定值的在一组。比如将{
最近在面试,所以就把遇到的题目总结在这里,供大家(自己)复习用。
题目描述:对于有n个数的数组,分为两组,这两组的数的个数尽可能相等(不超过1),同时两组的数之和的差值最小。 这个题目使用类似0-1背包问题,思路:从k个数中选i个数,求所有可能的和,并把这些和放在f
看到有的博客用使用C++写的,但是没有java版本的,所以在这里做一个补充。
基本动态规划之硬币问题问题描述假设有 1 元,3 元,5 元的硬币若干(无限),现在需要凑出 11 元,问如何组合才能使硬币的数量最少?问题分析乍看之下,我们简单的运用一下心算就能解出需要 2 个 5 元和 1 个 1 元的解。当然这里只是列出了这个问题比较简单的情况。当硬币的币制或者种类变化,并且需要凑出的总价值变大时,就很难靠简单的计算得出结论了。贪心算法可以在一定的程度上得出较优解,但不是每次都能得出最优解。这里运用动态规划的思路解决该问题。按照一般思路,我们先从最基本的情况来一步一步地推导。我们先假设一个函数 d(i) 来表示需要凑出 i 的总价值需要的最少硬币数量。当 i = 0 时,很显然我们可以知道 d(0) = 0。因为不要凑钱了嘛,当然也不需要任何硬币了。注意这是很重要的一步,其后所有的结果都从这一步延伸开来。当 i = 1 时,因为我们有 1 元的硬币,所以直接在第 1 步的基础上,加上 1 个 1 元硬币,得出 d(1) = 1。当 i = 2 时,因为我们并没有 2 元的硬币,所以只能拿 1 元的硬币来凑。在第 2 步的基础上,加上 1 个 1 元硬币,得出 d(2) = 2。当 i = 3 时,我们可以在第 3 步的基础上加上 1 个 1 元硬币,得到 3 这个结果。但其实我们有 3 元硬币,所以这一步的最优结果不是建立在第 3 步的结果上得来的,而是应该建立在第 1 步上,加上 1 个 3 元硬币,得到 d(3) = 1。...接着就不再举例了,我们来分析一下。可以看出,除了第 1 步这个看似基本的公理外,其他往后的结果都是建立在它之前得到的某一步的最优解上,加上 1 个硬币得到。得出:d(i) = d(j) + 1这里 j < i。通俗地讲,我们需要凑出 i 元,就在凑出 j 的结果上再加上某一个硬币就行了。那这里我们加上的是哪个硬币呢。嗯,其实很简单,把每个硬币试一下就行了:假设最后加上的是 1 元硬币,那 d(i) = d(j) + 1 = d(i - 1) + 1。假设最后加上的是 3 元硬币,那 d(i) = d(j) + 1 = d(i - 3) + 1。假设最后加上的是 5 元硬币,那 d(i) = d(j) + 1 = d(i - 5) + 1。我们分别计算出 d(i - 1) + 1,d(i - 3) + 1,d(i - 5) + 1 的值,取其中的最小值,即为最优解,也就是 d(i)。最后公式:代码示例这里用 Java 实现了基本的代码:public class CoinProblemBasicTest {private int[] d; // 储存结果private int[] coins = {1, 3, 5}; // 硬币种类privatevoidd_func(int i, int num) {if (i == 0) {d[i] = 0;d_func(i + 1, num);}else {int min = 9999999; // 初始化一个很大的数值。当最后如果得出的结果是这个数时,说明凑不出来。for (int coin : coins) {if (i >= coin && d[i - coin] + 1 < min) {min = d[i - coin] + 1;}}d[i] = min;if (i < num) {d_func(i + 1, num);}}}@Testpublicvoidtest() throws Exception {int sum = 11; // 需要凑 11 元d = new int[sum + 1]; // 初始化数组d_func(0, sum); // 计算需要凑出 0 ~ sum 元需要的硬币数量for (int i = 0; i <= sum; i++) {System.out.println("凑齐 " + i + " 元需要 " + d[i] + " 个硬币");}}}结果如下:凑齐 0 元需要 0 个硬币凑齐 1 元需要 1 个硬币凑齐 2 元需要 2 个硬币凑齐 3 元需要 1 个硬币凑齐 4 元需要 2 个硬币凑齐 5 元需要 1 个硬币凑齐 6 元需要 2 个硬币凑齐 7 元需要 3 个硬币凑齐 8 元需要 2 个硬币凑齐 9 元需要 3 个硬币凑齐 10 元需要 2 个硬币凑齐 11 元需要 3 个硬币
一个序列有N个数:A[1],A[2],…,A[N],求出最长非降子序列的长度。 (讲DP基本都会讲到的一个问题LIS:longest increasing subsequence)正如上面我们讲的,面对这样一个问题,我们首先要定义一个“状态”来代表它的子问题, 并且找到它的解。注意,大部分情况下,某个状态只与它前面出现的状态有关, 而独立于后面的状态。让我们沿用“入门”一节里那道简单题的思路来一步步找到“状态”和“状态转移方程”。 假如我们考虑求A[1],A[2],…,A[i]的最长非降子序列的长度,其中i<N, 那么上面的问题变成了原问题的一个子问题(问题规模变小了,你可以让i=1,2,3等来分析) 然后我们定义d(i),表示前i个数中以A[i]结尾的最长非降子序列的长度。OK, 对照“入门”中的简单题,你应该可以估计到这个d(i)就是我们要找的状态。 如果我们把d(1)到d(N)都计算出来,那么最终我们要找的答案就是这里面最大的那个。 状态找到了,下一步找出状态转移方程。为了方便理解我们是如何找到状态转移方程的,我先把下面的例子提到前面来讲。 如果我们要求的这N个数的序列是:5,3,4,8,6,7根据上面找到的状态,我们可以得到:(下文的最长非降子序列都用LIS表示)前1个数的LIS长度d(1)=1(序列:5)前2个数的LIS长度d(2)=1(序列:3;3前面没有比3小的)前3个数的LIS长度d(3)=2(序列:3,4;4前面有个比它小的3,所以d(3)=d(2)+1)前4个数的LIS长度d(4)=3(序列:3,4,8;8前面比它小的有3个数,所以 d(4)=max{d(1),d(2),d(3)}+1=3)前5个数的LIS长度d(5)=3(序列:3,4,6;)前六个数的LIS长度d(6)=4(序列:3,4,7,7)OK,分析到这,我觉得状态转移方程已经很明显了,如果我们已经求出了d(1)到d(i-1), 那么d(i)可以用下面的状态转移方程得到:d(i) = max{1, d(j)+1},其中j<i,A[j]<=A[i]用大白话解释就是,想要求d(i),就把i前面的各个子序列中, 最后一个数不大于A[i]的序列长度加1,然后取出最大的长度即为d(i)。 当然了,有可能i前面的各个子序列中最后一个数都大于A[i],那么d(i)=1, 即它自身成为一个长度为1的子序列。参考代码为:public class Lis {public static void main(String[] args) {// TODO Auto-generated method stubint [] num={5,3,4,8,6,7};System.out.print(liss(num));}public static int liss(int num[]){int len=1;int []res=new int[num.length];for(int i=0; i<num.length; ++i){res[i] = 1;for(int j=0; j<i; ++j)if(num[j]<=num[i] && res[j]+1>res[i])res[i] = res[j] + 1;if(res[i]>len) len = res[i];}return len;}} 
一、时间复杂度1.用来评估算法运行效率的一个东西。2.一般来说,时间复杂度高的算法比复杂度低的算法慢3.常见的时间复杂度(效率排序)o(1)<o(logn)<o(n)<o(nlogn)<o(n^2)<o(n^ 2logn)<o(o^3)4.不常见的时间复杂度o(n!)/o(2^n)/o(n^n)5.如何一眼判断时间复杂度?-循环的过程中出现了循环减半-----o(logn)-几次n的循环就是n的几次方的复杂度二、空间复杂度1.空间复杂度:用来评估算法内存占用大小的一个式子2."空间换时间"三、递归1、递归的两个特点:-调用自身-有结束条件四、列表查找1.列表查找:从列表中查找指定元素-输入:列表、待查找元素。->无序列表-输出:元素下标或未查找到元素。有序列表 2.顺序查找-从列表第一个元素开始,顺序进行搜索,知道找到为止3.二分查找-从有序列表的候选区data[0:n]开始,通过对待查找的值与候选区中间值的比较,可以使候选区减少一半。"""顺序查找"""li = list(range(0,1000,2))def linear_search(li,value):try:i =li.index(val)return iexcept:return None"""二分查找"""def bin_search(li,value):low =0high = len(li)-1while low <=high:mid = (low+high)//2if data_set[mid] == value:return midelif data_set[mid] > value:high = mid-1else:low = mid+1else:return None"""二分查找-递归"""def bin_search_rec(data_set,value,low,high):if low<=high:mid = (low+high)//2if data_set[mid]==value:return midelif data_set[mid]>value:return bin_search_rec(data_set,value,low,mid-1)else:return bin_search_rec(data_set,value,mid+1,high)else:return 4.汉诺塔问题def hanota(n,a,b,c):if n>0:hanota(n-1,a,c,b)print("#%d:moving from %s to %s"%(num,a,c))hanota(n-1,b,a,c)"""当有n个盘子时:1.把n-1个圆盘从A经过C移动到B2.把第n个圆盘从A移动到C3.把n-1个小圆盘从B经过A移动到C汉诺塔移动次数的递归式:h(x) = 2h(x-1)+1""" 五、排序方法1.冒泡排序def bubble_sort(li):for i in range(len(li)-1):for j in range(len(li)-i-1):if li[j]>li[j+1]:li[j],li[j+1] = li[j+1],li[j]#时间复杂度:o(n^2)"""优化"""def bubble_sort_1(li):for i in range(len(li)-1):exchange = Falsefor j in range(len(li)-i-1):if li[j]>li[j+1]:li[j],li[j+1] = li[j+1],li[j]exchange = Trueif not exchange:return2.选择排序def select_sort(li):for i in range(len(li)-1):min_loc = ifor j in range(i+1,len(li)):if li[j]<li[min_loc]:min_loc = jif min_loc ! =i:li[i],li[min_loc]=li[min_loc],li[i]#时间复杂度:o(n^2)3.插入排序"""思路-列表被分为有序区和无序区两个部分。最初有序区只有一个元素-每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空"""def insert_sort(li):for i in range(1,len(li)):tmp =li[i]j = i-1while j>=0 and tmp<li[j]:li[j+1]=li[j]j=j-1li[j+1]=tmp#时间复杂度:o(n^2)4.快速排序"""-取一个元素p(第一个元素),使元素p归位;-列表被p分为两部分,左边都比p小,右边都比p大;-递归完成排序"""def quick_sort(data,left,right):if left < right:mid = partition(data,left,right)quick_sort(data,left,mid-1)quick_sort(data,mid+1,right)def partiton(data,left,right):tmp = data[left]while left<right:while left<right and data[right] >=tmp:right-=1data[left] = data[right]while left <right and data[left]<=tmp:left+=1data[right] = data[left]data[left] = tmpreturn left5.堆排序"""1.建立堆2.得到堆顶元素,为最大元素3.去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一个次调整重新使堆有序4.堆顶元素为第二大元素5.重复步骤3,知道堆变空"""def sift(data,low,high):i =lowj = 2*2+1while j<=high:if j<high and data[j]<data[j+1]:j+=1if tmp < data[j]:data[i] = data[j]i =jj = 2*i+1else:breakdata[i] =tmpdef heap_sort(data):n = len(data)for i in range(n//2-1,-1,-1):sift(data,i,n-1)for i in range(n-1,-1,-1):data[0],data[i] = data[i],data[0]sift(data,0,i-1)#python内置排序--heapq-heapify(x)-heappush(heap,item)-heappop(heap)优先队列:一些元素的集合,pop操作每次执行都会从优先队列中弹出最大(或最小)的元素堆---优先队列#利用heapq模块实现堆排序def heapsort(li):h=[]for value in li:heappush(h,value)return [heappop(h) for i in range(len(h))]#Top-k问题#现在有n个数,设计算法找出前k大的数(k<n)"""解决思路-取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数。-依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整。-遍历列表所有元素后,倒序弹出堆顶。"""def topk(li,k):heap = li[0:k]for i in range(k//2-1,-1,-1):sift(heap,i,k-1)for i in range(k,len(li)):heap[0] = li[i]sift(heap,0,k-1)for i in range(k-1,-1,-1):heap[0],heap[i] = heap[i],heap[0]sift(heap,0,i-1)6.归并排序def merge(li,low,mid,high):i =lowj = mid+1ltmp = []while i <=mid and j <=high:if li[i]<=li[j]:ltmp.append(li[i])i+=1else:ltmp.append(li[j])j+=1while i <=mid:ltmp.append(li[i])i+=1while j<=high:ltmp.append(li[j])j+=1li[low:high+1] = ltmp#################################def mergesort(li,low,high):if low<high:mid = (low+high)//2mergesort(li,low,mid)mergesort(li,mid+1,high)merge(li,low,mid,high)"""时间复杂度:O(nlogn)空间复杂度:O(n)""" 
1、将0-1串进行排序,你可以交换任意两个位置,问最少交换多少次?(用快速排序的思想)int answer=0;for(int i=0,j=len-1;i<j;++i,--j){while((i<j)&&(a[i]=='0'))++i;// for(;(i<j)&&(a[i]=='0');++i));while((i<j)&&(a[j]=='1'))--j;//for(;(i<j)&&(a[j]=='1');--j);if(i<j)++answer;}2、删除所有的a,并复制所有的b(字符数组足够大)1>先删除a,可以利用原来的字符串空间int n=0,numb=0;for(int i=0;a[i];++i){if(s[i]!='a'){s[n++]=a[i];}if(s[i]=='b'){++numb;}}2>复制b,技巧:倒着复制int newLength=n+numb;s[newLength]=0;for(int i=newLength-1,j=n-1;j>=0;--j){s[i--]=s[j];if(s[j]=='b'){s[i--]='b';}}3、一个字符串只包含*和数字,请把字符串内所有的*都放在开头方法一:数字的相对顺序会变for(int i=0,j=0;j<n;++j){if(s[j]=='*')swap(s[i++],s[j])}方法二:数字的相对顺序不变int j=n-1;for(int i=n-1;i>=0;--i){if(isdigit(s[i]))s[j--]=s[i];}for(;j>=0;--j){s[j]='*';}4、给定两个串a,b,问b是否是a的子串的变位词,例如输入a=hello,b=lel,lle,ello都是true,但是b=elo是false,解题思想:滑动窗口的思想,动态维护一个窗口,例如b的长度为3,我们考察a[0..2],a[1..3],a[2..4]是否和b是变位词。用一个hash,基于字符串的特性,用一个[0..255]或[0..65535]的数组,在这,暂且认为它们都是小写英文字母,用[0-25]来表示b中的每个单词出现的次数      用nonZero来存出现不同字母的个数,num[]来存每个字母出现的个数1>先遍历一下b,存b中的不同字母的个数nonZero,和每个字母出现的个数num[];int nonZero=0;for(int i=0;i<lenb;++i){if(++num[b[i]-'a']==1)++nonZero;}2>再遍历a[0..lenb-1],用b中的次数减去a中的一个窗口内字符种类,如果结果全是0,则找到这样的子串,注意:num[]的含义变为字符种类差。for(int i=0;i<lenb;++i){int c=a[i]-'a';--num[c];if(num[c]==0){//b与a中的该字母个数差为0,正好,可以将nonZero减1了--nonZero;}else if(num[c]==-1){//这种情况不是b中没有这个字母,就是a中该字母个数比b中要多++nonZero;}}if(nonZero==0)return true;3>a窗口滑动右移i=lenb;新窗口:a[i-lenb+1..i]旧窗口:a[i-lenb..i-1]需要去掉a[i-lenb],加入a[i]for(int i=lenb;i<lena;++i){//去掉旧窗口的第一个字母,将它(b与nonZero)返回到原来的状态int c=a[i-lenb]-'a';++num[c];if(num[c]==1){++nonZero;}else if(num[c]==0){--nonZero;}//加入新窗口的最后一个字母,因为中间的字母与旧窗口重叠。c=a[i]-'a';--num[c];if(num[c]==0){--nonZero;}else if(num[c]==-1){++nonZero;}if(nonZero==0)return true;}5、单词翻转while(i<j){swap(s[i++],s[j--]);}例1: I'm a student变为student a I'm方法:先将整个句子翻转,tneduts a m'I再将每个单词单独翻转,student a I'm例2:字符串循环移位 abcdbcda,cdab,dabc思路:长度为n,移动m次,相当于移动m%n次将前m%n翻转,再将后n-m%n翻转,最后总体翻转一下