算法与数据结构 数组

目录

 

● 手写代码:合并两个排序数组

参考回答:

● 手写代码:最大子数组问题(要求时间复杂度最佳)

参考回答:

● 手写代码:筛选数组arr中重复的元素,考虑时间复杂度。

参考回答:

● 写出一个函数,输入是两个数组,输出是将两个数组中所有元素排序以后用一个数组输出。

参考回答:

● 手写代码:合并两个有序数组

参考回答:

● 手写代码:有三种面值的硬币k1 < k2 < k3 ,找k面值的零钱,最少需要多少硬币

参考回答:

● 手写代码:合并有序数组

参考回答:

● 手写代码:一个数组找出重复的元素

参考回答:

● 请问如何防止数组越界

参考回答:

● 请回答数组和链表的区别,以及优缺点,另外有没有什么办法能够结合两者的优点

参考回答:

 


● 手写代码:合并两个排序数组

参考回答:

class Solution {

public:

void merge(int *num1,int m,int *num2,int n){

int i=m-1;

int j=n-1;

while(i>=0&&j>=0)

{

if(nums1[i]>nums2[j])

{

nums1[i+j+1]=nums1[i];

i--;

}

else{

nums1[i+j+1]=nums2[j];

j--;

}

}

while(j>=0)

{

nums1[i+j+1]=nums[j];

j--;

}

}

};

● 手写代码:最大子数组问题(要求时间复杂度最佳)

参考回答:

线性时间算法:该算法在每次元素累加和小于0时,从下一个元素重新开始累加。实现代码如下:

/*

时间复杂度O(n)

和最大的子序列的第一个元素肯定是正数

因为元素有正有负,因此子序列的最大和一定大于0

*/

int MaxSubSum(int *arr,int len)

{

int i;

int MaxSum = 0;

int CurSum = 0;

for(i=0;i<len;i++)

{

CurSum += arr[i];

if(CurSum > MaxSum)

MaxSum = CurSum;

//如果累加和出现小于0的情况,

//则和最大的子序列肯定不可能包含前面的元素,

//这时将累加和置0,从下个元素重新开始累加

if(CurSum < 0)

CurSum = 0;

}

return MaxSum;

}

● 手写代码:筛选数组arr中重复的元素,考虑时间复杂度。

参考回答:

function duplicates(arr) {

//声明两个数组,a数组用来存放结果,b数组用来存放arr中每个元素的个数

var a = [],b = [];

//遍历arr,如果以arr中元素为下标的的b元素已存在,则该b元素加1,否则设置为1

for(var i = 0; i < arr.length; i++){

if(!b[arr[i]]){

b[arr[i]] = 1;

continue;

}

b[arr[i]]++;

}

//遍历b数组,将其中元素值大于1的元素下标存入a数组中

for(var i = 0; i < b.length; i++){

if(b[i] > 1){

a.push(i);

}

}

return a;

}

时间复杂度为O(n)

● 写出一个函数,输入是两个数组,输出是将两个数组中所有元素排序以后用一个数组输出。

参考回答:

class Solution {

public:

int *sort(int *a,int lenA,int *b,int lenB){

fastSort(a,0,lenA);

fastSort(b,0,lenB);

return merge(a,lenA,b,lenB);

}

private:

//快速排序

void fastSort(int *a,int start,int end){

if(a==NULL || end-start<=1 || start<0)

return;

int pivotPos = start;

int pivot = a[start];

int temp;

for(int i=start+1;i<end;++i){

if(a[i]<pivot){

if(++pivotPos!=i){

temp = a[i];

a[i] = a[pivotPos];

a[pivotPos] = temp;

}

}

}

a[start] = a[pivotPos];

a[pivotPos] = pivot;

fastSort(a,start,pivotPos-1);

fastSort(a,pivotPos+1,end);

}

//两路归并

int *merge(int *a,int lenA,int *b,int lenB){

if(a==NULL || lenA<=0)

return b;

if(b==NULL || lenB<=0)

return a;

int *arry = new int[lenA+lenB];

if(arry==NULL){

cerr << "内存分配失败" << endl;

exit(1);

}

int posA = 0, posB = 0 ,pos = 0;

while(posA<lenA && posB<lenB){

if(a[posA]<b[posB])

arry[pos++] = a[posA++];

else

arry[pos++] = b[posB++];

}

while(posA<lenA)

arry[pos++] = a[posA++];

while(posB<lenB)

arry[pos++] = b[posB++];

return arry;

}

};

● 手写代码:合并两个有序数组

参考回答:

解法一:

class Solution {

public:

void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {

int num[m+n];//新建一个数组,对nums1和nums2排序,排完序赋值给nums1

int i = 0,j = 0,k = 0;

while(i<m && j<n){

if(nums1[i] <= nums2[j])

num[k++] = nums1[i++];

else

num[k++] = nums2[j++];

}

while(i < m) num[k++] = nums1[i++];

while(j < n) num[k++] = nums2[j++];

copy(num,num+m+n,nums1.begin());

}

};

解法二:

class Solution {

public:

void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {

vector<int> num(m+n);//与解法一类似,不过是新建一个vector

int i = 0,j = 0,k = 0;

while(i<m && j<n){

if(nums1[i] <= nums2[j])

num[k++] = nums1[i++];

else

num[k++] = nums2[j++];

}

while(i < m) num[k++] = nums1[i++];

while(j < n) num[k++] = nums2[j++];

nums1.assign(num.begin(),num.end());//nums1.swap(num)也可以

}

};

解法三:直接在nums1里进行操作,从nums1的尾部开始,取nums1和nums2中的最大值放入其中。如果n先到达0就能直接得到合并好的数组;如果m先到达0,只需将n剩下的元素复制到nums1中即可。

class Solution {

public:

void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {

int k=m+n;

while(m>0 && n>0){

if(nums1[m-1] >= nums2[n-1]){

nums1[k-1] = nums1[m-1];

--k;

--m;

}

else{

nums1[k-1] = nums2[n-1];

--k;

--n;

}

}

while(n > 0){

nums1[k-1] = nums2[n-1];

--k;

--n;

}

}

};

● 手写代码:有三种面值的硬币k1 < k2 < k3 ,找k面值的零钱,最少需要多少硬币

参考回答:

假设有1 元,3 元,5 元的硬币,假设一个函数 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)。

最后公式:

代码示例:

public class CoinProblemBasicTest {

private int[] d; // 储存结果

private int[] coins = {1, 3, 5}; // 硬币种类

private void d_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);

}

}

}

public void test() 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] + " 个硬币");

}

}

}

● 手写代码:合并有序数组

参考回答:

解法一:从结尾开始归并,不会覆盖元素。从A[n+m-1]处开始往前一个元素一个元素的求,每次都要比较A[i]和B[j]的大小。需要注意的是,要考虑到: A和B有一个为空时的情况

class Solution {

public:

void merge(int A[], int m, int B[], int n) {

int i , j , k ;

for( i = m - 1, j = n - 1, k = n + m -1; k >= 0; --k)

{

if( i >= 0 &&(j < 0 || A[i] >= B[j]) )

A[k] = A[i--];

else

A[k] = B[j--];

}

}

};

解法二:由于合并后A数组的大小必定是m+n,所以从最后面开始往前赋值,先比较A和B中最后一个元素的大小,把较大的那个插入到m+n-1的位置上,再依次向前推。如果A中所有的元素都比B小,那么前m个还是A原来的内容,没有改变。如果A中的数组比B大的,当A循环完了,B中还有元素没加入A,直接用个循环把B中所有的元素覆盖到A剩下的位置。

class Solution {

public:

void merge(int A[], int m, int B[], int n) {

int count = m + n - 1;

--m; --n;

while (m >= 0 && n >= 0) A[count--] = A[m] > B[n] ? A[m--] : B[n--];

while (n >= 0) A[count--] = B[n--];

}

};

● 手写代码:一个数组找出重复的元素

参考回答:

function duplicates(arr) {

//声明两个数组,a数组用来存放结果,b数组用来存放arr中每个元素的个数

var a = [],b = [];

//遍历arr,如果以arr中元素为下标的的b元素已存在,则该b元素加1,否则设置为1

for(var i = 0; i < arr.length; i++){

if(!b[arr[i]]){

b[arr[i]] = 1;

continue;

}

b[arr[i]]++;

}

//遍历b数组,将其中元素值大于1的元素下标存入a数组中

for(var i = 0; i < b.length; i++){

if(b[i] > 1){

a.push(i);

}

}

return a;

}

时间复杂度为O(n)

● 请问如何防止数组越界

参考回答:

由于数组的元素个数默认情况下是不作为实参内容传入调用函数的,因此会带来数组访问越界的相关问题

防止数组越界:

1)检查传入参数的合法性。

2)可以用传递数组元素个数的方法,即:用两个实参,一个是数组名,一个是数组的长度。在处理的时候,可以判断数组的大小,保证自己不要访问超过数组大小的元素。

3)当处理数组越界时,打印出遍历数组的索引十分有帮助,这样我们就能够跟踪代码找到为什么索引达到了一个非法的值

4)Java中可以加入try{ } catch(){ }

● 请回答数组和链表的区别,以及优缺点,另外有没有什么办法能够结合两者的优点

参考回答:

1.数组:

数组是将元素在内存中连续存放,由于每个元素占用内存相同,可以通过下标迅速访问数组中任何元素。但是如果要在数组中增加一个元素,需要移动大量元素,在内存中空出一个元素的空间,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。如果应用需要快速访问数据,很少插入和删除元素,就应该用数组。

2.链表:

链表中的元素在内存中不是顺序存储的,而是通过存在元素中的指针联系到一起,每个结点包括两个部分:一个是存储数据元素的数据域,另一个是存储下一个结点地址的指针。如果要访问链表中一个元素,需要从第一个元素开始,一直找到需要的元素位置。但是增加和删除一个元素对于链表数据结构就非常简单了,只要修改元素中的指针就可以了。如果应用需要经常插入和删除元素你就需要用链表。

3.区别:

(1)存储位置上:

数组逻辑上相邻的元素在物理存储位置上也相邻,而链表不一定;

(2)存储空间上:

链表存放的内存空间可以是连续的,也可以是不连续的,数组则是连续的一段内存空间。一般情况下存放相同多的数据数组占用较小的内存,而链表还需要存放其前驱和后继的空间。

(3)长度的可变性:

链表的长度是按实际需要可以伸缩的,而数组的长度是在定义时要给定的,如果存放的数据个数超过了数组的初始大小,则会出现溢出现象。

(4)按序号查找时,数组可以随机访问,时间复杂度为O(1),而链表不支持随机访问,平均需要O(n);

(5)按值查找时,若数组无序,数组和链表时间复杂度均为O(1),但是当数组有序时,可以采用折半查找将时间复杂度降为O(logn);

(6)插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可;

(7)空间分配方面:

数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;即数组从栈中分配空间,,对于程序员方便快速,但自由度小。

链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;即链表从堆中分配空间, 自由度大但申请管理比较麻烦。

哈希表可以结合数组和链表的优点

 

猜你喜欢

转载自blog.csdn.net/u012369559/article/details/89511100