Java实现LeetCode 题号:781 - 790

135 阅读8分钟

「这是我参与2022首次更文挑战的第24天,活动详情查看:2022首次更文挑战」。

LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode

781. 森林中的兔子

森林中,每个兔子都有颜色。其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。我们将这些回答放在 answers 数组里。

返回森林中兔子的最少数量。

示例: 输入: answers = [1, 1, 2] 输出: 5 解释: 两只回答了 "1" 的兔子可能有相同的颜色,设为红色。 之后回答了 "2" 的兔子不会是红色,否则他们的回答会相互矛盾。 设回答了 "2" 的兔子为蓝色。 此外,森林中还应有另外 2 只蓝色兔子的回答没有包含在数组中。 因此森林中兔子的最少数量是 5: 3 只回答的和 2 只没有回答的。

输入: answers = [10, 10, 10] 输出: 11

输入: answers = [] 输出: 0 说明:

answers 的长度最大为1000。 answers[i] 是在 [0, 999] 范围内的整数。

class Solution {
        public int numRabbits(int[] answers) {
        Map<Integer,Integer> map = new HashMap<>();
        int num = 0;
        for (int answer : answers) {
            //如果存在当前的数量的话,那么我们可以认为这两个是一样的颜色
            if(map.containsKey(answer)&&map.get(answer)>0){
                //然后给这个数量-1,因为是一样的了
                //这里是因为,我第一次如果是6只,我已经加进去了,
                //所以后面再出现相同的6只六次以内得话
                //我是不需要计算的,因为前面我们已经放进去了
                map.put(answer,map.get(answer)-1);
            }else {
                //这里描述得是,我其他还有多少兔子和我颜色一样,
                //所以这个颜色得兔子数是answer+1
                num+=answer+1;
                map.put(answer,answer);
            }
        }
        return num;
    }
}

783. 二叉搜索树节点最小距离

给定一个二叉搜索树的根节点 root,返回树中任意两节点的差的最小值。

示例:

输入: root = [4,2,6,1,3,null,null] 输出: 1 解释: 注意,root是树节点对象(TreeNode object),而不是数组。

给定的树 [4,2,6,1,3,null,null] 可表示为下图:

      4
    /   \
  2      6
 / \    
1   3  

最小的差值是 1, 它是节点1和节点2的差值, 也是节点3和节点2的差值。

注意:

二叉树的大小范围在 2 到 100。 二叉树总是有效的,每个节点的值都是整数,且不重复。 本题与 530:leetcode-cn.com/problems/mi… 相同

PS: 递归遍历 递归过程中,计算最小差的绝对值
 
class Solution {
    Integer prev = null, ans = Integer.MAX_VALUE;

    public int minDiffInBST(TreeNode root) {
        test(root);
        return ans;
    }

    public void test(TreeNode treeNode) {
        if (treeNode == null) {
            return;
        }
        test(treeNode.left);
        if (prev != null) {
            ans = Math.min(ans, treeNode.val - prev);
        }
        prev = treeNode.val;
        test(treeNode.right);
    }
}

784. 字母大小写全排列

给定一个字符串S,通过将字符串S中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

示例: 输入: S = "a1b2" 输出: ["a1b2", "a1B2", "A1b2", "A1B2"]

输入: S = "3z4" 输出: ["3z4", "3Z4"]

输入: S = "12345" 输出: ["12345"] 注意:

S 的长度不超过12。 S 仅由数字和字母组成。

PS:
    全排列,不过这次全排列的时候不仅要一个了,如果是字母的话就要大写小写都进行排列

class Solution {
      public List<String> letterCasePermutation(String S) {
    	List<String> res = new ArrayList<String>();
    	dfs(S.toCharArray(), res, 0);
    	return res;
    }
	private void dfs(char[] charArray, List<String> res, int index) {
		if(index == charArray.length){
			res.add(String.valueOf(charArray));
			return;
		}
		char ch = charArray[index];
		if(Character.isLetter(ch)){
			charArray[index] = Character.toLowerCase(ch);
			dfs(charArray, res, index + 1);
			charArray[index] = Character.toUpperCase(ch);
			dfs(charArray, res, index + 1);
		}else{
			dfs(charArray, res, index + 1);
		}
	}
}

785. 判断二分图

给定一个无向图graph,当这个图为二分图时返回true。

如果我们能将一个图的节点集合分割成两个独立的子集A和B,并使图中的每一条边的两个节点一个来自A集合,一个来自B集合,我们就将这个图称为二分图。

graph将会以邻接表方式给出,graph[i]表示图中与节点i相连的所有节点。每个节点都是一个在0到graph.length-1之间的整数。这图中没有自环和平行边: graph[i] 中不存在i,并且graph[i]中没有重复的值。

示例 1:
输入: [[1,3], [0,2], [1,3], [0,2]]
输出: true
解释: 
无向图如下:
0----1
|    |
|    |
3----2
我们可以将节点分成两组: {0, 2} 和 {1, 3}。

示例 2:
输入: [[1,2,3], [0,2], [0,1,3], [0,2]]
输出: false
解释: 
无向图如下:
0----1
| \  |
|  \ |
3----2
我们不能将节点分割成两个独立的子集。

注意:

graph 的长度范围为 [1, 100]。 graph[i] 中的元素的范围为 [0, graph.length - 1]。 graph[i] 不会包含 i 或者有重复的值。 图是无向的: 如果j 在 graph[i]里边, 那么 i 也会在 graph[j]里边。

PS:
	首先二分图的判断就是,有两种颜色,相邻点的不能是相同颜色,
	如果出现了相同的颜色,就不能是二分图
class Solution {
     public boolean isBipartite(int[][] graph) {
        int n = graph.length;
        int[] color = new int[n];
        Arrays.fill(color, -1);

        for (int start = 0; start < n; ++start) {
            if (color[start] == -1) {
                Stack<Integer> stack = new Stack();
                stack.push(start);
                color[start] = 0;

                while (!stack.empty()) {
                    Integer node = stack.pop();
                    for (int nei: graph[node]) {
                        if (color[nei] == -1) {
                            stack.push(nei);
                            color[nei] = color[node] ^ 1;
                        } else if (color[nei] == color[node]) {
                            return false;
                        }
                    }
                }
            }
        }

        return true;
    } 
}

786. 第 K 个最小的素数分数

一个已排序好的表 A,其包含 1 和其他一些素数. 当列表中的每一个 p<q 时,我们可以构造一个分数 p/q 。

那么第 k 个最小的分数是多少呢? 以整数数组的形式返回你的答案, 这里 answer[0] = p 且 answer[1] = q.

示例: 输入: A = [1, 2, 3, 5], K = 3 输出: [2, 5] 解释: 已构造好的分数,排序后如下所示: 1/5, 1/3, 2/5, 1/2, 3/5, 2/3. 很明显第三个最小的分数是 2/5.

输入: A = [1, 7], K = 1 输出: [1, 7] 注意:

A 长度的取值范围在 2 — 2000. 每个 A[i] 的值在 1 —30000. K 取值范围为 1 —A.length * (A.length - 1) / 2

PS:
	因为求出来的都是小于1的,我们直接按照值得范围去查找
	大小堆主要是分堆得时候不好分
class Solution {
  public int[] kthSmallestPrimeFraction(int[] A, int K) {
        if (null == A || A.length <= 0) return A;
        double lo = 0, hi = 1;
        int[] ans = new int[2];
        while (lo <= hi) {
            double mid = lo + (hi - lo) / 2;
            int[] finds = findKthFraction(A, mid);
            if (finds[0] < K)
                lo = mid;
            else if (finds[0] > K)
                hi = mid;
            else {
                ans[0] = finds[1];
                ans[1] = finds[2];
                return ans;
            }
        }
        return ans;
    }

    private int[] findKthFraction(int[] fraction, double x) {
        int p = 0, q = 1, count = 0, i = -1; //p 分子  q 分母
        for (int j = 1; j < fraction.length; ++j) {
            //这里的筛选是,按照指定的值的范围去寻找,本来是fra[i+1]/fra[j]<x
            while (i < fraction.length && fraction[i + 1] < fraction[j] * x) ++i;
            count += i + 1;
            //  A / B - C / D = A * D / B * D - C * B / B * D;
            //=> A * D - B * C
            if (i >= 0 && p * fraction[j] < q * fraction[i]) {
                p = fraction[i];
                q = fraction[j];
            }
        }
        return new int[]{count, p, q};
    }
}

787. K 站中转内最便宜的航班

有 n 个城市通过 m 个航班连接。每个航班都从城市 u 开始,以价格 w 抵达 v。

现在给定所有的城市和航班,以及出发城市 src 和目的地 dst,你的任务是找到从 src 到 dst 最多经过 k 站中转的最便宜的价格。 如果没有这样的路线,则输出 -1。

示例 1:

输入: n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]] src = 0, dst = 2, k = 1 输出: 200 解释: 城市航班图如下

从城市 0 到城市 2 在 1 站中转以内的最便宜价格是 200,如图中红色所示。 示例 2:

输入: n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]] src = 0, dst = 2, k = 0 输出: 500 解释: 城市航班图如下

从城市 0 到城市 2 在 0 站中转以内的最便宜价格是 500,如图中蓝色所示。

提示:

n 范围是 [1, 100],城市标签从 0 到 n - 1. 航班数量范围是 [0, n * (n - 1) / 2]. 每个航班的格式 (src, dst, price). 每个航班的价格范围是 [1, 10000]. k 范围是 [0, n - 1]. 航班没有重复,且不存在环路

class Solution {

    //这个dp是第一个是到的目标的位置,第二个参数是步数
    //    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int k) {
    //     int dp[][]=new int[n][k+2];
    //     for(int i=0;i<n;i++)
    //         Arrays.fill(dp[i],Integer.MAX_VALUE);
    //     这里初始化了起始点的距离
    //     Arrays.fill(dp[src],0);
    //     for(int per=1;per<=k+1;per++)
    //     {
            
    //         for(int []num:flights)
    //         {
    //         int from=num[0];
    //         int to=num[1];
    //         int cost=num[2];
    //         //我当前的路程是不是存在路程,如果不存在就是MAX_VALUE
    //         //我当前路程的最小值就是
    //            if(dp[from][per-1]!=Integer.MAX_VALUE) dp[to][per]=Math.min(dp[to][per],dp[from][per-1]+cost);
    //         }
            
    //     }
    //     return dp[dst][k+1]==Integer.MAX_VALUE?-1:dp[dst][k+1];


    // }

    //这个dp第一个参数是起始位置,第二个参数是目标位置
    public int findCheapestPrice(int n, int[][] flights, int src, int dst, int K) {
  int[][] dp = new int[n][K+2];
        for(int i = 0; i < n; ++i) Arrays.fill(dp[i], Integer.MAX_VALUE);
        for(int k = 0; k <= K+1; ++k) dp[src][k] = 0;
        for(int k = 1; k <= K+1; ++k) {
            for(int[] flight : flights) {
                if(dp[flight[0]][k-1] != Integer.MAX_VALUE)
                    dp[flight[1]][k] = Math.min(dp[flight[1]][k], dp[flight[0]][k-1] + flight[2]);
            }
        }
        return dp[dst][K+1] == Integer.MAX_VALUE ? -1 : dp[dst][K+1];
    }
}

788. 旋转数字

我们称一个数 X 为好数, 如果它的每位数字逐个地被旋转 180 度后,我们仍可以得到一个有效的,且和 X 不同的数。要求每位数字都要被旋转。

如果一个数的每位数字被旋转以后仍然还是一个数字, 则这个数是有效的。0, 1, 和 8 被旋转后仍然是它们自己;2 和 5 可以互相旋转成对方(在这种情况下,它们以不同的方向旋转,换句话说,2 和 5 互为镜像);6 和 9 同理,除了这些以外其他的数字旋转以后都不再是有效的数字。

现在我们有一个正整数 N, 计算从 1 到 N 中有多少个数 X 是好数?

示例:

输入: 10 输出: 4 解释: 在[1, 10]中有四个好数: 2, 5, 6, 9。 注意 1 和 10 不是好数, 因为他们在旋转之后不变。

提示:

N 的取值范围是 [1, 10000]。

PS:
    创建一个数组保存
    memo第一位是长度,也就是N的第几位就代表几
    第二位是要规定是否小于 N 的第几位
    第三位是看是否是旋转后还是自己的,如果旋转后还是自己的字符旋转后没有什么意义
class Solution {
     public int rotatedDigits(int N) {
        char[] A = String.valueOf(N).toCharArray();
        int K = A.length;

        int[][][] memo = new int[K+1][2][2];
        memo[K][0][1] = memo[K][1][1] = 1;
        for (int i = K - 1; i >= 0; --i) {
            for (int eqf = 0; eqf <= 1; ++eqf)
                for (int invf = 0; invf <= 1; ++invf) {
                    int ans = 0;
                    for (char d = '0'; d <= (eqf == 1 ? A[i] : '9'); ++d) {
                        if (d == '3' || d == '4' || d == '7') continue;
                        boolean invo = (d == '2' || d == '5' || d == '6' || d == '9');
                        ans += memo[i+1][d == A[i] ? eqf : 0][invo ? 1 : invf];
                    }
                    memo[i][eqf][invf] = ans;
                }
        }

        return memo[0][1][0];
    }
}

789. 逃脱阻碍者

你在进行一个简化版的吃豆人游戏。你从 (0, 0) 点开始出发,你的目的地是 (target[0], target[1]) 。地图上有一些阻碍者,第 i 个阻碍者从 (ghosts[i][0], ghosts[i][1]) 出发。

每一回合,你和阻碍者们可以同时向东,西,南,北四个方向移动,每次可以移动到距离原位置1个单位的新位置。

如果你可以在任何阻碍者抓住你之前到达目的地(阻碍者可以采取任意行动方式),则被视为逃脱成功。如果你和阻碍者同时到达了一个位置(包括目的地)都不算是逃脱成功。

当且仅当你有可能成功逃脱时,输出 True。

示例 1: 输入: ghosts = [[1, 0], [0, 3]] target = [0, 1] 输出:true 解释: 你可以直接一步到达目的地(0,1),在(1, 0)或者(0, 3)位置的阻碍者都不可能抓住你。 示例 2: 输入: ghosts = [[1, 0]] target = [2, 0] 输出:false 解释: 你需要走到位于(2, 0)的目的地,但是在(1, 0)的阻碍者位于你和目的地之间。 示例 3: 输入: ghosts = [[2, 0]] target = [1, 0] 输出:false 解释: 阻碍者可以和你同时达到目的地。 说明:

所有的点的坐标值的绝对值 <= 10000。 阻碍者的数量不会超过 100。

PS:
这个题表达的意思是,想进一切办法,比其他人先到终点,或者让其他人走过终点,
他是重复你走的方向,所以只需要判断一下曼哈顿距离(就是表示两个点在标准坐标系上的绝对轴距之和)就好了,有不懂的欢迎评论
	
class Solution {
    public boolean escapeGhosts(int[][] ghosts, int[] target) {
        int[] source = new int[]{0, 0};
        for (int[] ghost: ghosts)
            if (taxi(ghost, target) <= taxi(source, target))
                return false;
        return true;
    }

    public int taxi(int[] P, int[] Q) {
        return Math.abs(P[0] - Q[0]) + Math.abs(P[1] - Q[1]);
    }
 
}

790. 多米诺和托米诺平铺

有两种形状的瓷砖:一种是 2x1 的多米诺形,另一种是形如 "L" 的托米诺形。两种形状都可以旋转。

XX <- 多米诺

XX <- "L" 托米诺 X 给定 N 的值,有多少种方法可以平铺 2 x N 的面板?返回值 mod 10^9 + 7。

(平铺指的是每个正方形都必须有瓷砖覆盖。两个平铺不同,当且仅当面板上有四个方向上的相邻单元中的两个,使得恰好有一个平铺有一个瓷砖占据两个正方形。)

示例: 输入: 3 输出: 5 解释: 下面列出了五种不同的方法,不同字母代表不同瓷砖: XYZ XXZ XYY XXY XYY XYZ YYZ XZZ XYY XXY 提示:

N 的范围是 [1, 1000]

class Solution {


    //dp[i][0]是第n行,并且是平铺
    //dp[i][1]是第n行,不平铺的

    // public int numTilings(int N) {
    //     long[][] dp = new long[N+1][3];
    //     dp[0][0] = 1;
    //     dp[0][1] = 0;
    //     int MOD = 1000000007;
    //     for(int i = 1 ; i <= N ; ++i){
    //         long temp = i < 2 ? 0 : dp[i -2][0];
    //         dp[i][0] = (temp + dp[i-1][0] + 2 * dp[i-1][1]) % MOD;
    //         dp[i][1] = (temp +dp[i-1][1]) % MOD;
    //     }
    //     return (int)dp[N][0];
    // }

    
    public int numTilings(int N) {
         int mod = 1000000007;
        int[] dp = new int[N+3];
        dp[0] = 1;
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 5;
        for(int i = 4; i <= N; i++){
            //这里全是平铺的,
            //我当前这一位,可以是我上一位平铺的+一个2*1的,
            //我可以把1*2的放到最上面,也可以放在最下面,是两种可能,所以*2
            //还可以是我三位前的那个,因为可以是两个L
            //但这里,我开头或者结尾可能是L的,如果我们在那个基础上加上L
            //可能会导致重复,以至于要/2,也就变成了三位前的那个*2/2==1
            dp[i] = (2*(dp[i-1] % mod) % mod + dp[i-3] % mod) % mod;
        }
        return dp[N] % mod;
    }

}