力扣461,剑指offer59-I,59-II,60,61

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了力扣461,剑指offer59-I,59-II,60,61相关的知识,希望对你有一定的参考价值。

461. 汉明距离

2021.5.27 每日一题

题目描述

两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。

给出两个整数 x 和 y,计算它们之间的汉明距离。

注意:
0 ≤ x, y < 231.

示例:

输入: x = 1, y = 4

输出: 2

解释:
1   (0 0 0 1)
4   (0 1 0 0)
       ↑   ↑

上面的箭头指出了对应二进制位不同的位置。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/hamming-distance
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

今日没有难度,直接异或,统计1出现的次数就行
m & m - 1就是把m最右侧的1变成0
m & -m 取得最右侧的1

class Solution {
    public int hammingDistance(int x, int y) {
        return Integer.bitCount(x ^ y);
    }
}

class Solution {
    public int hammingDistance(int x, int y) {
        //异或,然后统计1的个数
        int m = x ^ y;
        int count = 0;
        while(m > 0){
            m = m & (m - 1);
            count++;
        }
        return count;
    }
}

剑指 Offer 59 - I. 滑动窗口的最大值

题目描述

给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。

示例:

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/hua-dong-chuang-kou-de-zui-da-zhi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

单调栈的经典题目
用双端队列做了
从栈顶到栈底,单调递增,比栈顶元素小或等于就放入,比栈顶元素大弹出
这时候取出栈首部的值,如果不在窗口内了,就弹出,如果还在就是当前窗口的最大值

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //单调栈的经典题目
        //从栈顶到栈底,单调递增,比栈顶元素小或等于就放入,比栈顶元素大弹出,弹出的同时记录最大值
        //好像得使用双端队列
        int l = nums.length;
        if(l == 0)
            return new int[]{};
        int[] res = new int[l - k + 1];
        int max = Integer.MIN_VALUE;   
        Deque<Integer> deque = new LinkedList<>();
        for(int i = 0; i < l; i++){
            //比尾部元素大,弹出,否则放入
            while(!deque.isEmpty() && deque.peekLast() < nums[i]){
                int x = deque.pollLast();
            }
            deque.offerLast(nums[i]);
            
            if(i >= k - 1){
                //此时最大值就在队列首部,但是如果此时首部的值不在窗口内了,也弹出
                if(i >= k && deque.peekFirst() == nums[i - k])
                    deque.pollFirst();
                res[i - k + 1] = deque.peekFirst();
            }
        }
        return res;
    }
}

剑指 Offer 59 - II. 队列的最大值

题目描述

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/dui-lie-de-zui-da-zhi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

和上一个题基本一样吧,加了个普通队列用于压入和弹出操作

class MaxQueue {
    //就和上一个题一样呗,一个队列中就正常存放这些数
    //再创建另一个双端队列,用于取最大值
    Queue<Integer> queue;
    Deque<Integer> deque;
    public MaxQueue() {
        queue = new LinkedList<>();
        deque = new LinkedList<>();
    }
    
    public int max_value() {
        if(deque.isEmpty())
            return -1;
        return deque.peekFirst();
    }
    
    public void push_back(int value) {
        queue.offer(value);
        while(!deque.isEmpty() && deque.peekLast() < value){
            deque.pollLast();
        }
        deque.offerLast(value);
    }
    
    public int pop_front() {
        if(queue.isEmpty())
            return -1;
        int res = queue.poll();
        if(res == deque.peekFirst())
            deque.pollFirst();
        return res;
    }
}

/**
 * Your MaxQueue object will be instantiated and called as such:
 * MaxQueue obj = new MaxQueue();
 * int param_1 = obj.max_value();
 * obj.push_back(value);
 * int param_3 = obj.pop_front();
 */

剑指 Offer 60. n个骰子的点数

题目描述

把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。


你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。


示例 1:

输入: 1
输出: [0.16667,0.16667,0.16667,0.16667,0.16667,0.16667]
示例 2:

输入: 2
输出: [0.02778,0.05556,0.08333,0.11111,0.13889,0.16667,0.13889,0.11111,0.08333,0.05556,0.02778]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

规律想了半天没想出来,然后回溯走了一波,虽然慢,但是过了

class Solution {
    int N;
    public double[] dicesProbability(int n) {
        //一个骰子都是1/6
        //两个骰子,2是1/6 * 1/6,3 (12 21) 4(13 31 22) 5(14 41 23 32) 6 (15 51 24 42 33)
        //7(16 61 25 52 34 43) 8(26 62 35 53 44)9(36 63 45 54)10(46 64 55)11(56 65)12(66)
        //首先,数组是对称的;
        //第二点,数组中元素的个数n -> n * 6,也就是(5n + 1),最中间的数是3.5n,偶数时是一个数,奇数两个数
        //第三点,最小的概率,也就是res头尾元素的概率,(1/6)^n
        //中间元素的概率..
        //还真不容易推出规律

        //那么就先回溯写一下
        
        //创建一个存储和个数的数组,共有5n+1个元素,并且和为k的下标是k - n
        N = n;
        int[] sum = new int[5 * n + 1];
        recursion(n, sum, 0);
        double base = Math.pow((1.0 / 6), n);
        double[] res = new double[5 * n + 1];
        for(int i = 0; i < sum.length; i++){
            res[i] = base * sum[i];
        }
        return res;
    }

    public void recursion(int n, int[] sum, int temp){
        if(n == 1){
            for(int i = 1; i <= 6; i++){
                sum[temp + i - N]++;
            }
            return;
        }
        for(int i = 1; i <= 6; i++){
            //选了当前点数
            temp += i;
            //递归选下一个骰子
            recursion(n - 1, sum, temp);
            //回溯
            temp -= i;
        }
    }
}
动态规划

因为下一个骰子组成的点数是和上一个骰子有关的,并且不影响前面的结果,因此可以用动态规划
当前n个骰子得到x的情况个数,就等于n-1 个骰子,得到x-1,x-2,x-3,x-4,x-5,x-6的情况总和

class Solution {
    public double[] dicesProbability(int n) {
        //突然想到可以从正面来,创建一个哈希表,
        //第一个骰子1-6,次数是1,第二个骰子在前面结果每个上面加上一到六,
        //也就是动态规划
        int[][] sum = new int[n + 1][6 * n + 1];
        
        //第一个骰子
        for(int i = 1; i <= 6; i++){
            sum[1][i] = 1;
        }
        //遍历骰子个数
        for(int i = 2; i <= n; i++){
            //遍历6个点数
            for(int j = 1; j <= 6; j++){
                //遍历现在已经有的点数
                for(int k = 1;  k <= i * 6; k++){
                    if(k > i * 6)
                        break;
                    if(k - j > 0)
                        sum[i][k] += sum[i - 1][k - j];
                }
            }
        }
        double base = Math.pow((1.0 / 6), n);
        double[] res = new double[5 * n + 1];
        for(int i = 0; i < res.length; i++){
            res[i] = sum[n][n + i] * base;
        }
        return res;
    }
}

可以进行空间优化,因为当前阶段只和前一个阶段有关。但因为当前阶段的值是前一个阶段多个值相加,如果从小到大改变的话,会覆盖前一阶段的值,因此要改成从大到小修改当前阶段的值,这样就避免了创建一个临时数组,妙
参考思路:https://leetcode-cn.com/problems/nge-tou-zi-de-dian-shu-lcof/solution/nge-tou-zi-de-dian-shu-dong-tai-gui-hua-ji-qi-yo-3/

剑指 Offer 61. 扑克牌中的顺子

题目描述

从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。


示例 1:

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

示例 2:

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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/bu-ke-pai-zhong-de-shun-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

这个题巧妙的地方就在于要想到,如果存在顺子的话,那么数组中的最大值和最小值之差肯定小于5
再加上很容易想到的不能有重复,就可以了
我这里硬是想那个巧妙的方法了,结果感觉还不如统计0的个数来的直接了

class Solution {
    public boolean isStraight(int[] nums) {
        //我记得方法很巧妙哈哈
        //因为顺子的话,最大值和最小值之差肯定小于等于4,
        Arrays.sort(nums);
        int min = nums[0];
        for(int i = 1; i < 5; i++){
        	//判断重复
            if(nums[i] == nums[i - 1] && nums[i] != 0)
                return false;
            //记录最小值
            if(min == 0 && nums[i] != 0){
                min = nums[i];
            }
        }
            
        return nums[4] - min <= 4;
    }
}

还是人家的优雅,统计0的个数,然后第一个不是0的数下标正好是0的个数

class Solution {
    public boolean isStraight(int[] nums) {
        int joker = 0;
        //先排序,然后记录大小王的数量
        Arrays.sort(nums);
        for (int i = 0; i < 4; i++) {
            if (nums[i] == 0) {
                joker++;
            //如果有重复,就不能构成顺子
            } else if (nums[i] == nums[i + 1]) {
                return false;
            }
        }
        //如果最大最小值的差值小于5,就可以构成顺子
        return nums[4] - nums[joker] < 5;
    }
}

剑指 Offer 62. 圆圈中最后剩下的数字

题目描述

0,1,···,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字(删除后从下一个数字开始计数)。求出这个圆圈里剩下的最后一个数字。

例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。

 

示例 1:

输入: n = 5, m = 3
输出: 3
示例 2:

输入: n = 10, m = 17
输出: 2

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

约瑟夫环问题,之前学算法的时候看过,记得当时是环形链表处理的, 但是现在不想用链表,处理起来怎么感觉这么费劲,还是简单题。唉
做了半天,终于写了一个代码,但是超时了,想改进,改了半天还是错的

超时代码如下:

class Solution {
    public int lastRemaining(int n, int m) {
        //也是个经典的环问题
        //首先,m个循环删除
        if(n == 1)
            return 0;
        if(m == 1)
            return n - 1;
        int N = n;
        boolean[] nums = new boolean[n];
        Arrays.fill(nums, true);
        //如果删除了当前数字,相当于把当前数字变成了-1
        //如果最后只剩下一个数字,结束
        int index = 0;
        int count = 0;
        int pre = -1;
        //如果上一轮的下标和这一轮计算得到的下标是同一个,那么就说明只剩下一个了
        while(index != pre){
            //上一轮的下标
            pre = index;
            count++;
            if(count == m){
                nums[index] = false;
                count = 0;
            }
            index = (index + 1) % n;
            while(nums[index] == false)
                index = (index + 1) % n;
        }
        return index;
    }
}

看了答案,不管怎么说,还是学到了怎么处理这个问题吧
可以递归的想一想,首先,长度为n的序列肯定会删除第m % n个元素,然后剩下一个长度为 n - 1的序列,然后通过递归,求出n - 1序列最后剩下的第几个元素,假设是x。那么当前长度为n序列剩下的元素肯定在当前删除的元素后面x的位置(这里好好想一下),所以当前长度为n的序列留下的元素就是(x + m % n)% n,也就是(x + m) % n
递归的出口,只有1个数字时,留下的位置是0

class Solution {
	//递归
    public int lastRemaining(int n, int m) {
        if(n == 1)
            return 0;
        //n - 1个数留下的数
        int x = lastRemaining(n - 1, m);
        //当前留下的下标就是删除的元素m % n往前走x个
        return (x + m) % n;
    }
}
class Solution {
    public int lastRemaining(int n, int m) {
        //改成迭代
        //圈中只有一个数字,留下的位置是0
        int x = 0;
        //从圈中只有两个数字开始
        for(int i = 2; i <= n; i++){
            //当前留下的位置就是当前删除的位置向后移动x位,即(x + m % i) % i
            x = (x + m) % i;
        }
        return x;
    }
}

这个代码是我那种模拟思路的代码,我也想过用list集合,但是就是想用数组做出来,所以也没写…
这个我应该能写出来

class Solution {
    public int lastRemaining(int n, int m) {
        ArrayList<Integer> list = new ArrayList<>(n);
        for (int i = 0; i < n; i++) {
            list.add(i);
        }
        int idx = 0;
        while (n > 1) {
        	//每次删除的是集合中的第几个元素
            idx = (idx + m - 1) % n;
            list.remove(idx);
            //集合中元素减1
            n--;
        }
        return list.get(0);
    }
}

作者:sweetieeyi
链接:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/javajie-jue-yue-se-fu-huan-wen-ti-gao-su-ni-wei-sh/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

看了一圈高赞的答案,基本上都在讲这个从n - 1 个数到 n 个数,这个递推公式怎么得到的,我感觉也不用那么麻烦,其实官解那个想清楚以后也是很好理解的。而且看评论,感觉这个题真的不像个简单题,哈哈,又平衡了!!!
f(n,m)表示的是n个数最后剩余第几个数。所以知道f(n - 1, m)以后,因为当前删除的数是第m % n个,那么剩余的数f(n,m),就是当前删除的数后移f(n - 1, m)位

最后,再想一下这道题,我为什么没有直接加上m处理呢,是因为我觉得每次会删除一个数,而这个数的删除会导致这个数组发生改变,如果我每次加上m处理,不能判断是第几个数被删除,因而选择了一个个数的模拟这种方法。
其实,当前留下的数,可以通过之前留下的数推得,而且不用记录真实剩余的数字是几,只需要记录剩余的是第几个数就可以了!
回头再好好看一下!!!

以上是关于力扣461,剑指offer59-I,59-II,60,61的主要内容,如果未能解决你的问题,请参考以下文章

乱序版 ● 剑指offer每日算法题打卡题解——栈与队列(题号59)

力扣-剑指offer所有题

剑指 Offer 59II - 队列的最大值

剑指 Offer 59 - II. 队列的最大值

剑指offer(59)-II

剑指offer59 - II 至 剑指offer 64 题解