剑指offer刷题之——数组

1.斐波那契数列

题目描述:
斐波那契数列公式为:f[n] = f[n-1] + f[n-2], 初始值f[0]=0, f[1]=1,目标求f[n]

思路1:
普通递归版求法,这种方法通常和汉诺塔一起被放在课本的递归教学部分,应该是面试官不希望看到的算法。

int Fibonacci(int n) {
    if (n==0 || n==1) return n;
    return Fibonacci(n-1) + Fibonacci(n-2);
}

时间复杂度:O(2^n)
空间复杂度:递归栈

思路2:
方法二是从上往下递归的然后再从下往上回溯的,最后回溯的时候来合并子树从而求得答案。
那么动态规划不同的是,不用递归的过程,直接从子树求得答案。过程是从下往上。

代码:

int Fibonacci(int n) {
    vector<int> dp(n+1, 0);
        dp[1] = 1;
        for (int i=2; i<=n; ++i) {
            dp[i] = dp[i-1] + dp[i-2];
        }
        return dp[n];
}

时间复杂度:O(n)
空间复杂度:O(n)

优化后的动态规划:
发现计算f[5]的时候只用到了f[4]和f[3], 没有用到f[2]…f[0],所以保存f[2]…f[0]是浪费了空间。
只需要用3个变量即可。

int Fibonacci(int n) {
     if (n == 0 || n == 1) return n;
        int a = 0, b = 1, c;
        for (int i=2; i<=n; ++i) {
            c = a + b;
            a = b;
            b = c;
        }
        return c;
}

时间复杂度:O(n)
空间复杂度:O(1)

2.旋转数组的最小数字

题目描述:
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

思路:

采用二分法解答这个问题, mid = low + (high - low)/2 需要考虑三种情况:

  • (1)array[mid] > array[high]: 出现这种情况的array类似[3,4,5,6,0,1,2],此时最小数字一定在mid的右边。
    low = mid + 1
  • (2)array[mid] == array[high]: 出现这种情况的array类似 [1,0,1,1,1] 或者[1,1,1,0,1],此时最小数字不好判断在mid左边 还是右边,这时只好一个一个试 。 high = high - 1
  • (3)array[mid] < array[high]:
    出现这种情况的array类似[2,2,3,4,5,6,6],此时最小数字一定就是array[mid]或者在mid的左边。因为右边必然都是递增的。

high = mid 注意这里有个坑:如果待查询的范围最后只剩两个数,那么mid 一定会指向下标靠前的数字
比如 array = [4,6]:
array[low] = 4 ; array[mid] = 4 ; array[high] = 6 ;
如果high = mid - 1,就会产生错误, 因此high = mid 但情形(1)中low = mid + 1就不会错误。

class Solution {
public:
    int minNumberInRotateArray(vector<int> array) {
        int low=0; int high=array.size()-1;
        while(low<high){
            int mid = low+(high-low)/2;
            if(array[mid]>array[high])
                low = mid+1;
            else if(array[mid]==array[high])
                high =high-1;
            else
                high = mid;
        }
        return array[low];
    }
};

3.数组中出现次数超过一半的数字

题目描述:

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

思路1:
把数组排序后,如果符合条件的数存在,则一定是数组中间那个数。(比如:1,2,2,2,3;或2,2,2,3,4;或2,3,4,4,4等等)

这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(n*logn)并非最优;
代码1:

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty()) return 0;
        sort(numbers.begin(),numbers.end());
        int middle=numbers[numbers.size()/2];
        
        int count=0;
        for(int i=0;i<numbers.size();i++){
            if(numbers[i]==middle) ++count;
        }
        return (count>numbers.size()/2)?middle:0;
    
    }
};

思路2:
如果有符合条件的数字,则它出现的次数比其他所有数字出现的次数和还要多。

在遍历数组时保存两个值:一是数组中一个数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。然后再判断它是否符合条件即可。

代码2:

public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        if(numbers.empty()) return 0;
        
        int result=numbers[0];
        int count=1;
        for(int i=1;i<numbers.size();i++){
            if(count==0){
                result=numbers[i]; //更新result值为当前元素,并置次数为1
                count=1;
            }
            else if(numbers[i]==result)
                count++;
            else count--;
        }
        //判断result是否符合条件,即个数是否大于数组长度一半
        count=0;
        for(int i=0;i<numbers.size();i++){
            if(numbers[i]==result) count++;
        }
        return (count>numbers.size()/2)? result:0;
    
    }
};

4.调整数组顺序 使奇数在前,偶数在后

题目描述:
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

思路1:
开辟新数组,遍历一次数组,遇到奇数直接放入新开的数组中,再遍历一次数组,遇到偶数就继续放入新开的数组。最后再进行一次copy。
代码:

class Solution {
public:
    void reOrderArray(vector<int> &array) {
        vector<int> res;
        for(int i=0;i<array.size();i++){
            if(array[i]%2==1)
                res.push_back(array[i]);
        }
        for(int i=0;i<array.size();i++){
            if(array[i]%2==0)
                res.push_back(array[i]);
        }
        array=res;
                
    }
};

时间复杂度:O(n)
空间复杂度:O(n)

思路2:
初始化操作:记录一个变量i表示已经将奇数放好的下一个位置,显然最开始i=0,表示还没有一个奇数放好。
j 表示数组的下标,初始值为0, 表示从下标0开始遍历。

  • 如果遇到偶数,j++
  • 如果遇到奇数,假设位置为j,就将此奇数插入到i所指的位置,然后i往后移动一个位置,在插入之前。
  • 显然会涉及到数据的移动,也就是将[i,j-1]整体往后移动。直到整个数组遍历完毕,结束。

在这里插入图片描述

代码:

class Solution {
public:
  void reOrderArray(vector<int> &array) {
      int i = 0;
      for (int j=0; j<array.size(); ++j) {
          if (array[j]&1) {
              int tmp = array[j];
              for (int k=j-1; k>=i; --k) {
                  array[k+1] = array[k];//前一个值替换后一个值,从第j-1个向前推到第i个
              }
              array[i++] = tmp;
          }
      }
  }
};

时间复杂度:O(n^2),
假设数组中一般偶数在前,一半奇数在后,每次都要移动n/2个元素,是n^2/4

空间复杂度:O(1)

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值