笔试面试中关于数组的常见算法

参考:编程之美


最长递增子序列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;
}



结语:碰到数组有关排序的问题,尽量玩快排的方向思考;碰到移动交换元素的情况,考虑使用两个指针。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值