【算法练习】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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@u@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值