LeetCode 457. 环形数组是否存在循环(快慢指针判环)/1137. 第 N 个泰波那契数(矩阵快速幂)
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 457. 环形数组是否存在循环(快慢指针判环)/1137. 第 N 个泰波那契数(矩阵快速幂)相关的知识,希望对你有一定的参考价值。
457. 环形数组是否存在循环
2021.8.7 每日一题
题目描述
存在一个不含 0 的 环形 数组 nums ,每个 nums[i] 都表示位于下标 i 的角色应该向前或向后移动的下标个数:
如果 nums[i] 是正数,向前 移动 nums[i] 步
如果 nums[i] 是负数,向后 移动 nums[i] 步
因为数组是 环形 的,所以可以假设从最后一个元素向前移动一步会到达第一个元素,而第一个元素向后移动一步会到达最后一个元素。
数组中的 循环 由长度为 k 的下标序列 seq :
遵循上述移动规则将导致重复下标序列 seq[0] -> seq[1] -> … -> seq[k - 1] -> seq[0] -> …
所有 nums[seq[j]] 应当不是 全正 就是 全负
k > 1
如果 nums 中存在循环,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [2,-1,1,2,2]
输出:true
解释:存在循环,按下标 0 -> 2 -> 3 -> 0 。循环长度为 3 。
示例 2:
输入:nums = [-1,2]
输出:false
解释:按下标 1 -> 1 -> 1 … 的运动无法构成循环,因为循环的长度为 1 。根据定义,循环的长度必须大于 1 。
示例 3:
输入:nums = [-2,1,-1,-2,-2]
输出:false
解释:按下标 1 -> 2 -> 1 -> … 的运动无法构成循环,因为 nums[1] 是正数,而 nums[2] 是负数。
所有 nums[seq[j]] 应当不是全正就是全负。
提示:
1 <= nums.length <= 5000
-1000 <= nums[i] <= 1000
nums[i] != 0
进阶:你能设计一个时间复杂度为 O(n) 且额外空间复杂度为 O(1) 的算法吗?
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/circular-array-loop
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
我是这样想的,模拟这个过程,把当前位置变成0,表示达到过了,当再次到达为0的位置时,就表示有环;因为我觉得如果本次到达过的下标不能形成环的话,如果下次有个下标移动到这里了,那肯定还是不能形成环
class Solution {
public boolean circularArrayLoop(int[] nums) {
int l = nums.length;
//boolean[] used = new boolean[l];
int symbol = 1;
for(int i = 0; i < l; i++){
//如果这个点到达过了,就跳过
if(nums[i] == 0)
continue;
int temp = nums[i];
symbol = temp > 0 ? 1 : -1;
nums[i] = 0;
int next = (i + temp + l) % l;
if(next == i)
continue;
while(true){
if(nums[next] * symbol < 0)
break;
if(nums[next] == 0)
return true;
temp = nums[next];
nums[next] = 0;
int next2 = (next + temp + l) % l;
if(next2 == next)
break;
next = next2;
}
}
return false;
}
}
然后写了以后,发现第三个例子过不了,因为最后一个位置移动的时候,前面都是被遍历过的,肯定会返回true
那么怎么改呢,如何才能做到使这种情况消失呢,就想到了用一个数组表示当前位置是否可行,也就是说遍历到当前下标i的时候,那肯定前面的所有下标x(小于i)都是不可行的,所以在while循环中加一句判断,如果小于i,那么就break,意思就是要往前面的位置走的话,肯定不行
class Solution {
public boolean circularArrayLoop(int[] nums) {
int l = nums.length;
//boolean[] used = new boolean[l];
int symbol = 1;
for(int i = 0; i < l; i++){
//如果这个点到达过了,就跳过
if(nums[i] == 0)
continue;
int temp = nums[i];
//当前符号
symbol = temp > 0 ? 1 : -1;
//把遍历过的位置置为0
nums[i] = 0;
//下一个位置
int next = (i + temp % l + l) % l;
//如果直接和当期位置相等了,说明是自旋,不行
if(next == i)
continue;
//否则,开始走
while(true){
//如果next是比当前i小的,说明走到前面了,而前面都遍历过,不可能,所以跳出
if(next < i)
break;
//如果符号不同,跳出
if(nums[next] * symbol < 0)
break;
//如果当前位置遍历过了,那么就说明有环
if(nums[next] == 0)
return true;
temp = nums[next];
//置为0
nums[next] = 0;
//下一个位置
int next2 = (next + temp % l + l) % l;
if(next2 == next)
break;
next = next2;
}
}
return false;
}
}
然后[-1,-2,-3,-4,-5] 倒下了,因为遍历i = 1时,之前4的位置已经被标记成0了,那么就会返回true
然后就想到了,之前环内的肯定不行,所以想着怎么加一个是之前环内的判断
想了半天,也不好加
还是看题解吧:
快慢指针。我是真没想到
看了官解,发现其实快慢指针和我的想法是差不多的,我这里可以借鉴它的做法,在while中并不置0,而是在循环外才处理,也就是说,while里遇到的都是之前走过的,本次循环中走过的,是在while外进行置位,这样就可以有区分了,
过了,如果再另外弄个set,复杂度能在O(n)
class Solution {
public boolean circularArrayLoop(int[] nums) {
int l = nums.length;
//boolean[] used = new boolean[l];
int symbol = 1;
for(int i = 0; i < l; i++){
//如果这个点到达过了,就跳过
if(nums[i] == 0)
continue;
int temp = nums[i];
//当前符号
symbol = temp > 0 ? 1 : -1;
//把遍历过的位置置为0
nums[i] = 0;
//下一个位置
int next = (i + temp % l + l) % l;
//如果直接和当期位置相等了,说明是自旋,不行
if(next == i)
continue;
//否则,开始走
while(true){
//如果next是比当前i小的,说明走到前面了,而前面都遍历过,不可能,所以跳出
if(next < i)
break;
//如果符号不同,跳出
if(nums[next] * symbol < 0)
break;
//如果当前位置是之前遍历的,那么就跳出
if(nums[next] == 1001)
break;
//如果当前位置遍历过了,那么就说明有环
if(nums[next] == 0)
return true;
temp = nums[next];
//置为0
nums[next] = 0;
//下一个位置
int next2 = (next + temp % l + l) % l;
if(next2 == next)
break;
next = next2;
}
for(int j = 0; j < l; j++){
if(nums[j] == 0){
nums[j] = 1001;
}
}
}
return false;
}
}
快慢指针,其实思想是一样的
class Solution {
//双指针
public boolean circularArrayLoop(int[] nums) {
int n = nums.length;
for (int i = 0; i < n; i++) {
if (nums[i] == 0) {
continue;
}
//快慢指针
int slow = i, fast = next(nums, i);
//判断非零且方向相同,注意这个循环中,并没有把遍历过的点置为0
//而遇到为0的点,就不满足条件,跳出
while (nums[slow] * nums[fast] > 0 && nums[slow] * nums[next(nums, fast)] > 0) {
//如果快慢指针相遇了
if (slow == fast) {
//并且不是自旋
if (slow != next(nums, slow)) {
return true;
} else {
break;
}
}
slow = next(nums, slow);
fast = next(nums, next(nums, fast));
}
//把遍历过程中走过的点都置为0,表示已经走过了
int add = i;
while (nums[add] * nums[next(nums, add)] > 0) {
int tmp = add;
add = next(nums, add);
nums[tmp] = 0;
}
}
return false;
}
public int next(int[] nums, int cur) {
int n = nums.length;
return ((cur + nums[cur]) % n + n) % n; // 保证返回值在 [0,n) 中
}
}
然后看了三叶姐的题解,发现和我的思想是一样的啊啊啊,感动,最起码发现自己的方法还是可行的
但是三叶姐确能写到O(n)的复杂度,又学习了
可以用一个数组来表示,当前数和遍历过的轮次,used[i] = idx,表示当前位置i被遍历过,且是第idx轮遍历的
或者直接在原数组上标记,可以表示为1000+idx
class Solution {
int OFFSET = 10000;
public boolean circularArrayLoop(int[] nums) {
int l = nums.length;
//boolean[] used = new boolean[l];
int symbol = 1;
int idx = 1; //表示第几轮
for(int i = 0; i < l; i++){
//如果这个点到达过了,就跳过
if(nums[i] > OFFSET)
continue;
int cur = i;
int temp = nums[i];
//当前符号
symbol = temp > 0 ? 1 : -1;
//把遍历过的位置置为OFFSET + idx
nums[i] = OFFSET + idx;
//否则,开始走
while(true){
//下一个位置
int next = (cur + temp % l + l) % l;
temp = nums[next];
//如果下一个位置和当前位置相同,跳过
if(next == cur)
break;
//如果当前位置在本轮遍历过了,那么就说明有环
if(nums[next] == OFFSET + idx)
return true;
//如果符号不同,跳出
if(nums[next] * symbol < 0)
break;
//如果当前位置是之前遍历的,那么就跳出
if(nums[next] > OFFSET)
break;
nums[next] = OFFSET + idx;
cur = next;
}
idx++;
}
return false;
}
}
1137. 第 N 个泰波那契数
题目描述
泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。
示例 1:
输入:n = 4
输出:4
解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4
示例 2:
输入:n = 25
输出:1389537
提示:
0 <= n <= 37
答案保证是一个 32 位整数,即 answer <= 2^31 - 1。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/n-th-tribonacci-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
class Solution {
public int tribonacci(int n) {
//动态规划
if(n < 3)
return n == 0 ? 0 : 1;
int x = 0;
int y = 1;
int z = 1;
int res = 0;
for(int i = 3; i <= n; i++){
res = x + y + z;
x = y;
y = z;
z = res;
}
return res;
}
}
矩阵快速幂
class Solution {
public int tribonacci(int n) {
//写个矩阵快速幂
if(n < 3)
return n == 0 ? 0 : 1;
int[][] base = {{1,1,1},{1,0,0},{0,1,0}};
int[][] res = pow(base, n);
return res[2][0] + res[2][1];
}
//
public int[][] pow(int[][] base, int n){
int k = 0;
int[][] res = {{1,0,0},{0,1,0},{0,0,1}};
while(n > 0){
//如果当前二进制位为1
if((n & 1) == 1){
res = multiply(res, base);
}
base = multiply(base, base);
n = n >> 1;
}
return res;
}
public int[][] multiply(int[][] a, int[][] b){
int[][] res = new int[3][3];
for(int i = 0; i < 3; i++){
for(int j = 0; j < 3; j++){
res[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
}
}
return res;
}
}
以上是关于LeetCode 457. 环形数组是否存在循环(快慢指针判环)/1137. 第 N 个泰波那契数(矩阵快速幂)的主要内容,如果未能解决你的问题,请参考以下文章