剑指offer——数组

剑指offer

视频链接及牛客网刷题地址
剑指Offer(四):二维数组中的查找
添加链接描述
剑指Offer(十一):旋转数组的最小数字
添加链接描述
剑指Offer(二十一):调整数组顺序使奇数位于偶数前面
添加链接描述
剑指Offer(三十九):数组中出现次数超过一半的数字
添加链接描述
剑指Offer(四十二):连续子数组的最大和
添加链接描述
剑指Offer(四十五):把数组排成最小的数
添加链接描述
剑指Offer(五十一):数组中的逆序对
添加链接描述
剑指Offer(五十三):数字在排序数组中出现的次数
添加链接描述
剑指Offer(五十六):数组中只出现一次的数字
添加链接描述
剑指Offer(三):数组中重复的数字
添加链接描述
剑指Offer(六十六):构建乘积数组
添加链接描述

二. 数组(11道)

剑指Offer(一):二维数组中的查找

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

/*思路:从右上角开始扫描,判断当前数与目标值的大小关系。若大,则去除当前列,列行减1;若小,行号加1。*/
// 时间复杂度:O(n+m)
class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        if(array.empty() || array[0].empty()) return false;  // 矩阵不存在或矩阵为空
        int i = 0, j = array[0].size()-1;
        while(i < array[0].size() && j >= 0){  // 从右上角开始扫描
            if(array[i][j] == target) return true;
            else if(array[i][j] > target) j--;  // 整数在第j列的左边,去除第j列
            else i++;  // 整数在第i行的下边,去除第i行
        }
        return false;
    }
};

剑指Offer(六):旋转数组的最小数字

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

/*思路:去掉旋转数组最后的重复元素,二分*/
class Solution {
public:
    int minNumberInRotateArray(vector<int> nums) {
        int n = nums.size() - 1;
        if(n < 0 ) return 0;
        while(n && nums[n] == nums[0]) n--;  // 去掉尾部重复元素
        if(nums[n] > nums[0]) return nums[0];  // 去完后只有上升序列情况
        int l = 0, r = n;  // 二分
        while(l < r){
            int mid = l + r >> 1;  // [l, mid],[mid+1, r]
            if(nums[mid] < nums[0]) r = mid;
            else l = mid + 1;
        }
        return nums[r];
    }
};

剑指Offer(十三):调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分。

/*思路:双指针,两个指针从左右两边往中间走,在没有相遇之前,左边遇到奇数往右走,右边遇到偶数往左走,当停下来时,交换*/
class Solution {
public:
    void reOrderArray(vector<int> &array) {
        if(array.empty() || array.size() == 1) return;
        int i = 0, j = array.size()-1;
        while(i < j){
            while(i < j && array[i]%2==1) i++;
            while(i < j && array[j]%2==0) j--;
            if(i < j) swap(array[i], array[j]);  // 两个指针停下来,交换
        }
    }
};

限制:保证奇数和奇数,偶数和偶数之间的相对位置不变。

只能用额外数组,空间换时间0(n),否则时间复杂度为0(n^2)

# -*- coding:utf-8 -*-
class Solution:
    def reOrderArray(self, array):
        # write code here
        if not array:
            return []
        return [x for x in array if x&1] + [x for x in array if not x&1 ]

剑指Offer(二十八):数组中出现次数超过一半的数字

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

  • 方法一:哈希表,空间换时间,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> nums) {
        if(nums.empty()) return 0;
        map<int, int> map;
        for(int i = 0; i < nums.size(); i++){
            map[nums[i]]++;
            if(map[nums[i]] > nums.size()/2)
                return nums[i];
        }
    }
};
  • 方法二:时间复杂度O(n),空间复杂度O(1)
/*思路:遍历数组时保存两个值:一是数组中的数字,一是次数。遍历下一个数字时,若它与之前保存的数字相同,则次数加1,否则次数减1;若次数为0,则保存下一个数字,并将次数置为1。遍历结束后,所保存的数字即为所求。*/
class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> nums) {
        if(nums.empty()) return 0;
        int cnt = 0, val = -1;
        for(auto x : nums){  // 遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1
            if(!cnt) cnt = 1,val = x;  // 更新当前元素,cnt设为1
            else{
                if(x == val) cnt++;
                else cnt--;
            }
        }
        cnt = 0;  // 判断val是否符合条件,即出现次数大于数组长度的一半
        for(auto x : nums){
            if(x == val)
                cnt++;
        }
        return cnt > nums.size()/2 ? val : 0;
    }
};

剑指Offer(三十):连续子数组的最大和

HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

/*思路:分析数组规律,以一个变量来做动态规划。
定义一个变量s,表示当前值x之前的子数组所有数之和,从前往后遍历:
(1) s > 0, 最大和为 s + x
(2) s <= 0, 最大和为 x,丢弃掉子数组s*/
class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        int res = INT_MIN;
        int s = 0;
        for(auto x : array){
            if(s < 0) s = 0;
            s += x;
            res = max(res, s);
        }
        return res;
    }
};

剑指Offer(三十二):把数组排成最小的数

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

/*思路:自定义一种排序方式,判断所有数的顺序。
1.比a与b,转成字符串as与bs
2.比较asbs与bsas大小,小的放前面
3.依次输出所有字符串*/
class Solution {
public:
    static bool cmp(int a, int b){
        string as = to_string(a), bs = to_string(b);
        return as+bs < bs+as;
    }
    string PrintMinNumber(vector<int> numbers) {
        sort(numbers.begin(), numbers.end(), cmp);  // 自定义一种排序方式
        string res;
        for(auto x : numbers){
            res += to_string(x);
        }
        return res;
    }
};

剑指Offer(三十五):数组中的逆序对

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

/*思路:*/

剑指Offer(三十七):数字在排序数组中出现的次数

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

  • 方法一:遍历一遍数组,计数,时间复杂度O(n)
class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.empty()) return 0;
        int cnt = 0;
        for(int x : data){
            if(x == k)
                cnt++;
        }
        return cnt;
    }
};
  • 方法二:遍历一遍数组,存到哈希表,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.empty()) return 0;
        map<int, int> hash;
        for(int x : data) hash[x]++;
        return hash[k];
    }
};
  • 方法三:二分查找k第一次出现的位置和最后一次出现的位置,时间复杂度O(logn)
class Solution {
public:
    int GetNumberOfK(vector<int> data ,int k) {
        if(data.empty()) return 0;
        // 二分出k第一次出现的位置和最后一次出现的位置
        int l = 0, r = data.size()-1;
        while(l < r){
            int mid = (l + r) >> 1;
            if(data[mid] < k) l = mid + 1;
            else r = mid;
        }
        if(data[l] != k) return 0;
        int left = l;
        l = 0, r = data.size()-1;
        while(l < r){
            int mid = (l + r + 1) >> 1;
            if(data[mid] <= k) l = mid;
            else r = mid - 1;
        }
        return r - left + 1;
    }
};

扩展1: 0~(n-1)中缺失的数字。

一个长度为n-1的递增排序数组中的所有数字都唯一,并且每个数字都在范围0(n-1)之内,在范围0(n-1)之内的n个数字有且只有一个数字不在该数组中,找出这个数字。

思路:二分找到nums[i] == i的最后一个数字的位置,返回位置加一

class Solution {
public:
    int getMissingNumber(vector<int>& nums) {
        if(nums.empty()) return 0;
        int l = 0, r = nums.size() - 1;
        while(l < r){
            int mid = l + r >> 1;
            if (nums[mid] == mid) l = mid + 1;
            else r = mid;
        }
        if(nums[r] == r) r ++ ;  // 缺失的数字
        return r;
    }
};

扩展2: 数组中数值和下标相等的元素。
假设一个单调递增的数组里的每个元素都是整数并且是唯一的。
请编程实现一个函数找出数组中任意一个数值等于其下标的元素。
例如,在数组[-3, -1, 1, 3, 5]中,数字3和它的下标相等。
注意:如果不存在,则返回-1。

思路:二分到第一个nums[i] == i,左边nums[i] < i,右边nums[i] > i

class Solution {
public:
    int getNumberSameAsIndex(vector<int>& nums) {
        if(nums.empty()) return -1;
        int l = 0, r = nums.size()-1;
        while(l < r){
            int mid = l + r >> 1;
            if(nums[mid] < mid) l = mid + 1;
            else r = mid;
        }
        if(nums[r] != r) return -1;
        return r;
    }
};

剑指Offer(四十):数组中只出现一次的数字

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

/*思路:哈希表,时空复杂度O(n)*/
class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        map<int, int> hash;
        vector<int> res;
        for(int x : data) hash[x]++;
        for(int x : data)
            if(hash[x] == 1)
                res.push_back(x);
        *num1 = res[0], *num2 = res[1];
    }
};
  • 思路二:异或、位运算(最优,但不好想)
  1. 所有数异或得到 x^ y(x^x=0)

  2. 取 x与y中第k位为1的数

  3. 将数分为两个集合,第k位为1的集合和第k位不是1的集合,其中x y分别在这两个集合,且相同的元素是在同一个集合里面

于是将其转化成了求重复数字中的单个数值的问题 =>两个集合分别异或一遍
时间复杂度O(n),空间复杂度O(1)

class Solution {
public:
    void FindNumsAppearOnce(vector<int> data,int* num1,int *num2) {
        int sum = 0;
        for(int x : data) sum ^= x;  // num1^num2
        int k = 0;
        while(!(sum >> k & 1)) k++;  // 取x^y中第k位为1的数
        int first = 0;
        for(int x : data) 
            if(x >> k & 1)
                first ^= x;  // 第一个集合异或和即为第一个数
        *num1 = first;  
        *num2 = sum^first;  // num1^num2^num1 = num2
    }
};

剑指Offer(五十):数组中重复的数字

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

  • 方法一:
    给定的数组是乱序的,我们首先对数组排序,然后遍历数组对比一下当前值和下一个值,如果相等,则找到重复的数字。
    时间复杂度O(nlogn)
class Solution {
public:
    bool duplicate(int nums[], int length, int* duplication) {
        if(length <= 0) return 0;
        for(int i = 0; i < length; i++){
            if(nums[i] < 0 || nums[i] >= length) return 0;
        }
        sort(nums, nums+length);
        for(int i = 0; i < length; i++){
            if(i+1 && nums[i] == nums[i+1]){
                duplication[0] = nums[i];
                return 1;
            }
        }
        return 0;
    }
};
  • 方法二:哈希表,时间复杂度O(n),空间复杂度O(n)
class Solution {
public:
    bool duplicate(int nums[], int length, int* duplication) {
        if(length <= 0) return 0;
        for(int i = 0; i < length; i++){
            if(nums[i] < 0 || nums[i] >= length) return 0;
        }
        map<int, int> hash;
        for(int i = 0; i < length; i++){
            hash[nums[i]] ++;
            if(hash[nums[i]] > 1){
                duplication[0] = nums[i];
                return 1;
            }
        }
        return 0;
    }
};
  • 方法三:最优
如果数组中的元素没有重复的,那么排序之后数字i会出现在数组下标为i的位置。现在我们有重复的数字,所以有些位置可能存在多个数字,有一些位置可能没有数字。
首先从头至尾扫描数组,当扫描到位置i时,比较这个数字(m)是不是等于i,如果是,则接着扫描下一位;如果不是,则再拿它和第m个数字进行比较,如果相等,就找到了重复的数字;如果不相等,就交换两个数字,把m放到属于它的位置。接下来重复这个过程直到发现第一个重复的数字

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

class Solution {
public:
    bool duplicate(int nums[], int length, int* duplication) {
        if(length <= 0) return 0;
        for(int i = 0; i < length; i++){
            if(nums[i] < 0 || nums[i] >= length) return 0;
        }
        for(int i = 0; i < length; i++){
            while(i!=nums[i] && nums[nums[i]]!=nums[i]) swap(nums[i], nums[nums[i]]);
            if(nums[i]!=i && nums[nums[i]] == nums[i]){
                duplication[0] = nums[i];
                return 1;
            }
        }
        return 0;
    }
};

剑指Offer(五十一):构建乘积数组

给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]…*A[n-1]。不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],B[n-1] = A[0] * A[1] * … * A[n-2];)

对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

/*思路:*/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值