《剑指offer》 -day5-查找算法(中等)【二分】

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

在这里插入图片描述

暴力

思路:一个一个找,效率低。

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0) return false;
        int n = matrix.length;
        int m = matrix[0].length;
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                if (matrix[i][j] == target) {
                    return true;
                }
            }
        }
        return false;
    }
}
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2)
  • 空间复杂度: O ( 1 ) O(1) O(1)

二分

思路:

  • 因为数组 m a t r i x matrix matrix 中的 行、列 均有序,所以可以考虑使用 二分查找。

逐行二分

思路

  • matrix[i][0] <= target,则逐行二分查找 t a r g e t target target
  • 否则,说明当前 m a t r i x matrix matrix 中不存在 t a r g e t r targetr targetr.
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0 || matrix[0].length == 0) return false;
        int n = matrix.length;
        int m = matrix[0].length;
        // 对所有行,逐行进行二分
        for (int i = 0; i < n && matrix[i][0] <= target; i++) {
            int left = 0;
            int right = m - 1; // 左闭右闭
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (matrix[i][mid] < target) {
                    left = mid + 1;
                } else if (matrix[i][mid] > target) {
                    right = mid - 1;
                } else {
                    return true;
                }
            }
        }
        return false; // 不存在
    }
}
  • 时间复杂度: O ( n l o g n ) O(n logn) O(nlogn) (逐行二分)
  • 空间复杂度: O ( 1 ) O(1) O(1)

在这里插入图片描述

两次二分

注意:无法利用“二段性” 直接定位 t a r g e t target target “可能” 所在的行 row(定位到的 row 只是 > t a r g e t > target >target < t a r g e t < target <target 的 “行分界”)
在这里插入图片描述
但是,可以首先通过“二分”找到 row,然后从 row 开始逆序 逐行二分

思路 🤔

  1. 第一次二分:查找 row

    利用“二段性”,所以定位到的 row 只是 > t a r g e t > target >target < t a r g e t < target <target 的 “行分界”

  2. 从 row 开始 逆序逐行二分:在matrix[i]中查找target
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0 || matrix[0].length == 0) return false;
        int n = matrix.length;
        int m = matrix[0].length;
        // 1、第一次二分:查找row
        int left = 0;
        int right = n - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (matrix[mid][0] < target) {
                left = mid + 1;
            } else if (matrix[mid][0] > target) {
                right = mid - 1;
            } else {
                return true;
            }
        }
        int row = right; // target“可能”存在的行
        System.out.println("row = " + row);
        // 2、从row开始 逆序逐行二分:在matrix[i]中查找target
        for (int i = row; i >= 0; i--) {
            left = 0;
            right = m - 1;
            while (left <= right) {
                int mid = left + (right - left) / 2;
                if (matrix[i][mid] < target) {
                    left = mid + 1;
                } else if (matrix[i][mid] > target) {
                    right = mid - 1;
                } else {
                    return true;
                }
            }
        }
        return false; // 不存在
    }
}
  • 时间复杂度
    • 最坏 O ( n l o g n ) O(n logn) O(nlogn)row在中间一行,但是 t a r g e t target target在第一行,此时需要对 n / 2 n/2 n/2行 进行二分)
    • 最好 O ( l o g n ∗ l o g n ) O(logn * logn) O(lognlogn)row直接定位到了 t a r a g e t taraget taraget 所在行,只用进行对一行进行 二分)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    在这里插入图片描述

二分查找树 ⭐️

思路 🤔

  • 题目中说了 每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。所以,可以把整个 m a t r i x matrix matrix 看成一颗以 右上角(即,(0, m))为 root 的 BST.
  • 初始化:i = 0; j = m - 1;
  • m a t r i x [ i ] [ j ] > t a r g e t matrix[i][j] > target matrix[i][j]>target,表示 target “可能” 在 左子树中,所以 j--;
  • m a t r i x [ i ] [ j ] < t a r g e t matrix[i][j] < target matrix[i][j]<target,表示 target “可能” 在 右子树中,所以 i++;
class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0) { // 空矩阵,单独处理
            return false;
        }
        int n = matrix.length;
        int m = matrix[0].length;
        // 以右上角为 root,看成一颗 BST
        int i = 0;
        int j = m - 1;
        while (i < n && j >= 0) {
            if (matrix[i][j] > target) { // 左子树
                j--;
            } else if (matrix[i][j] < target) { // 右子树
                i++;
            } else {
                return true;
            }
        }
        return false;
    }
}
  • 时间复杂度: O ( n + m ) O(n + m) O(n+m)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    BST

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

在这里插入图片描述
提示:

  • n = = n u m b e r s . l e n g t h n == numbers.length n==numbers.length
  • 1 < = n < = 5000 1 <= n <= 5000 1<=n<=5000
  • − 5000 < = n u m b e r s [ i ] < = 5000 -5000 <= numbers[i] <= 5000 5000<=numbers[i]<=5000
  • numbers 原来是一个升序排序的数组,并进行了 1 至 n 次旋转

注意:本题与主站 154 题相同:https://leetcode-cn.com/problems/find-minimum-in-rotated-sorted-array-ii/

暴力

class Solution {
    public int minArray(int[] numbers) {
        int res = Integer.MAX_VALUE;
        int n = numbers.length;
        for (int i : numbers) {
            res = res < i ? res : i;
        }
        return res;
    }
}
  • 时间复杂度: O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    在这里插入图片描述

二分 ⭐️

二分专题 中的 81. 搜索旋转排序数组 II 类似,但较之更为简单一些。

思路 🤔

  • 由于本题中 n u m s nums nums 可能存在重复元素,所有首先需要“维护两段性”之后才能使用 二分

    参考: 二分专题 中的 81. 搜索旋转排序数组 II

  • 使用 二分查找“旋转点” index
    • n u m s nums nums 整体升序(即,“旋转点”为 n n n),此时 min n u m b e r [ 0 ] number[0] number[0]
    • 否则,即 i n d e x < n index < n index<n 时,则“旋转点” numbers[index] 即为 最小值 min。
class Solution {
    public int minArray(int[] numbers) {
        int n = numbers.length;
        int left = 0;
        int right = n - 1;
        // 维护“两段性”(数组只有一个元素时,不用维护,所以是 `<`)
        while (left < right && numbers[right] == numbers[0]) { 
            right--;
        }
        System.out.println("right1 = " + right);
        // 二分:找“旋转点”
        while (left <= right) { // 左闭右闭(这里是 <=)
            int mid = left + (right - left) / 2;
            if (numbers[mid] >= numbers[0]) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        int index = left; // 旋转点
        System.out.println("left = " + left);
        System.out.println("right = " + right);
        // 若nums整体升序(即,没有旋转、或称之为“旋转点”为n),此时min为number[0]
        return index >= n ? numbers[0] : numbers[index];
    }
}
  • 时间复杂度: O ( l o g n ) O(logn) O(logn)
  • 空间复杂度: O ( 1 ) O(1) O(1)
    在这里插入图片描述

类似题目

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

暴力 + HassMap

思路:

  • 使用 Map 统计 s 中每个字符出现的次数;
  • 再次对 s 从前向后扫描,找到第一次只出现一次的 字符。
class Solution {
    public char firstUniqChar(String s) {
        Map<Character, Integer> map = new HashMap<>();
        for (char c : s.toCharArray()) {
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        for (char c : s.toCharArray()) {
            if (map.get(c) == 1) {
                return c;
            }
        }
        return ' '; // 不存在
    }
}

本题使用 HashMap<Character, Boolean> 是更好的选择,当然 直接用 Set 其实也是可以的。

  • 时间复杂度: O ( n ∗ 2 ) O(n * 2) O(n2) (在忽略Map存、取时间消耗的前提下)
  • 空间复杂度: O ( n ) O(n) O(n)

暴力

LinkedHashMap 有序 ⭐️

参考:K佬题解

思路:

  • 由于 HashMap 无序,所以需要遍历两遍 s
  • LinkedHashMap 是有序的(可以保证 FIFO),可以只遍历一遍 s、再遍历一遍 LinkedHashMap即可(map 中至多不超过 26 26 26 个元素)
class Solution {
    public char firstUniqChar(String s) {
        HashMap<Character, Boolean> map = new LinkedHashMap<>();
        char[] arr = s.toCharArray();
        // 1、遍历s
        for (char c : arr) {
            map.put(c, !map.containsKey(c)); // 巧妙!
        }
        // 2、遍历map(最多不超过26)
        for (Map.Entry<Character, Boolean> entry : map.entrySet()) {
            if (entry.getValue()) {
                return entry.getKey();
            }
        }
        return ' '; // 不存在
    }
}
  • 时间复杂度: O ( n ∗ 1 ) O(n * 1) O(n1)
  • 空间复杂度: O ( n ) O(n) O(n)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值