简单直接的方法
1.将int数组转换成一个字符串
2.定义一个新数组长度是字符串的长度
3.用chartAt方法取出每一个下标下的char转为数字塞进新数组里
![]()
1.将int数组转换成一个字符串
2.定义一个新数组长度是字符串的长度
3.用chartAt方法取出每一个下标下的char转为数字塞进新数组里
![]()
给你一个正整数数组 nums ,请你返回一个数组 answer ,你需要将 nums 中每个整数进行数位分割后,按照 nums 中出现的 相同顺序 放入答案数组中。
对一个整数进行数位分割,指的是将整数各个数位按原本出现的顺序排列成数组。
10921 ,分割它的各个数位得到 [1,0,9,2,1] 。
示例 1:
输入:nums = [13,25,83,77] 输出:[1,3,2,5,8,3,7,7] 解释: - 分割 13 得到 [1,3] 。 - 分割 25 得到 [2,5] 。 - 分割 83 得到 [8,3] 。 - 分割 77 得到 [7,7] 。 answer = [1,3,2,5,8,3,7,7] 。answer 中的数字分割结果按照原数字在数组中的相同顺序排列。
示例 2:
输入:nums = [7,1,3,9] 输出:[7,1,3,9] 解释:nums 中每个整数的分割是它自己。 answer = [7,1,3,9] 。
提示:
1 <= nums.length <= 10001 <= nums[i] <= 105这道题要求将给定的正整数数组 $\textit{nums}$ 中的所有正整数按数位分割,保持数位顺序存入答案数组。需要首先遍历数组 $\textit{nums}$ 得到数位总数,然后将每个正整数按数位分割并存入答案数组。
首先遍历数组 $\textit{nums}$ 得到所有正整数的数位总数 $\textit{totalLength}$,然后创建长度为 $\textit{totalLength}$ 的答案数组 $\textit{answer}$,再次遍历数组 $\textit{nums}$,遍历过程中维护答案数组的当前下标 $\textit{index}$,对于每个正整数,执行如下操作。
用 $\textit{start}$ 表示当前正整数的数位填入答案数组的起始下标,$\textit{start} = \textit{index}$。
每次将 $\textit{num}$ 的最低位填入 $\textit{answer}[\textit{index}]$,然后将 $\textit{index}$ 的值增加 $1$,重复该操作直到 $\textit{num}$ 的所有位都填入答案数组。
当前正整数 $\textit{num}$ 填入答案数组的下标范围是 $[\textit{start}, \textit{index} - 1]$,为按照数位从低到高的顺序填入。为了和数组 $\textit{nums}$ 中的数位顺序保持一致,需要将答案数组的下标范围 $[\textit{start}, \textit{index} - 1]$ 的子数组翻转。
遍历结束之后,即可得到答案数组。
###Java
class Solution {
public int[] separateDigits(int[] nums) {
int totalLength = 0;
for (int num : nums) {
totalLength += getLength(num);
}
int[] answer = new int[totalLength];
int index = 0;
for (int num : nums) {
int start = index;
int temp = num;
while (temp != 0) {
answer[index] = temp % 10;
index++;
temp /= 10;
}
reverse(answer, start, index - 1);
}
return answer;
}
public int getLength(int num) {
int length = 0;
while (num != 0) {
length++;
num /= 10;
}
return length;
}
public void reverse(int[] answer, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
int temp = answer[i];
answer[i] = answer[j];
answer[j] = temp;
}
}
}
###C#
public class Solution {
public int[] SeparateDigits(int[] nums) {
int totalLength = 0;
foreach (int num in nums) {
totalLength += GetLength(num);
}
int[] answer = new int[totalLength];
int index = 0;
foreach (int num in nums) {
int start = index;
int temp = num;
while (temp != 0) {
answer[index] = temp % 10;
index++;
temp /= 10;
}
Reverse(answer, start, index - 1);
}
return answer;
}
public int GetLength(int num) {
int length = 0;
while (num != 0) {
length++;
num /= 10;
}
return length;
}
public void Reverse(int[] answer, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
int temp = answer[i];
answer[i] = answer[j];
answer[j] = temp;
}
}
}
###C++
class Solution {
public:
vector<int> separateDigits(vector<int>& nums) {
int totalLength = 0;
for (int num : nums) {
totalLength += getLength(num);
}
vector<int> answer(totalLength);
int index = 0;
for (int num : nums) {
int start = index;
int temp = num;
while (temp != 0) {
answer[index] = temp % 10;
index++;
temp /= 10;
}
reverse(answer, start, index - 1);
}
return answer;
}
int getLength(int num) {
int length = 0;
while (num != 0) {
length++;
num /= 10;
}
return length;
}
void reverse(vector<int>& answer, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
swap(answer[i], answer[j]);
}
}
};
###Python
class Solution:
def separateDigits(self, nums: List[int]) -> List[int]:
answer = []
index = 0
for num in nums:
start = index
temp = num
while temp != 0:
answer.append(temp % 10)
index += 1
temp //= 10
self.reverse(answer, start, index - 1)
return answer
def getLength(self, num: int) -> int:
length = 0
while num != 0:
length += 1
num //= 10
return length
def reverse(self, answer: List[int], start: int, end: int) -> None:
i, j = start, end
while i < j:
answer[i], answer[j] = answer[j], answer[i]
i += 1
j -= 1
###C
int getLength(int num) {
int length = 0;
while (num != 0) {
length++;
num /= 10;
}
return length;
}
void reverse(int* answer, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
int temp = answer[i];
answer[i] = answer[j];
answer[j] = temp;
}
}
int* separateDigits(int* nums, int numsSize, int* returnSize) {
int totalLength = 0;
for (int i = 0; i < numsSize; i++) {
totalLength += getLength(nums[i]);
}
int* answer = (int*) malloc(sizeof(int) * totalLength);
*returnSize = totalLength;
int index = 0;
for (int i = 0; i < numsSize; i++) {
int start = index;
int temp = nums[i];
while (temp != 0) {
answer[index] = temp % 10;
index++;
temp /= 10;
}
reverse(answer, start, index - 1);
}
return answer;
}
###Go
func separateDigits(nums []int) []int {
totalLength := 0
for _, num := range nums {
totalLength += getLength(num)
}
answer := make([]int, totalLength)
index := 0
for _, num := range nums {
start := index
temp := num
for temp != 0 {
answer[index] = temp % 10
index++
temp /= 10
}
reverse(answer, start, index - 1)
}
return answer
}
func getLength(num int) int {
length := 0
for num != 0 {
length++
num /= 10
}
return length
}
func reverse(answer []int, start int, end int) {
for i, j := start, end; i < j; i, j = i + 1, j - 1 {
answer[i], answer[j] = answer[j], answer[i]
}
}
###JavaScript
var separateDigits = function(nums) {
let totalLength = 0;
for (let num of nums) {
totalLength += getLength(num);
}
let answer = new Array(totalLength);
let index = 0;
for (let num of nums) {
let start = index;
let temp = num;
while (temp !== 0) {
answer[index] = temp % 10;
index++;
temp = Math.floor(temp / 10);
}
reverse(answer, start, index - 1);
}
return answer;
};
var getLength = function(num) {
let length = 0;
while (num !== 0) {
length++;
num = Math.floor(num / 10);
}
return length;
};
var reverse = function(answer, start, end) {
for (let i = start, j = end; i < j; i++, j--) {
let temp = answer[i];
answer[i] = answer[j];
answer[j] = temp;
}
};
###TypeScript
function separateDigits(nums: number[]): number[] {
let totalLength = 0;
for (let num of nums) {
totalLength += getLength(num);
}
let answer = new Array(totalLength);
let index = 0;
for (let num of nums) {
let start = index;
let temp = num;
while (temp !== 0) {
answer[index] = temp % 10;
index++;
temp = Math.floor(temp / 10);
}
reverse(answer, start, index - 1);
}
return answer;
};
function getLength(num: number): number {
let length = 0;
while (num !== 0) {
length++;
num = Math.floor(num / 10);
}
return length;
};
function reverse(answer: number[], start: number, end: number): void {
for (let i = start, j = end; i < j; i++, j--) {
let temp = answer[i];
answer[i] = answer[j];
answer[j] = temp;
}
};
时间复杂度:$O(n \log_{10} m)$,其中 $n$ 是数组 $\textit{nums}$ 的长度,$m$ 是数组 $\textit{nums}$ 的最大元素。计算答案数组的长度与将数位填入答案数组的时间是 $O(n \log_{10} m)$。
空间复杂度:$O(1)$。注意返回值不计入空间复杂度。
这个刚开始写的时候担心数组的长度和时间复杂度的原因,担心通过不了,结果没想过通过了。
我是用一个result数组来装将返回的分割数位值,考虑他们的长度,每一个元素的大小在10的五次方以内,分割之后100000共有六位,也就是最大是6,加上数组的长度最大是1000,于是我就把数组的大小取为6*10000=60000.
虽然我也知道浪费空间,但是我目前没有更好的方法,希望有大佬能够指点。
确定了返回数组了,然后就是然后把原始的整数拆分插入到结果数组中,后面我想到只要逐个不断求余就可以得到每一位数,后面我就在第一层遍历中添加一个循环得到整数的各个数位,但是提交的结果不符合要求,题目要求将数位按原本出现的顺序排列成数组,于是我就用一个数组作为辅助空间,将它反向添加,于是就得到了和题目意思相同的结果,辅助空间的大小就是一个整数拥有的各个位数的数量,由前面可以知道最大是6。
之后返回即可。
int* separateDigits(int* nums, int numsSize, int* returnSize){
int *result=(int*)malloc(sizeof(int)*60000);
int count=0;
//遍历整个数组
for(int i=0;i<numsSize;i++){
int tmp=nums[i];
int help[6];
int tmpCount=0;
//得到整数的各个数位,将他们储存在一个辅助数组中
while(tmp){
help[tmpCount++]=tmp%10;
tmp=tmp/10;
}
//把数组中的元素倒序添加到结果数组中
for(int j=tmpCount-1;j>=0;j--){
result[count++]=help[j];
}
}
*returnSize=count;
return result;
}
把 $\textit{nums}[i]$ 转成字符串,即可从高到低遍历数位。
class Solution:
def separateDigits(self, nums: List[int]) -> List[int]:
return [int(ch) for x in nums for ch in str(x)]
class Solution {
public int[] separateDigits(int[] nums) {
List<Integer> digits = new ArrayList<>();
for (int x : nums) {
for (char ch : String.valueOf(x).toCharArray()) {
digits.add(ch - '0');
}
}
int m = digits.size();
int[] ans = new int[m];
for (int i = 0; i < m; i++) {
ans[i] = digits.get(i);
}
return ans;
}
}
class Solution {
public:
vector<int> separateDigits(vector<int>& nums) {
vector<int> ans;
for (int x : nums) {
for (char ch : to_string(x)) {
ans.push_back(ch - '0');
}
}
return ans;
}
};
func separateDigits(nums []int) (ans []int) {
for _, x := range nums {
for _, ch := range strconv.Itoa(x) {
ans = append(ans, int(ch-'0'))
}
}
return
}
不断地把 $n$ 除以 $10$(下取整)直到 $0$,例如 $123\to 12\to 1\to 0$。在这个过程中的 $n\bmod 10$,即为每个数位。
这样做是从低到高遍历数位,和题目要求的顺序相反。
我们可以从右到左遍历 $\textit{nums}$,从低到高遍历 $\textit{nums}[i]$ 的数位。最后把遍历过的数位反转,即为答案。
class Solution:
def separateDigits(self, nums: list[int]) -> list[int]:
ans = []
for x in reversed(nums):
while x > 0:
ans.append(x % 10)
x //= 10
ans.reverse()
return ans
class Solution {
public int[] separateDigits(int[] nums) {
List<Integer> digits = new ArrayList<>();
for (int i = nums.length - 1; i >= 0; i--) {
for (int x = nums[i]; x > 0; x /= 10) {
digits.add(x % 10);
}
}
int m = digits.size();
int[] ans = new int[m];
for (int i = 0; i < m; i++) {
ans[i] = digits.get(m - 1 - i);
}
return ans;
}
}
class Solution {
public:
vector<int> separateDigits(vector<int>& nums) {
vector<int> ans;
for (int x : nums | views::reverse) {
for (; x > 0; x /= 10) {
ans.push_back(x % 10);
}
}
ranges::reverse(ans);
return ans;
}
};
func separateDigits(nums []int) (ans []int) {
for _, x := range slices.Backward(nums) {
for ; x > 0; x /= 10 {
ans = append(ans, x%10)
}
}
slices.Reverse(ans)
return
}
欢迎关注 B站@灵茶山艾府
给你一个下标从 0 开始、由 n 个整数组成的数组 nums 和一个整数 target 。
你的初始位置在下标 0 。在一步操作中,你可以从下标 i 跳跃到任意满足下述条件的下标 j :
0 <= i < j < n-target <= nums[j] - nums[i] <= target返回到达下标 n - 1 处所需的 最大跳跃次数 。
如果无法到达下标 n - 1 ,返回 -1 。
示例 1:
输入:nums = [1,3,6,4,1,2], target = 2 输出:3 解释:要想以最大跳跃次数从下标 0 到下标 n - 1 ,可以按下述跳跃序列执行操作: - 从下标 0 跳跃到下标 1 。 - 从下标 1 跳跃到下标 3 。 - 从下标 3 跳跃到下标 5 。 可以证明,从 0 到 n - 1 的所有方案中,不存在比 3 步更长的跳跃序列。因此,答案是 3 。
示例 2:
输入:nums = [1,3,6,4,1,2], target = 3 输出:5 解释:要想以最大跳跃次数从下标 0 到下标 n - 1 ,可以按下述跳跃序列执行操作: - 从下标 0 跳跃到下标 1 。 - 从下标 1 跳跃到下标 2 。 - 从下标 2 跳跃到下标 3 。 - 从下标 3 跳跃到下标 4 。 - 从下标 4 跳跃到下标 5 。 可以证明,从 0 到 n - 1 的所有方案中,不存在比 5 步更长的跳跃序列。因此,答案是 5 。
示例 3:
输入:nums = [1,3,6,4,1,2], target = 0 输出:-1 解释:可以证明不存在从 0 到 n - 1 的跳跃序列。因此,答案是 -1 。
提示:
2 <= nums.length == n <= 1000-109 <= nums[i] <= 1090 <= target <= 2 * 109思路
f[i]表示从0到下标i的最大跳跃次数;f[0]=0, f[i]=f[j]+1, 可以从j跳过来;class Solution {
public:
int maximumJumps(vector<int>& nums, int target) {
int n = nums.size();
vector<int> f(n, -1);
f[0] = 0;
/* 开始递推 */
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (f[j] != -1 && abs(nums[i] - nums[j]) <= target) {
f[i] = max(f[i], f[j] + 1);
}
}
}
return f[n - 1];
}
};
class Solution:
def maximumJumps(self, nums: List[int], target: int) -> int:
n = len(nums)
f = [-1] * n
f[0] = 0
for j in range(1, n):
for i in range(j):
if f[i] != -1 and abs(nums[i] - nums[j]) <= target:
f[j] = max(f[j], f[i] + 1)
return f[-1]
{:width=400}
此处撰写解题思路
###java
class Solution {
// jump[i] 表示从下标 0 开始跳到 nums[i] 所需的最大跳跃次数
int[] jump;
public int maximumJumps(int[] nums, int target) {
this.jump = new int[nums.length];
Arrays.fill(jump, Integer.MIN_VALUE);
jump[0] = 0;
int maximumJumps = dfs(nums, nums.length - 1, target);
return maximumJumps >= 0 ? maximumJumps : -1;
}
// 返回从下标 i 跳到下标 0 所需最大的跳跃次数
private int dfs(int[] nums, int i, int target) {
if (jump[i] != Integer.MIN_VALUE) {
return jump[i];
}
int max = Integer.MIN_VALUE;
for (int j = i - 1; j >= 0; j--) {
int val = nums[j] - nums[i];
if (-target <= val && val <= target) {
int jump = dfs(nums, j, target) + 1;
max = Math.max(max, jump);
}
}
return jump[i] = max;
}
}
如果有帮助到你,请给题解点个赞 和 收藏,让更多的人看到 ~ ("▔□▔)/
想一想,最后一步发生了什么?
最后一步,我们从某个满足条件的下标 $i$ 跳到了下标 $n-1$。
枚举满足条件的 $i$,问题变成:
这是和原问题相似的、规模更小的子问题,可以用递归解决。
注:从右往左思考,主要是方便把递归翻译成从左往右的递推。从左往右思考也是可以的。
根据上面的讨论,定义 $\textit{dfs}(j)$ 表示从下标 $0$ 到达下标 $j$ 所需的最大跳跃次数。
枚举满足 $0\le i<j$ 且 $|\textit{nums}[i]-\textit{nums}[j]|\le \textit{target}$ 的下标 $i$,问题变成从下标 $0$ 到达下标 $i$ 所需的最大跳跃次数,再加上从 $i$ 跳到 $j$ 的一次。
取最大值,得
$$
\textit{dfs}(j) = \max_{i} \textit{dfs}(i) + 1
$$
其中 $0\le i<j$ 且 $|\textit{nums}[i]-\textit{nums}[j]|\le \textit{target}$。
递归边界:
递归入口:$\textit{dfs}(n-1)$,这是原问题,也是答案。
考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
Python 用户可以无视上面这段,直接用
@cache装饰器。
关于记忆化搜索的原理,请看视频讲解 动态规划入门:从记忆化搜索到递推【基础算法精讲 17】,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
class Solution:
def maximumJumps(self, nums: List[int], target: int) -> int:
@cache # 缓存装饰器,避免重复计算 dfs(一行代码实现记忆化)
def dfs(j: int) -> int:
if j == 0: # 起点
return 0
res = -inf
for i in range(j):
if abs(nums[i] - nums[j]) <= target: # 可以从 i 跳到 j
res = max(res, dfs(i) + 1)
return res
ans = dfs(len(nums) - 1) # 终点
return -1 if ans < 0 else ans
class Solution {
public int maximumJumps(int[] nums, int target) {
int n = nums.length;
int[] memo = new int[n];
int ans = dfs(n - 1, nums, target, memo);
return ans < 0 ? -1 : ans;
}
private int dfs(int j, int[] nums, int target, int[] memo) {
if (j == 0) { // 起点
return 0;
}
if (memo[j] != 0) { // 之前计算过
return memo[j];
}
int res = Integer.MIN_VALUE;
for (int i = 0; i < j; i++) {
if (Math.abs(nums[i] - nums[j]) <= target) { // 可以从 i 跳到 j
res = Math.max(res, dfs(i, nums, target, memo) + 1);
}
}
memo[j] = res; // 记忆化
return res;
}
}
class Solution {
public:
int maximumJumps(vector<int>& nums, int target) {
int n = nums.size();
vector<int> memo(n);
auto dfs = [&](this auto&& dfs, int j) -> int {
if (j == 0) { // 起点
return 0;
}
int& res = memo[j]; // 注意这里是引用
if (res) { // 之前计算过
return res;
}
res = INT_MIN;
for (int i = 0; i < j; i++) {
if (abs(nums[i] - nums[j]) <= target) { // 可以从 i 跳到 j
res = max(res, dfs(i) + 1);
}
}
return res;
};
int ans = dfs(n - 1); // 终点
return ans < 0 ? -1 : ans;
}
};
func maximumJumps(nums []int, target int) int {
n := len(nums)
memo := make([]int, n)
var dfs func(int) int
dfs = func(j int) int {
if j == 0 { // 起点
return 0
}
p := &memo[j]
if *p != 0 { // 之前计算过
return *p
}
res := math.MinInt
for i, x := range nums[:j] {
if abs(x-nums[j]) <= target { // 可以从 i 跳到 j
res = max(res, dfs(i)+1)
}
}
*p = res // 记忆化
return res
}
ans := dfs(n - 1) // 终点
if ans < 0 {
return -1
}
return ans
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
具体来说,$f[j]$ 的定义和 $\textit{dfs}(j)$ 的定义是完全一样的,都表示从下标 $0$ 到达下标 $j$ 所需的最大跳跃次数。
相应的递推式(状态转移方程)也和 $\textit{dfs}$ 一样:
$$
f[j] = \max_{i} f[i] + 1
$$
其中 $0\le i<j$ 且 $|\textit{nums}[i]-\textit{nums}[j]|\le \textit{target}$。
如果没有满足条件的 $i$,那么 $f[j] = -\infty$。
初始值 $f[0]=0$,翻译自递归边界 $\textit{dfs}(0)=0$。
答案为 $f[n-1]$,翻译自递归入口 $\textit{dfs}(n-1)$。
class Solution:
def maximumJumps(self, nums: List[int], target: int) -> int:
n = len(nums)
f = [-inf] * n
f[0] = 0
for j in range(1, n):
for i in range(j):
if abs(nums[i] - nums[j]) <= target: # 可以从 i 跳到 j
f[j] = max(f[j], f[i] + 1)
return -1 if f[-1] < 0 else f[-1]
class Solution {
public int maximumJumps(int[] nums, int target) {
int n = nums.length;
int[] f = new int[n];
for (int j = 1; j < n; j++) {
f[j] = Integer.MIN_VALUE;
for (int i = 0; i < j; i++) {
if (Math.abs(nums[i] - nums[j]) <= target) { // 可以从 i 跳到 j
f[j] = Math.max(f[j], f[i] + 1);
}
}
}
return f[n - 1] < 0 ? -1 : f[n - 1];
}
}
class Solution {
public:
int maximumJumps(vector<int>& nums, int target) {
int n = nums.size();
vector<int> f(n, INT_MIN);
f[0] = 0;
for (int j = 1; j < n; j++) {
for (int i = 0; i < j; i++) {
if (abs(nums[i] - nums[j]) <= target) { // 可以从 i 跳到 j
f[j] = max(f[j], f[i] + 1);
}
}
}
return f[n - 1] < 0 ? -1 : f[n - 1];
}
};
func maximumJumps(nums []int, target int) int {
n := len(nums)
f := make([]int, n)
for j := 1; j < n; j++ {
f[j] = math.MinInt
for i, x := range nums[:j] {
if abs(x-nums[j]) <= target { // 可以从 i 跳到 j
f[j] = max(f[j], f[i]+1)
}
}
}
if f[n-1] < 0 {
return -1
}
return f[n-1]
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
如果 $n=10^5$,上面的做法就超时了。
遍历到 $\textit{nums}[j]$ 时,我们需要知道满足 $\textit{nums}[j]-\textit{target} \le \textit{nums}[i] \le \textit{nums}[j]+\textit{target}$ 的最大的 $f[i]$。
这可以用一棵值域线段树维护。线段树的区间是值域区间,例如区间 $[20,23]$ 指的是 $\textit{nums}$ 中的元素 $20,21,22,23$。线段树的每个节点保存的是值域区间对应的最大的 $f[i]$。例如 $\textit{nums}[4]=20$ 且 $f[4] = 3$,那么线段树维护的位置 $20$ 更新为 $3$。
如此一来,满足 $\textit{nums}[j]-\textit{target} \le \textit{nums}[i] \le \textit{nums}[j]+\textit{target}$ 的最大的 $f[i]$,可以通过线段树的区间最值查询得到。
# 完整的线段树模板见 https://leetcode.cn/circle/discuss/mOr1u6/
class SegmentTree:
def __init__(self, n: int) -> None:
self.t = [-inf] * (2 << (n - 1).bit_length())
def update(self, node: int, l: int, r: int, i: int, val: int) -> None:
if l == r: # 叶子
self.t[node] = val
return
m = (l + r) // 2
if i <= m: # i 在左子树
self.update(node * 2, l, m, i, val)
else: # i 在右子树
self.update(node * 2 + 1, m + 1, r, i, val)
self.t[node] = max(self.t[node * 2], self.t[node * 2 + 1])
def query(self, node: int, l: int, r: int, ql: int, qr: int) -> int:
if ql <= l and r <= qr: # 当前子树完全在 [ql, qr] 内
return self.t[node]
m = (l + r) // 2
if qr <= m: # [ql, qr] 在左子树
return self.query(node * 2, l, m, ql, qr)
if ql > m: # [ql, qr] 在右子树
return self.query(node * 2 + 1, m + 1, r, ql, qr)
return max(self.query(node * 2, l, m, ql, qr), self.query(node * 2 + 1, m + 1, r, ql, qr))
class Solution:
def maximumJumps(self, nums: List[int], target: int) -> int:
# 去重排序,便于离散化
sorted_nums = sorted(set(nums))
n = len(nums)
m = len(sorted_nums)
t = SegmentTree(m) # 值域线段树
# nums[0] 对应的 f[0] = 0
t.update(1, 0, m - 1, bisect_left(sorted_nums, nums[0]), 0)
for j in range(1, n):
l = bisect_left(sorted_nums, nums[j] - target) # >= nums[j]-target 的第一个数
r = bisect_right(sorted_nums, nums[j] + target) - 1 # <= nums[j]+target 的最后一个数
# t.query 返回满足 nums[j]-target <= nums[i] <= nums[j]+target 的最大的 f[i]
fj = t.query(1, 0, m - 1, l, r) + 1
t.update(1, 0, m - 1, bisect_left(sorted_nums, nums[j]), fj)
return -1 if fj < 0 else fj
class Solution {
// 完整的线段树模板见 https://leetcode.cn/circle/discuss/mOr1u6/
private int[] tree;
private void update(int node, int l, int r, int i, int val) {
if (l == r) { // 叶子
tree[node] = val;
return;
}
int m = (l + r) / 2;
if (i <= m) { // i 在左子树
update(node * 2, l, m, i, val);
} else { // i 在右子树
update(node * 2 + 1, m + 1, r, i, val);
}
tree[node] = Math.max(tree[node * 2], tree[node * 2 + 1]);
}
private int query(int node, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) { // 当前子树完全在 [ql, qr] 内
return tree[node];
}
int m = (l + r) / 2;
if (qr <= m) { // [ql, qr] 在左子树
return query(node * 2, l, m, ql, qr);
}
if (ql > m) { // [ql, qr] 在右子树
return query(node * 2 + 1, m + 1, r, ql, qr);
}
return Math.max(query(node * 2, l, m, ql, qr), query(node * 2 + 1, m + 1, r, ql, qr));
}
public int maximumJumps(int[] nums, int target) {
int n = nums.length;
int[] sorted = nums.clone(); // 用于离散化
Arrays.sort(sorted);
tree = new int[2 << (32 - Integer.numberOfLeadingZeros(n - 1))];
Arrays.fill(tree, Integer.MIN_VALUE);
// nums[0] 对应的 f[0] = 0
update(1, 0, n - 1, lowerBound(sorted, nums[0]), 0);
for (int j = 1; ; j++) {
int l = lowerBound(sorted, (long) nums[j] - target); // >= nums[j]-target 的第一个数
int r = lowerBound(sorted, (long) nums[j] + target + 1) - 1; // <= nums[j]+target 的最后一个数
// query 返回满足 nums[j]-target <= nums[i] <= nums[j]+target 的最大的 f[i]
int fj = query(1, 0, n - 1, l, r) + 1;
if (j == n - 1) {
return fj < 0 ? -1 : fj;
}
update(1, 0, n - 1, lowerBound(sorted, nums[j]), fj);
}
}
// 见 https://www.bilibili.com/video/BV1AP41137w7/
private int lowerBound(int[] nums, long target) {
int left = -1;
int right = nums.length;
while (left + 1 < right) {
int mid = (left + right) >>> 1;
if (nums[mid] >= target) {
right = mid;
} else {
left = mid;
}
}
return right;
}
}
// 完整的线段树模板见 https://leetcode.cn/circle/discuss/mOr1u6/
class SegmentTree {
vector<int> tree;
public:
SegmentTree(int n) : tree(2 << bit_width(n - 1u), INT_MIN) {}
void update(int node, int l, int r, int i, int val) {
if (l == r) { // 叶子
tree[node] = val;
return;
}
int m = (l + r) / 2;
if (i <= m) { // i 在左子树
update(node * 2, l, m, i, val);
} else { // i 在右子树
update(node * 2 + 1, m + 1, r, i, val);
}
tree[node] = max(tree[node * 2], tree[node * 2 + 1]);
}
int query(int node, int l, int r, int ql, int qr) const {
if (ql <= l && r <= qr) { // 当前子树完全在 [ql, qr] 内
return tree[node];
}
int m = (l + r) / 2;
if (qr <= m) { // [ql, qr] 在左子树
return query(node * 2, l, m, ql, qr);
}
if (ql > m) { // [ql, qr] 在右子树
return query(node * 2 + 1, m + 1, r, ql, qr);
}
return max(query(node * 2, l, m, ql, qr), query(node * 2 + 1, m + 1, r, ql, qr));
}
};
class Solution {
public:
int maximumJumps(vector<int>& nums, int target) {
// 排序去重,便于离散化
auto sorted = nums;
ranges::sort(sorted);
sorted.erase(ranges::unique(sorted).begin(), sorted.end());
int n = nums.size();
int m = sorted.size();
SegmentTree t(m); // 值域线段树
// nums[0] 对应的 f[0] = 0
int pos = ranges::lower_bound(sorted, nums[0]) - sorted.begin();
t.update(1, 0, m - 1, pos, 0);
long long tar = target;
for (int j = 1; ; j++) {
int l = ranges::lower_bound(sorted, nums[j] - tar) - sorted.begin(); // >= nums[j]-target 的第一个数
int r = ranges::upper_bound(sorted, nums[j] + tar) - sorted.begin() - 1; // <= nums[j]+target 的最后一个数
// t.query 返回满足 nums[j]-target <= nums[i] <= nums[j]+target 的最大的 f[i]
int fj = t.query(1, 0, m - 1, l, r) + 1;
if (j == n - 1) {
return fj < 0 ? -1 : fj;
}
pos = ranges::lower_bound(sorted, nums[j]) - sorted.begin();
t.update(1, 0, m - 1, pos, fj);
}
}
};
// 完整的线段树模板见 https://leetcode.cn/circle/discuss/mOr1u6/
type seg []int
func (t seg) update(node, l, r, i, val int) {
if l == r { // 叶子
t[node] = val
return
}
m := (l + r) / 2
if i <= m { // i 在左子树
t.update(node*2, l, m, i, val)
} else { // i 在右子树
t.update(node*2+1, m+1, r, i, val)
}
t[node] = max(t[node*2], t[node*2+1])
}
func (t seg) query(node, l, r, ql, qr int) int {
if ql <= l && r <= qr { // 当前子树完全在 [ql, qr] 内
return t[node]
}
m := (l + r) / 2
if qr <= m { // [ql, qr] 在左子树
return t.query(node*2, l, m, ql, qr)
}
if ql > m { // [ql, qr] 在右子树
return t.query(node*2+1, m+1, r, ql, qr)
}
return max(t.query(node*2, l, m, ql, qr), t.query(node*2+1, m+1, r, ql, qr))
}
func maximumJumps(nums []int, target int) int {
// 排序去重,便于离散化
sorted := slices.Clone(nums)
slices.Sort(sorted)
sorted = slices.Compact(sorted)
n := len(nums)
m := len(sorted)
t := make(seg, 2<<bits.Len(uint(m-1))) // 值域线段树
for i := range t {
t[i] = math.MinInt
}
// nums[0] 对应的 f[0] = 0
t.update(1, 0, m-1, sort.SearchInts(sorted, nums[0]), 0)
for j := 1; ; j++ {
l := sort.SearchInts(sorted, nums[j]-target) // >= nums[j]-target 的第一个数
r := sort.SearchInts(sorted, nums[j]+target+1) - 1 // <= nums[j]+target 的最后一个数
// t.query 返回满足 nums[j]-target <= nums[i] <= nums[j]+target 的最大的 f[i]
fj := t.query(1, 0, m-1, l, r) + 1
if j == n-1 {
if fj < 0 {
return -1
}
return fj
}
t.update(1, 0, m-1, sort.SearchInts(sorted, nums[j]), fj)
}
}
更多相似题目,见下面动态规划题单的「§11.4 树状数组/线段树优化 DP」和「专题:跳跃游戏」。
欢迎关注 B站@灵茶山艾府
本题是模拟题,由于每一圈互相独立,可以分别处理。
对于每一圈:
对于步骤 1,可以仿照 54. 螺旋矩阵 的 方法二,使用一个方向向量数组分别表示顺时针的右、下、左、上。对于最外圈,分别移动 $n-1,m-1,n-1,m-1$ 次;对于次外圈,分别移动 $n-3,m-3,n-3,m-3$ 次;依此类推。
DIRS = (0, 1), (1, 0), (0, -1), (-1, 0) # 右下左上
class Solution:
def rotateGrid(self, grid: List[List[int]], k: int) -> List[List[int]]:
m0, n0 = len(grid), len(grid[0])
# 从外到内枚举圈
for i in range(min(m0, n0) // 2):
m, n = m0 - i * 2, n0 - i * 2 # 这一圈的行数和列数
x, y = i, i # 这一圈的左上角
a = []
for dx, dy in DIRS:
for _ in range(n - 1):
a.append(grid[x][y])
x += dx
y += dy
m, n = n, m # 见 54. 螺旋矩阵 我的题解
shift = k % len(a)
a = a[shift:] + a[:shift]
# 注意此时 (x, y) 又回到了左上角
j = 0
for dx, dy in DIRS:
for _ in range(n - 1):
grid[x][y] = a[j]
j += 1
x += dx
y += dy
m, n = n, m
return grid
class Solution {
private static final int[][] DIRS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上
public int[][] rotateGrid(int[][] grid, int k) {
int m0 = grid.length, n0 = grid[0].length;
List<Integer> a = new ArrayList<>((m0 + n0 - 2) * 2); // 预分配空间
// 从外到内枚举圈
for (int i = 0; i < Math.min(m0, n0) / 2; i++) {
int m = m0 - i * 2, n = n0 - i * 2; // 这一圈的行数和列数
int x = i, y = i; // 这一圈的左上角
a.clear();
for (int[] dir : DIRS) {
for (int t = 0; t < n - 1; t++) {
a.add(grid[x][y]);
x += dir[0];
y += dir[1];
}
int tmp = m; // 见 54. 螺旋矩阵 我的题解
m = n;
n = tmp;
}
int shift = k % a.size();
Collections.rotate(a, -shift);
// 注意此时 (x, y) 又回到了左上角
int j = 0;
for (int[] dir : DIRS) {
for (int t = 0; t < n - 1; t++) {
grid[x][y] = a.get(j++);
x += dir[0];
y += dir[1];
}
int temp = m;
m = n;
n = temp;
}
}
return grid;
}
}
class Solution {
static constexpr int DIRS[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上
public:
vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
int m0 = grid.size(), n0 = grid[0].size();
vector<int> a;
a.reserve((m0 + n0 - 2) * 2); // 预分配空间
// 从外到内枚举圈
for (int i = 0; i < min(m0, n0) / 2; i++) {
int m = m0 - i * 2, n = n0 - i * 2; // 这一圈的行数和列数
int x = i, y = i; // 这一圈的左上角
a.resize(0);
for (auto& [dx, dy] : DIRS) {
for (int t = 0; t < n - 1; t++) {
a.push_back(grid[x][y]);
x += dx;
y += dy;
}
swap(m, n); // 见 54. 螺旋矩阵 我的题解
}
int shift = k % a.size();
ranges::rotate(a, a.begin() + shift);
// 注意此时 (x, y) 又回到了左上角
int j = 0;
for (auto& [dx, dy] : DIRS) {
for (int t = 0; t < n - 1; t++) {
grid[x][y] = a[j++];
x += dx;
y += dy;
}
swap(m, n);
}
}
return grid;
}
};
var dirs = [4][2]int{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} // 右下左上
func rotateGrid(grid [][]int, k int) [][]int {
m0, n0 := len(grid), len(grid[0])
a := make([]int, 0, (m0+n0-2)*2) // 预分配空间
// 从外到内枚举圈
for i := range min(m0, n0) / 2 {
m, n := m0-i*2, n0-i*2 // 这一圈的行数和列数
x, y := i, i // 这一圈的左上角
a = a[:0]
for _, dir := range dirs {
for range n - 1 {
a = append(a, grid[x][y])
x += dir[0]
y += dir[1]
}
m, n = n, m // 见 54. 螺旋矩阵 我的题解
}
shift := k % len(a)
a = append(a[shift:], a[:shift]...)
// 注意此时 (x, y) 又回到了左上角
j := 0
for _, dir := range dirs {
for range n - 1 {
grid[x][y] = a[j]
j++
x += dir[0]
y += dir[1]
}
m, n = n, m
}
}
return grid
}
利用 189. 轮转数组 的技巧,可以做到 $\mathcal{O}(1)$ 空间。详见 我的题解。
class Solution:
def rotateGrid(self, grid: List[List[int]], k: int) -> List[List[int]]:
m0, n0 = len(grid), len(grid[0])
# 从外到内枚举圈
for i in range(min(m0, n0) // 2):
m, n = m0 - i * 2 - 1, n0 - i * 2 - 1 # 注意这里减一了
# 返回这一圈顺时针下标 p 对应 grid 的位置 (x, y)
def index(p: int) -> Tuple[int, int]:
# 左上角在 (i, i)
if p < n:
return i, i + p
if p < n + m:
return i + p - n, i + n
if p < n * 2 + m:
return i + m, i - p + n * 2 + m
return i - p + (n + m) * 2, i
def reverse(l: int, r: int) -> None:
while l < r:
x1, y1 = index(l)
x2, y2 = index(r)
grid[x1][y1], grid[x2][y2] = grid[x2][y2], grid[x1][y1]
l += 1
r -= 1
# 189. 轮转数组(改成向左轮转)
size = (m + n) * 2
shift = k % size
reverse(0, shift - 1)
reverse(shift, size - 1)
reverse(0, size - 1)
return grid
class Solution {
public int[][] rotateGrid(int[][] grid, int k) {
int m0 = grid.length, n0 = grid[0].length;
// 从外到内枚举圈
for (int i = 0; i < Math.min(m0, n0) / 2; i++) {
int m = m0 - i * 2 - 1, n = n0 - i * 2 - 1; // 注意这里减一了
// 189. 轮转数组(改成向左轮转)
int size = (m + n) * 2;
int shift = k % size;
reverse(grid, i, m, n, 0, shift - 1);
reverse(grid, i, m, n, shift, size - 1);
reverse(grid, i, m, n, 0, size - 1);
}
return grid;
}
private void reverse(int[][] grid, int i, int m, int n, int l, int r) {
while (l < r) {
int[] p1 = index(i, m, n, l);
int[] p2 = index(i, m, n, r);
int x1 = p1[0], y1 = p1[1];
int x2 = p2[0], y2 = p2[1];
int tmp = grid[x1][y1];
grid[x1][y1] = grid[x2][y2];
grid[x2][y2] = tmp;
l++;
r--;
}
}
private int[] index(int i, int m, int n, int p) {
// 左上角在 (i, i)
if (p < n) {
return new int[]{i, i + p};
}
if (p < n + m) {
return new int[]{i + p - n, i + n};
}
if (p < n * 2 + m) {
return new int[]{i + m, i - p + n * 2 + m};
}
return new int[]{i - p + (n + m) * 2, i};
}
}
class Solution {
public:
vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
int m0 = grid.size(), n0 = grid[0].size();
// 从外到内枚举圈
for (int i = 0; i < min(m0, n0) / 2; i++) {
int m = m0 - i * 2 - 1, n = n0 - i * 2 - 1; // 注意这里减一了
// 返回这一圈顺时针下标 p 对应 grid 的位置 (x, y)
auto index = [&](int p) -> pair<int, int> {
// 左上角在 (i, i)
if (p < n) {
return {i, i + p};
}
if (p < n + m) {
return {i + p - n, i + n};
}
if (p < n * 2 + m) {
return {i + m, i - p + n * 2 + m};
}
return {i - p + (n + m) * 2, i};
};
auto reverse = [&](int l, int r) {
while (l < r) {
auto [x1, y1] = index(l);
auto [x2, y2] = index(r);
swap(grid[x1][y1], grid[x2][y2]);
l++;
r--;
}
};
// 189. 轮转数组(改成向左轮转)
int size = (m + n) * 2;
int shift = k % size;
reverse(0, shift - 1);
reverse(shift, size - 1);
reverse(0, size - 1);
}
return grid;
}
};
func rotateGrid(grid [][]int, k int) [][]int {
m0, n0 := len(grid), len(grid[0])
// 从外到内枚举圈
for i := range min(m0, n0) / 2 {
m, n := m0-i*2-1, n0-i*2-1 // 注意这里减一了
// 返回这一圈顺时针下标 p 对应 grid 的位置 (x, y)
index := func(p int) (x, y int) {
// 左上角在 (i, i)
if p < n {
return i, i + p
}
if p < n+m {
return i + p - n, i + n
}
if p < n*2+m {
return i + m, i - p + n*2 + m
}
return i - p + (n+m)*2, i
}
reverse := func(l, r int) {
for l < r {
x1, y1 := index(l)
x2, y2 := index(r)
grid[x1][y1], grid[x2][y2] = grid[x2][y2], grid[x1][y1]
l++
r--
}
}
// 189. 轮转数组(改成向左轮转)
size := (m + n) * 2
shift := k % size
reverse(0, shift-1)
reverse(shift, size-1)
reverse(0, size-1)
}
return grid
}
欢迎关注 B站@灵茶山艾府
给你一个大小为 m x n 的整数矩阵 grid ,其中 m 和 n 都是 偶数 ;另给你一个整数 k 。
矩阵由若干层组成,如下图所示,每种颜色代表一层:
![]()
矩阵的循环轮转是通过分别循环轮转矩阵中的每一层完成的。在对某一层进行一次循环旋转操作时,层中的每一个元素将会取代其 逆时针 方向的相邻元素。轮转示例如下:
返回执行 k 次循环轮转操作后的矩阵。
示例 1:
输入:grid = [[40,10],[30,20]], k = 1 输出:[[10,20],[40,30]] 解释:上图展示了矩阵在执行循环轮转操作时每一步的状态。
示例 2:
输入:grid = [[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]], k = 2 输出:[[3,4,8,12],[2,11,10,16],[1,7,6,15],[5,9,13,14]] 解释:上图展示了矩阵在执行循环轮转操作时每一步的状态。
提示:
m == grid.lengthn == grid[i].length2 <= m, n <= 50m 和 n 都是 偶数
1 <= grid[i][j] <=50001 <= k <= 109题目的条件很友好地指明 m 和 n 均为偶数,那么我们易得:在一个 $m \times n$ 的矩阵中,总共有 $\min(m, n) / 2$ 圈。我们可设最外层为第 $0$ 圈,那么我们易得该圈的元素个数 sz 为 $2(m+n)-4$,根据数学归纳法,我们可知第 $i$ 圈的元素个数 sz 为 $2(m+n-4i) - 4$。因此我们对第 $i$ 圈进行旋转 $k$ 次时,实际上相当于我们对该圈旋转了 k % sz 次。
现在我们需要推出第 $i$ 圈元素的坐标,同理,我们可从第 $0$ 圈获得启发。易知第 $0$ 圈的元素坐标从左上角顺时针依次为 $(0, 0), (0,1), \cdots, (0, n-1), (1, n-1), \cdots, (m-1, n-1), \cdots, (m-1, 1), (m-1, 0), \cdots, (1, 0)$。我们可推出第 $i$ 圈的元素坐标从左上角顺时针依次为 $(i, i), \cdots, (i, n-i-1), \cdots, (m-i-1, n-i-1), \cdots, (m-i-1, i), \cdots, (i+1, i)$。
对于第 $i$ 圈上的点坐标 $(x, y)$,我们可分为四个边:
每次旋转我们需要完成 sz - 1次交换。
###C++
class Solution {
private:
int m, n, layer;
void rotateLayer(vector<vector<int>>& grid, int i, int k) {
int sz = 2 * (m + n - 4*i) - 4;
k %= sz;
while (k--) {
int x = i, y = i;
for (int j = 0; j < sz-1; ++j) {
if (x == i && y < n-i-1) {
swap(grid[x][y], grid[x][y+1]);
++y;
} else if (x < m-i-1 && y == n-i-1) {
swap(grid[x][y], grid[x+1][y]);
++x;
} else if (x == m-i-1 && y > i) {
swap(grid[x][y], grid[x][y-1]);
--y;
} else if (x > i && y == i) {
swap(grid[x][y], grid[x-1][y]);
--x;
}
}
}
}
public:
vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
m = grid.size(), n = grid[0].size();
layer = min(m, n) / 2;
for (int i = 0; i < layer; ++i) {
rotateLayer(grid, i, k);
}
return grid;
}
};
###Golang
func rotateGrid(grid [][]int, k int) [][]int {
m, n := len(grid), len(grid[0])
layer := m / 2
if n < m {
layer = n / 2
}
rotateLayer := func(i int) {
sz := 2 * (m + n - 4*i) - 4
kk := k % sz
for kk > 0 {
x, y := i, i
for j := 0; j < sz-1; j++ {
if x == i && y < n-i-1 {
grid[x][y], grid[x][y+1] = grid[x][y+1], grid[x][y]
y++
} else if x < m-i-1 && y == n-i-1 {
grid[x][y], grid[x+1][y] = grid[x+1][y], grid[x][y]
x++
} else if x == m-i-1 && y > i {
grid[x][y], grid[x][y-1] = grid[x][y-1], grid[x][y]
y--
} else if x > i && y == i {
grid[x][y], grid[x-1][y] = grid[x-1][y], grid[x][y]
x--
}
}
kk--
}
}
for i := 0; i < layer; i++ {
rotateLayer(i)
}
return grid
}
思路与算法
对于一个 $m \times n$ 的矩阵 $\textit{grid}$,它的层数为 $\min(m / 2, n / 2)$。我们可以从外向内枚举矩阵的每一层模拟循环轮转操作。
为了方便模拟,我们从左上角起按照逆时针方向遍历每一层的元素。在本文中,我们将遍历过程分为四个部分,每个部分按顺序遍历每条边除了最后一个元素以外的所有元素。
我们将这些元素的行坐标、列坐标与数值保存在对应的数组 $r, c, \textit{val}$ 中,并计算元素总数,即数组的长度 $\textit{total}$。此时,如果对该层元素进行 $\textit{total}$ 次循环轮转操作,那么该层元素不会改变。因此,实际的循环轮转操作数量即为 $\textit{kk} = k % \textit{total}$。
那么,这一层中遍历到的第 $i$ 个位置在轮转操作后存放的值对应 $\textit{val}$ 数组中下标为 $(i - \textit{kk} + \textit{total}) % \textit{total}$ 的值。此处在取模时加上 $\textit{total}$ 是为了避免出现负数。
我们遍历行列坐标数组,并在 $\textit{grid}$ 中更新每个坐标对应的轮转操作后的取值。当枚举并更新完所有层后,$\textit{grid}$ 即为轮转操作后的矩阵。
代码
###C++
class Solution {
public:
vector<vector<int>> rotateGrid(vector<vector<int>>& grid, int k) {
int m = grid.size();
int n = grid[0].size();
int nlayer = min(m / 2, n / 2); // 层数
// 从左上角起逆时针枚举每一层
for (int layer = 0; layer < nlayer; ++layer){
vector<int> r, c, val; // 每个元素的行下标,列下标与数值
for (int i = layer; i < m - layer - 1; ++i){ // 左
r.push_back(i);
c.push_back(layer);
val.push_back(grid[i][layer]);
}
for (int j = layer; j < n - layer - 1; ++j){ // 下
r.push_back(m - layer - 1);
c.push_back(j);
val.push_back(grid[m-layer-1][j]);
}
for (int i = m - layer - 1; i > layer; --i){ // 右
r.push_back(i);
c.push_back(n - layer - 1);
val.push_back(grid[i][n-layer-1]);
}
for (int j = n - layer - 1; j > layer; --j){ // 上
r.push_back(layer);
c.push_back(j);
val.push_back(grid[layer][j]);
}
int total = val.size(); // 每一层的元素总数
int kk = k % total; // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for (int i = 0; i < total; ++i){
int idx = (i + total - kk) % total; // 轮转后取值对应的下标
grid[r[i]][c[i]] = val[idx];
}
}
return grid;
}
};
###Python
class Solution:
def rotateGrid(self, grid: List[List[int]], k: int) -> List[List[int]]:
m, n = len(grid), len(grid[0])
nlayer = min(m // 2, n // 2) # 层数
# 从左上角起逆时针枚举每一层
for layer in range(nlayer):
r = [] # 每个元素的行下标
c = [] # 每个元素的列下标
val = [] # 每个元素的数值
for i in range(layer, m - layer - 1): # 左
r.append(i)
c.append(layer)
val.append(grid[i][layer])
for j in range(layer, n - layer - 1): # 下
r.append(m - layer - 1)
c.append(j)
val.append(grid[m-layer-1][j])
for i in range(m - layer - 1, layer, -1): # 右
r.append(i)
c.append(n - layer - 1)
val.append(grid[i][n-layer-1])
for j in range(n - layer - 1, layer, -1): # 上
r.append(layer)
c.append(j)
val.append(grid[layer][j])
total = len(val) # 每一层的元素总数
kk = k % total # 等效轮转次数
# 找到每个下标对应的轮转后的取值
for i in range(total):
idx = (i + total - kk) % total # 轮转后取值对应的下标
grid[r[i]][c[i]] = val[idx]
return grid
###Java
class Solution {
public int[][] rotateGrid(int[][] grid, int k) {
int m = grid.length;
int n = grid[0].length;
int nlayer = Math.min(m / 2, n / 2); // 层数
// 从左上角起逆时针枚举每一层
for (int layer = 0; layer < nlayer; ++layer){
List<Integer> r = new ArrayList<>();
List<Integer> c = new ArrayList<>();
List<Integer> val = new ArrayList<>(); // 每个元素的行下标,列下标与数值
for (int i = layer; i < m - layer - 1; ++i){ // 左
r.add(i);
c.add(layer);
val.add(grid[i][layer]);
}
for (int j = layer; j < n - layer - 1; ++j){ // 下
r.add(m - layer - 1);
c.add(j);
val.add(grid[m - layer - 1][j]);
}
for (int i = m - layer - 1; i > layer; --i){ // 右
r.add(i);
c.add(n - layer - 1);
val.add(grid[i][n - layer - 1]);
}
for (int j = n - layer - 1; j > layer; --j){ // 上
r.add(layer);
c.add(j);
val.add(grid[layer][j]);
}
int total = val.size(); // 每一层的元素总数
int kk = k % total; // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for (int i = 0; i < total; ++i){
int idx = (i + total - kk) % total; // 轮转后取值对应的下标
grid[r.get(i)][c.get(i)] = val.get(idx);
}
}
return grid;
}
}
###C#
public class Solution {
public int[][] RotateGrid(int[][] grid, int k) {
int m = grid.Length;
int n = grid[0].Length;
int nlayer = Math.Min(m / 2, n / 2); // 层数
// 从左上角起逆时针枚举每一层
for (int layer = 0; layer < nlayer; ++layer){
List<int> r = new List<int>();
List<int> c = new List<int>();
List<int> val = new List<int>(); // 每个元素的行下标,列下标与数值
for (int i = layer; i < m - layer - 1; ++i){ // 左
r.Add(i);
c.Add(layer);
val.Add(grid[i][layer]);
}
for (int j = layer; j < n - layer - 1; ++j){ // 下
r.Add(m - layer - 1);
c.Add(j);
val.Add(grid[m - layer - 1][j]);
}
for (int i = m - layer - 1; i > layer; --i){ // 右
r.Add(i);
c.Add(n - layer - 1);
val.Add(grid[i][n - layer - 1]);
}
for (int j = n - layer - 1; j > layer; --j){ // 上
r.Add(layer);
c.Add(j);
val.Add(grid[layer][j]);
}
int total = val.Count; // 每一层的元素总数
int kk = k % total; // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for (int i = 0; i < total; ++i){
int idx = (i + total - kk) % total; // 轮转后取值对应的下标
grid[r[i]][c[i]] = val[idx];
}
}
return grid;
}
}
###Go
func rotateGrid(grid [][]int, k int) [][]int {
m := len(grid)
n := len(grid[0])
nlayer := min(m / 2, n / 2) // 层数
// 从左上角起逆时针枚举每一层
for layer := 0; layer < nlayer; layer++ {
r := make([]int, 0)
c := make([]int, 0)
val := make([]int, 0) // 每个元素的行下标,列下标与数值
for i := layer; i < m - layer - 1; i++ { // 左
r = append(r, i)
c = append(c, layer)
val = append(val, grid[i][layer])
}
for j := layer; j < n - layer - 1; j++ { // 下
r = append(r, m - layer - 1)
c = append(c, j)
val = append(val, grid[m-layer - 1][j])
}
for i := m - layer - 1; i > layer; i-- { // 右
r = append(r, i)
c = append(c, n - layer - 1)
val = append(val, grid[i][n - layer - 1])
}
for j := n - layer - 1; j > layer; j-- { // 上
r = append(r, layer)
c = append(c, j)
val = append(val, grid[layer][j])
}
total := len(val) // 每一层的元素总数
kk := k % total // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for i := 0; i < total; i++ {
idx := (i + total - kk) % total // 轮转后取值对应的下标
grid[r[i]][c[i]] = val[idx]
}
}
return grid
}
###C
int** rotateGrid(int** grid, int gridSize, int* gridColSize, int k, int* returnSize, int** returnColumnSizes) {
int m = gridSize;
int n = gridColSize[0];
*returnSize = m;
*returnColumnSizes = (int*)malloc(m * sizeof(int));
for (int i = 0; i < m; i++) {
(*returnColumnSizes)[i] = n;
}
int nlayer = fmin(m / 2, n / 2); // 层数
// 从左上角起逆时针枚举每一层
for (int layer = 0; layer < nlayer; ++layer) {
int maxSize = 2 * (m + n - 4 * layer - 2);
int* r = (int*)malloc(maxSize * sizeof(int));
int* c = (int*)malloc(maxSize * sizeof(int));
int* val = (int*)malloc(maxSize * sizeof(int)); // 每个元素的行下标,列下标与数值
int idx = 0;
for (int i = layer; i < m - layer - 1; ++i) { // 左
r[idx] = i;
c[idx] = layer;
val[idx] = grid[i][layer];
idx++;
}
for (int j = layer; j < n - layer - 1; ++j) { // 下
r[idx] = m - layer - 1;
c[idx] = j;
val[idx] = grid[m - layer - 1][j];
idx++;
}
for (int i = m - layer - 1; i > layer; --i) { // 右
r[idx] = i;
c[idx] = n - layer - 1;
val[idx] = grid[i][n - layer - 1];
idx++;
}
for (int j = n - layer - 1; j > layer; --j) { // 上
r[idx] = layer;
c[idx] = j;
val[idx] = grid[layer][j];
idx++;
}
int total = idx; // 每一层的元素总数
int kk = k % total; // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for (int i = 0; i < total; ++i) {
int pos = (i + total - kk) % total; // 轮转后取值对应的下标
grid[r[i]][c[i]] = val[pos];
}
free(r);
free(c);
free(val);
}
return grid;
}
###JavaScript
var rotateGrid = function(grid, k) {
const m = grid.length;
const n = grid[0].length;
const nlayer = Math.min(Math.floor(m / 2), Math.floor(n / 2)); // 层数
// 从左上角起逆时针枚举每一层
for (let layer = 0; layer < nlayer; ++layer) {
const r = [];
const c = [];
const val = []; // 每个元素的行下标,列下标与数值
for (let i = layer; i < m - layer - 1; ++i) { // 左
r.push(i);
c.push(layer);
val.push(grid[i][layer]);
}
for (let j = layer; j < n - layer - 1; ++j) { // 下
r.push(m - layer - 1);
c.push(j);
val.push(grid[m - layer - 1][j]);
}
for (let i = m - layer - 1; i > layer; --i) { // 右
r.push(i);
c.push(n - layer - 1);
val.push(grid[i][n - layer - 1]);
}
for (let j = n - layer - 1; j > layer; --j) { // 上
r.push(layer);
c.push(j);
val.push(grid[layer][j]);
}
const total = val.length; // 每一层的元素总数
const kk = k % total; // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for (let i = 0; i < total; ++i) {
const idx = (i + total - kk) % total; // 轮转后取值对应的下标
grid[r[i]][c[i]] = val[idx];
}
}
return grid;
};
###TypeScript
function rotateGrid(grid: number[][], k: number): number[][] {
const m: number = grid.length;
const n: number = grid[0].length;
const nlayer: number = Math.min(Math.floor(m / 2), Math.floor(n / 2)); // 层数
// 从左上角起逆时针枚举每一层
for (let layer = 0; layer < nlayer; ++layer) {
const r: number[] = [];
const c: number[] = [];
const val: number[] = []; // 每个元素的行下标,列下标与数值
for (let i = layer; i < m - layer - 1; ++i) { // 左
r.push(i);
c.push(layer);
val.push(grid[i][layer]);
}
for (let j = layer; j < n - layer - 1; ++j) { // 下
r.push(m - layer - 1);
c.push(j);
val.push(grid[m - layer - 1][j]);
}
for (let i = m - layer - 1; i > layer; --i) { // 右
r.push(i);
c.push(n - layer - 1);
val.push(grid[i][n - layer - 1]);
}
for (let j = n - layer - 1; j > layer; --j) { // 上
r.push(layer);
c.push(j);
val.push(grid[layer][j]);
}
const total: number = val.length; // 每一层的元素总数
const kk: number = k % total; // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for (let i = 0; i < total; ++i) {
const idx: number = (i + total - kk) % total; // 轮转后取值对应的下标
grid[r[i]][c[i]] = val[idx];
}
}
return grid;
}
###Rust
impl Solution {
pub fn rotate_grid(mut grid: Vec<Vec<i32>>, k: i32) -> Vec<Vec<i32>> {
let m = grid.len();
let n = grid[0].len();
let nlayer = (m / 2).min(n / 2); // 层数
let k = k as usize;
// 从左上角起逆时针枚举每一层
for layer in 0..nlayer {
let mut r = Vec::new();
let mut c = Vec::new();
let mut val = Vec::new(); // 每个元素的行下标,列下标与数值
for i in layer..m - layer - 1 { // 左
r.push(i);
c.push(layer);
val.push(grid[i][layer]);
}
for j in layer..n - layer - 1 { // 下
r.push(m - layer - 1);
c.push(j);
val.push(grid[m - layer - 1][j]);
}
for i in (layer + 1..=m - layer - 1).rev() { // 右
r.push(i);
c.push(n - layer - 1);
val.push(grid[i][n - layer - 1]);
}
for j in (layer + 1..=n - layer - 1).rev() { // 上
r.push(layer);
c.push(j);
val.push(grid[layer][j]);
}
let total = val.len(); // 每一层的元素总数
let kk = k % total; // 等效轮转次数
// 找到每个下标对应的轮转后的取值
for i in 0..total {
let idx = (i + total - kk) % total; // 轮转后取值对应的下标
grid[r[i]][c[i]] = val[idx];
}
}
grid
}
}
复杂度分析
时间复杂度:$O(mn)$,其中 $m$ 和 $n$ 分别为 $\textit{grid}$ 的行数和列数。即为遍历 $\textit{grid}$ 并进行旋转的时间复杂度。
空间复杂度:$O(m + n)$,即为存储每一层行列与数值的辅助数组大小。事实上,我们可以利用原地旋转将空间复杂度优化至 $O(1)$,但这样写出的代码并不直观,因此本题解中不给出空间复杂度最优的写法。
我们首先把每一层的元素按顺时针取出来,放到数组 data 中。
然后对 data 旋转 k % (data.length) 次,这里使用队列来简化。
然后再放回去就好了。
具体见程序吧。
###java
class Solution {
public int[][] rotateGrid(int[][] grid, int k) {
// 矩阵尺寸
int m = grid.length, n = grid[0].length;
// 计算一共有多少层
int layerNumber = Math.min(m, n) / 2;
// 逐层处理
for (int i = 0; i < layerNumber; ++i) {
// 计算出来当前层需要多大的数组来存放, 计算方法是当前层最大尺寸 - 内部下一层尺寸.
int[] data = new int[(m - 2 * i) * (n - 2 * i) - (m - (i + 1) * 2) * (n - (i + 1) * 2)];
int idx = 0;
// 从左往右
for (int offset = i; offset < n - i - 1; ++offset)
data[idx++] = grid[i][offset];
// 从上往下
for (int offset = i; offset < m - i - 1; ++offset)
data[idx++] = grid[offset][n - i - 1];
// 从右往左
for (int offset = n - i - 1; offset > i; --offset)
data[idx++] = grid[m - i - 1][offset];
// 从下往上
for (int offset = m - i - 1; offset > i; --offset)
data[idx++] = grid[offset][i];
// 把旋转完的放回去
Integer[] ret = rotateVector(data, k);
idx = 0;
// 从左往右
for (int offset = i; offset < n - i - 1; ++offset)
grid[i][offset] = ret[idx++];
// 从上往下
for (int offset = i; offset < m - i - 1; ++offset)
grid[offset][n - i - 1] = ret[idx++];
// 从右往左
for (int offset = n - i - 1; offset > i; --offset)
grid[m - i - 1][offset] = ret[idx++];
// 从下往上
for (int offset = m - i - 1; offset > i; --offset)
grid[offset][i] = ret[idx++];
}
return grid;
}
private Integer[] rotateVector(int[] vector, int offset) {
// 取余, 否则会有无用功, 超时
offset = offset % vector.length;
Deque<Integer> deque = new ArrayDeque<>();
for (int item : vector) deque.add(item);
// 旋转操作
while (offset-- > 0) {
deque.addLast(deque.pollFirst());
}
return deque.toArray(new Integer[0]);
}
}
给你一个长度为 n 的整数数组 nums。
你从下标 0 开始,目标是到达下标 n - 1。
在任何下标 i 处,你可以执行以下操作之一:
i + 1 或 i - 1,如果该下标在边界内。nums[i] 是一个质数 p,你可以立即跳到任何满足 nums[j] % p == 0 的下标 j 处,且下标 j != i 。返回到达下标 n - 1 所需的 最少 跳跃次数。
质数 是一个大于 1 的自然数,只有两个因子,1 和它本身。
示例 1:
输入: nums = [1,2,4,6]
输出: 2
解释:
一个最优的跳跃序列是:
i = 0 开始。向相邻下标 1 跳一步。i = 1,nums[1] = 2 是一个质数。因此,我们传送到索引 i = 3,因为 nums[3] = 6 可以被 2 整除。因此,答案是 2。
示例 2:
输入: nums = [2,3,4,7,9]
输出: 2
解释:
一个最优的跳跃序列是:
i = 0 开始。向相邻下标 i = 1 跳一步。i = 1,nums[1] = 3 是一个质数。因此,我们传送到下标 i = 4,因为 nums[4] = 9 可以被 3 整除。因此,答案是 2。
示例 3:
输入: nums = [4,6,5,8]
输出: 3
解释:
0 → 1 → 2 → 3 移动。因此,答案是 3。
提示:
1 <= n == nums.length <= 1051 <= nums[i] <= 106整体是个 BFS 的框架。
设 $v=\textit{nums}[i]$。
怎么知道 $v$ 的倍数在哪?
预处理。在执行 BFS 之前,对于 $\textit{nums}$ 中的每个数 $x=\textit{nums}[i]$,把 $x$ 的的质因子 $p$ 和下标 $i$ 插入哈希表。哈希表的 key 是质数,value 是下标列表。
预处理后,从哈希表中可以直接获取到 $v$ 的倍数的下标。
注意遍历完下标列表后,要把列表从哈希表中删除(或者清空),避免反复遍历列表。比如一个有很多质数 $2$ 的数组,我们把这些 $2$ 的下标入队,出队的时候,不能重复遍历 $2$ 的倍数的下标列表。
代码实现时:
###py
# 预处理每个数的质因子列表,思路同埃氏筛
MX = 1_000_001
prime_factors = [[] for _ in range(MX)]
for i in range(2, MX):
if not prime_factors[i]: # i 是质数
for j in range(i, MX, i): # i 的倍数有质因子 i
prime_factors[j].append(i)
class Solution:
def minJumps(self, nums: List[int]) -> int:
n = len(nums)
groups = defaultdict(list)
for i, x in enumerate(nums):
for p in prime_factors[x]:
groups[p].append(i) # 对于质数 p,可以跳到下标 i
vis = [False] * n
vis[0] = True
q = [0]
for ans in count(0):
tmp = q
q = []
for i in tmp:
if i == n - 1:
return ans
idx = groups[nums[i]]
idx.append(i + 1)
if i:
idx.append(i - 1)
for j in idx: # 可以从 i 跳到 j
if not vis[j]:
vis[j] = True
q.append(j)
idx.clear() # 避免重复访问下标列表
###java
class Solution {
private static final int MX = 1_000_001;
private static final List<Integer>[] primeFactors = new ArrayList[MX];
private static boolean initialized = false;
// 这样写比 static block 更快
public Solution() {
if (initialized) {
return;
}
initialized = true;
Arrays.setAll(primeFactors, _ -> new ArrayList<>());
// 预处理每个数的质因子列表,思路同埃氏筛
for (int i = 2; i < MX; i++) {
if (primeFactors[i].isEmpty()) { // i 是质数
for (int j = i; j < MX; j += i) { // i 的倍数有质因子 i
primeFactors[j].add(i);
}
}
}
}
public int minJumps(int[] nums) {
int n = nums.length;
Map<Integer, List<Integer>> groups = new HashMap<>();
for (int i = 0; i < n; i++) {
for (int p : primeFactors[nums[i]]) {
// 对于质数 p,可以跳到下标 i
groups.computeIfAbsent(p, _ -> new ArrayList<>()).add(i);
}
}
boolean[] vis = new boolean[n];
vis[0] = true;
List<Integer> q = List.of(0);
for (int ans = 0; ; ans++) {
List<Integer> tmp = q;
q = new ArrayList<>();
for (int i : tmp) {
if (i == n - 1) {
return ans;
}
List<Integer> idx = groups.computeIfAbsent(nums[i], _ -> new ArrayList<>());
idx.add(i + 1);
if (i > 0) {
idx.add(i - 1);
}
for (int j : idx) { // 可以从 i 跳到 j
if (!vis[j]) {
vis[j] = true;
q.add(j);
}
}
idx.clear(); // 避免重复访问下标列表
}
}
}
}
###cpp
const int MX = 1'000'001;
vector<int> prime_factors[MX];
int init = [] {
// 预处理每个数的质因子列表,思路同埃氏筛
for (int i = 2; i < MX; i++) {
if (prime_factors[i].empty()) { // i 是质数
for (int j = i; j < MX; j += i) { // i 的倍数有质因子 i
prime_factors[j].push_back(i);
}
}
}
return 0;
}();
class Solution {
public:
int minJumps(vector<int>& nums) {
int n = nums.size();
unordered_map<int, vector<int>> groups;
for (int i = 0; i < n; i++) {
for (int p : prime_factors[nums[i]]) {
groups[p].push_back(i); // 对于质数 p,可以跳到下标 i
}
}
vector<int8_t> vis(n);
vis[0] = true;
vector<int> q = {0};
for (int ans = 0; ; ans++) {
auto tmp = q;
q.clear();
for (int i : tmp) {
if (i == n - 1) {
return ans;
}
auto& idx = groups[nums[i]];
idx.push_back(i + 1);
if (i > 0) {
idx.push_back(i - 1);
}
for (int j : idx) { // 可以从 i 跳到 j
if (!vis[j]) {
vis[j] = true;
q.push_back(j);
}
}
idx.clear(); // 避免重复访问下标列表
}
}
}
};
###go
const mx = 1_000_001
var primeFactors = [mx][]int{}
func init() {
// 预处理每个数的质因子列表,思路同埃氏筛
for i := 2; i < mx; i++ {
if primeFactors[i] == nil { // i 是质数
for j := i; j < mx; j += i { // i 的倍数有质因子 i
primeFactors[j] = append(primeFactors[j], i)
}
}
}
}
func minJumps(nums []int) (ans int) {
n := len(nums)
groups := map[int][]int{}
for i, x := range nums {
for _, p := range primeFactors[x] {
groups[p] = append(groups[p], i) // 对于质数 p,可以跳到下标 i
}
}
vis := make([]bool, n)
vis[0] = true
q := []int{0}
for ; ; ans++ {
tmp := q
q = nil
for _, i := range tmp {
if i == n-1 {
return
}
idx := groups[nums[i]]
idx = append(idx, i+1)
if i > 0 {
idx = append(idx, i-1)
}
for _, j := range idx { // 可以从 i 跳到 j
if !vis[j] {
vis[j] = true
q = append(q, j)
}
}
delete(groups, nums[i]) // 避免重复访问下标列表
}
}
}
预处理的时间和空间不计入。
从终点 $n-1$ 跳到起点 $0$。
跳跃规则反过来,从 $i$ 跳到 $\textit{nums}[i]$ 的质因子 $p$ 的下标。
在执行 BFS 之前,对于 $\textit{nums}$ 中的每个数 $x=\textit{nums}[i]$,如果 $x$ 是质数,把 $x$ 和下标 $i$ 插入哈希表。哈希表的 key 是质数,value 是下标列表。
###py
# 预处理每个数的质因子列表,思路同埃氏筛
MX = 1_000_001
prime_factors = [[] for _ in range(MX)]
for i in range(2, MX):
if not prime_factors[i]: # i 是质数
for j in range(i, MX, i): # i 的倍数有质因子 i
prime_factors[j].append(i)
class Solution:
def minJumps(self, nums: List[int]) -> int:
n = len(nums)
groups = defaultdict(list)
for i, x in enumerate(nums):
if prime_factors[x] and prime_factors[x][0] == x: # x 是质数
groups[x].append(i)
ans = 0
vis = [False] * n
vis[-1] = True
q = [n - 1]
for ans in count(0):
tmp = q
q = []
for i in tmp:
if i == 0:
return ans
if not vis[i - 1]:
vis[i - 1] = True
q.append(i - 1)
if i < n - 1 and not vis[i + 1]:
vis[i + 1] = True
q.append(i + 1)
# 逆向思维:从 i 倒着跳到 nums[i] 的质因子 p 的下标 j
for p in prime_factors[nums[i]]:
idx = groups[p]
for j in idx:
if not vis[j]:
vis[j] = True
q.append(j)
idx.clear() # 避免重复访问下标列表
###java
class Solution {
private static final int MX = 1_000_001;
private static final List<Integer>[] primeFactors = new ArrayList[MX];
private static boolean initialized = false;
// 这样写比 static block 更快
public Solution() {
if (initialized) {
return;
}
initialized = true;
Arrays.setAll(primeFactors, _ -> new ArrayList<>());
// 预处理每个数的质因子列表,思路同埃氏筛
for (int i = 2; i < MX; i++) {
if (primeFactors[i].isEmpty()) { // i 是质数
for (int j = i; j < MX; j += i) { // i 的倍数有质因子 i
primeFactors[j].add(i);
}
}
}
}
public int minJumps(int[] nums) {
int n = nums.length;
Map<Integer, List<Integer>> groups = new HashMap<>();
for (int i = 0; i < n; i++) {
int x = nums[i];
if (!primeFactors[x].isEmpty() && primeFactors[x].get(0) == x) { // x 是质数
groups.computeIfAbsent(x, _ -> new ArrayList<>()).add(i);
}
}
boolean[] vis = new boolean[n];
vis[n - 1] = true;
List<Integer> q = List.of(n - 1);
for (int ans = 0; ; ans++) {
List<Integer> tmp = q;
q = new ArrayList<>();
for (int i : tmp) {
if (i == 0) {
return ans;
}
if (!vis[i - 1]) {
vis[i - 1] = true;
q.add(i - 1);
}
if (i + 1 < n && !vis[i + 1]) {
vis[i + 1] = true;
q.add(i + 1);
}
// 逆向思维:从 i 倒着跳到 nums[i] 的质因子 p 的下标 j
for (int p : primeFactors[nums[i]]) {
List<Integer> idx = groups.remove(p); // 避免重复访问下标列表
if (idx != null) {
for (int j : idx) {
if (!vis[j]) {
vis[j] = true;
q.add(j);
}
}
}
}
}
}
}
}
###cpp
const int MX = 1'000'001;
vector<int> prime_factors[MX];
int init = [] {
// 预处理每个数的质因子列表,思路同埃氏筛
for (int i = 2; i < MX; i++) {
if (prime_factors[i].empty()) { // i 是质数
for (int j = i; j < MX; j += i) { // i 的倍数有质因子 i
prime_factors[j].push_back(i);
}
}
}
return 0;
}();
class Solution {
public:
int minJumps(vector<int>& nums) {
int n = nums.size();
unordered_map<int, vector<int>> groups;
for (int i = 0; i < n; i++) {
int x = nums[i];
if (!prime_factors[x].empty() && prime_factors[x][0] == x) { // x 是质数
groups[x].push_back(i);
}
}
vector<int8_t> vis(n);
vis[n - 1] = true;
vector<int> q = {n - 1};
for (int ans = 0; ; ans++) {
auto tmp = q;
q.clear();
for (int i : tmp) {
if (i == 0) {
return ans;
}
if (!vis[i - 1]) {
vis[i - 1] = true;
q.push_back(i - 1);
}
if (i < n - 1 && !vis[i + 1]) {
vis[i + 1] = true;
q.push_back(i + 1);
}
// 逆向思维:从 i 倒着跳到 nums[i] 的质因子 p 的下标 j
for (int p : prime_factors[nums[i]]) {
auto it = groups.find(p);
if (it != groups.end()) {
for (int j : it->second) {
if (!vis[j]) {
vis[j] = true;
q.push_back(j);
}
}
groups.erase(it); // 避免重复访问下标列表
}
}
}
}
}
};
###go
const mx = 1_000_001
var primeFactors = [mx][]int{}
func init() {
// 预处理每个数的质因子列表,思路同埃氏筛
for i := 2; i < mx; i++ {
if primeFactors[i] == nil { // i 是质数
for j := i; j < mx; j += i { // i 的倍数有质因子 i
primeFactors[j] = append(primeFactors[j], i)
}
}
}
}
func minJumps(nums []int) (ans int) {
n := len(nums)
groups := map[int][]int{}
for i, x := range nums {
if len(primeFactors[x]) > 0 && primeFactors[x][0] == x { // x 是质数
groups[x] = append(groups[x], i)
}
}
vis := make([]bool, n)
vis[n-1] = true
q := []int{n - 1}
for ; ; ans++ {
tmp := q
q = nil
for _, i := range tmp {
if i == 0 {
return
}
if !vis[i-1] {
vis[i-1] = true
q = append(q, i-1)
}
if i < n-1 && !vis[i+1] {
vis[i+1] = true
q = append(q, i+1)
}
// 逆向思维:从 i 倒着跳到 nums[i] 的质因子 p 的下标 j
for _, p := range primeFactors[nums[i]] {
for _, j := range groups[p] {
if !vis[j] {
vis[j] = true
q = append(q, j)
}
}
delete(groups, p) // 避免重复访问下标列表
}
}
}
}
预处理的时间和空间不计入。
相邻下标间的移动很好处理:将每个下标看成点,向相邻下标连权值为 $1$ 的边。
质数传送怎么办呢?将每个质数看成特殊点,值为质数的下标向对应特殊点连权值为 $1$ 的边。同时对每个下标的值进行质因数分解,从每个质因数特殊点向该下标连权值为 $0$ 的边即可。
因为边权都是 $0$ 和 $1$,可以用 01 BFS 求最短路。复杂度 $\mathcal{O}(X\log X + n\log X)$,其中 $X = 10^6$ 是权值。
#define MAXA ((int) 1e6)
bool inited = false;
vector<int> fac[MAXA + 5];
void init() {
if (inited) return;
inited = true;
// XlogX 预处理所有数的质因数
for (int i = 2; i <= MAXA; i++) if (fac[i].empty()) for (int j = i; j <= MAXA; j += i) fac[j].push_back(i);
}
class Solution {
public:
int minJumps(vector<int>& nums) {
init();
int n = nums.size();
// 把要用到的质数都挑出来
unordered_map<int, int> mp;
for (int i = 0; i < n; i++) if (fac[nums[i]].size() == 1) mp[nums[i]] = 1;
int K = 0;
for (auto &p : mp) p.second = n + K, K++;
// 建图
vector<int> e[n + K], v[n + K];
for (int i = 0; i < n; i++) {
// 相邻下标移动
if (i > 0) e[i].push_back(i - 1), v[i].push_back(1);
if (i + 1 < n) e[i].push_back(i + 1), v[i].push_back(1);
// 质数传送:值为质数的下标向特殊点连边
if (fac[nums[i]].size() == 1) e[i].push_back(mp[nums[i]]), v[i].push_back(1);
// 质数传送:质因数特殊点向下标连边
for (int x : fac[nums[i]]) if (mp.count(x)) {
int idx = mp[x];
e[idx].push_back(i);
v[idx].push_back(0);
}
}
// 模板:01 BFS
int dis[n + K];
memset(dis, -1, sizeof(dis));
typedef pair<int, int> pii;
deque<pii> dq;
dq.push_back({0, 0}); dis[0] = 0;
while (!dq.empty()) {
pii p = dq.front(); dq.pop_front();
int sn = p.first;
if (dis[sn] != p.second) continue;
if (sn == n - 1) return dis[sn];
auto update = [&](int fn, int val) {
int d = dis[sn] + val;
if (dis[fn] == -1 || dis[fn] > d) {
dis[fn] = d;
if (val == 0) dq.push_front({fn, d});
else dq.push_back({fn, d});
}
};
for (int i = 0; i < e[sn].size(); i++) update(e[sn][i], v[sn][i]);
}
return -1;
}
};