剑指Offer(C++实现)系列更新中。。。

目录

【面试题3】

【面试题4】

【面试题11】:旋转数组的最小数字

【面试题21】:调整数组顺序使奇数位于偶数前面

【面试题22】

【面试题25】

【面试题39】数组中出现次数超过一半的数字

【面试题42】连续子数组的最大和

【面试题45】把数组排成最小的数

【面试题51】数组中的逆序对

【面试题53】数字在排序数组中的次数


 

【面试题3】

题目描述:

在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。

解题思路:

充分利用题干信息。

首先判断当前值与下标是否相等。

    相等的话,continue。不等的话,判断number[i] 和 number[number[i]]是否相等。

    相等的话,就认为找到了。不等的话,就交换number[i] 和 number[number[i]],直到当前位置的值与下标相等位置。

时间复杂度:O(n)

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) 
    {
        //判断数组是否为空,或者长度不大零
        if (numbers == nullptr || length <=0)
            return false;
        //判断数组中的元素是否满足[0,length-1]
        for (int i=0;i<length;i++)
        {
            if (numbers[i] > length-1 || numbers[i] < 0)
                return false;
        }
        //
        for(int i = 0;i<length;i++)
        {
            while (numbers[i] != i)
            {
                if (numbers[i] == numbers[numbers[i]])
                {
                    *duplication = numbers[i];
                    return true;
                }
                int temp = numbers[i];
                numbers[i] = numbers[temp];
                numbers[temp] = temp;
            }
        }
        return false;
        
    }
};

【面试题4】

题目描述:

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

解题思路:因为二维数组是有序的,因此假设从右上角开始查找,如果该值小于target,就在下一行寻找,如果该值大于target,就在上一列中寻找。直到找到,或者满足退出条件为止

时间复杂度:O(n)

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) 
    {        
        int row = 0;
        int col = array[0].size()-1;
        while (row <= array.size()-1 && col >= 0 )
        {
            if (target > array[row][col])
                row++;
            else if (target < array[row][col])
                col--;
            else
                return true;
        }
        return false;
        
    }
};

【面试题11】:旋转数组的最小数字

题目描述:

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。 例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。 NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

解题思路:

因为存在两个有序的子数组,因此可以考虑二分查找。

定义left和right,分别表示前一个数组最大的元素和后一个数组最小的元素。

若arr[left] 小于arr[mid],说明mid之前都是单调递增的数组,不会有最小值,因此在[mid,right]之间查找

若arr[left] 大于arr[mid],说明在[left,mid]之间存在两个数组的交界处,肯定存在最小值,因此在[mid,right]中寻找

当right和left相差1的时候,right对应的值就是最小值时,

时间复杂度:O(n)

class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        int size = rotateArray.size();
        if(size == 0){
            return 0;
        }//if
        int left = 0,right = size - 1;
        int mid = 0;
        // rotateArray[left] >= rotateArray[right] 确保旋转
        while(rotateArray[left] >= rotateArray[right]){
            // 分界点
            if(right - left == 1){
                mid = right;
                break;
            }//if
            mid = left + (right - left) / 2;
            // rotateArray[left] rotateArray[right] rotateArray[mid]三者相等
            // 无法确定中间元素是属于前面还是后面的递增子数组
            // 只能顺序查找
            if(rotateArray[left] == rotateArray[right] && rotateArray[left] == rotateArray[mid]){
                return MinOrder(rotateArray,left,right);
            }//if
            // 中间元素位于前面的递增子数组
            // 此时最小元素位于中间元素的后面
            if(rotateArray[mid] >= rotateArray[left]){
                left = mid;
            }//if
            // 中间元素位于后面的递增子数组
            // 此时最小元素位于中间元素的前面
            else{
                right = mid;
            }//else
        }//while
        return rotateArray[mid];
    }
private:
    // 顺序寻找最小值
    int MinOrder(vector<int> &num,int left,int right){
        int result = num[left];
        for(int i = left + 1;i < right;++i){
            if(num[i] < result){
                result = num[i];
            }//if
        }//for
        return result;
    }
};

【面试题21】:调整数组顺序使奇数位于偶数前面

题目描述:

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

解题思路:

新建一个数组,先遍历一遍把奇数放进去,在遍历一遍把偶数放进去

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

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

        for (int i = 0; i< array.size(); i++){
	        if (!(array[i]&1)) {
			    ret.push_back(array[i]);
		    }//if
        }//for
        array = ret;
    }    
};

【面试题22】

题目描述:输入一个链表,输出该链表中倒数第k个结点。

解题思路:开始时让两个指针指向头指针,然后让一个指针先走k-1步,然后在一起走,当先走的指针到达最后一个结点时,后走的指针指向的结点就是导数第k个结点

时间复杂度:O(n)

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) 
    {
        ListNode *pSlow = pListHead;
        ListNode *pFast = pListHead;
        int i=0;
        for (;pFast!=nullptr;i++)
        {
            //让pRight先走k-1步,pLeft再走
            if (i >= k)    
                pSlow = pSlow->next;
            pFast = pFast ->next;
        }
        //如果链表的长度小于k的话,返回空指针
        return i<k?nullptr:pSlow;
    
    }
};

【面试题25】

题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则

解题思路:

1. 递归思路。

先判断两个链表link1和link2的第一个结点的值的大小,如果link1的结点值更小,就放在新链表link的头结点。

然后对link1->next和link2递归。反之,就把link2的头结点放在link的头结点。对link1和link2->next递归

 

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        if (pHead1 == nullptr) 
            return pHead2;
        if (pHead2 == nullptr)
            return pHead1;
        ListNode *pMergedHead = nullptr;
        if (pHead1->val < pHead2->val)
        {
            pMergedHead = pHead1;
            pMergedHead->next = Merge(pHead1->next,pHead2);
        }
        else
        {
            pMergedHead = pHead2;
            pMergedHead->next = Merge(pHead1,pHead2->next);
        }
        return pMergedHead;
    }
};

2. 循环思路。

定义一个新链表,然后遍历比较两个链表的结点值的大小,把值小的结点放入新链表。

class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
        //判断时候是空指针
        if (pHead1 == nullptr) 
            return pHead2;
        if (pHead2 == nullptr)
            return pHead1;
        //定义两个指针,一个指向头指针,一个新链表的结点
        ListNode *pMergedHead = nullptr;
        ListNode *pMergedNode = nullptr;

        while (pHead1!=nullptr && pHead2!=nullptr)
        {
            if (pHead1->val < pHead2->val)
            {
                //指向头结点
                if (pMergedHead == nullptr)
                    pMergedHead = pMergedNode = pHead1;
                else
                {
                    //结点的遍历
                    pMergedNode->next = pHead1;
                    pMergedNode = pMergedNode->next;
                }
                pHead1 = pHead1->next;
            }
            else
            {
                if (pMergedHead == nullptr)
                    pMergedHead = pMergedNode = pHead2;
                else
                {
                    pMergedNode->next = pHead2;
                    pMergedNode = pMergedNode->next;
                }
                pHead2 = pHead2->next;
            }
        }
        //循环的停止条件是两个链表任意一个为空,因此还需要判断式哪个为空
        if (pHead1 == nullptr) 
            pMergedNode->next = pHead2;
        if (pHead2 == nullptr)
            pMergedNode->next = pHead1;   
        
        return pMergedHead;
    }
};

【面试题39】数组中出现次数超过一半的数字

题目描述:

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

解题思路:

因为有一个数字出现的字数比其他所有字数出现的次数之和还要多。因此遍历数组时记录数组中一个数字lastNum和次数。

当 遍历下一个数字时,如果与lastNum相等,count加1.如果不相等,count--。当count等于时,lastNum保存下一个数字,并count等于1

因此最后一次使count设为1的数就是要的解。

时间复杂度:O(n)

class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
        int size = numbers.size();
        int lastNum = numbers[0];
        int count = 1;
        for (int i = 1;i<size;i++){
            if (count == 0){
                lastNum = numbers[i];
                count = 1;
            }//if
            else if (numbers[i] == lastNum){
                count++;
            }//else if
            else{
                count--;
            }//else
        }//for
        if (!checkMoreThanHalf(numbers,lastNum)){
            return 0;
        }//if
        return lastNum;
    }
private:
    bool checkMoreThanHalf(vector<int>numbers,int num){
        int count = 0;
        int size = numbers.size();
        for (int i=0;i<size;i++){
            if (numbers[i] == num){
                count++;
            }//if
        }//for
        if (count>size/2){
            return true;
        }//if
        return false;
    }
};

【面试题42】连续子数组的最大和

题目描述:

连续子数组的最大和。例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8

解题思路:

定义一个累计和cumsum和最大和maxsum。遍历数组,如果累计和大于最大和的话,更新最大和。如果累计和小于等于0的话,加上任何数都会使累积和减小,因此抛弃之前的子数组,从下一个数开始累积。

时间复杂度:O(n)

int FindGreatestSumOfSubArray(vector<int> array) {
        if(array.empty())
            return 0;
         
        int cSum = 0;
        int result = array[0]; // result存储最大和,不能初始为0,存在负数
        for(int i = 0;i<array.size();++i)
        {
            if(cSum < 0) // 当前和<0,抛弃不要
                cSum = array[i];
            else
                cSum += array[i];
             
            if(cSum > result) // 存储最大结果
                result = cSum;
        }
        return result;
    }

【面试题45】把数组排成最小的数

题目描述:

输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。

解题思路:

所以在这里自定义一个比较大小的函数,比较两个字符串s1, s2大小的时候,先将它们拼接起来,比较s1+s2,和s2+s1那个大,如果s1+s2大,那说明s2应该放前面,所以按这个规则,s2就应该排在s1前面。

时间复杂度:O(nlogn)

#include <string>
class Solution {
public:
    static bool cmp(int a,int b){
        string A = "";
        string B = "";
        A += to_string(a);
        A += to_string(b);
        B += to_string(b);
        B += to_string(a);
        return A<B;
    }
public:
    string PrintMinNumber(vector<int> numbers) {
        string ret = "";
        sort(numbers.begin(),numbers.end(),cmp);
        for(int i = 0;i<numbers.size();i++){
            ret += to_string(numbers[i]);
        }
        return ret;
    }
};

【面试题51】数组中的逆序对

题目描述:

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007

解题思路:

如果直接遍历数组,然后与后面所有的数字作比较的话,好理解,但是时间复杂度是O(n^2),因此需要寻找更快的方法。
归并思路:先把数组分割成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,我们不难发现这个过程实际上就是归并排序。

时间复杂度:O(nlogn)

class Solution {
public:
    int InversePairs(vector<int> data) {
        int length = data.size();
        if (length<=0)
        {
            return 0;
        }
        vector <int> copy;
        for (int i=0;i<length;i++)
        {
            copy.push_back(data[i]);
        }
        long long count = InversePairsCore(data,copy,0,length-1);
        return count%1000000007;
    }
    long long InversePairsCore(vector<int>&data,vector<int>&copy,int start,int end)
    {
        // 区间是左开右闭
        //递归出口,当数组中只有一个数字时,数组内部的逆序对为0
        if (start == end)
        {
            copy[start] = data[start];
            return 0;
        }
        int length = (end - start)/2;
        long long left = InversePairsCore(copy,data,start,start+length);
        long long right = InversePairsCore(copy,data,start+length+1,end);
        
        int i = start + length;
        int j = end;
        int indexCopy = end;
        long long count = 0;
        // 统计数组之间的逆序对的数目
        // 不断将比较过的数字加入到copy中,作为上一级的子数组
        while (i >= start && j >= start + length+1)
        {
            if (data[i] > data[j])
            {
                copy[indexCopy--] = data[i--];
                count += j - start - length;
            }
            else
            {
                copy[indexCopy--] = data[j--];
            }
        }
        for (;i>=start;i--)
        {
            copy[indexCopy--] = data[i];
        }
        for (;j>=start+length+1;j--)
        {
            copy[indexCopy--] = data[j];
        }
        return left + right + count;
            
    }
};

【面试题53】数字在排序数组中的次数

题目描述:

统计一个数字在排序数组中出现的次数。

解题思路:

利用二分查找,找到这个数字的第一次出现和最后一次出现的位置

如果没找到,就返回0

时间复杂度:O(logn)

class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.empty())
            return 0;
        int number = 0;
        int first = GetFirstIndex(data,k,0,data.size()-1);
        int last = GetLastIndex(data,k,0,data.size()-1);
        if(first>-1 && last>-1)
            number = last - first +1;
        return number;
         
    }
    int GetFirstIndex(vector<int> &data,int k,int start,int end){
        if(start > end)
            return -1;
        int mid = start+(end-start)/2;
        if(data[mid] == k){
            if((mid == start) || (data[mid-1] != k))
                return mid;
            else
                end = mid-1;
        }
        else{
            if(data[mid] > k)
                end = mid - 1;
            else
                start = mid + 1;
        }
        return GetFirstIndex(data,k,start,end);
    }
    int GetLastIndex(vector<int> &data,int k,int start,int end){
        if(start > end)
            return -1;
        int mid = start+(end-start)/2;
        if(data[mid]==k){
            if((mid == end) || (data[mid+1] != k))
                return mid;
            else
                start = mid +1;
        }
        else{
            if(data[mid]>k)
                end = mid-1;
            else
                start = mid+1;
        }
        return GetLastIndex(data,k,start,end);
    }
};

 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值