NewCoder算法基础 Class 1
[1]只要高阶项,不要低阶项
[2]不要高阶项系数
例题1: 一个有序数组A,另一个无序数组B,请打印B中的所有不在A中的数,A数组长度为N,B数组长度为M。
[ 1 ] 方法一:对于数组B中的每一个数,都在A中通过遍历的方式找一下
int Search(int arrA[], int arrB[], int ALength, int BLength){
int *help=(int *)malloc(sizeof(int)*BLength);
int k=0;
for(int i=0; i<BLength; i++){
for(int j=0; j<ALength; j++){
if(arrB[i]==arrA[j]){
break;
}
if(j==ALength){
help[k++]=arrB[i];
}
}
}
}
[ 2 ] 方法二:对于数组B中的每一个数,都在A中通过二分的方式找一下
int BinarySearch(int arr[], int L, int R, int key){
int L=0, R=arr.size()-1;
while(L<=R){
mid=(L+R)/2;
if(arr[mid]==key){
return mid;
}
else if(arr[mid]>key){
R=mid-1;
}
else{
L=mid+1;
}
}
return -1;
}
Note:
mid=(L+R)/2 这种写法中L+R容易造成越界,所以可以写成L+(R-L)>>1
[ 3 ] 方法三:先把数组B排序,然后用类似外排的方式打印所有在A中出现的数
i
A 1 2 4 6 7
j
B 3 6 9
过程:
1) 1<3 i++
2)2<3 i++
3)4>3 print arrB[j] j++
4)4<6 i++
5)6=6 j++
6)6<9 i++
7)7<9 i++ print arrB[j]
(i, j 任意一方越界循环停止)
int External(int arrA[], int arrB[]){
int ALength=arrA.size();
int BLength=arrB.size();
int *help=(int *)malloc(sizeof(int)*BLength);
int i=0,j=0,k=0;
while(i<ALength && j<BLength){
if(arrB[j]>arrrA[i]){
i++;
}
else{
if(arrB[j]<arrA[i]){
help[k++]=arrB[j++];
}
else{
j++;
}
}
}
}
3 2 0 4 7
3 2 0 4 7--->2 3 0 4 7--->2 0 3 4 7--->2 0 3 4 7
int BubbleSort(int arr[], int len){
for(int i=0; i<len; i++){
for(int j=0; j<len-1-i; j++){
if(arr[j]>arr[j+1]){
swap(a[j], a[j+1]);
}
}
}
}
int BubbleSort(int arr[], int len){
for(int end=len-1; end>=0; end--){
for(int i=0; i<end ;i++){
if(arr[j]>arr[j+1]){
swap(a[j], a[j+1]);
}
}
}
}
T(n)=n+(n-1)+(n-2)+......+1=(1+n)*n/2
=O(n^2)
下标: 0~~~~~~~~~~~~~~~~~~~~~~~n-1
min
1~~~~~~~~~~~~~~~~~~~~~n-1
min'
2~~~~~~~~~~~~~~~~~~~n-1
min''
int SelectionSort(int arr[], int len){
for(int i=0; i<len; i++){
int min=arr[i];
for(int j=i+1; j<len; j++){
if(arr[j]<min){
swap(arr[j], min);
}
}
}
}
int SelectionSort(int arr[], int len){
for(int i=0; i<len; i++){
int min=INT_MAX;//INT_MAX为正无穷
for(int j=i; j<len; j++){
if(arr[j]<min){
swap(arr[j], min);
}
}
}
}
T(n)=n+(n-1)+(n-2)+......+1=(1+n)*n/2
=O(n^2)
5 3 4 0 6
5 | 3 4 0 6
swap(5, 3)
5 3 | 4 0 6 ---> 5 3 | 4 0 6 ---> 3 5 | 4 0 6
swap(5, 4)
3 5 4 | 0 6 ---> 3 5 4 | 0 6 ---> 3 4 5 | 0 6 ---> 3 4 5 | 0 6
swap(5, 0) swap(4, 0) swap(3, 0)
3 4 5 0 | 6 ---> 3 4 5 0 | 6 ---> 3 4 0 5 | 6 ---> 3 0 4 5 | 6 ---> 0 3 4 5 | 6
0 3 4 5 6 ---> 0 3 4 5 6 --->0 3 4 5 6 ---> 0 3 4 5 6 ---> 0 3 4 5 6 ---> 0 3 4 5 6
int InsertSort(int arr[], int len){
for(int i=1; i<len; i++){
for(int j=i-1; j>=0; j--){
if(arr[j]>a[j+1]){
swap(a[j], a[j+1]);//此处也可使用三目运算符
}
}
}
}
最好情况:O(n)
最坏情况:O(n^2)
- 无OJ线上测试,可以验证自己写的是否正确
- 小样本正确,大样本WA 希望依然使用小样本检测问题
- 贪心策略 用对数器无需证明
对数器构造
- 产生堆积样本的发生器
- 寻找一个完全准确的方法(容易实现,无需考虑时间复杂度)
- 数据完全拷贝两份,分别传递到测试函数和完全正确的函数中,进行结果比较
对数器的概念和使用
- 有一个你想要测的方法a
- 实现一个绝对正确但是复杂度不好的方法b
- 实现一个随机样本产生器
- 实现比对的方法
- 把方法a和方法b比对很多次来验证方法a是否正确。
- 如果有一个样本使得比对出错,打印样本分析是哪个方法出错
- 当样本数量很多时比对测试依然正确,可以确定方法a已经正确。
递归思想(例如数组中找最大值)
L | R
max_L max_R
Max(max_L, max_R)
int getMax(int arr[], int L, int R){
if(L==R){
return arr[L];
}
int mid=L+(R-L)>>1;
int maxLeft=getMax(arr, L, mid);
int maxRight=getMax(arr, mid+1, R);
return max(maxLeft, maxRight);
}
max(arr, 0, 3)
mid=1
max(arr, 0, 1)
mid=0
max(arr, 0, 0)
return
max(arr, 0, 3)
mid=1
max(arr, 0, 1)
mid=0 maxLeft=4
Note:
在递归的过程中,系统会将代码行、形参、局部变量等信息压入系统栈中,当遇到return后将栈顶信息弹出还原现场并将值带回,然后执行接下来的语句
任何递归行为都可以改成非递归(无需系统压栈,自己手动压栈)---> 从递归改为迭代
T(n)=aT(n/b)+O(n^d)
比如二分查找 T(n)=2T(n/2)+O(1)
Master公式
log b a > d − − − > O ( n l o g b a ) \log_ba>d \kern{10px} ---> \kern{10px} O(n^{log_ba}) logba>d−−−>O(nlogba)
log b a = d − > O ( n d ∗ l o g b a ) \log_ba=d \kern{10px} -> \kern{10px} O(n^d*{log_ba}) logba=d−>O(nd∗logba)
log b a < d           − − − > O ( n d ) \log_ba<d \:\:\:\:\:\:\:\:\:---> \kern{10px} O(n^d) logba<d−−−>O(nd)
Merge过程
5 3 6 | 2 0 1
3 5 6 | 0 1 2
i | j
将小的数值填入到辅助数组help中,对应区域指针向后移一位
void MergeSort(int arr[], int L, int R){
if(L==R){
return ;
}
int mid=L+(R-L)>>1;
MergeSort(arr, L, mid);
MergeSort(arr, mid+1, R);
merge(arr, L, mid, R);
}
void merge(int arr[], int L, int mid, int R){
int i=L, j=mid+1, k=0;
int *help=(int *)malloc(sizeof(int)*(R-L+1));
//两个有且只有一个越界
while(i<=mid && j<=R){
help[k++]=arr[i]<arr[j]?arr[i++]:arr[j++];
}
while(i<=mid){
help[k++]=arr[i++];
}
while(j<=R){
help[k++]=arr[j++];
}
}
例题2:小和问题
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子: [1,3,4,2,5]
1左边比1小的数,没有;
3左边比3小的数,1;
4左边比4小的数,1、3;
2左边比2小的数,1;
5左边比5小的数,1、3、4、2;
所以小和为1+1+3+1+1+3+4+2=16
[ 1 ] 方法一:暴力 遍历所有比current小的数进行累和
int function(int arr[], int len){
int sum=0;
for(int i=1; i<len; i++){
for(int j=0; j<i; j++){
if(arr[j]<arr[i]){
sum+=arr[j];
}
}
}
}
[ 2 ] 方法二:在归并过程中计算小和
1 3 4 | 2 5
/ \
1 3 | 4 2 | 5
/
1 | 3
1)1 | 3 归并成 1 3 产生小和1
2)2 | 5 归并成 2 5 产生小和2
3)1 3 | 4 归并成 1 3 4 产生小和1 3
i j arr[i]
Note:
merge过程加速了累和(原因:有效地利用循环过程)
注:位运算运算速度快于基本运算