文章目录
前言
需要开通vip的题目暂时跳过
笔记导航
点击链接可跳转到所有刷题笔记的导航链接
481. 神奇字符串
神奇的字符串 S 只包含 ‘1’ 和 ‘2’,并遵守以下规则:
字符串 S 是神奇的,因为串联字符 ‘1’ 和 ‘2’ 的连续出现次数会生成字符串 S 本身。
字符串 S 的前几个元素如下:S = “1221121221221121122 …”
如果我们将 S 中连续的 1 和 2 进行分组,它将变成:
1 22 11 2 1 22 1 22 11 2 11 22 …
并且每个组中 ‘1’ 或 ‘2’ 的出现次数分别是:
1 2 2 1 1 2 1 2 2 1 2 2 …
你可以看到上面的出现次数就是 S 本身。
给定一个整数 N 作为输入,返回神奇字符串 S 中前 N 个数字中的 ‘1’ 的数目。
注意:N 不会超过 100,000。
-
解答
public int magicalString(int n) { StringBuilder sb = new StringBuilder("122"); for (int i = 2, k = 1; i < n; i++, k = 3 - k){ for (int j = sb.charAt(i) - '0'; j> 0; j--){ sb.append(k + ""); } } int res = 0; for (int i = 0; i < n; i++){ if (sb.charAt(i) == '1'){ res++; } } return res; }
-
分析
- 根据规则构建神奇字符串。
- 初始化字符串“122”,从索引2开始遍历,插入字符“1”。根据当前遍历的数字,选择插入的字符的个数。1插入完之后 换成插入2,2插入完之后换成插入1.
- 直到遍历到n
- 然后统计1出现的次数。
-
提交结果
482. 密钥格式化
有一个密钥字符串 S ,只包含字母,数字以及 ‘-’(破折号)。其中, N 个 ‘-’ 将字符串分成了 N+1 组。
给你一个数字 K,请你重新格式化字符串,使每个分组恰好包含 K 个字符。特别地,第一个分组包含的字符个数必须小于等于 K,但至少要包含 1 个字符。两个分组之间需要用 ‘-’(破折号)隔开,并且将所有的小写字母转换为大写字母。
给定非空字符串 S 和数字 K,按照上面描述的规则进行格式化。
-
解答
public String licenseKeyFormatting(String S, int K) { StringBuilder sb = new StringBuilder(); S = S.toUpperCase(); int number = 0; for (int i = 0; i < S.length(); i++) { if (S.charAt(i) != '-') number++; } int firstNumber = number % K; int i = 0; while (firstNumber > 0) { char cur = S.charAt(i); if (cur != '-') { sb.append(cur); firstNumber--; } i++; } if (sb.length() > 0) sb.append('-'); int temp = 0; for (int j = i; j < S.length(); j++) { char cur = S.charAt(j); if (cur != '-') { sb.append(cur); temp++; if (temp == K) { sb.append('-'); temp = 0; } } } if(sb.length() == 0)return ""; if (sb.charAt(sb.length() - 1) == '-') sb.deleteCharAt(sb.length() - 1); return sb.toString(); }
-
分析
- 首先计算出除了"-"以外字符的个数
- 根据K的取值计算出第一组字符分配的数量,除了第一组字符之外,其余每组字符的数量必须等于K
- 有了第一组字符的数量之后
- 遍历字符串S
- 构建第一组字符,若第一组字符的个数不为0,则在后面加一个"-"分组符号
- 继续遍历剩余的字符,将出了"-"以外的字符按照每组K个划分,添加到StringBuilder中
- 最后若答案字符串最后一位是"-",则删除它
-
提交结果
485. 最大连续1的个数
-
解答
public int findMaxConsecutiveOnes(int[] nums) { int res = 0; int temp = 0; for(int i = 0;i < nums.length;i++){ if(nums[i] == 1)temp++; else{ res = Math.max(res,temp); temp = 0; } } return Math.max(res,temp); }
-
分析
- 太简单了,略
-
提交结果
486. 预测赢家
给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。
给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。
-
解答
public boolean PredictTheWinner(int[] nums) { return total(nums, 0, nums.length - 1) >= 0; } public int total(int[] nums, int start, int end) { if (start == end) { return nums[start]; } int getStart = nums[start] - total(nums,start + 1,end); int getEnd = nums[end] - total(nums,start,end - 1); return Math.max(getStart,getEnd); } public boolean PredictTheWinner(int[] nums) { int length = nums.length; int[][] dp = new int[length][length]; for (int i = 0; i < length; i++) { dp[i][i] = nums[i]; } for (int i = length - 2; i >= 0; i--) { for (int j = i + 1; j < length; j++) { dp[i][j] = Math.max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]); } } return dp[0][length - 1] >= 0; }
-
分析
-
递归,每次选择拿取开头的数字 或者结束的数字,赢得相应的分数
-
拿完之后是下一个人拿,也就是输掉相应的分数。
-
即赢的分数 - 前输的分数,等于当前选择结果的得分。
-
比较拿取开头和结束的两种结果的得分,返回较大者。
-
若最后的结果是大于等于0,说明一号玩家胜利。
-
将上述递归的过程改为动态规划dp[i] [j] 表示在i-j的范围内一号玩家取得的最优比赛结果
-
初始化dp[i] [i] = nums[i]
-
状态转移方程
dp[i] [j] = Math.max(nums[i] - dp[i+1] [j],nums[j] - dp[i] [j-1] );
-
最后返回dp[0] [j-1]是否大于等于0即可
-
-
提交结果
方法1
方法2
488. 祖玛游戏
回忆一下祖玛游戏。现在桌上有一串球,颜色有红色®,黄色(Y),蓝色(B),绿色(G),还有白色(W)。 现在你手里也有几个球。
每一次,你可以从手里的球选一个,然后把这个球插入到一串球中的某个位置上(包括最左端,最右端)。接着,如果有出现三个或者三个以上颜色相同的球相连的话,就把它们移除掉。重复这一步骤直到桌上所有的球都被移除。
找到插入并可以移除掉桌上所有球所需的最少的球数。如果不能移除桌上所有的球,输出 -1 。
-
解答
private int result = Integer.MAX_VALUE; private int[] map = new int[26]; private char[] colors = {'R', 'Y', 'B', 'G', 'W'}; public int findMinStep(String board, String hand) { for (int i = 0; i < hand.length(); i++) { map[hand.charAt(i) - 'A']++; } dfs(new StringBuilder(board), 0); return result == Integer.MAX_VALUE ? -1 : result; } private void dfs(StringBuilder board, int step) { if (step >= result) { return; } if (board.length() == 0) { result = Math.min(step, result); return; } for (int i = 0; i < board.length(); i++) { char c = board.charAt(i); int j = i; while (j + 1 < board.length() && board.charAt(j + 1) == c) { j++; } if (j == i && map[c - 'A'] >= 2) { //只有单个球 StringBuilder tmp = new StringBuilder(board); tmp.insert(i, c + "" + c); map[c - 'A'] -= 2; dfs(eliminate(tmp), step + 2); map[c - 'A'] += 2; } else if (j == i + 1) { //存在两个颜色相同且相邻的球 if (map[c - 'A'] >= 1) { StringBuilder tmp = new StringBuilder(board); tmp.insert(i, c); map[c - 'A']--; dfs(eliminate(tmp), step + 1); map[c - 'A']++; } for (char color : colors) { if (color == c) { continue; } if (map[color - 'A'] >= 1) { StringBuilder tmp = new StringBuilder(board); tmp.insert(i + 1, color); //尝试往这两个颜色相同且相邻的球中间插入一个颜色不同的球 map[color - 'A']--; dfs(eliminate(tmp), step + 1); map[color - 'A']++; } } } } } private StringBuilder eliminate(StringBuilder sb) { boolean flag = true; while (flag) { flag = false; for (int i = 0; i < sb.length(); i++) { int j = i + 1; while (j < sb.length() && sb.charAt(j) == sb.charAt(i)) { j++; } if (j - i >= 3) { sb.delete(i, j); flag = true; } } } return sb; }
-
分析
-
回溯法
-
递归退出条件
当前步数大于已得的最小结果,停止递归
字符串全部消除,即长度为0,更新结果,停止递归
-
遍历字符串
-
以一个球为基准,判断是否有相邻相同的球
-
若只有单个球,则需要使用2个球来消除这一个球
-
若存在2个颜色相同的球,则可以使用1个相同颜色的球消除
-
或在两个颜色相同的球之间放入一颗颜色不同的球
-
处理完球的插入,调用消除函数eliminate,while循环去掉连续的3个相同颜色的球。然后递归,步数 + 1
-
-
提交结果
491. 递增子序列
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
-
解答
List<List<Integer>> res = new ArrayList<>(); public List<List<Integer>> findSubsequences(int[] nums) { dfs(nums, -1, new ArrayList<>()); return res; } private void dfs(int[] nums, int idx, List<Integer> curList) { if (curList.size() > 1) { res.add(new ArrayList<>(curList)); } Set<Integer> set = new HashSet<>(); for (int i = idx + 1; i < nums.length; i++) { // 如果 set 中已经有与 nums[i] 相同的值了,说明加上 nums[i] 后的所有可能的递增序列之前已经被搜过一遍了,因此停止继续搜索。 if (set.contains(nums[i])) { continue; } set.add(nums[i]); if (idx == -1 || nums[i] >= nums[idx]) { curList.add(nums[i]); dfs(nums, i, curList); curList.remove(curList.size() - 1); } } }
-
分析
-
回溯法
-
用set集合,表示这一层递归不会选择到相同的数字
-
递归的两种情况
第一种是curList为空,加入元素
第二种是当前元素大雨curList中的最后一个元素值。
-
-
提交结果
492. 构造矩形
作为一位web开发者, 懂得怎样去规划一个页面的尺寸是很重要的。 现给定一个具体的矩形页面面积,你的任务是设计一个长度为 L 和宽度为 W 且满足以下要求的矩形的页面。要求:
-
解答
public int[] constructRectangle(int area) { int L = (int)Math.sqrt(area); int W = L; while(L * W != area){ if(L * W < area){ L++; }else if (L * W > area){ W--; } } return new int[]{L,W}; }
-
分析
-
先将area开方取整。
-
初始化L和W
-
while循环
若L * W小于 area 则L+1
若L * W大于area 则W-1;
-
找到等于area的L和W返回
-
-
提交结果
493. 翻转对
给定一个数组 nums ,如果 i < j 且 nums[i] > 2*nums[j] 我们就将 (i, j) 称作一个重要翻转对。
你需要返回给定数组中的重要翻转对的数量。
-
解答
int N; long[] tr; public int reversePairs(int[] nums) { List<Long> ys = new ArrayList(); for(int i: nums) {//离散化 ys.add((long)i); ys.add((long)i * 2); } Collections.sort(ys);//排序 ys = unique(ys);//去重 N = ys.size(); tr = new long[N + 1];//树状数组 int ans = 0; for(int i = 0; i < nums.length; i++){//从左到右遍历数组 long target = (long)nums[i] * 2;//目标值 int left = binaryFind(ys, target) + 1;//寻找target的位置 int right = N; ans += query(right) - query(left);//树状数组中 大于 target部分的个数 add(binaryFind(ys, nums[i]) + 1, 1);//更新树状数组的结点和 } return ans; } //更新 public void add(int x, int c){ for(int i = x; i <= N; i += lowBit(i)) tr[i] += c; } //查询 public int query(int x){ int res = 0; for(int i = x; i > 0; i -= lowBit(i)) res += tr[i]; return res; } //计算x最右侧的1 public int lowBit(int x){return x & -x;} //去重 public List<Long> unique(List<Long> list){ List<Long> res = new ArrayList(list.size()); for(int i = 0; i < list.size(); i++){ if(res.isEmpty() || res.get(res.size() - 1) - list.get(i) != 0){ res.add(list.get(i)); } } return res; } //二分查找 public int binaryFind(List<Long> list, long target){ int l = 0, r = list.size() - 1; while(l < r){ int mid = l + r >> 1; if(list.get(mid) >= target) r = mid; else l = mid + 1; } return l; }
-
分析
- 统计数组中的值以及每个值✖️2的结果,排序后去重。
- 根据去重后的数量,构建树状数组。
- 树状数组中保存的是已经遍历过的数字及其的子结点的个数。
- 从左往右遍历原始数组。
- 当前遍历的数字✖️2得到树状数组中查询的目标值。
- 根据这个目标值,在树状数组中找比他大的值的和,加入到答案中。
- 更新树状数组,将遍历的数字加入
- 下图是树状数组的更新和查询的过程
-
提交结果
494. 目标和
给定一个非负整数数组,a1, a2, …, an, 和一个目标数,S。现在你有两个符号 + 和 -。对于数组中的任意一个整数,你都可以从 + 或 -中选择一个符号添加在前面。
返回可以使最终数组和为目标数 S 的所有添加符号的方法数。
-
解答
public int findTargetSumWays(int[] nums, int S) { int len = nums.length; int[] preSum = new int[len + 1]; for(int i = 0;i < len;i++){ preSum[i + 1] = preSum[i] + nums[i]; } if(preSum[len] < S)return 0; dfs(preSum,nums,0,0,S,len); return res; } int res = 0; public void dfs(int[] preSum,int[] nums,int temp,int position,int S,int len){ if(position == len && temp == S){ res++; return; } if(position == len)return; if(temp + preSum[len] - preSum[position] < S)return; dfs(preSum,nums,temp - nums[position],position + 1,S,len); dfs(preSum,nums,temp + nums[position],position + 1,S,len); }
-
分析
- 先计算前缀和
- 若nums的总和小于S,返回0
- 回溯法
- 剪枝条件:当前得到的数组和 加上后续数组中所有数字的和小于目标的话,剪枝
- 若已遍历完所有的数组且得到的和等于目标,则答案加1
- 递归,两种情况,添加负号或者加号
-
提交结果
495. 提莫攻击
在《英雄联盟》的世界中,有一个叫 “提莫” 的英雄,他的攻击可以让敌方英雄艾希(编者注:寒冰射手)进入中毒状态。现在,给出提莫对艾希的攻击时间序列和提莫攻击的中毒持续时间,你需要输出艾希的中毒状态总时长。
你可以认为提莫在给定的时间点进行攻击,并立即使艾希处于中毒状态。
-
解答
public int findPoisonedDuration(int[] timeSeries, int duration) { if(timeSeries.length == 0)return 0; int res = 0; int cur = timeSeries[0]; for(int i = 1;i < timeSeries.length;i++){ if(timeSeries[i] - cur > duration){ res += duration; }else{ res += timeSeries[i] - cur; } cur = timeSeries[i]; } return res + duration; }
-
分析
- 判断两次攻击之前的间隔是否大于duration 若大于 则中毒时间 + duration
- 若小于 则中毒时间 + 两次攻击的间隔时间
- 最后再加上最后一次攻击后中毒的时间 也就是duration
-
提交结果
496. 下一个更大元素 I
给定两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。找到 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1 。
-
解答
public int[] nextGreaterElement(int[] nums1, int[] nums2) { int len = nums1.length; int[] res = new int[len]; Map<Integer,Integer> map = new HashMap<>(); for(int i = 0;i < nums2.length;i++){ map.put(nums2[i],i); } for(int i = 0;i < len;i++){ int cur = nums1[i]; boolean flag = false; for(int j = map.get(cur);j < nums2.length;j++){ if(nums2[j] > cur){ res[i] = nums2[j]; flag = true; break; } } if(!flag)res[i] = -1; } return res; }
-
分析
- 用map记录下数组2中 元素对应的索引位置
- 遍历数组1,当前遍历的数字记为cur
- 从map中找到数组2中对应cur的索引位置开始遍历数组2,找到第一个比当cur大的数字,记录在res当中。若没有找到则记为-1
-
提交结果
497. 非重叠矩形中的随机点
给定一个非重叠轴对齐矩形的列表 rects,写一个函数 pick 随机均匀地选取矩形覆盖的空间中的整数点。
提示:
- 整数点是具有整数坐标的点。
- 矩形周边上的点包含在矩形覆盖的空间中。
- 第 i 个矩形 rects [i] = [x1,y1,x2,y2],其中 [x1,y1] 是左下角的整数坐标,[x2,y2] 是右上角的整数坐标。
- 每个矩形的长度和宽度不超过 2000。
- 1 <= rects.length <= 100
- pick 以整数坐标数组 [p_x, p_y] 的形式返回一个点。
- pick 最多被调用10000次。
-
解答
int[][] rects; List<Integer> psum = new ArrayList<>(); int tot = 0; Random rand = new Random(); public Solution(int[][] rects) { this.rects = rects; for (int[] x : rects){ tot += (x[2] - x[0] + 1) * (x[3] - x[1] + 1); psum.add(tot); } } public int[] pick() { int targ = rand.nextInt(tot); int lo = 0; int hi = rects.length - 1; while (lo != hi) { int mid = (lo + hi) / 2; if (targ >= psum.get(mid)) lo = mid + 1; else hi = mid; } int[] x = rects[lo]; int width = x[2] - x[0] + 1; int height = x[3] - x[1] + 1; int base = psum.get(lo) - width * height; return new int[]{x[0] + (targ - base) % width, x[1] + (targ - base) / width}; }
-
分析
- 根据每个矩形的面积占比,得到每个矩形被选择的概率。
- 所以需要先计算每个矩形的面积,然后累加起来的结果存起来。这样每个区间对应一个矩形。
- 在所有面积和的范围内随机生成一个数targ
- 通过二分查找得到targ所在的矩形区间。也就确定了在哪一个矩形中
- 然后基于targ 生成坐标点
-
提交结果
498. 对角线遍历
给定一个含有 M x N 个元素的矩阵(M 行,N 列),请以对角线遍历的顺序返回这个矩阵中的所有元素,对角线遍历如下图所示。
-
解答
public int[] findDiagonalOrder(int[][] matrix) { if(matrix.length == 0 || matrix[0].length == 0)return new int[0]; int r = matrix.length; int l = matrix[0].length; int[] res = new int[r * l]; int rowIndex = 0; int colIndex = 0; boolean flag = true; int index = 0; while(true){ res[index++] = matrix[rowIndex][colIndex]; if(rowIndex == r-1 && colIndex == l-1)break; if(flag){ if(rowIndex - 1 >= 0 && colIndex + 1< l){ rowIndex--; colIndex++; }else if(colIndex + 1 < l){ colIndex++; flag = !flag; }else{ rowIndex++; flag = !flag; } }else{ if(rowIndex + 1 < r && colIndex - 1 >=0){ rowIndex++; colIndex--; }else if(rowIndex + 1 < r){ rowIndex++; flag = !flag; }else{ colIndex++; flag = !flag; } } } return res; }
-
分析
- 对角线遍历有两个方向,所以用flag来表示遍历的方向
- 每种遍历方向上有3种情况
- 换行换列
- 只换列
- 只换行
- 直到遍历到右下角结束
-
提交结果
500. 键盘行
给定一个单词列表,只返回可以使用在键盘同一行的字母打印出来的单词。键盘如下图所示。
-
解答
public String[] findWords(String[] words) { int[] ls = new int[]{2,3,3,2,1,2,2,2,1,2,2,2,3,3,1,1,1,1,2,1,1,3,1,3,1,3}; List<String> list = new ArrayList<>(); for(String str:words){ char[] ch = str.toCharArray(); int temp = -1; if(ch[0] >= 'A' && ch[0] <= 'Z')temp = ls[ch[0] - 'A']; if(ch[0] >= 'a' && ch[0] <= 'z')temp = ls[ch[0] - 'a']; for(int i = 1;i < ch.length;i++){ char cur = ch[i]; int num = - 1; if(ch[i] >= 'A' && ch[i] <= 'Z')num = ls[ch[i] - 'A']; if(ch[i] >= 'a' && ch[i] <= 'z')num = ls[ch[i] - 'a']; if(num != temp){ temp = -1; break; } } if(temp != -1)list.add(str); } return list.toArray(new String[list.size()]); }
-
分析
- 数组ls记录字母出现在键盘上的行号
- 遍历words
- 依次的判断每个字符串中的字符是否在同一行出现,是的话加入list
- 最后将list转换成数组输出
-
提交结果