LeetCode每日一题(三月)
一、Z字形变换(中等)
找规律的题,一定要先找到他的周期,用把下标对周期取余,就能很快做出判断。
class Solution {
public String convert(String s, int numRows) {
if (s.length() == 1 || numRows == 1 || numRows >= s.length()) return s;
char[] tmp = s.toCharArray();
char[][] ans = new char[numRows][s.length()];
int x = 0;
int y = 0;
// 周期数
int r = 2 * numRows - 2;
for (int i = 0; i < s.length(); i++) {
ans[x][y] = tmp[i];
if (i % r < numRows - 1) {
// 向下走
x++;
} else {
// 斜向上走
x--;
y++;
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < numRows; i++) {
for (int j = 0; j < s.length(); j++) {
if (ans[i][j] != 0) {
sb.append(ans[i][j]);
}
}
}
return sb.toString();
}
}
二、单词接龙(困难)
联想一下之前的最短路径问题,相连的节点,就相当于是一个字符不同的两个字符串,而且是无向、无权图的最短路径问题,那就不需要Dijkstra、SPFA,直接用普通的BFS就能实现无权图的最短路径。
既然可以用普通的BFS实现,那就不需要用SPFA等高级算法,直接用队列就能够实现。
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if (!wordList.contains(endWord)) return 0;
boolean[] vis = new boolean[wordList.size()];
Queue<String> queue = new LinkedList<>();
queue.offer(beginWord);
// 记录最短路径长度
int count = 1;
while (!queue.isEmpty()) {
int size = queue.size();
// 遍历当前队列中的所有单词
for (int i = 0; i < size; i++) {
String cur = queue.poll();
for (int j = 0; j < wordList.size(); j++) {
if (vis[j]) continue;
String next = wordList.get(j);
if (!checck(cur, next)) continue;
if (next.equals(endWord)) return count + 1;
vis[j] = true;
queue.offer(next);
}
}
count++;
}
return 0;
}
public boolean checck(String a, String b) {
int cnt = 0;
for (int i = 0; i < a.length(); i++) {
if (a.charAt(i) != b.charAt(i)) {
cnt++;
}
if (cnt > 1) {
return false;
}
}
return true;
}
}
可能会疑惑,为什么迷宫类问题,BFS里面就没有for循环去遍历每一个队列中的元素呢?
两种方式都没错,本题的方式更加贴合BFS的特性,每次都让队列中的元素向外扩展一格,而之前我写的BFS题目中,它直接把扩展出来的元素又继续放回队列中,通过对队列的遍历,实现对扩展元素的遍历,也就不需要这个for循环了,但是强烈建议使用for循环,它可以很好的方便记录路径长度。
例如仙岛求药这道题:https://nanti.jisuanke.com/t/T1212
按照上面for循环的写法,也可以写成下面这样:
import java.util.LinkedList;
import java.util.Queue;
import java.util.Scanner;
// Queue存储的结点
class node {
int x, y;
node() {}
node(int x, int y) {
this.x = x;
this.y = y;
}
}
public class Main {
public static void main(String[] args) {
int[][] vis = new int[30][30];
int[] x = new int[] {-1, 1, 0, 0};
int[] y = new int[] {0, 0, -1, 1};
int m, n;
Scanner scan = new Scanner(System.in);
m = scan.nextInt();
n = scan.nextInt();
char[][] map = new char[m][n];
Queue<node> Q = new LinkedList<>();
for (int i = 0; i < m; i++) {
map[i] = scan.next().toCharArray();
for (int j = 0; j < n; j++) {
if (map[i][j] == '@') {
// 计数包括初始位置的方块
node b = new node(i, j);
// 入队
Q.offer(b);
}
}
}
int count = 1;
boolean flag = false;
// 开始BFS
while(!Q.isEmpty()) {
if (flag) {
break;
}
// 直接遍历当前队列中所有节点
int size = Q.size();
for (int i = 0; i < size; i++) {
if (flag) {
break;
}
node tmp = Q.poll();
vis[tmp.x][tmp.y] = 1;
// 遍历四个方向
for (int j = 0; j < 4; j++) {
node temp = new node();
temp.x = tmp.x + x[j];
temp.y = tmp.y + y[j];
// 不满足条件就continue
if (temp.x < 0 || temp.y < 0 || temp.x >= m || temp.y >= n || vis[temp.x][temp.y] == 1 || map[temp.x][temp.y] == '#') {
continue;
}
if (map[temp.x][temp.y] == '*') {
flag = true;
break;
}
// 入队标记为true
vis[temp.x][temp.y] = 1;
Q.offer(temp);
}
}
// 整体完成一轮,路径长度 + 1
count++;
}
if (flag) {
System.out.println(count - 1);
} else {
System.out.println(-1);
}
}
}
三、子数组范围和(中等、区间DP)
区间DP,dp[i][j][k],代表区间i、j的最大最小值,k取0、1,0代表最小值、1代表最大值,先预处理整个数组,再统计和即可。
class Solution {
public long subArrayRanges(int[] nums) {
int n = nums.length;
int[][][] dp = new int[n][n][2];
for (int i = 0; i < n; i++) {
// 只包含当前数的最大最小值就是自身
dp[i][i][0] = nums[i];
dp[i][i][1] = nums[i];
}
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
dp[i][j][0] = Math.min(dp[i][j - 1][0], nums[j]);
dp[i][j][1] = Math.max(dp[i][j - 1][1], nums[j]);
}
}
long ans = 0;
for (int i = 0; i < n; i++) {
for (int j = i; j < n; j++) {
ans += dp[i][j][1] - dp[i][j][0];
}
}
return ans;
}
}
四、适合打劫银行的日子(中等、简单DP)
先从左往右统计每一天警卫非递增的天数,然后从右往左统计每一天警卫非递减的天数,最后对于每一天,判断当前天的前序部分的天数是否>=time,后序部分的天数是否>=time,即可。
class Solution {
public List<Integer> goodDaysToRobBank(int[] security, int time) {
List<Integer> ans = new LinkedList<>();
if (time == 0) {
// time=0每一天都可以
for (int i = 0; i < security.length; i++) {
ans.add(i);
}
} else {
// 从左到右统计前连续
int[] cnt = new int[security.length];
cnt[0] = 1;
for (int i = 1; i < security.length; i++) {
if (security[i] <= security[i - 1]) {
cnt[i] = cnt[i - 1] + 1;
} else {
cnt[i] = 1;
}
}
// 从右往左统计后连续
int[] cntt = new int[security.length];
cntt[security.length - 1] = 1;
for (int i = security.length - 2; i >= 0; i--) {
if (security[i] <= security[i + 1]) {
cntt[i] = cntt[i + 1] + 1;
} else {
cntt[i] = 1;
}
}
for (int i = 0; i < security.length; i++) {
if (cnt[i] - 1 >= time && cntt[i] - 1 >= time) {
ans.add(i);
}
}
}
return ans;
}
}