目录

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 持续更新...)相关推荐

  1. 剑指OFFER(持续更新中...)

    基本用python,涉及到栈,链表,二叉树,数组,字符串 python中的类中属性元素加self.和不加self.的区别: 如果不加self,表示是类的一个属性(可以通过"类名.变量名&qu ...

  2. 数组中重复的元素(剑指Offe.03)

    数组中重复的元素(剑指Offe.03) 题目描述: 在一个长度为 n 的数组 nums 里的所有数字都在 0-n-1 的范围内.数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了 ...

  3. leetcode剑指offe刷题-第一题-用两个栈实现队列

    leetcode剑指offe刷题-第一题 文章目录 leetcode剑指offe刷题-第一题 前言 一.用两个栈实现队列 1.思路 2.代码如下 总结 前言 记录一下自己刷算法的路程. leetcod ...

  4. 剑指offer java版 test3—从尾到头打印链表

    标题:剑指offer java版 test3-从尾到头打印链表 题目:输入一个链表,按链表从尾到头的顺序返回一个ArrayList. 解答:知识不够全面,用ArrayList做的 但是看到大佬们还可以 ...

  5. 剑指 Offer(专项突击版)Java 持续更新....

    剑指 Offer(专项突击版) 刷题链接: https://leetcode-cn.com/problem-list/e8X3pBZi/?page=1 No.001 题目: 整数除法 1. 刷题链接: ...

  6. 剑指offer最新版_剑指Offer——Java版本(持续更新)

    0 前言 邻近校招,算法要命!!! 本文为研究剑指Offer过程中的笔记,整理出主要思路以及Java版本题解,以便记忆和复习. 参考整理来自<剑指Offer 第二版>. 特别注意,对每道题 ...

  7. 【剑指 Offe】剑指 Offer 18. 删除链表的节点

    目录标题 算法汇总 题目 关键点 代码 1.解体方法 思路 代码 时间和空间复杂度 2.解题方法,如暴力法 思路 代码 时间和空间复杂度 算法汇总 以下是所有算法汇总,包括GitHub源码地址链接:力 ...

  8. 【剑指 Offe】剑指 Offer 11. 旋转数组的最小数字

    目录标题 算法汇总 题目 关键点 代码 1.解体方法 - 二分法 思路 代码 时间和空间复杂度 2.解题方法,如暴力法 思路 代码 时间和空间复杂度 算法汇总 以下是所有算法汇总,包括GitHub源码 ...

  9. 【剑指 Offe】剑指 Offer 17. 打印从1到最大的n位数

    目录标题 算法汇总 题目 关键点 代码 1.解体方法 思路 代码 时间和空间复杂度 2.解题方法,如暴力法 思路 代码 时间和空间复杂度 算法汇总 以下是所有算法汇总,包括GitHub源码地址链接:力 ...

  10. 牛客网剑指offer——Java题解

    剑指offer JZ1 二维数组中的查找 题目描述 在一个二维数组array中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这 ...

最新文章

  1. c语言统计26个英文字母各出现个数,网上答案汇总与分析——【输入一串英文,统计各单词出现的个数】...
  2. vb Select Case的使用 字符串整形的转换 输入错误str的直接输出
  3. (文档挂起)打印机为什么打印失败?
  4. python深度学习进阶之行为检测详细学习路线(主要实现人员的行为类别、空间定位、时间定位)
  5. 好多游戏,大部分都有修改器,大家赶紧下!
  6. React Native--移动端开发的救星
  7. 每个知识库管理系统必备的七大关键功能
  8. PHP 中的 use function是什么意思
  9. 国内大厂首次推出Android统一标准--安卓绿色联盟
  10. 在Groovy中使用字符串 - 51CTO.COM
  11. 基于python的毕业论文邮箱收发系统_基于python语言的自动化邮件发送总结
  12. kali Linux的 安装详细步骤
  13. 【玩转Jetson TX2 NX】(七)TX2 NX YoLoV4环境搭建+板载摄像头实时目标检测(详细教程+错误解决)
  14. 蓝桥杯 算法训练 Cowboys 递推 动态规划
  15. 字节三面:对于 Spring 你了解多少?如果你会了这 150 道题 ,吊打面试官岂不是洒洒水
  16. Coding and Paper Letter(二十八)
  17. CPU外频、FSB前端总线和内存频率的关系
  18. R语言基础 chapter2
  19. c语言实训指导书答案,《C语言程序设计》实验指导书答案
  20. c# windows form 音频切割解决方案

热门文章

  1. 聊聊 iframe 的优缺点以及使用场景
  2. Cutting Bamboos
  3. js判断当前浏览器的环境是微信、pc、还是手机端非微信环境
  4. MacOS 校验iso sha256值、md5值,linux
  5. 字符转ASII码以及大小写之间的转换
  6. 实时训练Real-Time Training 教程
  7. UVA12096 - The SetStack Computer(set + map映射)
  8. 没有学历,别担心,请看高中或中专毕业的程序员应该如何生存!
  9. c语言编译kbhit出现问题,kbhit用C语言
  10. Refseq : accession id to taxonomy lineage.