算法入门:数组

算法入门 - 数组

1. 二维数组中的查找

二维数组中的查找 (nowcoder.com)

查找的本质就是排除,一次排除的越多,查找的越快。

右上角开始遍历

class Solution {
    
    
public:
    //查找的本质就是排除,一次排除的越多,查找的越快
    //由于数组左向右递增,上向下递增,故从右上角开始判断:
    //    当当前元素比右上元素大,说明元素不会出现在当前行,排除当前行
    //    当当前元素比右上元素小,说明元素不会出现在当前列,排除当前列
    //排除当前行就i++,排除当前列就j--
    bool Find(int target, vector<vector<int> > array) {
    
    
        //写出右上元素下标
        int i = 0;
        int j = array[i].size() - 1;
        while (i < array.size() && j >= 0) {
    
    
            //当前元素比右上元素小
            if (target < array[i][j]) {
    
    
                j--;
            }
            //当前元素比右上元素大
            else if (target > array[i][j]) {
    
    
                i++;
            }
            else {
    
    
                return true;
            }
        }
        return false;
    }
};

左上角开始遍历

class Solution {
    
    
public:
    //由于数组是从左向右递增的,采用从左向右找的策略,
    //从第1行开始,判断元素是否小于a[0][1],如果小说明不存在,如果大,在和a[0][n-1]比较
    //    如果小说明可能在第一行的中间,遂遍历第一行,有则有没有就没有。
    //    如果大,说明可能在下面,重复上述判断步骤。
    //从第i行开始,判断元素是否小于a[i][1],如果小说明不存在,如果大,在和a[i][n-1]比较
    //    如果小说明可能在第i行的中间,遂遍历第i行,有则有没有就没有。
    //直至遍历结束。
    bool Find(int target, vector<vector<int> > array) {
    
    
        //空数组特判
        for (int i = 0; i < array.size(); i++) {
    
    
            if (target < array[i][0]) {
    
     //元素小于a[i][1] -- 当前行的起始元素
                return false;
            }
            else if (target < array[i][array[i].size() - 1]) {
    
     //元素小于a[0][n-1] -- 当前行的末尾元素
                //遍历当前行,寻找匹配元素
                for (int j = 1; j < array[i].size() - 1; j++) {
    
    
                    if (target == array[i][j]) {
    
     
                        return true;
                    }
                }
            }
            else if (target == array[i][0]) {
    
    
                return true;
            }
            else if (target == array[i][array[i].size() - 1]) {
    
    
                return true;
            }
        }
        return false;
    }
};

2. 旋转数组的最小值

旋转数组的最小数字 (nowcoder.com)

非递减数组旋转后,会将数组分为前后两部分,两部分都是递增序列。

可以看出非递减数组任意左旋后(除全旋)都满足最左边的元素大于等于最右边的元素。

二分查找则是将数组中间的值和最左最右的元素比较,以判断最小值在中间元素的左边还是右边或是中间。

  1. 若 mid 元素小于 left 元素,说明左旋位置在左区间中,遂收紧区间 right = mid
  2. 若 mid 元素大于 left 元素,说明左旋位置在右区间中,遂收紧区间 left = mid
  3. 若 mid 元素等于 left 元素等于 right 元素,则无法判断左旋位置在左右区间,只能线性遍历。

二分法

class Solution {
    
    
public:
    //二分法:从中间开始判断,不断收紧,寻找最小值(将最小值所在位置定义为左旋位置)
    //定义指针:left mid right
    //    若mid元素小于left元素,说明[left.mid]区间不满足非递减条件,
    //        故左旋位置在左区间中,遂收紧区间right = mid
    //    若mid元素大于left元素,说明[left.mid]区间满足非递减条件,
    //        故左旋位置在右区间中,遂收紧区间left = mid
    //    若mid元素==left元素==right元素,则无法判断直接线性查找即可。
    int minNumberInRotateArray(vector<int> nums) {
    
    
        int left = 0, right = nums.size() - 1;
        int mid = 0;
        //除全等和全旋的特殊情况外,所有左旋结果都满足nums[left]>=nums[right]
        //遇全等和全旋的特殊情况,不进循环,直接返回首元素即可。
        //使用该表达式作为判断条件,可以保证二分查找的区间是满足条件的有效区间
        while (nums[left] >= nums[right]) {
    
     
            //元素个数为2是返回right即可,因为只要旋转右面的就是小的
            if (right - left == 1) {
    
     
                mid = right;
                break;
            }
            mid = (left + right) / 2;
            
            //无法判断,线性查找
            if (nums[left] == nums[mid] && nums[mid] == nums[right]) {
    
    
                int ret = nums[left];
                for (int i = left + 1; i < right ; i++) {
    
    
                    if (ret > nums[i]) {
    
    
                        ret = nums[i];
                    }
                }
                return ret;
            }
            
            //二分查找
            if (nums[mid] >= nums[left]) {
    
    
                left = mid;
            }
            else {
    
    
                right = mid;
            }
        }
        return nums[mid];
    }
};

遍历法

线性遍历的方法效率都差不多,查找看的是排除的效率,二分法才是排除的高效之选。

class Solution {
    
    
public:
    //由于数组是个非递减数组的左旋结果,遂遍历数组,
    //    当当前元素比下一个元素大时,说明该位置就是左旋的位置,当前的下一个元素就是最小值
    //特殊情况:当遍历数组没有发现上述情况,说明数组元素完全相等或全部左旋,统一返回首元素即可。
    int minNumberInRotateArray(vector<int> rotateArray) {
    
    
        for (int i = 0; i < rotateArray.size() - 1; i++) {
    
    
            if (rotateArray[i] > rotateArray[i + 1]) {
    
    
                return rotateArray[i + 1];
            }
        }
        //特殊情况
        return rotateArray[0];
    }
};

3. 调整数组奇偶顺序

调整数组奇偶数顺序 (nowcoder.com)

调整奇数至偶数之前(相对顺序不变),本质就是向后寻找奇数与其之前的偶数序列交换。

相对顺序不变

找奇数以确定偶数区间是最优解。

class Solution {
    
    
public:
    //遍历数组,跳过偶数,直到遇到奇数时,将前面的偶数序列,后移一位并将奇数放到前面。
    void reOrderArray(vector<int>& nums) {
    
    
        int begin = 0;
        //遍历数组
        for (int end = 0; end < nums.size(); end++) {
    
    
            //遇到奇数时:[begin, end)就是偶数区间,begin所在位置就是起始位置
            if ((nums[end] & 1) == 1) {
    
    
                int tmp = nums[end]; //提前保存奇数
                //后移偶数序列
                for (int j = end; j > begin; j--) {
    
    
                    nums[j] = nums[j - 1];
                }
                nums[begin] = tmp; //前插奇数
                begin++;
            }
        }
    }
};

线性遍历
class Solution {
    
    
public:
    //开辟等大小的数组,遍历两边一遍找奇一遍找偶,再向新数组中拷贝。这样可以保证相对位置不变
    void reOrderArray(vector<int>& nums) {
    
    
        int begin = 0, end = nums.size() - 1;
        vector<int> tmp(nums);//定义暂存数组
        int index = 0;
        for (int i = 0; i < nums.size(); i++) {
    
    
            if (nums[i] % 2 == 1) {
    
    
                tmp[index++] = nums[i];
            }
        }
        for (int i = 0; i < nums.size(); i++) {
    
    
            if (nums[i] % 2 == 0) {
    
    
                tmp[index++] = nums[i];
            }
        }
        swap(tmp, nums);
    }
};

遍历两边一遍找奇一遍找偶,和定义头尾指针头找奇尾找偶,两种方法效率上没区别。

相对顺序改变

class Solution {
    
    
public:
    //采用头尾指针的方式;头指针找偶数,尾指针找奇数,两指针分别向中间遍历,并交换,直至相遇。
    //这样能使奇数在前偶数在后。
    void reOrderArray(vector<int>& nums) {
    
    
        int begin = 0, end = nums.size() - 1;
        while (begin < end) {
    
    
            while (begin < end && nums[begin] % 2 != 0) {
    
     //头指针找偶数
                begin++;
            }
            while (begin < end && nums[end] % 2 != 1) {
    
     //尾指针找奇数
                end--;
            }
            //交换
            swap(nums[begin], nums[end]);
            begin++, end--;
        }
    }
};

4. 出现次数过半数字

数组中出现次数超过一半的数字 (nowcoder.com)

抵消不同元素

遇到相异的元素,可以将其抵消掉,最后剩下的元素就是出现次数最多的数字。

class Solution {
    
    
public:
    //抵消不同元素,最后剩下的元素就是出现超出一半的数字
    //定义当前元素值和出现次数变量,向后遍历数组:
    //    元素变量和当前元素相同,次数++,用来统计次数
    //    元素变量和当前元素相异。次数--,用来模拟抵消元素
    //    当次数变为0时,相当于当前元素抵消完了,更新元素变量并重置次数,继续向后遍历
    int MoreThanHalfNum_Solution(vector<int> nums) {
    
    
        int num = nums[0]; //从首元素开始,提出出来以便之后的操作
        int times = 1; 
        for (int i = 1; i < nums.size(); i++) {
    
    
            if (num == nums[i]) {
    
     //遇到相同元素
                times++;
            }
            else {
    
     //遇到相异元素
                times--;
            }
            if (times == 0) {
    
     //抵消完毕
                num = nums[i]; //将当前元素设置为待比较元素(前面的都已经抵消完)
                times = 1; //修正次数
            }
        }
        return num;
    }
};

统计出现次数

用图存储各个元素和次数的对应关系。

class Solution {
    
    
public:
    int MoreThanHalfNum_Solution(vector<int> nums) {
    
    
        unordered_map<int, int> map;
        for (int i = 0; i < nums.size(); i++) {
    
    
            auto it  = map.find(nums[i]);
            if (it == map.end()) {
    
     //图中不存在该元素
                map.insert({
    
    nums[i], 1});//插入元素和次数
            }
            else {
    
     //图中存在
                map[nums[i]]++; //次数++
            }
            if (map[nums[i]] > nums.size() / 2) {
    
     //出现次数超出一般
                return nums[i];
            }
        }
        return 0;
    }
};

利用长度为2的数组存储数值和次数。

class Solution {
    
    
public:
    //利用数组存储元素和其出现个数,遍历寻找最大值
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    
    
        sort(numbers.begin(), numbers.end());
        int begin = 0, end = 0;
        int retArr[2] = {
    
     0 };
        //前后遍历寻找当前元素的最后一个位置
        while (end < numbers.size()) {
    
    
            while (end < numbers.size()) {
    
    
                if (numbers[end] != numbers[begin]) {
    
    
                    break;
                }
                end++;//向后遍历
            }
            if ((end - begin) > retArr[0]) {
    
    
                retArr[0] = end - begin;//存入数组
                retArr[1] = numbers[begin];
            }
            //把begin变成下一个元素的起始位置,继续向后遍历
            begin = end;
        }
        return retArr[1];
    }
};

排序后判断对应位置

排序后,老老实实地进行线性查找。

class Solution {
    
    
public:
    //先排序,从头开始遍历,
    //    若当前元素和超其数组长度一半的位置的元素相等,则该数字即为出现次数过半数字
    int MoreThanHalfNum_Solution(vector<int> nums) {
    
    
        sort(nums.begin(), nums.end());
        int n = nums.size();
        for (size_t i = 0; i < n / 2 + 1; i++) {
    
    
            if (nums[i] == nums[i + n / 2]) {
    
    
                return nums[i];
            }
        }
        return -1;
    }
};

排序后检查中间元素是否出现次数超出数组长度一半。

class Solution {
    
    
public:
    //先排序,个数超出数组长度一半的元素必然在数组中部,判断该元素出现的个数:
    //从中部位置开始向前后双指针遍历,相同则++不同则放弃。检查元素出现个数是否超出一半。
    int MoreThanHalfNum_Solution(vector<int> nums) {
    
    
        sort(nums.begin(), nums.end());
        int half = nums.size() / 2;
        int cnt = 1;//half位置本值占一个
        int i = 1;
        while (i <= half) {
    
    
            if (nums[half - i] == nums[half]) {
    
     //half位置和之前元素比较
                cnt++;
            }
            if (nums[half] == nums[half + i]) {
    
     //half位置和之后元素比较
                cnt++;
            }
            i++;
        }
        if (cnt > half) {
    
     // 元素个数超出数组长度的一半
            return nums[half];
        }
        return 0;
    }
};

“投机取巧”的方式:

class Solution {
    
    
public:
    //先排序,个数超出数组长度一半的元素必然在数组中部,直接返回中间位置的值即可。
    int MoreThanHalfNum_Solution(vector<int> nums) {
    
    
        sort(nums.begin(), nums.end());     
        return nums[nums.size() / 2];
    }
};

猜你喜欢

转载自blog.csdn.net/yourfriendyo/article/details/124245908