文章目录
前言
需要开通vip的题目暂时跳过
笔记导航
点击链接可跳转到所有刷题笔记的导航链接
401. 二进制手表
二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。
每个 LED 代表一个 0 或 1,最低位在右侧。
例如,上面的二进制手表读取 “3:25”。
给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。
-
解答
int[] hs = {1,2,4,8}; int[] ms = {1,2,4,8,16,32}; public List<String> readBinaryWatch(int num) { List<String> res = new ArrayList<>(); if(num > 9)return res; for(int i = 0;i <= num;i++){ List<String> mList = new ArrayList<>(); List<String> hList = new ArrayList<>(); getM(i,0,new int[6],0,mList,0); getH(num - i,0,new int[4],0,hList,0); for(String strH : hList){ for(String strM : mList){ StringBuilder sb = new StringBuilder(); sb.append(strH).append(":").append(strM); res.add(sb.toString()); } } } return res; } public void getM(int n,int temp,int[] visited,int index,List<String> mList,int number){ if(number == n){ if(temp == 0){ mList.add("00"); }else if(temp < 10) mList.add("0" + temp); else mList.add(String.valueOf(temp)); return; } for(int i = index;i<ms.length;i++){ if(visited[i] == 0 && temp +ms[i] <= 59){ visited[i] = 1; getM(n,temp + Integer.valueOf(ms[i]),visited,i+1,mList,number + 1); visited[i] = 0; } } } public void getH(int n,int temp,int[] visited,int index,List<String> hList,int number){ if(number == n){ hList.add(String.valueOf(temp)); return; } for(int i = index;i<hs.length;i++){ if(visited[i] == 0 && temp +hs[i] <= 11){ visited[i] = 1; getH(n,temp + Integer.valueOf(hs[i]),visited,i+1,hList,number + 1); visited[i] = 0; } } }
-
分析
- 当给定的n>9的时候 直接返回空集合
- 对于给定的n。可以遍历n
- i个灯在小时数的地方亮着,n-i个灯在分钟数的地方亮着。
- 分别去得到 i个灯 可以得到的小时数 以及 n-i个灯可以得到的分钟数,然后对他们进行排列组合即可。
-
提交结果
402. 移掉K位数字
给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。
注意:
-
num 的长度小于 10002 且 ≥ k。
-
num 不会包含任何前导零。
-
解答
//方法1 public String removeKdigits(String num, int k) { if(k == num.length()) return "0"; String res = dfs(num,k); while(res.length() > 1 && res.charAt(0) == '0'){ res = res.substring(1); } return res; } public String dfs(String num,int k){ if(k == 0)return num; for(int i = 0;i < num.length() - 1;i++){ if(num.charAt(i) > num.charAt(i + 1)){ return dfs(num.substring(0,i) + num.substring(i+1),k-1); } } return dfs(num.substring(0,num.length()-1),k-1); } //方法2 public String removeKdigits(String num, int k) { if(k == num.length()) return "0"; LinkedList<Character> stack = new LinkedList<>(); stack.push(num.charAt(0)); for(int i = 1;i < num.length();){ char ch = num.charAt(i); if(stack.size() > 0 && stack.getLast() > ch && k > 0){ stack.removeLast(); k--; }else { stack.addLast(ch); i++; } } while( k > 0){ stack.removeLast(); k--; } StringBuilder res = new StringBuilder(); while(!stack.isEmpty()){ char ch = stack.removeFirst(); if(res.length() == 0 && ch == '0') continue; res.append(ch); } return res.length() == 0 ? "0" : res.toString(); }
-
分析
- 移除k位数的到的最小值是在在移除k-1位数的到的最小值的基础上再移除一位数。
- 所以可以每次选择移除1位数得到当前的最小值。
- 如何得到最小值。就是每次移除从左往右中 满足递增序列的最后一个字符。例如 12574 从左往右的递增序列1,2,5,7 此时移除掉7 得到的数是最小的。
- 所以可以使用递归来实现上面的操作。
- 移除k个数字后,需要去掉前导零
- 方法2
- 是对上面方法的改进,可以使用单调栈来实现递归的思想,思路是一样的。
-
提交结果
方法1
方法2
403. 青蛙过河
一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。
给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。
如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。
请注意:
- 石子的数量 ≥ 2 且 < 1100;
- 每一个石子的位置序号都是一个非负整数,且其 < 231;
- 第一个石子的位置永远是0。
-
解答
//方法1 public boolean canCross(int[] stones) { int[][] memo = new int[stones.length][stones.length]; for (int[] row : memo) { Arrays.fill(row, -1); } return canCross(stones,0,0,memo) == 1; } public int canCross(int[] stones,int index,int len,int[][] memo){ if(memo[index][len] >= 0){ return memo[index][len]; } for(int i = index + 1;i<stones.length;i++){ int gap = stones[i] - stones[index]; if(gap >= len -1 && gap <= len +1){ if(canCross(stones,i,gap,memo) == 1){ memo[index][len] = 1; return 1; } } } memo[index][len] = (index == stones.length-1) ? 1 : 0; return memo[index][len]; } //方法2 public boolean canCross(int[] stones) { HashMap<Integer, Set<Integer>> map = new HashMap<>(); for (int i = 0; i < stones.length; i++) { map.put(stones[i], new HashSet<Integer>()); } map.get(0).add(0); for (int i = 0; i < stones.length; i++) { for (int k : map.get(stones[i])) { for (int step = k - 1; step <= k + 1; step++) { if (step > 0 && map.containsKey(stones[i] + step)) { map.get(stones[i] + step).add(step); } } } } return map.get(stones[stones.length - 1]).size() > 0; }
-
分析
- 方法1 记忆化递归
- memo[index] [len]表示当前位置index 上一步为len 最终能否到达最后一个石子,1表示可以
- 每次遍历index+1位置开始到最后的石子,计算当前位置的石子距离 index石子的长度。若在范围内,则递归的判断下一个位置能否到达最后一个石子。
- 若能够递归到 index = stones.length - 1 表示可以到达最后一个石子。记录下memo[index] [len],并返回。
- 方法2 动态规划
- map 记录 key为石子的位置,value记录 前一步以多少的步长可以到达该石子。
- 初始条件是 key为0 起始位置,value中有一个值 就是0。
- 遍历stones数组,
- 基于当前位置 可以得到步长集合。当前的步长是根据上一步的步长k决定的,遍历step 从 k-1 ~ k+1 的步长 若步长大于0 并且 当前位置+步长 有石子 ,也就是在map中能找到对应的key 则记录下map.get(stones[i] + step).add(step)
- 最后返回map中 key为 最后一个石子 对应的value是否大于0即可。
-
提交结果
方法1
方法2
404. 左叶子之和
计算给定二叉树的所有左叶子之和。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-62PFnE4l-1603334936483)(/Users/gongsenlin/Library/Application Support/typora-user-images/截屏2020-10-22 上午10.27.26.png)]
-
解答
int result = 0; public int sumOfLeftLeaves(TreeNode root) { if (root == null) return 0; dfs(root, 0); return result; } public void dfs(TreeNode node, int isLeft) { if (node.left == null && node.right == null && isLeft == 1) result += node.val; if (node.left != null) dfs(node.left, 1); if (node.right != null) { dfs(node.right, 0); } }
-
分析
- 递归的遍历树,前中后序都可以,标示当前结点是否是左叶子。如果是的话,则将这个结点的值加入到答案中。
-
提交结果
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nYEFoabz-1603334936484)(/Users/gongsenlin/Library/Application Support/typora-user-images/截屏2020-10-22 上午10.28.42.png)]
405. 数字转换为十六进制数
给定一个整数,编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用 补码运算 方法。
注意:
- 十六进制中所有字母(a-f)都必须是小写。
- 十六进制字符串中不能包含多余的前导零。如果要转化的数为0,那么以单个字符’0’来表示;对于其他情况,十六进制字符串中的第一个字符将不会是0字符。
- 给定的数确保在32位有符号整数范围内。
- 不能使用任何由库提供的将数字直接转换或格式化为十六进制的方法。
-
解答
public String toHex(int num) { StringBuilder buffer = new StringBuilder(); char[] arr = "0123456789abcdef".toCharArray(); if (num == 0) return "0"; while (num != 0) { int tmp = num & 15; buffer.append(arr[tmp]); num = num >>> 4; } return buffer.reverse().toString(); }
-
分析
- 转换成2进制,每4位组成一个16进制
-
提交结果
406. 根据身高重建队列
假设有打乱顺序的一群人站成一个队列。 每个人由一个整数对(h, k)表示,其中h是这个人的身高,k是排在这个人前面且身高大于或等于h的人数。 编写一个算法来重建这个队列。
注意:
- 总人数少于1100人。
-
解答
public int[][] reconstructQueue(int[][] people) { Arrays.sort(people,new Comparator<int[]>(){ public int compare(int[] o1,int[] o2){ if(o1[0] != o2[0]){ return o2[0] - o1[0]; } return o1[1] - o2[1]; } }); List<int[]> list = new LinkedList<>(); for(int[] p : people){ list.add(p[1],p); } int n = people.length; return list.toArray(new int[n][2]); }
-
分析
- 按照身高降序排列,若升高相同,按照k值升序排列。
- 然后遍历数组,每次将当前位置的元素添加到对应的k值作为索引的地方。
-
提交结果
407. 接雨水 II
给你一个 m x n 的矩阵,其中的值均为非负整数,代表二维高度图每个单元的高度,请计算图中形状最多能接多少体积的雨水。
-
解答
public int trapRainWater(int[][] heights) { if (heights == null || heights.length == 0) return 0; int n = heights.length; int m = heights[0].length; // 用一个vis数组来标记这个位置有没有被访问过 boolean[][] vis = new boolean[n][m]; // 优先队列中存放三元组 [x,y,h] 坐标和高度 升序 PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o1[2] - o2[2]); // 先把最外一圈放进去 for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (i == 0 || i == n - 1 || j == 0 || j == m - 1) { pq.offer(new int[]{i, j, heights[i][j]}); vis[i][j] = true; } } } int res = 0; // 方向数组,把dx和dy压缩成一维来做 int[] dirs = {-1, 0, 1, 0, -1}; while (!pq.isEmpty()) { int[] poll = pq.poll(); // 看一下周围四个方向,没访问过的话能不能往里灌水 for (int k = 0; k < 4; k++) { int nx = poll[0] + dirs[k]; int ny = poll[1] + dirs[k + 1]; // 如果位置合法且没访问过 if (nx >= 0 && nx < n && ny >= 0 && ny < m && !vis[nx][ny]) { // 如果外围这一圈中最小的比当前这个还高,那就说明能往里面灌水啊 if (poll[2] > heights[nx][ny]) { res += poll[2] - heights[nx][ny]; } // 如果灌水高度得是你灌水后的高度了,如果没灌水也要取高的 pq.offer(new int[]{nx, ny, Math.max(heights[nx][ny], poll[2])}); vis[nx][ny] = true; } } } return res; }
-
分析
- 优先队列存放三元组,坐标及高度。表示最外围的高度。
- 先将最外围放入优先级队列。
- 然后每次从优先级队列中取出一个三元组。看周围4个方法上 是否有比当前取出三元组的高度要小的,并且没有访问过。说明可以往里面灌水。
- 然后将新的三元组放入优先级队列,高度取Math.max(heights[nx] [ny], poll[2]),位置是当前访问的位置。设置访问标记位为true。
-
提交结果
409. 最长回文串
给定一个包含大写字母和小写字母的字符串,找到通过这些字母构造成的最长的回文串。
在构造过程中,请注意区分大小写。比如 “Aa” 不能当做一个回文字符串。
注意:
- 假设字符串的长度不会超过 1010。
-
解答
public int longestPalindrome(String s) { int[] count = new int[52]; for(int i = 0;i < s.length();i++){ char ch = s.charAt(i); if(ch < 'a') count[ch - 'A' + 26]++; else count[ch - 'a']++; } int res = 0; boolean flag = false; int index; for(index = 0;index < 52;index++){ if(count[index] % 2 == 1){ flag = true; break; }else res += count[index] / 2 * 2; } for(int i = index; i < 52;i++){ res += count[i] / 2 * 2; } if(flag)res++; return res; }
-
分析
- 统计各个字符出现的次数。
- 成对出现的必定是可以用来组成回文的。
- 还需要考虑这些字符中有没有多余的字符,也就是奇数个。如果有一个奇数个的话。那么最后的答案结果要加1
-
提交结果
410. 分割数组的最大值
给定一个非负整数数组和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组。设计一个算法使得这 m 个子数组各自和的最大值最小。
注意:
- 数组长度 n 满足以下条件:
- 1 ≤ n ≤ 1000
- 1 ≤ m ≤ min(50, n)
-
解答
public int splitArray(int[] nums, int m) { int n = nums.length; int[][] dp = new int[n + 1][m + 1]; for(int i = 0;i <= n;i++){ Arrays.fill(dp[i],Integer.MAX_VALUE); } int[] sub = new int[n + 1]; for(int i = 0;i < n ;i++){ sub[i+1] = sub[i] + nums[i]; } dp[0][0] = 0; for(int i = 1;i <= n;i++){ for (int j = 1; j <= Math.min(i, m); j++) { for (int k = 0; k < i; k++) {//k之前分成 j-1段 那么 k~i 就是一组 dp[i][j] = Math.min(dp[i][j], Math.max(dp[k][j - 1], sub[i] - sub[k])); } } } return dp[n][m]; } //方法二 public int splitArray(int[] nums, int m) { int max = 0; int count = 0; for(int i = 0; i< nums.length;i++){ max = Math.max(max,nums[i]); count += nums[i]; } int left = max; int right = count; while(left < right){ int mid = left + (right - left)/2; int split = split(nums,mid); if(split > m){ left = mid + 1; }else right = mid; } return left; } public int split(int[] nums,int number){ int num = 1; int curCount = 0; for(int i = 0;i < nums.length;i++){ if(curCount + nums[i] > number){ num++; curCount = 0; } curCount += nums[i]; } return num; }
-
分析
- 方法1 动态规划
- dp[i] [j]表示前i段分割j段最大连续子数组和的最小值
- 当j > i的时候 前i段无法分割成j段。所以可以令其为最大值。
- 当i = 0 j = 0 的时候 值为0 所以dp[0] [0] = 0
- 最外层循环,前i个数 1~n
- 第二层循环,分割j段 j 从1 到 Math.min(i, m) 不能超过可分割的段数。
- 内层循环,选择分割点,前k个数的分成j-1段 表示为dp[k] [j-1] 那么k + 1个数到第i个数就是一段,计算这一段的和。
- 因此就可以得到动态转移方程
- dp[i] [j] = Math.min(dp[i] [j], Math.max(dp[k] [j - 1], sub[i] - sub[k]));
- 方法2 2分查找
- 数组nums中的最大值 作为left,数组的和作为right
- 二分查找。mid作为分割依据。当连续子数组的和大于mid的时候,则分割一段。判断nums可以分割多少段。若根据mid 可以分割的段数大于m 说明mid太小了
- 此时将left 设置为mid + 1
- 否则将right 设置为mid 逐渐逼近最终答案。
-
提交结果
方法1
方法2
412. Fizz Buzz
写一个程序,输出从 1 到 n 数字的字符串表示。
- 如果 n 是3的倍数,输出“Fizz”;
- 如果 n 是5的倍数,输出“Buzz”;
- 如果 n 同时是3和5的倍数,输出 “FizzBuzz”。
-
解答
public List<String> fizzBuzz(int n) { List<String> res = new ArrayList<>(); for(int i = 1; i <= n;i++){ if(i % 15 == 0){ res.add("FizzBuzz"); }else if(i % 3 == 0){ res.add("Fizz"); }else if(i % 5 == 0){ res.add("Buzz"); }else res.add(String.valueOf(i)); } return res; }
-
分析
- 略
-
提交结果
413. 等差数列划分
如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。
数组 A 包含 N 个数,且索引从0开始。数组 A 的一个子数组划分为数组 (P, Q),P 与 Q 是整数且满足 0<=P<Q<N 。
如果满足以下条件,则称子数组(P, Q)为等差数组:
元素 A[P], A[p + 1], …, A[Q - 1], A[Q] 是等差的。并且 P + 1 < Q 。
函数要返回数组 A 中所有为等差数组的子数组个数。
-
解答
//方法1 public int numberOfArithmeticSlices(int[] A) { List<List<Integer>> list = new ArrayList<>(); List<Integer> temp = new ArrayList<>(); int cha = 0; for (int i = 0; i < A.length; i++) { if (temp.size() == 0) { temp.add(A[i]); } else if (temp.size() == 1) { cha = A[i] - temp.get(0); temp.add(A[i]); } else { if (A[i] - temp.get(temp.size() - 1) == cha) { temp.add(A[i]); } else { if (temp.size() >= 3) { list.add(new ArrayList<>(temp)); } List<Integer> l = new ArrayList<>(); int last = temp.get(temp.size() - 1); l.add(last); l.add(A[i]); temp = l; cha = A[i] - last; } } } if (temp.size() >= 3) { list.add(new ArrayList<>(temp)); } int res = 0; for (List<Integer> subList : list) { res += getNumber(subList.size()); } return res; } public int getNumber(int n) { return (n - 1) * (n - 2) / 2; } //方法2 public int numberOfArithmeticSlices(int[] A) { int[] dp = new int[A.length]; int sum = 0; for (int i = 2; i < dp.length; i++) { if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) { dp[i] = 1 + dp[i - 1]; sum += dp[i]; } } return sum; }
-
分析
-
方法1
-
找到所有的最长的等差数列
-
子数列包含在最长的等差数列当中
-
例如一个最长等差数列长度为n
-
那么它可以得到的所有等差子数列 为 (n - 1) * (n - 2) / 2;
-
方法2 官方题解 看图就很清楚
-
-
提交结果
方法1
方法2
414. 第三大的数
给定一个非空数组,返回此数组中第三大的数。如果不存在,则返回数组中最大的数。要求算法时间复杂度必须是O(n)。
-
解答
public int thirdMax(int[] nums) { //由于题目没给范围,所以先将存储前三大的数的变量设置成最小值 int max = Integer.MIN_VALUE, two = Integer.MIN_VALUE, three = Integer.MIN_VALUE, cnt = 1; //然后再用3个变量来判断数组中是否有三个不同的值(先将他们都赋值为数组第一个数) int temp1 = nums[0], temp2 = nums[0], temp3 = nums[0]; //循环找到前三大的数 for (int i = 0; i < nums.length; i++) { //这里我们要保证temp2和temp3都与temp1不相同 if (nums[i] != temp1) { if (temp1 != temp2) { temp3 = nums[i]; } else { temp2 = nums[i]; } } //如果找到一个更大的数,则将当前的max变成two,two变成three if (nums[i] > max) { three = two; two = max; max = nums[i]; } //如果这个数不是更大的数,则与第二大的数比较 else if (nums[i] < max && nums[i] > two) { three = two; two = nums[i]; } //否则再与第三大的数比较 else if (nums[i] < max && nums[i] < two && nums[i] > three) three = nums[i]; } if (temp2 == temp3 || temp1 == temp3) return max; else return three; }
-
分析
- temp1,temp2,temp3用来判断是否有重复的数字
- max,two,three用来保存第一大,第二大,第三大的数
- 首先将nums[0]赋值给三个temp
- 然后遍历数组。若当前nums[i] 不等于temp1 则和temp2 比较,若不等则将temp3 赋值为nums[i]。否则temp2赋值为nums[i]。这一步是为了保证3个temp的值尽可能不一样。
- 之后就是判断最大还是第二大还是第三大。更新max,two,three即可
- 最后比较3个temp是否存在相同的,存在则返回最大的。否则返回第三大。
-
提交结果
415. 字符串相加
给定两个字符串形式的非负整数 num1 和num2 ,计算它们的和。
提示:
-
num1 和num2 的长度都小于 5100
-
num1 和num2 都只包含数字 0-9
-
num1 和num2 都不包含任何前导零
-
你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式
-
解答
public String addStrings(String num1, String num2) { StringBuilder sb = new StringBuilder(); int flag = 0; int index = num1.length()-1; int index2 = num2.length()-1; while(index >= 0 || index2 >= 0){ char ch1 = index >= 0 ? num1.charAt(index) : '0'; char ch2 = index2 >= 0 ? num2.charAt(index2) : '0'; int sum = ch1 -'0' + ch2 -'0' + flag; if(sum >= 10){ sum = sum % 10; flag = 1; }else flag = 0; sb.insert(0,sum); index--; index2--; } if(flag == 1) sb.insert(0,1); return sb.toString(); }
-
分析
- 模拟加法竖式计算
- 每位两两想加,并计算进位。
- 注意最后一个进位如果是1 需要在前面补上一个1
-
提交结果
416. 分割等和子集
给定一个只包含正整数的非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
-
解答
//方法一 动态规划 public boolean canPartition(int[] nums) { int sum = 0; int len = nums.length; for(int i = 0;i<len;i++){ sum += nums[i]; } if(sum % 2 == 1)return false; int target = sum/2; boolean[][] dp = new boolean[len][target + 1]; if(nums[0] < target) dp[0][nums[0]] = true; for(int i = 1;i<len;i++){ for(int j = 0;j<= target;j++){ if(nums[i] == j){ dp[i][j] = true; }else if(nums[i] < j){ dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i]]; } dp[i][j] = dp[i-1][j]; } } return dp[len-1][target]; }
-
分析
- 动态转移方程来实现
- dp[i] [j] 表示0-i 的物品中 能否拿出价值总和为j的商品
- 计算所有价值的总和
- 若价值总和是奇数 则不可以
- 初始化 dp[0] [num[0]] 表示第0件物品能否拿出价值总和为num[0] 的商品,因为目标价值是自己本身的价值所以是true
- 之后就是外层循环遍历物品的区间,内存循环遍历目标价值
- 若当前第i件物品的价值等于j。那么0-i范围内必定可以拿出价值总和为j的商品所以dp[i] [j] = true
- 若第i件物品小于目标价值j。那么有两种情况 一种是不拿第i件物品 此时dp[i] [j] = dp[i-1] [j],还有一种情况是拿第i件物品 此时dp[i] [j] = dp[i-1] [j-nums[i]]
- 若第i件物品大于目标价值j 此时dp[i] [j] = dp[i-1] [j];
- 最后返回dp[len-1] [target]即可
-
提交结果
417. 太平洋大西洋水流问题
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
提示:
- 输出坐标的顺序不重要
- m 和 n 都小于150
-
解答
public List<List<Integer>> pacificAtlantic(int[][] matrix) { List<List<Integer>> res = new ArrayList<>(); int row = matrix.length; if(row == 0)return res; int col = matrix[0].length; if(col == 0)return res; boolean[][] taipingyang = new boolean[row][col];//记忆集 能否到达太平洋 boolean[][] daxiyang = new boolean[row][col];// 记忆集 能否到达大西洋 //初始化边界 可以抵达 太平洋或大西洋 for (int i = 0; i < row; i++) { taipingyang[i][0] = true; daxiyang[i][col - 1] = true; } for (int i = 0; i < col; i++) { taipingyang[0][i] = true; daxiyang[row - 1][i] = true; } // 遍历每个点 for (int i = 0; i < row; i++) { for (int j = 0; j < col; j++) { int[][] visited = new int[row][col]; int[][] visited2 = new int[row][col]; List<Integer> list = new ArrayList<>(); visited[i][j] = 1; visited2[i][j] = 1; if (GoToTPY(matrix, i, j, taipingyang, visited) && GoToDXY(matrix, i, j, daxiyang, visited2)) { list.add(i); list.add(j); res.add(list); } } } return res; } // 记忆化递归 能否到达太平洋 public boolean GoToTPY(int[][] matrix, int i, int j, boolean[][] taipingyang, int[][] visited) { if (taipingyang[i][j]) return true; if (i == 0 || j == 0) return true; if (i - 1 >= 0 && matrix[i - 1][j] <= matrix[i][j] && visited[i - 1][j] == 0) { visited[i - 1][j] = 1; if (GoToTPY(matrix, i - 1, j, taipingyang, visited)) { taipingyang[i][j] = true; return true; } visited[i - 1][j] = 0; } if (i + 1 < matrix.length && matrix[i + 1][j] <= matrix[i][j] && visited[i + 1][j] == 0) { visited[i + 1][j] = 1; if (GoToTPY(matrix, i + 1, j, taipingyang, visited)) { taipingyang[i][j] = true; return true; } visited[i + 1][j] = 0; } if (j - 1 >= 0 && matrix[i][j - 1] <= matrix[i][j] && visited[i][j - 1] == 0) { visited[i][j - 1] = 1; if (GoToTPY(matrix, i, j - 1, taipingyang, visited)) { taipingyang[i][j] = true; return true; } visited[i][j - 1] = 0; } if (j + 1 < matrix[0].length && matrix[i][j + 1] <= matrix[i][j] && visited[i][j + 1] == 0) { visited[i][j + 1] = 1; if (GoToTPY(matrix, i, j + 1, taipingyang, visited)) { taipingyang[i][j] = true; return true; } visited[i][j + 1] = 0; } return false; } // 记忆化递归 能否到达大西洋 public boolean GoToDXY(int[][] matrix, int i, int j, boolean[][] daxiyang, int[][] visited) { if (daxiyang[i][j]) return true; if (i == matrix.length - 1 || j == matrix[0].length - 1) return true; if (i - 1 >= 0 && matrix[i - 1][j] <= matrix[i][j] && visited[i - 1][j] == 0) { visited[i - 1][j] = 1; if (GoToDXY(matrix, i - 1, j, daxiyang, visited)) { daxiyang[i][j] = true; return true; } visited[i - 1][j] = 0; } if (i + 1 < matrix.length && matrix[i + 1][j] <= matrix[i][j] && visited[i + 1][j] == 0) { visited[i + 1][j] = 1; if (GoToDXY(matrix, i + 1, j, daxiyang, visited)) { daxiyang[i][j] = true; return true; } visited[i + 1][j] = 0; } if (j - 1 >= 0 && matrix[i][j - 1] <= matrix[i][j] && visited[i][j - 1] == 0) { visited[i][j - 1] = 1; if (GoToDXY(matrix, i, j - 1, daxiyang, visited)) { daxiyang[i][j] = true; return true; } visited[i][j - 1] = 0; } if (j + 1 < matrix[0].length && matrix[i][j + 1] <= matrix[i][j] && visited[i][j + 1] == 0) { visited[i][j + 1] = 1; if (GoToDXY(matrix, i, j + 1, daxiyang, visited)) { daxiyang[i][j] = true; return true; } visited[i][j + 1] = 0; } return false; }
-
分析
- 记忆化递归实现
- 准备两个数组,分别表示能否到达太平洋和大西洋
- 初始化边界条件,靠近海洋的为true
- 记忆化递归每一个点。需要一个访问数组,避免重复走进入死循环。
-
提交结果
419. 甲板上的战舰
给定一个二维的甲板, 请计算其中有多少艘战舰。 战舰用 'X’表示,空位用 '.'表示。 你需要遵守以下规则:
- 给你一个有效的甲板,仅由战舰或者空位组成。
- 战舰只能水平或者垂直放置。换句话说,战舰只能由 1xN (1 行, N 列)组成,或者 Nx1 (N 行, 1 列)组成,其中N可以是任意大小。
- 两艘战舰之间至少有一个水平或垂直的空位分隔 - 即没有相邻的战舰。
你不会收到这样的无效甲板 - 因为战舰之间至少会有一个空位将它们分开。
进阶:
-
你可以用一次扫描算法,只使用O(1)额外空间,并且不修改甲板的值来解决这个问题吗?
-
解答
int res = 0; public int countBattleships(char[][] board) { int row = board.length; int col = board[0].length; int res = 0; for(int i = 0;i < row;i++){ for(int j = 0;j < col;j++){ if(board[i][j] == 'X'){ if(i-1 >=0 && board[i-1][j] == 'X')continue; if(j-1 >=0 && board[i][j-1] == 'X')continue; res++; } } } return res; }
-
分析
- 这题很简单,因为不会出现无效的船舰。
- 只需要数船头就行了,规定 一艘船 如果是横着的 那么就是最左边是船头。如果是竖着的,那么就是最上边是船头。
- 遍历board 当前位置是甲板,如果左边或者上边也是甲板,则跳过。因为当前位子属于船身并不是船头。否则当前位置是船头。总数+1
-
提交结果
420. 强密码检验器
一个强密码应满足以下所有条件:
- 由至少6个,至多20个字符组成。
- 至少包含一个小写字母,一个大写字母,和一个数字。
- 同一字符不能连续出现三次 (比如 “…aaa…” 是不允许的, 但是 “…aa…a…” 是可以的)。
编写函数 strongPasswordChecker(s),s 代表输入字符串,如果 s 已经符合强密码条件,则返回0;否则返回要将 s 修改为满足强密码条件的字符串所需要进行修改的最小步数。
插入、删除、替换任一字符都算作一次修改。
-
解答
/** * 记录连续出现的字符 起始和终止坐标 */ class SameChar { int st; int en; char c; SameChar(int st, int en, char c) { this.st = st; this.en = en; this.c = c; } } public int strongPasswordChecker(String str) { // 统计小写字符 int lowerCase = 0; // 统计大写字符 int upwerCase = 0; // 统计数字 int number = 0; // 统计连续字符出现的位置 ArrayList<SameChar> sameChars = new ArrayList<>(); char[] chars = str.toCharArray(); if (chars.length == 0) { return 6; } // 记露连续出现的字符 SameChar sameChar = new SameChar(0, 0, '\0'); for (int i = 0; i < chars.length; i++) { if (chars[i] >= 'a' && chars[i] <= 'z') { lowerCase++; } else if (chars[i] >= 'A' && chars[i] <= 'Z') { upwerCase++; } else if (chars[i] >= '0' && chars[i] <= '9') { number++; } if (sameChar.c != chars[i]) { if (sameChar.en - sameChar.st >= 2) { sameChars.add(new SameChar(sameChar.st, sameChar.en, sameChar.c)); } sameChar.c = chars[i]; sameChar.st = i; sameChar.en = i; } else { sameChar.en = i; } } if (sameChar.en - sameChar.st >= 2) { sameChars.add(new SameChar(sameChar.st, sameChar.en, sameChar.c)); } // 缺失的类型. 只可能是1 or 2 int needType = count0(lowerCase, upwerCase, number); // 连续的字符出现的要消除的个数 连续值-2 int[] chages = new int[sameChars.size()]; for (int j = 0; j < sameChars.size(); j++) { chages[j] = sameChars.get(j).en - sameChars.get(j).st - 1; } int res = 0; // 如果长度小于6 , 很简单 要补的字符和缺失的类型择大 if (str.length() < 6) { return Integer.max(6 - str.length(), needType); } // 删除的时候 要有优先概念 if (str.length() > 20) { int index = -1; while (needType > 0 && (index = find(chages, 0)) > -1) {//缺少类型 chages[index] = Integer.max(chages[index] - 3, 0); res++; needType--; } int d = str.length() - 20; while (d > 0 && (index = find(chages, 1)) > -1) {//需要改变的 d--; res++; chages[index]--; } int n = 0; for (int l = 0; l < chages.length; l++) {//剩余需要改变的 n += chages[l] % 3 == 0 ? chages[l] / 3 : chages[l] / 3 + 1; } return res + d + needType + n; } int n = 0; for (int l = 0; l < chages.length; l++) { n += chages[l] % 3 == 0 ? chages[l] / 3 : chages[l] / 3 + 1; } return Integer.max(n, needType); } private int count0(int... array) { int n = 0; for (int i = 0; i < array.length; i++) { if (array[i] == 0) { n++; } } return n; } private int find(int[] array, int n) { int n0 = -1; int n1 = -1; int n2 = -1; for (int i = 0; i < array.length; i++) { if (array[i] > 0 && array[i] % 3 == 0) { n0 = i; } if (array[i] > 0 && array[i] % 3 == 1) { n1 = i; } if (array[i] > 0 && array[i] % 3 == 2) { n2 = i; } } if (n == 0) { return n0 > -1 ? n0 : (n2 > -1 ? n2 : n1); } if (n == 1) { return n1 > -1 ? n1 : (n2 > -1 ? n2 : n0); } return -1; }
-
分析
-
主要先考虑如果去消除连续字符,n 代表步数,s 代表连续的个数,最后的目标都是小于 3。
- 删除 效率最低 s-n* 1<3
- 插入 效率其次 s-n* 2<3
- 替换 效率最高 s-n* 3<3
-
举例 aaaaa 五连字符,要正确的话如果只删除要 3 步, 如果插入的话要 2步,如果替换只需要替换中间的 a 一步就可以完成。
接下来 分情况讨论
长度 <6 ,步数=缺失类型和缺失长度取大者。
长度 (6,20),这时候我们不需要低效的插入和删除来处理连续字符,直接替换步数就等于处理连续字和缺失类型取大者。
比较负载的是 >20,我们需要知道优先级,一样优先处理连续数组。
优先处理缺失类型,用替换的方式来处理,这时候要替换的连续组的连续数 %32 -> 连续数%31 -> 连续数%30,然后处理多余字符,删除的优先级是连续组的连续数 %30 -> 连续数%31 -> 连续数%32。
-
-
提交结果