LeetCode刷题笔记(Java)---第361-380题

前言

需要开通vip的题目暂时跳过

笔记导航

点击链接可跳转到所有刷题笔记的导航链接

363.矩形区域不超过 K 的最大数值和

给定一个非空二维矩阵 matrix 和一个整数 k,找到这个矩阵内部不大于 k 的最大矩形和。

在这里插入图片描述

说明:

  • 矩阵内的矩形区域面积必须大于 0。

  • 如果行数远大于列数,你将如何解答呢?

  • 解答

    public int maxSumSubmatrix(int[][] matrix, int k) {
            int row = matrix.length;
            int col = matrix[0].length;
            int max = Integer.MIN_VALUE;
            for(int left = 0;left<col;left++){ // 滑动窗口左边界
                int[] sumRow = new int[row];
                for(int right = left;right<col;right++){ // 滑动窗口右边界
                    for(int r = 0;r<row;r++){//计算滑动窗口内,每一行的和
                        sumRow[r] += matrix[r][right];
                    }
                    max = Math.max(max,getMax(sumRow,k));//在滑动窗口内找到一个最大的矩阵和
                    if (max == k) return k;
                }
            }
            return max;
        }
    
    
        public int getMax(int[] sumRow,int k){
            int result = sumRow[0];
            int resultMax = result;
            for (int i = 1; i < sumRow.length; i++) {//最大子序列和算法
                if (result > 0) result += sumRow[i];
                else result = sumRow[i];
                if (result > resultMax) resultMax = result;
            }
            if(resultMax <= k) return resultMax;
            resultMax = Integer.MIN_VALUE;
            for(int i = 0;i<sumRow.length;i++){//暴力求解
                result = 0;
                for(int j = i;j<sumRow.length;j++){
                    result += sumRow[j];
                    if(result > resultMax && result <= k) resultMax = result;
                    if(resultMax == k)return result;
                }
            }
            return resultMax;
        }
    
  • 分析

    1. 滑动窗口来解决。第一个for循环 限制滑动窗口左边的区间
    2. 在左边区间固定的基础上,新建一个数组,来存储每一行的和。
    3. 第二个for循环 限制滑动窗口的右区间,sumRow数组就是用来存储滑动窗口内每一行的和。
    4. 得到了滑动窗口内每一行的和之后,在这个窗口内找一个最大矩阵和。宽度不变,滑动窗口左右区间限制好了,就是找不同高度的矩阵,最大的和。
    5. 问题就变成了找最大子序列和的算法。
    6. 但是如果最大子序列和算出来的最大值 不满足 题目限制的小于等于k这个条件。
    7. 那么就使用暴力来找最大的小于等于k的子序列和。
  • 提交结果

    在这里插入图片描述

365. 水壶问题

有两个容量分别为 x升 和 y升 的水壶以及无限多的水。请判断能否通过使用这两个水壶,从而可以得到恰好 z升 的水?

如果可以,最后请用以上水壶中的一或两个来盛放取得的 z升 水。

你允许:

  • 装满任意一个水壶

  • 清空任意一个水壶

  • 从一个水壶向另外一个水壶倒水,直到装满或者倒空

  • 解答

    public boolean canMeasureWater(int x, int y, int z) {
            if (z == 0) {
                return true;
            }
            if (x + y < z) {
                return false;
            }
            Queue<Pair> queue = new ArrayDeque<>();
            Pair<Integer, Integer> start = new Pair(0, 0);
            queue.add(start);
            Set<Pair> visited = new HashSet<>();
            visited.add(start);
            while (!queue.isEmpty()) {
                Pair<Integer, Integer> entry = queue.poll();
                int curX = entry.getKey();
                int curY = entry.getValue();
                if (curX == z || curY == z || curX + curY == z) {
                    return true;
                }
                if (curX == 0) {
                    // 把第一个桶填满
                    addIntoQueue(queue, visited, new Pair(x, curY));
                }
                if (curY == 0) {
                    // 把第二个桶填满
                    addIntoQueue(queue, visited, new Pair(curX, y));
                }
                if (curY < y) {
                    // 把第一个桶倒空
                    addIntoQueue(queue, visited, new Pair(0, curY));
                }
                if (curX < x) {
                    // 把第二个桶倒空
                    addIntoQueue(queue, visited, new Pair(curX, 0));
                }
    
                // y - curY是第二个桶还可以再加的水的升数,但是最多只能加curX升水。
                int moveSize = Math.min(curX, y - curY);
                // 把第一个桶里的moveSize升水倒到第二个桶里去。
                addIntoQueue(queue, visited, new Pair(curX - moveSize, curY + moveSize));
                // 反过来同理,x - curX是第一个桶还可以再加的升数,但是最多只能加curY升水。
                moveSize = Math.min(curY, x - curX);
                // 把第二个桶里的moveSize升水倒到第一个桶里去。
                addIntoQueue(queue, visited, new Pair(curX + moveSize, curY - moveSize));
            }
            return false;
        }
    
        private void addIntoQueue(Queue<Pair> queue,
                                  Set<Pair> visited,
                                  Pair<Integer, Integer> newEntry) {
            if (!visited.contains(newEntry)) {
                visited.add(newEntry);
                queue.add(newEntry);
            }
        }
    
  • 分析

    1. queue存储每次操作之后的两个桶中的水
    2. visited存储已经出现过的两个桶中的水量,避免重复的计算。
    3. 首先两个桶都是0L水,开始操作,每次操作入队,相当于BFS。有如下几种情况
      1. 把第一个桶罐满
      2. 把第二个桶罐满
      3. 把第一个桶倒空,前提是第二个桶不空,如果空的话,再把第一个桶倒空那就变回初始状态,没有意义
      4. 把第二个桶倒空,条件和上面同理。
      5. 把第一个桶里的水倒入第二个桶,但是要计算第二个桶剩余可倒入的水和第一个桶中的水哪个更少。如果第一个桶中剩余的水多余第二个桶中可倒入的水,那么只能将一部分倒入第二个桶中,否则全部倒入第二个桶中。所以只需要求更小的那个值,来转移到第二个桶中即可。
      6. 把第二个桶中的水倒入第一个桶中,原理同上。
    4. 当curX == z || curY == z || curX + curY == z这个条件成立,则返回true。
  • 提交结果

    在这里插入图片描述

367.有效的完全平方数

给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。

说明:不要使用任何内置的库函数,如 sqrt。

在这里插入图片描述

  • 解答

    //方法一
    	public boolean isPerfectSquare(int num) {
            long temp = num;
            while(temp > 1){
                temp /= 2;
                if(temp * temp == num)return true;
                if((temp * temp) < num){
                    for(long i = temp;i< 2 * temp;i++){
                        if( i * i == num)return true;
                    }
                    return false;
                }
            }
            return true;
        }
    
    方法二
      	public boolean isPerfectSquare(int num) {
            if (num < 2) {
                return true;
            }
            long left = 2, right = num / 2, x, guessSquared;
            while (left <= right) {
                x = left + (right - left) / 2;
                guessSquared = x * x;
                if (guessSquared == num) {
                    return true;
                }
                if (guessSquared > num) {
                    right = x - 1;
                } else {
                    left = x + 1;
                }
            }
            return false;
        }
    
  • 分析

    1. 每次除以2,当找到结果平方 小于 num的时候,在temp到2*tmep的范围内寻找满足平方和等于 num的值,如果存在 则返回true。否则循环结束返回false。
    2. 最后面的一个返回true,是当参数num = 1的时候返回true。
    3. 方法二对方法一的改进,使用二分查找
  • 提交结果

    方法一
    在这里插入图片描述

    方法二
    在这里插入图片描述

368.最大整除子集

给出一个由无重复的正整数组成的集合,找出其中最大的整除子集,子集中任意一对 (Si,Sj) 都要满足:Si % Sj = 0 或 Sj % Si = 0。

如果有多个目标子集,返回其中任何一个均可。

在这里插入图片描述

  • 解答

    	//方法一
    	ArrayList<Integer> res = new ArrayList<>();
        public List<Integer> largestDivisibleSubset(int[] nums) {
            Arrays.sort(nums);
            backTrack(new ArrayList<>(),nums,0);
            return res;
        }
    
        public void backTrack(ArrayList<Integer> temp,int[] nums,int index){
            if(temp.size() > res.size()){
                res = new ArrayList<>(temp);
            }
            for(int i = index;i<nums.length;i++){
                if(isCanPut(temp,nums[i]) && (temp.size() + nums.length - i) > res.size()){
                    temp.add(nums[i]);
                    backTrack(temp,nums,i+1);
                    temp.remove(temp.size()-1);
                }
            }
        }
    
        public boolean isCanPut(ArrayList<Integer> temp,int num){
            if(temp.size() == 0)return true;
            int biggest = temp.get(temp.size()-1);
            if(num % biggest == 0)return true;
            return false;
        }
    	//方法二
    	public List<Integer> largestDivisibleSubset(int[] nums) {
            if(nums.length == 0)return new ArrayList<>();
            Arrays.sort(nums);
            ArrayList<ArrayList<Integer>> lists = new ArrayList<>();
            for(int i = 0;i<nums.length;i++){//遍历数组
                int cur = nums[i];
                ArrayList<Integer> temp = new ArrayList<>();
                for(ArrayList<Integer> list : lists){//遍历之前的已知的集合。
                    int lastVal = list.get(list.size()-1);//拿出集合中的最后一个值进行判断
                    if(cur % lastVal == 0 && list.size() + 1 > temp.size()){//若可以整除,并且这个最大整除子集大于当前找到的集合。则更新temp。
                        temp = new ArrayList<>(list);
                        temp.add(cur);
                    }
                }
                if(temp.size() == 0){//之前没有可以整除的集合。这里返回自己作为一个集合。
                    temp.add(cur);
                }
                lists.add(new ArrayList<>(temp));
            }
    
            int maxLen = 0;
             ArrayList<Integer> res = new ArrayList<>();
            for(ArrayList<Integer> list : lists){
                if(list.size() > maxLen){
                    res = list;
                    maxLen = list.size();
                }
            }
            return res;
        }
    
  • 分析

    1. 回溯来实现

    2. 首先先对数组进行排序,

    3. 然后每次往候选集合中尝试的放入一个数字。

    4. 这个数字必须要可以和当前集合中最大的一个数字可以整除。

    5. 因为当前集合中最大的数字可以整除前面的所有数字,所有如果准备添加进去的数字可以整除最大的数字,那么就可以整除其余的数字。

    6. (temp.size() + nums.length - i) > res.size() 剪枝。若剩余可添加的数字 加上候选集的大小 没有大于 答案集合中的数字个数。那么就不可能存在更优的解,直接返回。

    7. 方法二

      记录下当前遍历的数组的过程中,以这个数字为结尾的最大整除子集。

  • 提交结果

    方法一
    在这里插入图片描述

    方法二
    在这里插入图片描述

371.两整数之和

不使用运算符 + 和 - ,计算两整数 a 、b 之和。

在这里插入图片描述

  • 解答

    public int getSum(int a, int b) {
            while(b != 0){//直到进位为0
                int temp = a ^ b;//无进位相加
                b = (a & b) << 1;//获得进位,b保留进位
                a = temp;//保留相加的结果
            }
            return a;
        }
    
  • 分析

    1. 异或运算可以得到无进位的加法结果。
    2. 与运算 并且左移一位可以得到进位,
    3. 直到进位为0,返回结果。
  • 提交结果
    在这里插入图片描述

372.超级次方

你的任务是计算 ab 对 1337 取模,a 是一个正整数,b 是一个非常大的正整数且会以数组形式给出。

在这里插入图片描述

  • 解答

    int base = 1337;
        public int superPow(int a, int[] b) {
               int len = b.length;
               int ans = indexPow(a, b, len);
               return ans;   
        }
    
        private int myPow(int a,int k){
            a %=base;
            int ans=1;
            for(int i = 0;i<k;i++){
                ans *= a;
                ans %=base;
            }
            return ans;
        }
        private int indexPow(int a,int[] b,int index){
            if(index < 1 )return 1;
    
            int part1 = myPow(a, b[index-1]);
            index--;
            int part2 = myPow(indexPow(a, b, index), 10);
    
            return part1*part2%base;
        }
    
  • 分析

    1. 如下图所示,从数组b的个位开始看起,如果不是0,例如个位为8,那么就将这一位改成0,再后面✖️上2的8次方。
    2. 如果当前位是0,那么就这一位删去,在外面套一个10次方。
    3. 删去的操作,可以用索引移动位置来代替。索引index的位置就等同于图中的数组b的最后一个数字。
    4. 就这样可以建立一个递归的逻辑。
    5. 递归出口就是索引 < 1 返回1。
    6. 每一层的递归。都包括了两种,底数数组次方的运算 * 底数数字次方的运算。
    7. 注意:两个因子乘积的模,等于两个因子模的乘积再取模。

    在这里插入图片描述

  • 提交结果 在这里插入图片描述

373. 查找和最小的K对数字

给定两个以升序排列的整形数组 nums1 和 nums2, 以及一个整数 k。

定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2。

找到和最小的 k 对数字 (u1,v1), (u2,v2) … (uk,vk)。

在这里插入图片描述

  • 解答

    //方法一		
    List<List<Integer>> res = new ArrayList<>();
        public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
          	if(nums1.length == 0 || nums2.length == 0)return res;
            PriorityQueue<P> priorityQueue = new PriorityQueue<>(new Comparator<P>() {
                @Override
                public int compare(P o1, P o2) {
                    return o1.sum-o2.sum;
                }
            });
            for(int i = 0;i<nums1.length;i++){
                for(int j = 0;j<nums2.length;j++){
                    priorityQueue.add(new P(nums1[i],nums2[j]));
                }
            }
    
            for(int i = 0;i<k;i++){
                P p = priorityQueue.poll();
                if(p!=null){
                    int number1 = p.number[0];
                    int number2 = p.number[1];
                    List<Integer> list = new ArrayList<>();
                    list.add(number1);
                    list.add(number2);
                    res.add(list);
                }
            }
            return res;
        }
    
        class P{
            int sum;
            int[] number;
            public P(int a,int b){
                number = new int[]{a,b};
                sum = a+b;
            }
        }
    
    //方法二
    List<List<Integer>> res = new ArrayList<>();
        public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
            if(nums1.length == 0 || nums2.length == 0)return res;
            PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1,int[] o2) {
                    return nums1[o1[0]] + nums2[o1[1]] - (nums1[o2[0]] + nums2[o2[1]]);
                }
            });
            for(int i = 0;i<nums1.length;i++){
                priorityQueue.add(new int[]{i,0});
            }
            for(int i = 0;i<k;i++){
                int[] p = priorityQueue.poll();
                if(p!=null){
                    int index1 = p[0];
                    int index2 = p[1];
                    List<Integer> list = new ArrayList<>();
                    list.add(nums1[index1]);
                    list.add(nums2[index2]);
                    res.add(list);
                    if(index2 + 1 < nums2.length){
                        priorityQueue.add(new int[]{index1,index2+1});
                    }
                }
            }
            return res;
        }
    
  • 分析

    1. 方法一
    2. 使用小顶堆暴力求解,将两个数组中所有的组合成对的放入到优先级队列中,根据组合的和来维护小顶堆。
    3. 之后在从小顶堆中取出k个即可。
    4. 方法二
    5. 在方法一基础上的改进
    6. 优先级队列中,不需要保存准确数值对以及它们的和,只需要记录下两个数值在数组中的索引坐标即可。
    7. 并且一开始不需要将所有的组合都放入到优先级队列中。
    8. 因为两个数组都是有序的,所以最小的一对和,肯定是两个数组索引都是0的时候。
    9. 所以一开始只需要将其中一个数组中的索引和另个数组中的索引0 构成组合对放入优先级队列中。
    10. 然后在每一次的从堆中取出一对之后,再往优先级队列中放入当前数组1的索引和数组2的索引+1的那一对。
    11. 这样可以先少插入堆中的次数。
  • 提交结果

    方法一在这里插入图片描述

    方法二在这里插入图片描述

374.猜数字大小

猜数字游戏的规则如下:

  • 每轮游戏,系统都会从 1 到 n 随机选择一个数字。 请你猜选出的是哪个数字。
  • 如果你猜错了,系统会告诉你,你猜测的数字比系统选出的数字是大了还是小了。

你可以通过调用一个预先定义好的接口 guess(int num) 来获取猜测结果,返回值一共有 3 种可能的情况(-1,1 或 0):

在这里插入图片描述

  • 解答

     public int guessNumber(int n) {
            int start = 1;
            int end = n;
            for(;;){
                int mid = start + (end-start)/2;
                if(guess(mid) == 0)
                    return mid;
                if(guess(mid) == -1)
                    end = mid -1;
                else if(guess(mid) == 1)
                    start = mid +1;
            }
        }
    
  • 分析

    1. 其实就是二分查找,找到那个数字
  • 提交结果在这里插入图片描述

375. 猜数字大小 II

我们正在玩一个猜数游戏,游戏规则如下:

我从 1 到 n 之间选择一个数字,你来猜我选了哪个数字。

每次你猜错了,我都会告诉你,我选的数字比你的大了或者小了。

然而,当你猜了数字 x 并且猜错了的时候,你需要支付金额为 x 的现金。直到你猜到我选的数字,你才算赢得了这个游戏。

在这里插入图片描述

  • 解答

    //方法一
    public int getMoneyAmount(int n) {
            int[][] dp = new int[n+1][n+1];
            for(int len = 2;len <= n;len++){//区间长度
                for(int start = 1;start <= n-len+1;start++){//区间起始位置
                    int min = Integer.MAX_VALUE;
                    for(int i = start;i<start + len -1;i++){//遍历区间
                        min = Math.min(min,i + Math.max(dp[start][i-1],dp[i+1][start + len - 1]));
                    }
                    dp[start][start + len - 1] = min;
                }
            }
            return dp[1][n];
        }
    //方法二
    public int getMoneyAmount(int n) {
            int[][] dp = new int[n+1][n+1];
            for(int len = 2;len <= n;len++){
                for(int start = 1;start <= n-len+1;start++){
                    int min = Integer.MAX_VALUE;
                    for(int i = start + (len - 1)/2;i<start + len -1;i++){
                        min = Math.min(min,i + Math.max(dp[start][i-1],dp[i+1][start + len - 1]));
                    }
                    dp[start][start + len - 1] = min;
                }
            }
            return dp[1][n];
        }
    
  • 分析

    1. 方法一
    2. 将问题拆分成子问题,在一个区间内选择一个数字,区间被拆成左右两个。左区间和右区间得到的结果是不一样的。保留大的那个就是区间内选择一个数字后的至少需要多少现金。
    3. 规模最小的子问题是1个数字,此时不需要支付现金也可以得到。所以是0.
    4. 之后就求区间长度为2的结果,选择一个点支付现金,剩余部分就是区间为1的结果。
    5. 依次类推,最后得到区间为n的结果。
    6. dp[i] [j]表示i - j的数字范围内猜中至少需要多少现金。
    7. 方法二
    8. 是对方法一的改进,每个区间不需要从头开始遍历,因为猜大的数字开销会更大,所以只需要从中间开始遍历即可。
  • 提交结果

    方法一在这里插入图片描述
    方法二在这里插入图片描述

376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

在这里插入图片描述

  • 解答

    public int wiggleMaxLength(int[] nums) {
            if(nums.length < 2)return nums.length;
            ArrayList<Integer> list = new ArrayList<>();
            list.add(nums[0]);
            if(nums[0] != nums[1])
                list.add(nums[1]);
            int tf = nums[1] - nums[0];
            for(int i = 2;i<nums.length;i++){
                int last = list.get(list.size() -1);
                int temp = nums[i] - last;
                if((temp > 0 && tf > 0) || (temp <0 && tf < 0)){
                    list.remove(list.size()-1);
                }
                if(temp != 0){
                    list.add(nums[i]);
                    tf = temp;
                }
            }
            return list.size();
        }
    
  • 分析

    1. 首先将数组中的前两个放入队列中,然后计算出他们是升序的关系还是降序的关系
    2. 从数组的第三个数字开始遍历
    3. 每次和队尾的元素做差,得到升序关系或者降序关系。
    4. 如果和前者相同,则替换掉当前的队尾元素。
    5. 如果和前者不同,则插入到队尾,并更新tf为新的升序或降序关系。用于下次的判断。
    6. 最后返回队列的长度即可。
  • 提交结果在这里插入图片描述

377. 组合总和 Ⅳ

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。
在这里插入图片描述

  • 解答

    		// 方法一 记忆化搜索
    		int[] memo;
        public int combinationSum4(int[] nums, int target) {
            memo = new int[target + 1];
            Arrays.fill(memo, -1);
            memo[0] = 1;
            return backTrack(nums,target);
        }
        public int backTrack(int[] nums,int target){
            if(memo[target] != -1)
                return memo[target];
            int res = 0;
            for(int num:nums){
                if(target >= num)
                    res += backTrack(nums,target-num);
            }
            memo[target] = res;
            return res;
        }
    		// 方法二 dp
    		int[] memo;
        public int combinationSum4(int[] nums, int target) {
            int[] dp = new int[target+1];
            dp[0] = 1;
            for(int i = 1; i<= target;i++){
                for(int num:nums){
                    if(i >= num){
                        dp[i] += dp[i-num];
                    }
                }
            }
            return dp[target];
        }
    
  • 分析

    1. 直接使用回溯会导致超时
    2. 所以使用记忆化递归的方式,这样可以减少重复的计算。
    3. 方法二使用动态规划
    4. 外层循环是目标值,内层循环是遍历数组nums
    5. 如果当目标值大于数组中的值。
    6. 那么当前位置的解就可以加上 dp[i-num];
    7. 最后返回dp[target]即可
  • 提交结果

    方法一在这里插入图片描述

    方法二在这里插入图片描述

378. 有序矩阵中第K小的元素

给定一个 n x n 矩阵,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是排序后的第 k 小元素,而不是第 k 个不同的元素。

在这里插入图片描述

  • 解答

    //方法一
    public int kthSmallest(int[][] matrix, int k) {
            PriorityQueue<Integer> priority = new PriorityQueue<>();
            for(int i =0;i<matrix.length;i++){
                for(int j = 0;j<matrix[0].length;j++){
                    priority.add(matrix[i][j]);
                }
            }
            int res = 0;
            while(k > 0){
                res = priority.poll();
                k--;
            }
            return res;
        }
    //方法二
    public int kthSmallest(int[][] matrix, int k) {
            int n = matrix.length;
            int left = matrix[0][0];
            int right = matrix[n-1][n-1];
            while(left < right){
                int mid = left + (right - left)/2;
                if(dfs(matrix,mid,k)){
                    right = mid;
                }else{
                    left = mid + 1;
                }
            }
            return left;
        }
    
        public boolean dfs(int[][] matrix,int mid,int k){
            int i = matrix.length -1;
            int j = 0;
            int num = 0;
            while(i >= 0 && j < matrix.length){
                if(matrix[i][j] <= mid){
                    num += i + 1;
                    j++;
                }else{
                    i--;
                }
            }
            return num >= k;
        }
    
  • 分析

    1. 方法一 暴力求解

    2. 将所有的元素都放入到优先级队列中,也就是小顶堆

    3. 然后从堆顶取k次,第k次就是第k小的元素。

    4. 方法二

    5. 利用矩阵 行升序 列升序的性质

    6. 可以将矩阵从中间拆开 分成大于某一个数的部分和小于某一个数字的部分

      如下图所示

      在这里插入图片描述

    7. 此时可以从左下脚开始 遇到红线之前 计算小于等于8的部分,同时右移动,否则就上移。

    8. 这样遍历下来可以计算出小于等于8的个数。

    9. 参考这种遍历的方式可以使用二分查找的思路。

    10. left是数字中的最小值 也就是这里的,在nums[0] [0]的位置, right是数字中的最大值 也就是这里的16,在nums[n-1] [n-1]的位置

    11. 计算中值mid = (left + right )/2

    12. 然后就是走上面的搜索矩阵的思路,计算有多少个小于等于mid的数字。

    13. 如果计算出来的结果大于给定的目标k,那么说明mid太大了。找到了太多的比mid小的数字

    14. 所以需要缩小右边界,也就是使right变小。令right = mid

    15. 如果计算出来的结果小于给定的目标k,那么说明mid太小了,需要增大左边界,也就是使left变大。令left = mid +1;

    16. 然后就是迭代上面的过程。

    17. 最终输出left或者right就是所要的结果。

  • 提交结果

    方法一在这里插入图片描述

    方法二在这里插入图片描述

380.常数时间插入、删除和获取随机元素

设计一个支持在平均 时间复杂度 O(1) 下,执行以下操作的数据结构。

  • insert(val):当元素 val 不存在时,向集合中插入该项。

  • remove(val):元素 val 存在时,从集合中移除该项。

  • getRandom:随机返回现有集合中的一项。每个元素应该有相同的概率被返回。

  • 解答

    class RandomizedSet {
        HashMap<Integer, Integer> map;
        ArrayList<Integer> list;
    
        /**
         * Initialize your data structure here.
         */
        public RandomizedSet() {
            map = new HashMap<>();
            list = new ArrayList<>();
        }
    
        /**
         * Inserts a value to the set. Returns true if the set did not already contain the specified element.
         */
        public boolean insert(int val) {
            if (!map.containsKey(val)) {
                list.add(val);
                map.put(val, list.size()-1);
                return true;
            }
            return false;
        }
    
        /**
         * Removes a value from the set. Returns true if the set contained the specified element.
         */
        public boolean remove(int val) {
            if (!map.containsKey(val)) {
                return false;
            } else {
                int index = map.get(val);
                list.set(index, list.get(list.size() - 1));
                map.put(list.get(index), index);
                list.remove(list.size() - 1);
                map.remove(val);
                return true;
            }
        }
    
        /**
         * Get a random element from the set.
         */
        public int getRandom() {
            Random random = new Random();
            int i = random.nextInt(list.size());
            return list.get(i);
        }
    }
    
  • 分析

    1. 数组可以做到随机的访问,所以使用ArrayList来存储数据,
    2. 但是删除并不能做到O(1)的时间复杂度,因为需要移动数组里面的值,所以考虑仅删除最后一个数,这样就不需要移动数据。
    3. 但是如何定位到要删除的数呢?需要一个HashMap来存储数字对应的索引。这样就可以根据HashMap 获取到索引。
    4. 然后将数组中最后一个数字替换到这个索引的位置,再删除数组中的最后一个数字,然后将HashMap中的删除数字的索引关系的键值对 删除掉。
    5. 添加操作,就是添加再数组的最后面,保留数字和索引的关系记录在HashMap中。
  • 提交结果在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值