力扣刷题
一、在一个无序数组找到该数组中的最大值,要求时间复杂度低最快方法是用二分法:时间复杂度为 O ( l o g N ) O(logN) O(logN))
int process(int arr[], int L, int R)
{ //如果最左边的下标等于最右边的下标,则证明只有一个数,则返回
if (L == R)
{
return arr[L];
}
int mid = L + ((R - L) >> 1);
int leftMax = process(arr, L, mid);
int rightMax = process(arr, mid + 1, R);
return max(leftMax, rightMax);
};
void test12()
{
int arr[] = { 2, 2, 3, 1, 7, 1, 5, 6 };
int a = process(arr, 0, sizeof(arr) / sizeof(arr[0]) - 1);
cout << a << endl;
}
对以上递归函数的内容进行解释:
int mid = L + ((R - L) >> 1);
这句是对整个数组取中间值的操作。我们在数学上取中间值的操作为(L+R)/2,但是这样操作起来会出现越界的情况,所以我们可以将上述的操作改为L + (R-L)/2。我们也可以将/2改写为右移一位,即>>1。这样会优化代码的计算效率。
int leftMax = process(arr, L, mid);
这句是取二分法之后左边的最大值,再进行一个二分操作。操作完成后进行递归,具体的递归细节请自行了解。
上述的时间复杂度符合master公式:
T
(
N
)
=
a
∗
T
(
N
/
b
)
+
O
(
N
d
)
T(N) = a * T(N/b)+ O(N^d)
T(N)=a∗T(N/b)+O(Nd)
我们只考虑一层递归的操作,N为数组的总量,因为是二分法,所以b = 2,又因为一层递归只需要求出左右两侧的最大值,所 a = 2,而上述代码中除了递归之外并没有其余的操作,所以
O
(
N
d
)
O(N^d)
O(Nd) 为1。综上,上述的时间复杂度为
2
∗
T
(
N
/
2
)
+
O
(
N
0
)
2 * T(N/2)+ O(N^0)
2∗T(N/2)+O(N0)
令master公式还有如下定律:
(1)
l
o
g
(
b
,
a
)
>
d
log(b,a)>d
log(b,a)>d 则其时间复杂度为
O
(
N
l
o
g
(
b
,
a
)
)
O(N^log(b,a))
O(Nlog(b,a))
(2)
l
o
g
(
b
,
a
)
=
=
d
log(b,a)==d
log(b,a)==d 则其时间复杂度为
O
(
N
d
∗
l
o
g
N
)
O(N^d*logN)
O(Nd∗logN)
(2)
l
o
g
(
b
,
a
)
<
d
log(b,a)<d
log(b,a)<d 则其时间复杂度为
O
(
N
d
)
O(N^d)
O(Nd)
二、归并排序
void merge1(int* arr, int L, int M, int R)
{
int i = 0; //定义p的初始指针位置
int *p = new int[R - L + 1]; //定义一个临时的数组
int p1 = L; //左半部分的下标
int p2 = M + 1; //右半部分的下标
while (p1 <= M && p2 <= R)
{
p[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
while (p1 <= M)
{
p[i++] = arr[p1++];
}
while (p2 <= R)
{
p[i++] = arr[p2++];
}
int k = 0; //从小标为 0 开始传送
for (int i = L; i <= R; i++) //将 b 数组的值传递给数组 a
{
arr[i] = p[k++];
}
delete[]p;
}
int process1(int *arr, int L, int R)
{
if (L == R)
{
return arr[L];
}
int mid = L + ((R - L) >> 1);
process1(arr, L, mid);
process1(arr, mid + 1, R);
merge1(arr, L, mid, R);
};
void test13()
{
int arr[] = { 0, 3, 4, 1, 2, 3 };
process1(arr, 0, (sizeof(arr) / sizeof(arr[0])) - 1);
for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++)
{
cout << arr[i] << " ";
}
cout << endl;
}
原理图如下:
二、求数组的小和
在一个数组中,每一个数左边比当前数小的数累加起来,叫做这个数组的小和。求一个数组的小和。
例子:[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右边有几个数比1大,则会小和会包括几个1,3右边有几个数比3小,则小和会包括几个3,以此类推。这一过程也可以抽象为一个归并过程。
c++代码如下:
#include<iostream>
using namespace std;
int merge2(int *arr, int L, int M, int R)
{
int *p = new int[R - L + 1]; //申请一个临时数组
int i = 0;
int p1 = L; //创建左半部分的指针
int p2 = M + 1; //创建右半部分的指针
int res = 0; //求小和暂存变量
//判断两个数组下标是否越界,如果越界则跳出循环
while (p1 <= M && p2 <= R)
{
//通过排序的动作来实现数组小和求解,(R - p2 + 1)求的是右组有多少个比p1小的数字。
res += arr[p1] < arr[p2] ? (R - p2 + 1) * arr[p1] : 0;
p[i++] = arr[p1] < arr [p2] ? arr[p1++] : arr[p2++];
}
//如果一边数组没有越界,则进入循环
while (p1 <= M)
{
p[i++] = arr[p1++];
}
while (p2 <= R)
{
p[i++] = arr[p2++];
}
int k = 0; //临时变量p应该从0开始
for (int i = L; i <= R; i++)
{
arr[i] = p[k++];
}
delete[]p;
return res;
}
int process2(int *arr, int L, int R)
{
//如果数组只存在一个数,则抛出
if (L == R)
{
return 0;
}
int mid = L + ((R - L) >> 1); //取中间值下标
process2(arr, L, mid); //递归数组左半部分
process2(arr, mid + 1, R); //递归数组右半部分
merge2(arr, L, mid, R); //开始归并
return process2(arr, L, mid) + process2(arr, mid + 1, R) + merge2(arr, L, mid, R);
}
void test15()
{
int arr[] = { 1, 3, 4, 2, 5 };
int a = process2(arr, 0, (sizeof(arr) / sizeof(arr[0])) - 1);
for (int i = 0; i < (sizeof(arr) / sizeof(arr[0])); i++)
{
cout << arr[i] << " ";
}
cout << endl;
cout << a << endl;
}
int main() {
test15();
system("pause");
return 0;
}
三、求数组的逆序对
逆序对问题,在一个数组中,左边的数如果比右边的数大,则这两个数构成一个逆序对,请打印所有逆序对。例子:[1,3,4,2,5],1左边比1小的数,没有;3左边比3小的数为1则为3,1;4左边比4小的数为1、3则为4,1;4,3以此类推。与上一个操作方法类似,这里就不在赘述了。
四、荷兰国旗问题
问题一:
给定一个数组arr,和一个数num,请把小于等于num的数放在数组的左边,大于num的数放在数组的右边。要求额外空间复杂度0(1),时间复杂度O(N)。
思路:
1、首先确定小于等于num的边界值,这里我们从数组第一个元素之前为界开始划定。
2、比较数组第一个元素与num的大小,如果小于等于num则与边界右边的数值做交换,对于第一个元素则是自己与自己交换(因为第一个数值是边界范围右侧的第一个数值),然后将指针移动到下一个位置,最小边界移动到第一个元素处。反之则最小边界不移动。以此类推。
c++代码如下:
void he(int* arr, int l, int n)
{
//如果数组为空或者只有一个数组,则返回
if (arr == NULL || n == 1)
{
return;
}
//最小边界的指针
int k= - 1;
for (int i = 0; i < l; i++)
{
cout << k << endl;
//如果当前指针的数小于等于我们指定的分界数,则与小于等于区域右侧位置第一个数交换,并且当前指针右移
if (arr[i]<=n)
{
int temp = arr[i];
arr[i] = arr[k+1];
arr[k + 1] = temp;
k++;
}
}
//排序结果输出
for (int i = 0; i < l; i++)
{
cout << arr[i] << " ";
}
}
问题二:
给定一个数组arr,和一个数num,请把小于num的数放在数组的左边,等于num的数放在数组的中间,大于num的数放在数组的右边。要求额外空间复杂度0(1),时间复杂度O(N)。
思路:
1、首先确定小于num的边界值(第一个元素之前),大于num的边界值(最后一个元素之后)。
2、比较数组第一个元素与num的大小,如果小于num则与小于边界右边的数值做交换,对于第一个元素则是自己与自己交换(因为第一个数值是边界范围右侧的第一个数值),然后将指针移动到下一个位置,最小边界移动到第一个元素处。如果当前数值与num相同,则直接将当前指针移动到下一个。如果当前数值大于num,则与大于的边界左侧值进行互换,边界值左移,指针不动。以此类推。
void he2(int* arr, int l, int n)
{
//如果数组为空或者只有一个数组,则返回
if (arr == NULL || n == 1)
{
return;
}
//设定小于区域的右边界
int s = -1;
//设定大于区域的左边界
int b = l;
//开始循环
int i = 0;
while (i != b)
{
//如果当前元素小于num,则与小于等于区域的右边界下一个数值交换,小于等于区域右移,指针右移
if (arr[i] < n)
{
int temp = arr[i];
arr[i] = arr[s + 1];
arr[s + 1] = temp;
s++;
i++;
}
//如果当前元素大于num,则与大于等于区域的左边界下一个数值交换,大于等于区域左移,指针原地不变
else if (arr[i] > n)
{
int temp1 = arr[i];
arr[i] = arr[b - 1];
arr[b - 1] = temp1;
b--;
}
//如果当前元素等于num,则当前指针右移
else
{
i++;
}
}
//排序结果输出
for (int i = 0; i < l; i++)
{
cout << arr[i] << " ";
}
}
五、快速排序随机数版本
快速排序随机数版本过程如下图所示:
void swap(vector<int>&arr, int i, int j)
{
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
vector<int> partition(vector<int> &arr, int l, int r)
{
int less = l - 1;//less为小于区边界,也就是等于区的开头
int more = r;//more为大于区边界,等于区的末尾
while (l < more)
{
if (arr[l] < arr[r])//当前数小于划分值
{
swap(arr, ++less, l++);//交换当前数与小于区边界
}
else if (arr[l] > arr[r])//当前数大于划分值
swap(arr, l, --more);//交换当前数和大于区边界
else
++l;//当前数与划分值相等,啥也不干,往下看下一个数。
}
swap(arr, more, r);//当l与more遇上了,则把划分值和大于区的边界交换回来
vector<int>p{ less + 1,more };//返回等于区的边界
return p;
}
void quickSort(vector<int>&arr, int l, int r)
{
if (l < r)
{
srand((int)time(NULL));
swap(arr, r, l + rand() % (r - l + 1));//先随机选这一个数,与末尾数字交换
vector<int>help = partition(arr, l, r);
quickSort(arr, l, help[0] - 1);
quickSort(arr, help[1] + 1, r);
}
}
void quickSort(vector<int>&a)
{
if (a.size() < 2)
return;
quickSort(a, 0, a.size() - 1);
}