需要开通vip的题目暂时跳过
点击链接可跳转到所有刷题笔记的导航链接
给定一个字符串 s 和一个整数 k,你需要对从字符串开头算起的每隔 2k 个字符的前 k 个字符进行反转。
如果剩余字符少于 k 个,则将剩余字符全部反转。
如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
解答
public String reverseStr(String s, int k) {
char[] chars = s.toCharArray();
for(int i =0;i < s.length();i += 2*k){
int start = i;
int end = i + 2 * k - 1;
if(end < s.length()){
reverse(chars,start,end - k);
}else{
end = s.length()-1;
if(end - start < k){
reverse(chars,start,end);
}else{
end = start + k - 1;
reverse(chars,start,end);
}
}
}
return new String(chars);
}
public void reverse(char[] chars,int start,int end){
for(int i = 0;i <= (end - start)/2;i++){
char temp = chars[start + i];
chars[start + i] = chars[end - i];
chars[end - i] = temp;
}
}
分析
给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。
两个相邻元素间的距离为 1 。
解答
//方法1 广度优先搜索
public int[][] updateMatrix(int[][] matrix) {
if (matrix == null || matrix.length == 0) return null;
int m = matrix.length, n = matrix[0].length;
int[][] res = new int[m][n];//结果集
boolean[][] visited = new boolean[m][n];//记录已经计算过的位置
Queue<int[]> queue = new LinkedList<>();//广搜队列
//遍历,将等于0的位置计入结果集并入队
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (matrix[i][j] == 0) {
res[i][j] = 0;
visited[i][j] = true;
queue.offer(new int[]{
i, j});
}
}
}
int[][] direction = {
{
-1, 0}, {
1, 0}, {
0, -1}, {
0, 1}};//上下左右
while (!queue.isEmpty()) {
int[] poll = queue.poll();
int i = poll[0], j = poll[1];
for (int k = 0; k < 4; k++) {
int di = i + direction[k][0], dj = j + direction[k][1];
if (di >= 0 && di < m && dj >= 0 && dj < n && !visited[di][dj]) {
res[di][dj] = res[i][j] + 1;
visited[di][dj] = true;
queue.offer(new int[]{
di, dj});
}
}
}
return res;
}
// 方法2 DP
public int[][] updateMatrix(int[][] matrix) {
int m = matrix.length, n = matrix[0].length;
// 初始化动态规划的数组,所有的距离值都设置为一个很大的数
int[][] dist = new int[m][n];
for (int i = 0; i < m; ++i) {
Arrays.fill(dist[i], Integer.MAX_VALUE / 2);
}
// 如果 (i, j) 的元素为 0,那么距离为 0
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (matrix[i][j] == 0) {
dist[i][j] = 0;
}
}
}
// 只有 水平向左移动 和 竖直向上移动,注意动态规划的计算顺序
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (i - 1 >= 0) {
dist[i][j] = Math.min(dist[i][j], dist[i - 1][j] + 1);
}
if (j - 1 >= 0) {
dist[i][j] = Math.min(dist[i][j], dist[i][j - 1] + 1);
}
}
}
// 只有 水平向左移动 和 竖直向下移动,注意动态规划的计算顺序
for (int i = m - 1; i >= 0; --i) {
for (int j = 0; j < n; ++j) {
if (i + 1 < m) {
dist[i][j] = Math.min(dist[i][j], dist[i + 1][j] + 1);
}
if (j - 1 >= 0) {
dist[i][j] = Math.min(dist[i][j], dist[i][j - 1] + 1);
}
}
}
// 只有 水平向右移动 和 竖直向上移动,注意动态规划的计算顺序
for (int i = 0; i < m; ++i) {
for (int j = n - 1; j >= 0; --j) {
if (i - 1 >= 0) {
dist[i][j] = Math.min(dist[i][j], dist[i - 1][j] + 1);
}
if (j + 1 < n) {
dist[i][j] = Math.min(dist[i][j], dist[i][j + 1] + 1);
}
}
}
// 只有 水平向右移动 和 竖直向下移动,注意动态规划的计算顺序
for (int i = m - 1; i >= 0; --i) {
for (int j = n - 1; j >= 0; --j) {
if (i + 1 < m) {
dist[i][j] = Math.min(dist[i][j], dist[i + 1][j] + 1);
}
if (j + 1 < n) {
dist[i][j] = Math.min(dist[i][j], dist[i][j + 1] + 1);
}
}
}
return dist;
}
分析
方法1 广度优先搜索
第一次遍历将元素为0的点的坐标入队,并标记为已经访问过。
之后就是广度搜索
元素出队,往4个方向去走,更新4个方向上到元素0的距离为res[di] [dj] = res[i] [j] + 1,并标记为访问过。
新的点入队。
直到队空为止
方法2 动态规划
对于矩阵中的任意一个 1 以及一个 0,我们如何从这个 1 到达 0 并且距离最短呢?根据上面的做法,我们可以从 1 开始,先在水平方向移动,只要与 0 在同一列。随后再在竖直方向上移动,直到到达 0 的位置。这样以来,从一个固定的 1 走到任意一个 0,在距离最短的前提下可能有四种方法:
这样以来,我们就可以使用动态规划解决这个问题了。我们用 f(i, j)f(i,j) 表示位置 (i, j)(i,j) 到最近的 0 的距离。如果我们只能「水平向左移动」和「竖直向上移动」,那么我们可以向上移动一步,再移动 f(i - 1, j)f(i−1,j) 步到达某一个 0,也可以向左移动一步,再移动 f(i, j - 1)f(i,j−1) 步到达某一个 0。因此我们可以写出如下的状态转移方程:
官方题解还提到 可以仅考虑第一种和第四种移动的方法。
提交结果
给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。
解答
int max = Integer.MIN_VALUE;
public int diameterOfBinaryTree(TreeNode root) {
if(root == null)return 0;
dfs(root);
return max-1;
}
public int dfs(TreeNode root){
if(root == null)return 0;
int left = dfs(root.left);
int right = dfs(root.right);
max = Math.max(max,1 + left + right);
return 1 + Math.max(left,right);
}
分析
给出一些不同颜色的盒子,盒子的颜色由数字表示,即不同的数字表示不同的颜色。
你将经过若干轮操作去去掉盒子,直到所有的盒子都去掉为止。每一轮你可以移除具有相同颜色的连续 k 个盒子(k >= 1),这样一轮之后你将得到 k*k 个积分。
当你将所有盒子都去掉之后,求你能获得的最大积分和。
解答
int[][][] dp = new int[100][100][100];
public int removeBoxes(int[] boxes) {
return dfs(boxes,0,boxes.length-1,0);
}
public int dfs(int[] boxes,int l,int r,int k){
if(l > r) return 0;
if(dp[l][r][k] != 0)return dp[l][r][k];
dp[l][r][k] = dfs(boxes,l,r-1,0) + (k + 1) * (k + 1);
for(int i = l;i<r;i++){
if(boxes[i] == boxes[r]){
dp[l][r][k] = Math.max(dp[l][r][k],dfs(boxes,l,i,k+1) + dfs(boxes,i+1,r-1,0));
}
}
return dp[l][r][k];
}
分析
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
解答
public int findCircleNum(int[][] M) {
int N = M.length;
int[] visited = new int[N];
int res = 0;
for(int i = 0;i < N;i++){
if(visited[i] == 0){
visited[i]=1;
res++;
dfs(M,i,visited);
}
}
return res;
}
public void dfs(int[][] M,int cur,int[] visited){
for(int i = 0;i< M.length;i++){
if(visited[i] == 0 && M[cur][i] == 1 && cur !=i){
visited[i] = 1;
dfs(M,i,visited);
}
}
}
分析
给定一个正整数 n,返回长度为 n 的所有可被视为可奖励的出勤记录的数量。 答案可能非常大,你只需返回结果mod 109 + 7的值。
学生出勤记录是只包含以下三个字符的字符串:
如果记录不包含多于一个’A’(缺勤)或超过两个连续的’L’(迟到),则该记录被视为可奖励的。
解答
public int checkRecord(int n) {
long mod = 1000000007;
long[][][] dp = new long[n+1][2][3];
dp[1][0][0] = 1;
dp[1][1][0] = 1;
dp[1][0][1] = 1;
for(int i = 2;i <= n;i++){
//+p
dp[i][0][0] = (dp[i-1][0][0] + dp[i-1][0][1] + dp[i-1][0][2]) % mod;
dp[i][1][0] = (dp[i-1][1][0] + dp[i-1][1][1] + dp[i-1][1][2]) % mod;
//+L
dp[i][0][1] = dp[i-1][0][0];
dp[i][0][2] = dp[i-1][0][1];
dp[i][1][1] = dp[i-1][1][0];
dp[i][1][2] = dp[i-1][1][1];
//+A
dp[i][1][0] += (dp[i-1][0][0] + dp[i-1][0][1] + dp[i-1][0][2]) % mod;
}
return (int)((dp[n][0][0] + dp[n][1][0] + dp[n][0][1] + dp[n][0][2] + dp[n][1][1] + dp[n][1][2]) % mod);
}
分析
对于字母A,我们只在乎它有没有出现过,即出现1次或0次,若出现过了 则不能再出现。
对于字母L,我们只在乎它有没有连续的出现,记连续出现的次数
可以在已有字母的基础上追加字母ALP,这样仅需要考虑最后的两个字母是否是连续的L即可。
所以可以用dp[n] [i] [j]表示n个字母的时候,出现了i个A和末位有j个L的字符串的数量。
一共有6种状态
初始条件
从2开始追加字母
追加字母P的动态转移方程为
dp[i] [0] [0] = (dp[i-1] [0] [0] + dp[i-1] [0] [1] + dp[i-1] [0] [2]) % mod;
dp[i] [1] [0] = (dp[i-1] [1] [0] + dp[i-1] [1] [1] + dp[i-1] [1] [2]) % mod;
追加字母L的动态转移方程为
dp[i] [0] [1] = dp[i-1] [0] [0];
dp[i] [0] [2] = dp[i-1] [0] [1];
dp[i] [1] [1] = dp[i-1] [1] [0];
dp[i] [1] [2] = dp[i-1] [1] [1];
追加字母A的动态转移方程为
dp[i] [1] [0] += (dp[i-1] [0] [0] + dp[i-1] [0] [1] + dp[i-1] [0] [2]) % mod;
注意这里是+= 因为在追加字母P的时候已经计算过dp[i] [1] [0]了,所以需要加上。
给定一组正整数,相邻的整数之间将会进行浮点除法操作。例如, [2,3,4] -> 2 / 3 / 4 。
但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。你需要找出怎么添加括号,才能得到最大的结果,并且返回相应的字符串格式的表达式。你的表达式不应该含有冗余的括号。
解答
//递归
public String optimalDivision(int[] nums) {
T t = optimal(nums, 0, nums.length - 1);
return t.max_str;
}
class T {
float max_val, min_val;
String min_str, max_str;
}
public T optimal(int[] nums, int start, int end) {
T t = new T();
if (start == end) {
t.max_val = nums[start];
t.min_val = nums[start];
t.min_str = "" + nums[start];
t.max_str = "" + nums[start];
return t;
}
t.min_val = Float.MAX_VALUE;
t.max_val = Float.MIN_VALUE;
t.min_str = t.max_str = "";
for (int i = start; i < end; i++) {
T left = optimal(nums, start, i);
T right = optimal(nums, i + 1, end);
if (t.min_val > left.min_val / right.max_val) {
t.min_val = left.min_val / right.max_val;
t.min_str = left.min_str + "/" + (i + 1 != end ? "(" : "") + right.max_str + (i + 1 != end ? ")" : "");
}
if (t.max_val < left.max_val / right.min_val) {
t.max_val = left.max_val / right.min_val;
t.max_str = left.max_str + "/" + (i + 1 != end ? "(" : "") + right.min_str + (i + 1 != end ? ")" : "");
}
}
return t;
}
//方法2 记忆化递归
public String optimalDivision(int[] nums) {
T[][] memo = new T[nums.length][nums.length];
T t = optimal(nums, 0, nums.length - 1,memo);
return t.max_str;
}
class T {
float max_val, min_val;
String min_str, max_str;
}
public T optimal(int[] nums, int start, int end,T[][] memo) {
if(memo[start][end] != null)
return memo[start][end];
T t = new T();
if (start == end) {
t.max_val = nums[start];
t.min_val = nums[start];
t.min_str = "" + nums[start];
t.max_str = "" + nums[start];
return t;
}
t.min_val = Float.MAX_VALUE;
t.max_val = Float.MIN_VALUE;
t.min_str = t.max_str = "";
for (int i = start; i < end; i++) {
T left = optimal(nums, start, i,memo);
T right = optimal(nums, i + 1, end,memo);
if (t.min_val > left.min_val / right.max_val) {
t.min_val = left.min_val / right.max_val;
t.min_str = left.min_str + "/" + (i + 1 != end ? "(" : "") + right.max_str + (i + 1 != end ? ")" : "");
}
if (t.max_val < left.max_val / right.min_val) {
t.max_val = left.max_val / right.min_val;
t.max_str = left.max_str + "/" + (i + 1 != end ? "(" : "") + right.min_str + (i + 1 != end ? ")" : "");
}
}
memo[start][end] = t;
return t;
}
//
public String optimalDivision(int[] nums) {
if (nums.length == 1)
return nums[0] + "";
if (nums.length == 2)
return nums[0] + "/" + nums[1];
StringBuilder res = new StringBuilder(nums[0] + "/(" + nums[1]);
for (int i = 2; i < nums.length; i++) {
res.append("/" + nums[i]);
}
res.append(")");
return res.toString();
}
分析
提交结果
你的面前有一堵矩形的、由多行砖块组成的砖墙。 这些砖块高度相同但是宽度不同。你现在要画一条自顶向下的、穿过最少砖块的垂线。
砖墙由行的列表表示。 每一行都是一个代表从左至右每块砖的宽度的整数列表。
如果你画的线只是从砖块的边缘经过,就不算穿过这块砖。你需要找出怎样画才能使这条线穿过的砖块数量最少,并且返回穿过的砖块数量。
你不能沿着墙的两个垂直边缘之一画线,这样显然是没有穿过一块砖的。
解答
public int leastBricks(List<List<Integer>> wall) {
Map<Integer,Integer> map = new HashMap<>();
int max = 0;
for(List<Integer> w : wall){
int res = 0;
for(int i = 0;i < w.size() - 1;i++){
int num = w.get(i);
res += num;
int number = map.getOrDefault(res,0);
map.put(res,number + 1);
max = Math.max(number + 1,max);
}
}
return wall.size() - max;
}
分析
给定一个32位正整数 n,你需要找到最小的32位整数,其与 n 中存在的位数完全相同,并且其值大于n。如果不存在这样的32位整数,则返回-1。
解答
public int nextGreaterElement(int n) {
char[] a = ("" + n).toCharArray();
int i = a.length - 2;
while (i >= 0 && a[i + 1] <= a[i]) {
i--;
}
if (i < 0)
return -1;
int j = a.length - 1;
while (j >= 0 && a[j] <= a[i]) {
j--;
}
swap(a, i, j);
reverse(a, i + 1);
try {
return Integer.parseInt(new String(a));
} catch (Exception e) {
return -1;
}
}
private void reverse(char[] a, int start) {
int i = start, j = a.length - 1;
while (i < j) {
swap(a, i, j);
i++;
j--;
}
}
private void swap(char[] a, int i, int j) {
char temp = a[i];
a[i] = a[j];
a[j] = temp;
}
分析
二进制矩阵中的所有元素不是 0 就是 1 。
给你两个四叉树,quadTree1 和 quadTree2。其中 quadTree1 表示一个 n * n 二进制矩阵,而 quadTree2 表示另一个 n * n 二进制矩阵。
请你返回一个表示 n * n 二进制矩阵的四叉树,它是 quadTree1 和 quadTree2 所表示的两个二进制矩阵进行 按位逻辑或运算 的结果。
注意,当 isLeaf 为 False 时,你可以把 True 或者 False 赋值给节点,两种值都会被判题机制 接受 。
四叉树数据结构中,每个内部节点只有四个子节点。此外,每个节点都有两个属性:
我们可以按以下步骤为二维区域构建四叉树:
如果当前网格的值相同(即,全为 0 或者全为 1),将 isLeaf 设为 True ,将 val 设为网格相应的值,并将四个子节点都设为 Null 然后停止。
如果当前网格的值不同,将 isLeaf 设为 False, 将 val 设为任意值,然如下图所示,将当前网格划分为四个子网格。
使用适当的子网格递归每个子节点。
如果你想了解更多关于四叉树的内容,可以参考 wiki 。
四叉树格式:
输出为使用层序遍历后四叉树的序列化形式,其中 null 表示路径终止符,其下面不存在节点。
它与二叉树的序列化非常相似。唯一的区别是节点以列表形式表示 [isLeaf, val] 。
如果 isLeaf 或者 val 的值为 True ,则表示它在列表 [isLeaf, val] 中的值为 1 ;如果 isLeaf 或者 val 的值为 False ,则表示值为 0 。
解答
public Node intersect(Node quadTree1, Node quadTree2) {
if(quadTree1.isLeaf){
if(quadTree1.val){
return quadTree1;
}else{
return quadTree2;
}
}else{
if(quadTree2.isLeaf){
if(quadTree2.val)
return quadTree2;
else return quadTree1;
}else{
Node topLeft = intersect(quadTree1.topLeft,quadTree2.topLeft);
Node topRight = intersect(quadTree1.topRight,quadTree2.topRight);
Node bottomLeft = intersect(quadTree1.bottomLeft,quadTree2.bottomLeft);
Node bottomRight = intersect(quadTree1.bottomRight,quadTree2.bottomRight);
if(topLeft.isLeaf && topLeft.val && topRight.isLeaf && topRight.val && bottomLeft.isLeaf && bottomLeft.val && bottomRight.isLeaf && bottomRight.val){
return new Node(true,true,null,null,null,null);
}
return new Node(true,false,topLeft,topRight,bottomLeft,bottomRight);
}
}
}
分析
给定一个 N 叉树,找到其最大深度。
最大深度是指从根节点到最远叶子节点的最长路径上的节点总数。
N 叉树输入按层序遍历序列化表示,每组子节点由空值分隔(请参见示例)。
提示:
树的深度不会超过 1000 。
树的节点数目位于 [0, 104] 之间。
解答
public int maxDepth(Node root) {
if(root == null)return 0;
List<Node> list = new ArrayList<>();
list.add(root);
int res = 0;
while(list.size()>0){
res++;
List<Node> temp = new ArrayList<>();
int index =0;
while(index < list.size()){
Node cur = list.get(index++);
temp.addAll(cur.children);
}
list = temp;
}
return res;
}
分析
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
解答
//方法1
public int subarraySum(int[] nums, int k) {
int count = 0;
for (int start = 0; start < nums.length; ++start) {
int sum = 0;
for (int end = start; end >= 0; --end) {
sum += nums[end];
if (sum == k) {
count++;
}
}
}
return count;
}
//方法2
public int subarraySum(int[] nums, int k) {
Map<Integer,Integer> map = new HashMap<>();
int preSum = 0;
int res = 0;
map.put(0, 1);
for(int i = 0;i < nums.length;i++){
preSum += nums[i];
res += map.getOrDefault(preSum - k,0);
map.put(preSum,map.getOrDefault(preSum,0) + 1);
}
return res;
}
分析
方法1 暴力前缀和。
方法2 前缀和+hashMap优化
此题在乎的是连续的子数组的个数,并不需要具体的组合情况,所以可以用map来记录前缀和对应的前缀和出现的次数。
一次遍历得到每一步的前缀和preSum
那么此时需要考虑前面有多少个前缀和为preSum - k的组合。
因为 preSum - (preSum - k) = k。这样就可以得到基于当前的前缀和计算出有多少个联系的子数组 可以构成k。
维护map 前缀和的个数。
提交结果