剑指offeⅤ(Java 持续更新...)
目录
DAY22
59-Ⅰ:滑动窗口的最大值
59-Ⅱ:队列的最大值
DAY23
37:序列化二叉树(太难了,第一轮刷放过了。下一轮再看)
38:字符串的排列(不行 太难了 回看)
DAY24
19:正则表达式匹配(回看)
49:丑数(多领悟)
60:n个骰子的点数
DAY25
17:打印从1到最大的n位数
51:
DAY26
14-Ⅱ:剪绳子
43:1~n整数中1出现的次数
44:数组序列中某一位的数字
46:把数字翻译成字符串
48:最长不含重复字符的子字符串
DAY22
59-Ⅰ:滑动窗口的最大值
思路:单调队列。窗口的边界为 i 和 j ,注意左边界的起始位置和结果数组的长度。双端队列实现高效操作头尾元素,算法保证队列中元素始终按照从大到小排列,所以头部元素就是最大值。
- 在第一个滑动窗口形成之前只考虑将大的元素放入队列,无需考虑删除队列中窗口丢掉的元素
- 从第一个窗口形成时开始记录每滑动一次,窗口中的最大值
- 滑动的时候,如果左边要丢掉的数据是滑动前窗口中最大值,则要同时删除队列中的头元素。右边新扩充进来的元素需要和队列里的元素比较,循环删除队列中比新元素小的元素,此时新元素便是队列中最小的元素,将其添加至队尾即可(若没有就不用删除,直接添加到队尾)
- 因为队列是从大到小的排列顺序,所以将队列头元素添加至结果数组就是当前窗口的最大值
class Solution {public int[] maxSlidingWindow(int[] nums, int k) {if(nums.length == 0 || k == 0) return new int[0];Deque<Integer> deque = new LinkedList<>(); //双端队列,里面的数字从大到小排列int[] res = new int[nums.length - k + 1];for(int j = 0, i = 1 - k; j < nums.length; i++, j++){//如果窗口滑动删除的元素是滑动前窗口里最大的元素,则要将队列里的一起删除if(i > 0 && deque.peekFirst() == nums[i - 1])deque.removeFirst();//删除队列中比新扩充进来的元素小的while(!deque.isEmpty() && deque.peekLast() < nums[j])deque.removeLast();deque.addLast(nums[j]);if(i >= 0)res[i] = deque.peekFirst();}return res;}
}
59-Ⅱ:队列的最大值
思路:辅助队列。队列queue负责存放数据,deque负责存放最大值
入队:
- 将元素直接加入queue中
- 当deque不为空时,将双端队列中比新元素小的都移除,然后再将其添加至队尾。(若没有直接添加至队尾)。以保证双端队列里的元素从小到大排列
最大值
- deque不为空的时候返回头元素即可
移除元素
- 需要判断移除的是不是当前最大值,若queue中移除的元素与deque的头元素相同,那么要同时移除deque中的元素
- 若不是当前最大值直接移除返回即可
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) {while(!deque.isEmpty() && deque.peekLast() < value)deque.pollLast(); //返回并移除deque.offerLast(value); //添加成功返回true,否则falsequeue.offer(value);}public int pop_front() {if(queue.isEmpty()) return -1;/* int rmv = queue.poll();if(rmv == deque.peekFirst()) deque.pollFirst();return rmv; 更快*/if(queue.peek().equals(deque.peekFirst())) deque.pollFirst();return queue.poll();}
}/*** 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();*/
DAY23
37:序列化二叉树(太难了,第一轮刷放过了。下一轮再看)
思路:BFS
/*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode(int x) { val = x; }* }*/
public class Codec {// Encodes a tree to a single string.public String serialize(TreeNode root) {if(root == null) return "[]";StringBuilder res = new StringBuilder("[");Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};while(!queue.isEmpty()){TreeNode node = queue.poll();if(node != null){res.append(node.val + ",");queue.add(node.left);queue.add(node.right);}else res.append("null,");}//System.out.println(res.toString());res.deleteCharAt(res.length() - 1);res.append("]");return res.toString();}// Decodes your encoded data to tree.public TreeNode deserialize(String data) {if(data.equals("[]")) return null;String[] vals = data.substring(1, data.length() - 1).split(",");TreeNode root = new TreeNode(Integer.parseInt(vals[0])); //字符串第一个是rootQueue<TreeNode> queue = new LinkedList<>(){{add(root);}};int i = 1;while(!queue.isEmpty()){TreeNode node = queue.poll();if(!vals[i].equals("null")){node.left = new TreeNode(Integer.parseInt(vals[i]));queue.add(node.left);}i++;if(!vals[i].equals("null")){node.right = new TreeNode(Integer.parseInt(vals[i]));queue.add(node.right);}i++;}return root;}
}// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));
38:字符串的排列(不行 太难了 回看)
思路:DFS。如下图,可看成一个二叉树。x为固定位。
- 将c[ i ]放入set中,遇到重复的跳过进行下一轮循环
- 交换c[ i ]和c[ x ],即固定c[ i ]为当前字符
- 递归,调用dfs(x + 1),即开始固定x +1 个字符(累加固定)
- 还原c[ i ] 和c [ x ]
class Solution {List<String> res = new LinkedList<>();char[] c;public String[] permutation(String s) {c = s.toCharArray();dfs(0);return res.toArray(new String[res.size()]);}void dfs(int x){if(x == c.length - 1){res.add(String.valueOf(c)); //将c转换成字符串添加到res里return;}HashSet<Character> set = new HashSet<>();for(int i = x; i < c.length; i++){if(set.contains(c[i])) continue; //已经存在的 剪枝set.add(c[i]);swap(i, x);dfs(x + 1);swap(i, x);}}void swap(int a, int b){char temp = c[a];c[a] = c[b];c[b] = temp;}
}
DAY24
19:正则表达式匹配(回看)
49:丑数(多领悟)
思路:动态规划。首先要知道丑数一定是通过前面的数乘以2、3或5得来的。但是如果给每个数都分别乘以2,3,5排下去就不一定是按顺序的了。
为什么乘以几就要让几对应的指针+1?因为p2表示:dp[p2]之前的数字都已经和2乘过了,下一个需要乘2的就是dp[p2](这个数只是还没和2相乘,有没有和3/5相乘不知道)。p3和p5同理表示。
下一个丑数一定是通过前面的丑数*2/3/5得来的,所以只需要挑出里面最小的作为下一个丑数即可。得到丑数后我们就需要判断这个丑数是dp[p几]*几。
当一个丑数可以通过多个dp[p]*p得到,那么可以得到该丑数的p应该同时+1.
class Solution {public int nthUglyNumber(int n) {int[] dp = new int[n + 1];dp[1] = 1; //dp[0] = 0int p2 = 1, p3 = 1, p5 = 1;for(int i = 2; i <= n; i++){int num2 = dp[p2] * 2, num3 = dp[p3] * 3, num5 = dp[p5] * 5;dp[i] = Math.min(Math.min(num2, num3), num5);if(dp[i] == num2){p2++;}if(dp[i] == num3){p3++;}if(dp[i] == num5){p5++;}}return dp[n];}
}
60:n个骰子的点数
思路:动态规划。
只有1个骰子时,dp[1]是代表当骰子点数之和为2时的概率,它会对当有2个骰子时的点数之和为3、4、5、6、7、8产生影响,因为当有一个骰子的值为2时,另一个骰子的值可以为1~6,产生的点数之和相应的就是3~8;比如dp[2]代表点数之和为3,它会对有2个骰子时的点数之和为4、5、6、7、8、9产生影响;所以k在这里就是对应着第i个骰子出现时可能出现六种情况。
class Solution {public double[] dicesProbability(int n) {double[] dp = new double[6];Arrays.fill(dp, 1.0 / 6.0); //当只有一个骰子时1~6概率都为1/6for(int i = 2; i <= n; i++){//每次的点数之和范围会有点变化,点数之和的值最大是i*6,最小是i*1,i之前的结果值是不会出现的;//比如i=3个骰子时,最小就是3了,不可能是2和1,所以点数之和的值的个数是6*i-(i-1),化简:5*i+1double[] sum = new double[5*i + 1]; for(int j = 0; j < dp.length; j++){for(int k = 0; k <6; k++){sum[j + k] += dp[j] / 6.0;}} dp = sum;}return dp;}
}
DAY25
17:打印从1到最大的n位数
打印数的公式是
思路一:普通解法
思路二:全排列(不如思路一快)
- 数组num存放的就是每个数字,所以num[0]的取值范围是1~9,首位不能为0
- dfs中的for循环就是给num中每一位一次添加数字,当遇到终止条件时就将此时num的字符串转化为成数字返回。
思路一:
class Solution {public int[] printNumbers(int n) {int[] res = new int[(int)Math.pow(10, n) - 1];for(int i = 0; i < res.length; i++){res[i] = i + 1;}return res;}
}
思路二
class Solution {int[] res;int count = 0;public int[] printNumbers(int n) {res = new int[(int)Math.pow(10, n) - 1];for(int i = 1; i <= n; i++){ //i是位数for(char j = '1'; j <= '9'; j++){ //j是首位,首位不能为0char[] num = new char[i];num[0] = j;dfs(1, num, i);}}return res;}void dfs(int index, char[] num, int digit){if(index == digit){ //当遍历到最后一位后,将数组中的字符转化为整数类型输出res[count++] = Integer.parseInt(String.valueOf(num));return;}for(char i = '0'; i <= '9'; i++){ //除了首位意外取值范围都是0~9num[index] = i;dfs(index + 1, num, digit);}}
}
51:
思路:分支思想。归并排序。分割到每组只剩下两个,然后小的在前大的在后,若交换了位置说明原本是逆序的,计数器+1。依次合并,排序换位置的次数为原本逆序的对数,计数。
递归合并算法:指针 i 在左部分遍历,j 在右部分遍历,每部分都是从小到大排列的。
- 当左部分的第 i 个数大于右部分第 j 个数,那么 左部分i 后面的数全部都大于第 j 个数,计数,并移动指针 i,将第 j 个数放入最终有序的大数组中 。
- 当左部分的第 i 个数小于右部分第 j 个数,则移动指针 i ,计数,并将第 i 个数放入最终有序大数组中,继续判断。
nums是原始数组,最后便利结束后变成了从小到大排列的顺序数组;temp是中间需要分割比较的数组。(暂存数组 nums闭区间 [i, r] 内的元素至辅助数组temp)
class Solution {int[] nums, temp;public int reversePairs(int[] nums) {this.nums = nums;temp = new int[nums.length];return mergeSort(0, nums.length - 1);}int mergeSort(int left, int right){if(left >= right) return 0; //终止条件int m = (left + right) / 2; //从中间分割int res = mergeSort(left, m) + mergeSort(m + 1, right); //递归划分//合并int i = left, j = m + 1;for(int k = left; k <= right; k++)temp[k] = nums[k];for(int k = left; k <= right; k++){if(i == m + 1) //左子数组已合并完,因此添加右子数组当前元素 tmp[j],并执行j=j+1 nums[k] = temp[j++];else if(j == right + 1 || temp[i] <= temp[j])//右子数组已合并完,因此添加左子数组当前元素 tmp[i],并执行i=i+1//temp[i] <= temp[j]的操作也是i+=1,所以合并成一个ifnums[k] = temp[i++];else{nums[k] = temp[j++];res += m - i + 1; //从i到m的数都比第j个数大}}return res;}
}
DAY26
14-Ⅱ:剪绳子
思路:与Ⅰ的区别就是绳长的范围变大了,所以需要有大数取余的过程。
由算术几何均值不等式得:当且仅当时候等号成立。
推论1:将绳子以相等的长度等分为多段 ,得到的乘积最大。
推导过程:https://leetcode.cn/problems/jian-sheng-zi-ii-lcof/solution/mian-shi-ti-14-ii-jian-sheng-zi-iitan-xin-er-fen-f/
推论2:尽可能将绳子以长度3等分为多段时,乘积最大。
所以可得切分规则:
- 把绳子尽可能切为多个长度为3的片段,留下的最后一段绳子长度可能是0,1,2
- 若最后一段为2,直接乘即可,相当于和上一段凑成了3*2,比将它拆开1*1大
- 若最后一段为1,与上一段合并为4,拆分成2*2比3*1要大
大数越界:当a增大时,最后返回的3^a大小以指数级别增长,可能超出 int32
甚至 int64
的取值范围,导致返回值错误。
求余问题:在仅使用 int32
类型存储的前提下,正确计算 x^a 对p求余的值。(越界后除以p取余数继续验算,对最后的结果无影响!!)
class Solution {public int cuttingRope(int n) {if( n <= 3) return n - 1; //n从2开始int b = n % 3, p = 1000000007; //将绳子切成每段长度为3的,最后余下的那一段的长度b可能是0/1/2long res = 1;int a = n / 3; //一共切成了a段for(int i = 1; i < a; i++)res = (res * 3) % p; //从第一段开始验算3^a是否越界,一共验算a-1次if(b == 0) return (int)(res * 3 % p);if(b == 1) return (int)(res * 4 % p); //余1的时候可以将多余的和前一个3合并为4,4拆成2*2更大return (int)(res * 6 % p); //余2的时候和上一段合并为5 拆成2*3}
}// 求 (x^a) % p —— 循环求余法。固定搭配建议背诵public long remainder(int x,int a,int p){ //x为底数,a为幂,p为要取的模long rem = 1 ;for (int i = 0; i < a; i++) {rem = (rem * x) % p ; }return rem;}
43:1~n整数中1出现的次数
思路:将1~n的个位、十位、百位...的1的次数相加即可。
- 当前值为0时,此位 1 的出现次数只由高位 high 决定,计算公式为:high×digit
- 当前值为1时,计算公式为:high×digit+low+1
- 当前值>1时,计算公式为(high+1)*digit
用23041举例,我的理解是:
当前cur在百位为0,现在要求百位1的个数,那么取值范围应该是00100~22199。也就是说高位部分有00~22共23种可取值,而地位部分从00到99都可以取不影响,共100种,所以低位部分可取值范围就是digit,根据排列组合可得共有23*100种,也就是high*digit
用23141举例:
当前cur在百位为1,那么取值范围应该是00100~23141。高位部分可能取值是00~23,这里要注意的是当高位部分等于23的时候,低位部分的可取值范围就不是00~99了,而是00~41,所以才和低位是有关系的。这样就可以得出高位是00~22的时候低位是00~99,共high*digit中,再加上高位是23时,低位是00~41共42种可能,也就是low + 1。所以计算公式为high*digit+low+1
用23441举例
当前cur在百位为4,取值范围应该是00100~23199。高位部分可取范围是00~23,这个与上一个不同的是当高位部分取23的时候,低位部分可取值范围是00~99,所以与低位无关了。那么计算公式就是(high+1)*digit。
class Solution {public int countDigitOne(int n) {int res = 0, digit = 1; // 表示位,从个位开始int high = n / 10, cur = n % 10, low = 0; //高位从十位开始,cur从个位开始while(high != 0 || cur != 0){if(cur == 0) res += high *digit;else if(cur == 1) res += high * digit + low + 1;else res += (high + 1) * digit;low += cur * digit; //low是cur后面数字组成的数cur = high % 10;high /= 10; //high是cur前面的数字组成的数,不用乘位数digit *= 10;}return res;}
}
44:数组序列中某一位的数字
数位数量就例如是2位数90个共占用180个数位
- 先计算出第n为数字是属于几位数部分的
- 然后再计算出这位是属于那个数字
- 最后计算这位是属于数字里的第几位
看着蛮简单的,但是中间计算过程好费脑。首先判断n是几位数的时候用它循环减去一位两位三位数...所占的位数,当n递减的值不再大于某位数占用的位数时候则说明它属于某位数。
注意数字和n都是从0开始的。所以后面是n-1
比如n=13时候,先减去一位数占的位数9之后剩下4,二位数占180个位,所以第十三位是属于两位数的。
而现在n的值4就可以计算出它所在的数字是多少。每位数的起始位是0/10/100...,所以用刚才循环里记录的start,加上(n-1)除以位数(2),也就是每两位组成一个数字,便可得到它所属的数字是多少。
最后要判断它是这个数字里的第几位(从0开始),(n-1)除以位数的余数就是它所在数字中的位数,将这个作为索引输出转化为字符串的num对应的位就是结果。
class Solution {public int findNthDigit(int n) {int digit = 1;long start = 1;long count = 9;while(n > count){n -= count; //减去个位数占得位数,百位数占的位数...digit += 1; //位数每次+1,即一位数到两位数到三位数到...start *= 10; //n位数的起始就是1/10/100/1000 start*10即可count = digit * start * 9; //数位数量}long num = start + (n - 1) / digit; //得到第n位所在的数字是多少//将数字转化为字符,输出它的第0/1/2/3..位return Long.toString(num).charAt((n - 1) % digit) - '0';}
}
46:把数字翻译成字符串
思路:动态规划。对于num里的一个数有两种情况:
- 只翻译自己
- 和前一个数字组成10~25之间的两位数翻译
类同青蛙跳台阶,只不过这个一次跳两阶有条件限制
- 当大于10小于25,dp[ i ] = dp[ i - 1 ] + dp[ i - 2 ]
- 不在范围内则是dp[ i ] = dp[ i - 1 ]
class Solution {public int translateNum(int num) {String s = String.valueOf(num); //转换成字符串int a = 1, b = 1;for(int i = 2; i <= s.length(); i++){String temp = s.substring(i - 2, i);//当两位数大于10小于25的时候也可以翻译,否则就只能单独翻译int c = temp.compareTo("10") >= 0 && temp.compareTo("25") <= 0 ? a + b : a;/*int value=str1.compareTo(str2);当str1小于str2时,返回小于0的值,当str1与str2相同时,返回0,当str1大于str2时,返回大于0的值。*/b = a;a = c;}return a;}
}
48:最长不含重复字符的子字符串
思路:动态规划。
- 字符不重复:长度+1,此时最大子串长度 = 上一位的最大子串长度 + 1
- 字符重复:不考虑中间存在其他重复的前提下,最大子串长度 = 现在的位置 - 相同字符上次出现的位置
/*
字符 a b c c c b a d
长度 1 2 3 1 1 2 3 4
*/
class Solution {public int lengthOfLongestSubstring(String s) {int ans = 0, n = s.length(), last = 0;HashMap<Character, Integer> map = new HashMap<>(n);for(int i = 0; i < n; i++){Character c = s.charAt(i);last = Math.min(i - map.getOrDefault(c, -1), last + 1);ans = Math.max(ans, last);map.put(c, i);}return ans;}
}
剑指offeⅤ(Java 持续更新...)相关推荐
- 剑指OFFER(持续更新中...)
基本用python,涉及到栈,链表,二叉树,数组,字符串 python中的类中属性元素加self.和不加self.的区别: 如果不加self,表示是类的一个属性(可以通过"类名.变量名&qu ...
- 数组中重复的元素(剑指Offe.03)
数组中重复的元素(剑指Offe.03) 题目描述: 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了 ...
- leetcode剑指offe刷题-第一题-用两个栈实现队列
leetcode剑指offe刷题-第一题 文章目录 leetcode剑指offe刷题-第一题 前言 一.用两个栈实现队列 1.思路 2.代码如下 总结 前言 记录一下自己刷算法的路程. leetcod ...
- 剑指offer java版 test3—从尾到头打印链表
标题:剑指offer java版 test3-从尾到头打印链表 题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 解答:知识不够全面,用ArrayList做的 但是看到大佬们还可以 ...
- 剑指 Offer(专项突击版)Java 持续更新....
剑指 Offer(专项突击版) 刷题链接: https://leetcode-cn.com/problem-list/e8X3pBZi/?page=1 No.001 题目: 整数除法 1. 刷题链接: ...
- 剑指offer最新版_剑指Offer——Java版本(持续更新)
0 前言 邻近校招,算法要命!!! 本文为研究剑指Offer过程中的笔记,整理出主要思路以及Java版本题解,以便记忆和复习. 参考整理来自<剑指Offer 第二版>. 特别注意,对每道题 ...
- 【剑指 Offe】剑指 Offer 18. 删除链表的节点
目录标题 算法汇总 题目 关键点 代码 1.解体方法 思路 代码 时间和空间复杂度 2.解题方法,如暴力法 思路 代码 时间和空间复杂度 算法汇总 以下是所有算法汇总,包括GitHub源码地址链接:力 ...
- 【剑指 Offe】剑指 Offer 11. 旋转数组的最小数字
目录标题 算法汇总 题目 关键点 代码 1.解体方法 - 二分法 思路 代码 时间和空间复杂度 2.解题方法,如暴力法 思路 代码 时间和空间复杂度 算法汇总 以下是所有算法汇总,包括GitHub源码 ...
- 【剑指 Offe】剑指 Offer 17. 打印从1到最大的n位数
目录标题 算法汇总 题目 关键点 代码 1.解体方法 思路 代码 时间和空间复杂度 2.解题方法,如暴力法 思路 代码 时间和空间复杂度 算法汇总 以下是所有算法汇总,包括GitHub源码地址链接:力 ...
- 牛客网剑指offer——Java题解
剑指offer JZ1 二维数组中的查找 题目描述 在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这 ...
最新文章
- c语言统计26个英文字母各出现个数,网上答案汇总与分析——【输入一串英文,统计各单词出现的个数】...
- vb Select Case的使用 字符串整形的转换 输入错误str的直接输出
- (文档挂起)打印机为什么打印失败?
- python深度学习进阶之行为检测详细学习路线(主要实现人员的行为类别、空间定位、时间定位)
- 好多游戏,大部分都有修改器,大家赶紧下!
- React Native--移动端开发的救星
- 每个知识库管理系统必备的七大关键功能
- PHP 中的 use function是什么意思
- 国内大厂首次推出Android统一标准--安卓绿色联盟
- 在Groovy中使用字符串 - 51CTO.COM
- 基于python的毕业论文邮箱收发系统_基于python语言的自动化邮件发送总结
- kali Linux的 安装详细步骤
- 【玩转Jetson TX2 NX】(七)TX2 NX YoLoV4环境搭建+板载摄像头实时目标检测(详细教程+错误解决)
- 蓝桥杯 算法训练 Cowboys 递推 动态规划
- 字节三面:对于 Spring 你了解多少?如果你会了这 150 道题 ,吊打面试官岂不是洒洒水
- Coding and Paper Letter(二十八)
- CPU外频、FSB前端总线和内存频率的关系
- R语言基础 chapter2
- c语言实训指导书答案,《C语言程序设计》实验指导书答案
- c# windows form 音频切割解决方案
热门文章
- 聊聊 iframe 的优缺点以及使用场景
- Cutting Bamboos
- js判断当前浏览器的环境是微信、pc、还是手机端非微信环境
- MacOS 校验iso sha256值、md5值,linux
- 字符转ASII码以及大小写之间的转换
- 实时训练Real-Time Training 教程
- UVA12096 - The SetStack Computer(set + map映射)
- 没有学历,别担心,请看高中或中专毕业的程序员应该如何生存!
- c语言编译kbhit出现问题,kbhit用C语言
- Refseq : accession id to taxonomy lineage.