重写剑指offer上的一些题:数组中重复的数字+最小的k个数+数组中的第k个最大元素

1.找出数组中重复的数字。

题目:在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了.......

原地交换的思想。还是有必要把最简单的思路记下来,比如这道题目可以建立哈希表这样就能以O(1)的时间复杂度来进行查找了。

在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。参考leetcode评论,这种原地置换的目的主要是为了降低空间复杂度。因为哈希表的方法(比如使用一个set)使用了一个大小为O(n)的哈希表。但我没有明白为什么每个数字最多只要交换两次就能找到属于它自己的位置,这样就使得代码中尽管有一个两重循环,但每个数字最多只要交换两次就能找到属于它自己的位置,因此总的时间复杂度为O(n)

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
     if(nums.size()==0) return -1;//无效输入测试,空
      //无效输入测试,有不合法的数字
      for(auto it=nums.begin();it!=nums.end();++it){
          if(*it<0||*it>nums.size()-1) 
             return -1;
      }
     int i=0;
     for(;i<nums.size();++i){
         //首先比较这个数字是不是等于i
         while(nums[i]!=i){
             if(nums[i]==nums[nums[i]]){
                 return nums[i];
             }
             swap(nums[i],nums[nums[i]]);
         }
         }
         return -1; 
     }
};

相关问题:不修改数组找出重复的数字

长度为n+1的数组里所有的数字都在1到n的范围内,所以数组中至少有一个数字是重复的。

不修改数组找出重复的数字,这道题目有助于理解二分查找的思想。使用sort对vector进行非降序的排序方法sort(vec.begin(),vec.end())

int countRange(vector<int> &nums,int start,int end);
int getduplication(vector<int> &nums){//不修改数组找出重复的数字,长度为n+1的数组,元素都在1到n的范围内
     if(nums.size()==0||nums.size()==1) return -1;//不会有重复的数字
     int start=1;
     int end=nums.size()-1;//end对应n
     while(start<=end){
        int mid=start+(end-start)/2;
        int count=countRange(nums,start,mid);
        if(mid==start){
            if(count>1) return mid;
            else {
                return end;
            }
        }
        /*if(end==start){//这样写好像也是可以的,J剑指offer的写法
            if(count>1)
                return start;
            else
                break;
        }*/
        if(count>=mid-start+1){//因此这些数字一定有重复的,即start到mid中间的数
            end=mid;
        }
        else{
            start=mid+1;
        }
     }
     return -1;
}
int countRange(vector<int> &nums,int start,int end){
    if(nums.size()==0) return 0;
    int n=nums.size();
    int count=0;
    for(int i=0;i<n;++i){
        if(nums[i]>=start&&nums[i]<=end){
            count++;
        }
    }
    return count;
}

2.用两个栈实现队列

class Solution
{
public:
    void push(int node) {
        stack1.push(node);
    }
 
    int pop() {
        if(stack2.size()<=0){
        while(!stack1.empty()){
            stack2.push(stack1.top());
            stack1.pop();
        }
        }
        int res=stack2.top();
        stack2.pop();
        return res;
    }
 
private:
    stack<int> stack1;
    stack<int> stack2;
};

3.最小的k个数

输入n个整数,找出其中最小的k个数。

方法一:基于Partition函数来解决,但为什么这样的时间复杂度为O(n)???.快排的时间复杂度为O(nlogn)

class Solution {
public:
int Partition(vector<int>& arr,int low,int high){
int pivotkey=arr[low];
while(low<high){
    while(low<high&&arr[high]>=pivotkey) high--;
    arr[low]=arr[high];
    while(low<high&&arr[low]<=pivotkey)  low++;
    arr[high]=arr[low];
}
arr[low]=pivotkey;
return low;
}
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
    int n=arr.size();
    vector<int> res;
    if(n<=0||k>n||k<=0){
        return res;
    }
    int start=0;
    int end=n-1;
    int index=Partition(arr,start,end);
    while(index!=k-1){
        if(index>k-1){
           end=index-1;
           index=Partition(arr,start,end);
        }
        else{
            start=index+1;
            index=Partition(arr,start,end);
        }
    }
    for(int i=0;i<index+1;i++){
res.push_back(arr[i]);
    }
    return res;
    }
};

方法二:时间复杂度为O(nlogk)的算法,

特别适合处理海量的数据。我们容易想到用最大堆,在最大堆中根节点的值总是大于它的子树中任意节点的值,set和multiset都是基于红黑树实现的。这种解法虽然慢一点,但它由两个明显的有限,一是没有修改输入的数据,适合海量数据的输入

vector<int> getLeastNumbers(vector<int>& arr, int k) {
    int n=arr.size();
    vector<int> res;
    if(n<=0||k<=0||k>n){
        return res;
    }
    priority_queue<int> q;
    for(int i=0;i<n;++i){
        if(i<k){
            q.push(arr[i]);
        }
        else{
            if(arr[i]<q.top()){
                q.pop();
                q.push(arr[i]);
            }
        }
    }
    for(int i=0;i<k;i++){
        res.push_back(q.top());
        q.pop();
     }
     return res;
    }

3.数组中的第k个最大元素

注意在这两道题目中注意都以pivotloc作为循环的条件。

方法一:利用partition函数

class Solution {
public:
int Partition(vector<int>& nums,int low,int high){
    int pivotkey=nums[low];
    while(low<high){
        while(low<high&& nums[high]>=pivotkey) high--;
        nums[low]=nums[high];
        while(low<high&& nums[low]<=pivotkey) low++;
        nums[high]=nums[low];
    }
    nums[low]=pivotkey;
    return low;
}
//第k个最大就是第n-k+1最小
     int findKthLargest(vector<int>& nums, int k) {//可以假设k总是有效的
        int n=nums.size();
if(n<=0||k>n||k<1) return -1;
int start=0, end=n-1;
int pivotloc=Partition(nums,start,end);
while(pivotloc!=n-k){//n-k+1=5,写小于等于的话partition算法没有意义.在这两道题目中注意都以pivotloc作为循环的条件
//cout<<pivotloc<<endl;
if(pivotloc==n-k){
    break;
}
else if(n-k<pivotloc){
    end=pivotloc-1;
    pivotloc=Partition(nums,start,end);
}
else{
    start=pivotloc+1;
    pivotloc=Partition(nums,start,end);
}
}
return nums[pivotloc];
    }
};

方法二:利用堆排序

此处用到的是大根堆,题解中有用到一个删除的元素的?没太懂

下面这个HeapAdjust是根据数据结构书上写的,假设输出堆顶元素之后,以堆中最后一个元素代替它,此时根节点的左右子树均为对,则只需自上至下进行调整即可。

堆排序的方法对记录数较小的文件并不提倡

class Solution {
public:
    void HeapAdjust(vector<int>& a,int s,int m){
    //已知记录中的关键字出a[s]外均满足堆的定义,本函数调整a[s],使得a[s]到a[m]成为一个大顶堆。s是开始的位置
    int rc=a[s];
    //沿k较大的子节点向下筛选
    for(int j=2*s+1;j<=m;j=j*2+1){
        if(j<m&&(a[j]<a[j+1]))  ++j;  //j为key较大的记录的下标
        if(rc>=a[j]) break;   //rc应插入在位置s上,注意这一句:如果记录大于等于a[j]的话说明当前的位置是可以的,不需要挪了
        a[s]=a[j];
        s=j;
    }
    a[s]=rc;      //插入
}//HeapAdjust
int findKthLargest(vector<int>& nums, int k){
     int n=nums.size();
     for(int i=n/2-1;i>=0;--i)//把a[0]~a[n-1]建成大顶堆;主要思路:第一次保证0~0位置大根堆结构(废话),
        //第二次保证0~1位置大根堆结构,第三次保证0~2位置大根堆结构...直到保证0~n-1位置大根堆结构
       HeapAdjust(nums,i,n-1);
       for(int i=n-1;i>=n-k;--i){
        swap(nums[0],nums[i]);
        HeapAdjust(nums,0,i-1);
       }
       return nums[n-k];
}
};

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值