参考:编程之美
最长递增子序列LIS(子序列不要求连续)
LIS.h
/*
*返回一个数组中最长递增子序列的长度
*
*Author: Michael 2012-09-30
*/
#pragma once
int LIS(int a[], int n);
int LIS_Ex(int a[], int n);LIS.cpp
#include "LIS.h"
//时间复杂度O(n² + n) = O(n²)
int LIS(int a[], int n){
/*
*定义一个数组LIS
*LIS[i]表示从0到i的最长递增序列长度
*/
int* LIS = new int[n];
for(int i = 0; i < n; i++)
LIS[i] = 1; //初始化为1
for(int i = 1; i < n; i++){
for(int j = 0; j < i; j++){
if(a[i] > a[j] && LIS[j] + 1 > LIS[i])
LIS[i] = LIS[j] + 1;
}
}
//LIS数组中最大值即为所求
int MaxLIS = LIS[0];
for(int i = 0; i < n; i++){
if(LIS[i] > MaxLIS)
MaxLIS = LIS[i];
}
return MaxLIS;
}
int Min(int a[], int n){
int Min = a[0];
for(int i = 0; i < n; i++){
if(Min > a[i])
Min = a[i];
}
return Min;
}
int LIS_Ex(int a[], int n)
{
int* LIS = new int[n];
// 记录数组中的递增序列信息
int* MaxV = new int[n + 1];
/*
*MaxV数组下标表示最长递增序列长度,值表示序列中最后一个元素
*例如: MaxV[4] = 3表示最长递增序列为4,序列最大值为3
*/
MaxV[1] = a[0]; //数组中第一个值,边界值
MaxV[0] = Min(a, n);
// 初始化最长递增序列的信息
for(int i = 0; i < n; i++)
LIS[i] = 1;
int MaxLIS = 1;
for(int i = 0; i < n; i++){
// 遍历历史最长递增序列信息
int j;
for(j = MaxLIS; j >= 0; j--){
if(a[i] > MaxV[j]){
LIS[i] = j + 1;
break;
}
}
// 更新MaxV数组
if(LIS[i] > MaxLIS){
MaxLIS = LIS[i];
MaxV[LIS[i]] = a[i];
}
else if(MaxV[j] < a[i] && a[i] < MaxV[j + 1]){
MaxV[j + 1] = a[i];//更新长度为j+1的序列最大值
}
}
return MaxLIS;
}
数组中位数:一个数组中大小居中的数,如{1, 2, 3 }的中位数为2
Median.h
/*
*返回一个数组的中位数
*
*Author: Michael 2012-09-30
*/
#pragma once
int Median(int a[], int low, int high, int n);Median.cpp
#include "Median.h"
// 以一个元素为轴把数组分为两个部分
// 前面一部分小于轴,后面一部分大于轴
int Partition(int a[], int low, int high){
int pivot = a[low]; //a[low]的值作为轴
while(low < high){
//从后往前,找到第一个比pivot小的值,交换到low位置
while(low < high && a[high] >= pivot)
high--;
a[low] = a[high];
//从前往后,找到第一个比pivot大的值,交换到high位置
while(low < high && a[low] <= pivot)
low++;
a[high] = a[low];
}
a[low] = pivot;
return low; //low为轴所在的位置,把数组分为两部分
}
// 借助快排的思想。但是需要传递一个数组长度n
// 因为要判断每次Partition之后的low在不在中位,需要数组长度信息
// 时间复杂度:O(nlogn)
int Median(int a[], int low, int high, int n){
if(low < high){
int pos = Partition(a, low, high);
if(pos == (n - 1) / 2)//只考虑数组长度为奇数的情况。
return a[pos];
else if(pos > (n - 1) / 2)
return Median(a, 0, pos - 1, n);
else
return Median(a, pos + 1, high, n);
}
}
common.h
#pragma once
/* find the two numbers appear once in an array */
void FindNumberAppearOnce(int a[], int n, int* number1, int* number2);FindNumberAppearOnce.cpp
/*
* 找一个数组中只出现一次两个数
*/
/*
* 如果是找一个值出现一次的数,那么所有的数一起异或
* 得出的结果就是那个数,因为相同的数异或为0
* 考虑把所有数异或,得出的数为两个只出现一次的数异或的结果
* 由于这两个数不同,所以结果一定不为0
* 在结果中找到第一位为1的位置,通过这位是0或者是1把原数组分为两部分
* 那么通过这种方式,原数组被分为两个部分,且相同的数一定会被分到同一组
* 并且两个不同的数别分被分到两个数组
* 把两个子数组分别异或,得出两个数
*/
#include "common.h"
void FindNumberAppearOnce(int a[], int n, int* number1, int* number2){
int resultAll = 0;
*number1 = 0;
*number2 = 0;
for(int i = 0; i < n; i++){
resultAll = resultAll ^ a[i];
}
int FirstOne = 0x01;
while((resultAll & FirstOne) != FirstOne)// 注意括号,&运算需要加括号
FirstOne = FirstOne << 1;
for(int i = 0; i < n; i++){
if((a[i] & FirstOne) == 0)
*number1 = *number1 ^ a[i];
else
*number2 = *number2 ^ a[i];
}
}如果是有三个数只出现一次呢,如何求出这三个数?
求一个数在排序数组中出现的次数,条件:有且仅有这个数出现的次数超过1次
Note:是排序数组,那么相同的数肯定连续。找出第一个出现的位置,找出最后一个位置,就能得出结果。
common.h
#pragma once
/* find the count of a repeated number in a seqenced array */
int GetNumberofK(int a[], int k, int n);NumberofCount.cpp
/*
* 求一个数在排序数组中出现的次数
* 假设数组中只存在这一个数有重复
*
* 基本思想:使用改进的二分查找,找出第一个K和最后一个K
*/
#include "common.h"
// 找到k在数组中首次出现的位置
int GetFirstK(int a[], int k, int n){
int low = 0;
int high = n - 1;
int mid;
while(low <= high){
mid = (high + low) / 2;
if(a[mid] > k){
high = mid - 1;
}
else if(a[mid] < k){
low = mid + 1;
}
else{ // a[mid] == k
if((mid - 1 >= 0) && (a[mid - 1] == k))//若mid前面的数等于k,表示第一个k还在前面
high = mid - 1;
else
return mid;
}
}
return -1;
}
// 找到k在数组中最后出现的位置
int GetLastK(int a[], int k, int n){
int low = 0;
int high = n - 1;
int mid;
while(low <= high){
mid = (low + high) / 2;
if(a[mid] > k){
high = mid - 1;
}
else if(a[mid] < k){
low = mid + 1;
}
else{
if((mid + 1 < n) && (a[mid + 1] == k))
low = mid + 1;
else
return mid;
}
}
}
int GetNumberofK(int a[], int k, int n){
if(a == NULL)
return -1;
return GetLastK(a, k, n) - GetFirstK(a, k, n) + 1;
}调整一个数组,把奇数放在前半部分,偶数在后半部分
common.h
#pragma once
/* reorder odd and even */
void RecordOddEven(int begin[], int length);void Reorder(int begin[], int length, bool (*func)(int));
bool isEven(int num);
RecordOddEven.cpp
#include "common.h"
/*
* 调整一个数组,把奇数放在前半部分,偶数在后半部分
*/
void RecordOddEven(int begin[], int length){
if(begin == 0 || length <= 0)
return;
int* pBegin = begin;
int* pEnd = pBegin + length - 1;
while(pBegin < pEnd){
// 从前面找第一个偶数
while(pBegin < pEnd && ((*pBegin & 0x01) == 1))
pBegin++;
// 从后面找第一个奇数
while(pEnd > pBegin && ((*pEnd & 0x01) == 0))
pEnd--;
// 交换前部分的奇数和后部分的偶数
if(pBegin < pEnd){
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
pBegin++;
pEnd--;
}
}
}
/*
* 如果条件不仅仅是奇偶数,可能有其他很多条件
* 考虑到扩展性,就应该传递一个函数指针
* 通过不同的函数满足不同的条件
*/
void Reorder(int begin[], int length, bool (*func) (int)){
if(begin == 0 || length <= 0)
return;
int* pBegin = begin;
int* pEnd = pBegin + length - 1;
while(pBegin < pEnd){
// 从前面找第一个偶数
while(pBegin < pEnd && func(*pBegin))
pBegin++;
// 从后面找第一个奇数
while(pEnd > pBegin && !func(*pEnd))
pEnd--;
// 交换前部分的奇数和后部分的偶数
if(pBegin < pEnd){
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
pBegin++;
pEnd--;
}
}
}
bool isEven(int num){
if((num & 0x01) == 1)
return true;
else
return false;
}
本文深入讲解数组中的经典算法问题,包括最长递增子序列、数组中位数的寻找、特定数值出现次数的统计等,并提供了详细的实现代码。通过具体案例剖析,帮助读者掌握数组操作的核心技巧。
353

被折叠的 条评论
为什么被折叠?



