第三十七天
688 骑士在棋盘上的概率
在一个 n x n
的国际象棋棋盘上,一个骑士从单元格 (row, column)
开始,并尝试进行 k
次移动。行和列是 从 0 开始 的,所以左上单元格是(0,0)
,右下单元格是 (n - 1, n - 1)
。
象棋骑士有8种可能的走法,如下图所示。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。
每次骑士要移动时,它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。
骑士继续移动,直到它走了 k
步或离开了棋盘。
返回 骑士在棋盘停止移动后仍留在棋盘上的概率 。
方法
容易知道,如果我们知道了在棋盘的位置(i,j)
它的概率,那么对于它下一个能够跳到的位置(i + dx, j + dy)
,它的概率应该是所有能够跳到该位置的概率之和,而从i,j
跳到i + dx, j + dy
,它的概率是前者的概率乘上1/8
。基于上述事实,我们可以使用动态规划来解决这个问题。
首先我们定义dp[step][i][j]
表示用step
步,从i,j
出发,最终能够留在棋盘内的概率,则状态转移方程为:
dp[step][i][j]=18Σdp[step−1][i+dx][j+dy]
dp[step][i][j]=\frac 1 8 \Sigma dp[step - 1][i+dx][j+dy]
dp[step][i][j]=81Σdp[step−1][i+dx][j+dy]
初始状态,当step
为0
,概率为1
。
class Solution {
public static int[][] dirs = {
{2, 1}, {2, -1}, {-2, 1}, {-2, -1}, {1, 2}, {1, -2}, {-1, 2}, {-1, -2}
};
public double knightProbability(int n, int k, int row, int column) {
double[][][] dp = new double[k + 1][n + 1][n + 1];
for (int step = 0; step <= k; ++step){
for (int i = 0; i < n; ++i){
for (int j = 0; j < n; ++j){
if (step == 0) dp[step][i][j] = 1;
else{
for (int delta = 0; delta < 8; ++delta){
if (i + dirs[delta][0] >= 0 && i + dirs[delta][0] < n && j + dirs[delta][1] >= 0 && j + dirs[delta][1] < n){
dp[step][i][j] += 0.125 * dp[step - 1][i + dirs[delta][0]][j + dirs[delta][1]];
}
}
}
}
}
}
return dp[k][row][column];
}
}
10.02 变位词组
编写一种方法,对字符串数组进行排序,将所有变位词组合在一起。变位词是指字母相同,但排列不同的字符串。
**注意:**本题相对原题稍作修改
示例:
输入: ["eat", "tea", "tan", "ate", "nat", "bat"],
输出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
说明:
-
所有输入均为小写字母。
-
不考虑答案输出的顺序。
方法
使用一个长度为26
的数组记录每一个单词中字母出现的数量,用于作为是否属于同一类单词的唯一标识。
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<WordRegardLessOrder, List<String>> map = new HashMap<>();
for (String str : strs) {
WordRegardLessOrder word = new WordRegardLessOrder(str);
if (map.containsKey(word)) map.get(word).add(str);
else{
ArrayList<String> words = new ArrayList<>();
words.add(str);
map.put(word, words);
}
}
return new ArrayList<>(map.values());
}
}
class WordRegardLessOrder{
String word;
public WordRegardLessOrder(String s){word = s;}
@Override public boolean equals(Object p){
WordRegardLessOrder cmp = (WordRegardLessOrder) p;
if (word.length() != cmp.word.length()) return false;
int[] cnt = new int[26];
for (int i = 0; i < word.length(); ++i){
cnt[word.charAt(i) - 'a']++;
cnt[cmp.word.charAt(i) - 'a']--;
}
for (int i : cnt) if (i != 0) return false;
return true;
}
@Override public int hashCode(){
int res = 0;
for (int i = 0; i < word.length(); ++i) res ^= word.charAt(i);
return res;
}
}
1300 转变数组后最接近目标值的数组和
给你一个整数数组 arr
和一个目标值 target
,请你返回一个整数 value
,使得将数组中所有大于 value
的值变成 value
后,数组的和最接近 target
(最接近表示两者之差的绝对值最小)。
如果有多种使得和最接近 target
的方案,请你返回这些整数中的最小值。
请注意,答案不一定是 arr
中的数字。
示例 1:
输入:arr = [4,9,3], target = 10
输出:3
解释:当选择 value 为 3 时,数组会变成 [3, 3, 3],和为 9 ,这是最接近 target 的方案。
提示:
-
1 <= arr.length <= 10^4
-
1 <= arr[i], target <= 10^5
方法
容易知道答案的左边界是0
,右边界是数组的最大值,我们可以使用二分查找来解决这个问题。
如果我们当前正在搜索的值mid
所组成的新数组的和比target
大,我们调整r=mid-1
否则l=mid+1
返回答案时我们只需要比较l
和r
哪个的差值更小即可。
class Solution {
public int findBestValue(int[] arr, int target) {
int l = 0, r = 0;
for (int i : arr) r = Math.max(r, i);
while (l <= r){
System.out.println(l + " " + r);
int mid = (l + r) >> 1;
if (getDiffer(arr, mid) > target){
r = mid - 1;
}
else if (getDiffer(arr, mid) == target){
return mid;
}
else{
l = mid + 1;
}
}
//System.out.println(l + " " + r);
return Math.abs(getDiffer(arr, r) - target) > Math.abs(getDiffer(arr, l) - target) ? l : r;
}
public int getDiffer(int[] arr, int value) {
int res = 0;
for (int i : arr) {
if (i < value) res += i;
else res += value;
}
return res;
}
}