目录
● 写出一个函数,输入是两个数组,输出是将两个数组中所有元素排序以后用一个数组输出。
● 手写代码:有三种面值的硬币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)空间分配方面:
数组在静态存储分配情形下,存储元素数量受限制,动态存储分配情形下,虽然存储空间可以扩充,但需要移动大量元素,导致操作效率降低,而且如果内存中没有更大块连续存储空间将导致分配失败;即数组从栈中分配空间,,对于程序员方便快速,但自由度小。
链表存储的节点空间只在需要的时候申请分配,只要内存中有空间就可以分配,操作比较灵活高效;即链表从堆中分配空间, 自由度大但申请管理比较麻烦。
哈希表可以结合数组和链表的优点