算法面试(一)

之前面试的时候,遇到了许多关于基本算法的题目,对于其代码的编写以及特殊情况下的优化及改进等,及算法相关的学习有了更多的感悟与领会,与大家分享一下,此处以快排讲解为例;

快排的核心思想很简单:就是先选择一个杠杆元素(一般选第一个数),对剩下元素遍历,大的放右边,小的放左边;然后对于两边元素再次用以上方法进行递归处理;

基本的代码实现在我的以前博客(排序二)中已经介绍过了,此处借鉴一个简洁代码:

// v为pivot,初始存储在arr[0]的位置 
int j = 0; // 循环过程保持 arr[0...j] < v ; arr[j+1...i) > v 
for( int i = l + 1 ; i <= n ; i ++ ) 
    if( arr[i] < v ) 
        swap( arr[++j] , arr[i] ); 
swap( arr[l] , arr[j]); // 此时,j指向pivot的正确位置

实例:
  4 8 3 0 5 2 7
  4 j=0
  4 8 
  4 3 8 j=1
  4 3 0 8 j=2
  4 3 0 8 5 
  4 3 0 2 8 5 j=3
  4 3 0 2 8 5 7
  2 3 0 4 8 5 7

我们知道对于快速排序算法,当元素排序是随机无序的时候,也就是当每递归完一次的时候,其元素大约成双等分时其效率是最高的,类似于二叉排序树,时间复杂度为O(n*lg n);然而大多数情况下事与愿违,当元素接近有序或者拥有大量重复元素时其效率将大大降低为O(n^2),所以对于此种情况我们需要考虑在内,提出以下解决办法:
(1)如果我们一开始不知道数组是否基本有序,那么对于杠杆元素的选择上,我们可以实现随机选择一个元素作为pivot,这样同样可以降低有序的概率从而整体提高排序效率;
(2)如果我们事先知道数组含有大量重复元素,则对于上面算法还可以再次改进,称为三路快排:小于的为一路,等于的为一路,大于的为一路,算法如下:

// v为pivot,初始存储在arr[0]的位置 
int lt = 0; // 循环过程中保持 arr[1...lt] < v 
int i = 1; // 循环过程中保持 arr[lt+1...i) == v 
int gt = n + 1; // 循环过程中保持 arr[gt...n] > v 

while( i < gt ){ 
    if( arr[i] < v ){ 
        swap( arr[i++], arr[lt+1]); lt ++; 
    } else if( arr[i] > v ){ 
        swap( arr[i], arr[gt-1]); gt --; 
    } else      // arr[i] == v 
        i ++; 
    } 
swap( arr[l] , arr[lt] ); 
// 此时 arr[lt...gt-1]部分为数组中元素等于v的部分 
// 之后只需要递归地对arr[l...lt-1]和arr[gt...r]两部分进行三路快排即可

基于比较的排序算法,其时间复杂度至少为O(nlogn).

扩展面试题:有一个数组,其中的元素取值只有可能是012。为这样一个数组排序。

/// 解法一:直接使用排序接口,时间复杂度O(nlogn) 
class Solution { 
public: 
    void sortColors(vector<int>& nums) { 
        sort( nums.begin(), nums.end() ); 
        return; 
    } };

// 解法二:基于计数排序的解法,时间复杂度O(n),需要两遍遍历 ,
//第一次统计元素个数,第二次放回原数组
class Solution { 
public: 
    void sortColors(vector<int>& nums) {
        int count[3] = {0}; // count[i]表示元素i的个数 
        for( int i = 0 ; i < nums.size() ; i ++ ){ 
            assert( nums[i] >= 0 && nums[i] <= 2 ); 
            count[ nums[i] ] += 1;
        } 
int index = 0; 
for( int i = 0 ; i < count[0] ; i ++ ) // 安置count[0]个0 
    nums[index++] = 0; 
for( int i = 0 ; i < count[1] ; i ++ ) // 安置count[1]个1 
    nums[index++] = 1; 
for( int i = 0 ; i < count[2] ; i ++ ) // 安置count[2]个2 
    nums[index++] = 2; return; 
} };

// 解法三:基于三路快排的partition的解法,时间复杂度O(n),只需要一边遍历 
class Solution { 
public: 
    void sortColors(vector<int>& nums) { 
        int i = 0; // nums[0..<i) == 0 
        int j = 0; // nums[i..<j) == 1 
        int k = nums.size(); // nums[k..<n) == 2 
        while( j < k ){ 
            if( nums[j] == 1 ) j++; 
            else if( nums[j] == 0 ) swap( nums[i++] , nums[j++] ); 
            else{   // nums[j] == 2 
                assert( nums[j] == 2 ); 
                swap( nums[j] , nums[k-1] ); k --; } 
        } 
        return; 
      } 
};

借鉴有感:http://www.imooc.com/article/16141

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值