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 个泰波那契数(矩阵快速幂)的主要内容,如果未能解决你的问题,请参考以下文章

判断环形数组是否存在循环

判断环形数组是否存在循环

数据结构与算法之深入解析“环形数组是否存在循环”的求解思路与算法示例

环形队列—LeetCode 622. 设计循环队列

LeetCode:环形链表

判断环形数组是否存在环--强行dfs记忆化 or 快慢指针解决