JZ13、19、28数组系列、面试题3、4、66

面试题3 数组中重复的数字

面试题4 二维数组中的查找

JZ13调整数组顺序使奇数位于偶数前面

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

题解
若函数的类型为void func_name(array&)返回void,想让我们不开辟额外数组来解决,使用in-place就地算法

方法一:使用辅助数组
但是如果空间要求不高的话,我们还是可以开辟个额外数组,接下来的做法就非常简单了,遍历一次数组,遇到奇数直接放入新开的数组中,再遍历一次数组,遇到偶数就继续放入新开的数组。最后再进行一次copy。

class Solution {
    
    
public:
    void reOrderArray(vector<int> &array) {
    
    
        vector<int> arr;
        for (const int v : array) {
    
    
            if (v&1) arr.push_back(v); // 奇数,计算机默认存储二进制补码形式,二进制最后一位是1,一定是奇数
        }
        for (const int v : array) {
    
    
            if (!(v&1)) arr.push_back(v); // 偶数
        }
        copy(arr.begin(), arr.end(), array.begin());
    }
};

注:
copy的用法

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

方法二: 不开辟额外数组:双指针和快慢指针

考虑定义双指针 i , j 分列数组左右两端,循环执行:

  • 指针 i 从左向右寻找偶数;
  • 指针 j 从右向左寻找奇数;
  • 将 偶数 nums[i]和 奇数 nums[j] 交换。可始终保证: 指针 i 左边都是奇数,指针 j 右边都是偶数 。
class Solution {
    
    
    public int[] exchange(int[] nums) {
    
    
        int left = 0;
        int right = nums.length-1;
        while(left < right){
    
    
            while(left<right && (nums[left]&1) == 1) left++;
            while(left<right && (nums[right]&1) == 0) right--;
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp; 
        }
        return nums;

    }
}

时间复杂度:O(n),

空间复杂度:O(1)

  • 快慢指针
    因为要求数组后面是偶数,所以快指针指向的应该是偶数,若快指针指向的是奇数,则就和慢指针指向的数交换。
class Solution {
    
    
    public int[] exchange(int[] nums) {
    
    
        int fast = 0;
        int slow = 0;
        while(fast < nums.length){
    
    
            if((nums[fast]&1) ==1) swap(nums, slow++, fast);
            fast++;
        }
        return nums;
    }

    void swap(int[] nums, int slow, int fast){
    
    
        int temp = nums[slow];
        nums[slow] = nums[fast];
        nums[fast] = temp;
    }
}

时间复杂度:O(n),

空间复杂度:O(1)

JZ19 顺时针打印矩阵

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

示例1
输入
[[1,2],[3,4]]
返回值
[1,2,4,3]

题解:

简单来说,就是一圈一圈的不断地收缩矩阵的边界
在这里插入图片描述

代码:
java

class Solution {
    
    
    public int[] spiralOrder(int[][] matrix) {
    
    
        if(matrix.length == 0) return new int[0];
        int left = 0;
        int right = matrix[0].length-1;
        int top = 0;
        int bottom = matrix.length-1;
        int[] res = new int[(right+1)*(bottom+1)];
        int x = 0;
        while(true){
    
    
            for(int i =left; i <= right; i++) res[x++] = matrix[left][i];
            if(++top > bottom) break;
            for(int i = top; i <= bottom; i++) res[x++] = matrix[i][right];
            if(--right < left) break;
            for(int i = right; i >=left; i--) res[x++] = matrix[bottom][i];
            if(--bottom < top) break;
            for(int i= bottom; i >= top; i--) res[x++] = matrix[i][left];
            if(++left > right) break;
        }
        return res;

    }
}

C++

class Solution {
    
    
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
    
    
        vector<int> ret;
        if(matrix.empty()) return ret;
        int up=0;
        int down=matrix.size()-1;
        int left=0;
        int right=matrix[0].size()-1;
        while(true){
    
    //一圈一圈的打印,一次循环打印一圈;
            //最上面一行;第一步从左到右;
            for(int col=left;col<=right;col++){
    
    
                ret.push_back(matrix[up][col]);
            }
            //向下逼近,
            up++;
            if(up>down) break;
            
            
            //最右面一列,第二步从上向下;
            for(int row=up;row<=down;row++){
    
    
                ret.push_back(matrix[row][right]);
            }
            //向左逼近;
            right--;
            if(right<left) break;
            
            
            //最下面一行;第三步从右向左;
            for(int col=right;col>=left;col--){
    
    
                ret.push_back(matrix[down][col]);
            }
            //向上逼近;
            down--;
            if(up>down) break;
            
            
            //最左面一列;第四部从下向上;
            for(int row=down;row>=up;row--){
    
    
                ret.push_back(matrix[row][left]);
            }
            //向右逼近
            left++;
            if(left>right) break;//这是遍历一圈后,剩下的左边列标大于右边列标,即一列都没有,第一步都进行不下去,即退出
        }
        return ret;

    }
};

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

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

示例1
输入
[1,2,3,2,2,2,5,4,2]
返回值
2

注:
数组中一个元素出现的次数超过数组长度的一半,这个数成为数组的众数。由于出现的次数超过数组长度的一半,所以数组的众数有的话,那么就只有一个【隐藏点】。

方法一:哈希法
根据题目意思,显然可以先遍历一遍数组,在map中存每个元素出现的次数,然后再遍历一次数组,找出众数。

class Solution {
    
    
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    
    
        if(numbers.size()==0) return 0;
        map<int, int> hashmap;
        for(int& val:numbers){
    
    //范围for语句的声明的变量是对迭代器元素的复制,若需改变其值,需用引用类型,反之不需要但是也可用引用。即,全用引用即可。
            ++hashmap[val];
        }
        for(int& val:numbers){
    
    
            if(hashmap[val]>numbers.size()/2)
                return val;
        }
        return 0;
    
    }
};

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

方法二:排序法
由于众数出现的次数超过数组长度的一半,所以如果将数组排序,如果众数存在,则众数肯定在数组中间。
排序后,去中间元素,然后判断一下即可。

class Solution {
    
    
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    
    
        if(numbers.size()==0) return 0;
        sort(numbers.begin(), numbers.end());
        int ret=numbers[numbers.size()/2];
        int cnt=0;
        for(int& val:numbers){
    
    
            if(val==ret) ++cnt;
        }
        if(cnt>numbers.size()/2) return ret;
        else return 0;
    }
};

时间复杂度:O(nlongn)
空间复杂度:O(1)
方法三:摩尔投票法(最优解)
假如数组中存在众数,那么众数一定大于数组的长度的一半。
思想就是:如果两个数不相等,就消去这两个数,最坏情况下,每次消去一个众数和一个非众数,那么如果存在众数,最后留下的数肯定是众数。
在这里插入图片描述
由推论二可知,通过遍历,每轮假设发生 票数和 =0 都可以 缩小剩余数组区间 。当遍历完成时,最后一轮假设的数字即为可能的众数

  • 缩小剩余数组区间的原理:假设遍历的两个数票数和为0,若这两个数都不是众数,即众数一个也没消耗掉,剩余数组的众数当然不变;若两个数其中一个是众数,则一个众数一个非众数相互抵消,剩余数组的众数当然也不变。

具体思想参考

具体做法:
若是众数 票数+1,不是-1;
1、初始化:假设众数ret = -1, 候选人的投票数votes= 0
2、遍历数组,如果votes=0, 表示没有假设众数,则选取当前数为假设众数,并++votes
3、否则votes!=0, 表示有候选人,如果当前数==ret ,则++votes,否则–votes
直到数组遍历完毕,最后检查ret 是否为众数

java

class Solution {
    
    
    public int majorityElement(int[] nums) {
    
    
        int x = 0;//众数
        int votes = 0;
        for(int val : nums){
    
    
            if(votes == 0) x=val;
            if(val == x) ++votes;
            else --votes;
        }
        
        int count = 0;
        for(int val : nums){
    
    
            if(val == x) ++count;
        }
        if(count > nums.length/2) return x;
        else return 0;

    }
}

C++

class Solution {
    
    
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    
    
        if(numbers.size()==0) return 0;
       int ret=-1;//初始化众数ret.
        int votes=0;
        for(int i=0;i<numbers.size();i++){
    
    
            if(votes==0){
    
    //votes==0说明前面以抵消,需要重新假设众数
                ret=numbers[i];//随着不断的抵消,如果有众数,一定在留下的数中。
                ++votes;
            }
            else{
    
    
                if(ret==numbers[i]) ++votes;
                else --votes;
            }
        }
        int cnt=0;
        for(int& val:numbers){
    
    
            if(val==ret) ++cnt;
        }
        if(cnt>numbers.size()/2) return ret;
        else return 0;
    }
};

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

面试题:66. 构建乘积数组

知识点:

给定一个数组 A[0,1,…,n-1],请构建一个数组 B[0,1,…,n-1],其中 B[i] 的值是数组 A 中除了下标 i 以外的元素的积, 即 B[i]=A[0]×A[1]×…×A[i-1]×A[i+1]×…×A[n-1]。不能使用除法。

示例:

输入: [1,2,3,4,5]
输出: [120,60,40,30,24]

题解:

  • 我们可以将上面的关于 b[i] 的公式拆分为两部分:left[i] = a[0] * a[1] a[i - 1] 和 right[i] = a[i + 1] a[n - 1]。
  • 所以可以得出:b[i] = left[i] * right[i]。其中 b、left、right 的长度都等于输入的数组 a 的长度。
  • 我们从前往后填充 left 数组,从后往前填充 right 数组,最后就能计算出 b 的每一位。

特别的left[0]=1;right[n-1]=1:
在这里插入图片描述
代码:

Java

class Solution {
    
    
    public int[] constructArr(int[] a) {
    
    
        int length = a.length;
        if(length <=0) return new int[0];
        int[] left = new int[length];
        int[] right = new int[length];
        int[] B = new int[length];
        left[0] = 1;
        right[length-1] = 1;
        for(int i = 1; i<length ; i++){
    
    
            left[i]= a[i-1]*left[i-1];
        }
        B[length-1] = left[length-1] * right[length-1];
        for(int i = length-2 ; i >= 0 ; i--){
    
    
            right[i] = a[i+1] * right[i+1];
            B[i] = left[i] * right[i]; 
        }
        return B;

    }
}

C++

class Solution {
    
    
public:
    vector<int> constructArr(vector<int>& a) {
    
    
        int len = a.size();
        if(len==0) return vector<int>();
        if(len==1) return vector<int>(1, -10086);
        vector<int> b(len,1);
        vector<int> left(len,1);
        vector<int> right(len,1);
        left[0] = 1;
        right[len-1] = 1;
        for(int i=1; i<len; i++){
    
    
            left[i] = left[i-1]*a[i-1];
        } 
        b[len-1] = left[len-1] * right[len-1];
        for(int i=len-2; i>=0; i--){
    
    
            right[i] = right[i+1]*a[i+1];
            b[i] = left[i] * right[i];
        }
        return b;
    }
};

时间复杂度 O(N): 其中 N 为数组长度,两轮遍历数组 a ,使用 O(N)时间。
空间复杂度 O(N) : 创建了3个数组

1、摩尔投票法:
具体思想参考

猜你喜欢

转载自blog.csdn.net/qq_42647047/article/details/110144673