LeetCode 每日两题 第五天

417 阅读4分钟

「这是我参与11月更文挑战的第四天,活动详情查看:2021最后一次更文挑战

各位小伙伴大家好啊,今天是第五天了。已经有几天没有打卡了,主要是这两天满课,从早上八点上到晚上十点,确实没办法打卡。今天继续刷题,把之前的慢慢补上。

第一题 剑指 Offer 04. 二维数组中的查找

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

示例:

现有矩阵 matrix 如下:

[
  [1,   4,  7, 11, 15],
  [2,   5,  8, 12, 19],
  [3,   6,  9, 16, 22],
  [10, 13, 14, 17, 24],
  [18, 21, 23, 26, 30]
]

给定 target = 5,返回 true

给定 target = 20,返回 false

限制:

0 <= n <= 1000
0 <= m <= 1000

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/er…

解题思路

二分查找

题目中明确指出——每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。那么说明每一行都是有序数组,每一列也是有序数组。所以和上一次提到的思路一致,有序数列又是寻找元素,那么就可以考虑二分查找了。因为每一行最右边的数为当前行最大值,那么如果最大值都小于了target那就不用查找了,一定没有target。如果相等就直接返回。

时间复杂度:O(nlogn),空间复杂度:O(1)

线性查找

一般来说,二分查找就应该可以了,但是提交之后,发现依旧不是最优解。在翻阅题解后,发现了一种类似二叉排序树线性查找方法,在此记录下来。它从右上角开始,其左边的元素均小于它自己,下方的元素均大于它自己。那么要查找比它本身大的元素,row++,要查找比它本身小的元素,col--。此算法最多减m列,加n行,所以时间复杂度O(n + m)。

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

代码实现

// 二分查找
var findNumberIn2DArray = function(matrix, target) {
    let flag = -1;

    for(let arr of matrix) {
        if(arr[arr.length - 1] > target) {
            flag = binarySearch(arr, target);
            
            if(flag !== -1) {
                return true;
            }
        } else if(arr[arr.length - 1] === target) {
            return true
        }
    }

    return false;
};
// 二分查找函数实现
function binarySearch(arr, target) {
    let left = 0, right = arr.length - 1;
    let mid;
    while(left < right) {
        mid = Math.floor((left + right) / 2);

        if(arr[mid] < target) {
            left = mid + 1;
        } else if (arr[mid] > target) {
            right = mid - 1;
        } else {
            return mid;
        }
    }
    return arr[left] === target ? left : -1;
}

// 线性遍历
var findNumberIn2DArray = function(matrix, target) {
    if(matrix.length === 0 || matrix[0].length === 0) {
        return false;
    }

    let row = 0, col = matrix[0].length - 1, val;
    while(row < matrix.length && col >= 0) {
        val = matrix[row][col];

        if(val > target) {
            col--;
        } else if (val < target) {
            row++;
        } else {
            return true;
        }
    }

    return false;
};

第二题 剑指 Offer 11. 旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

示例 1:

输入:[3,4,5,1,2]
输出:1

示例 2:

输入:[2,2,2,0,1]
输出:0

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/xu…

解题思路:此时我们看到了一个关键字——递增排序数组。题目要求是查找最小值,可能大家都已经在猜想是否可以用二分查找了。但此题目的二分查找与寻常二分法不同,因为数组不是严格有序的。

二分查找

我们要找到最小值(旋转点),就要找到右排序数组的首个元素。

left:左边界,right:右边界,mid:中间值

分三种情况讨论:

  • arr[mid]<arr[right]:右排序数组的首个元素只可能在[left, mid]中。
  • arr[mid]>arr[right]:右排序数组的首个元素只可能在[mid+1, right]中。
  • arr[mid]===arr[right]:无法确定最小值范围,right--缩小范围。因为arr[mid]是等于arr[right]的,所以arr[right]的去除不会影响最小值。

**注意:**为什么要使用arr[right]而不使用arr[left]呢?正常情况下,arr[mid]>arr[left]右边界的首个元素应该在[mid+1, right]中,但如果旋转了0次,例如[1, 3, 5]。此时arr[mid]>arr[left],右排序数组的首个元素不在[mid+1, right]中,与左边界比较arr[mid]>arr[left]时所以无法确定右排序数组首个元素的位置。

代码实现

// 二分查找
var minArray = function(numbers) {
    let left=0, right=numbers.length - 1;
    let mid;

    while(left < right) {
        // (right + left) / 2有可能造成整数溢出问题
        mid = left + Math.floor((right - left) / 2);

        if(numbers[mid] < numbers[right]) {
            right = mid;
        } else if(numbers[mid] === numbers[right]) {
            right--;
        } else {
            left = mid + 1;
        }
    }

    return numbers[left];
};

第三题 剑指 Offer 50. 第一个只出现一次的字符

在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。

示例 1:

输入:s = "abaccdeff"
输出:'b'

示例 2:

输入:s = "" 
输出:' '

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/di…

解题思路:统计问题,一般来说可以尝试哈希表的做法。

哈希

让我们来看看这题能否使用哈希,我们利用哈希表来存储每个字符出现的个数,最后输出第一个只出现一次的字符。最后成功通过了,但是效率不是很高。

indexOf

利用JavaScript内置的函数indexOf查看在当前位置加一后是否依然有该字符出现,如果没有(indexOf返回-1)则返回该字符。

代码实现

// 哈希表
var firstUniqChar = function(s) {
    let map = new Map();
    for(let c of s) {
        // 字符出现过次数加一,否则存储1次
        map.has(c) ? map.set(c, map.get(c)+1) : map.set(c, 1);
    }
    for(let [key,val] of map) {
        if(val === 1) {
            return key;
        }
    }
    return ' ';
};

// indexOf
var firstUniqChar = function(s) {
    for(let c of s) {
        let index = s.indexOf(c, s.indexOf(c) + 1);
        if(index === -1){
            return c;
        }
    }

    return ' ';
};

总结

今天主要还是考察了二分查找算法,第三题可以用哈希来解决。二分查找算法一般对应于有序数组的查找问题,有时候他也会有变形,比如第二题将有序数组旋转之后再使用二分,但是其本质不变,还是分为左边界、右边界、中间值来讨论即可。第三题虽然用哈希解决了问题,但效率不高,反而用内置的indexOf的效率更高,所以有时候不要一味的追求规范方法。如果有什么不对的地方,或者有更好的方案,欢迎提出,大家一起讨论。