【七】【算法分析与设计】双指针(1)

283. 移动零

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

复制代码

输入: nums = [0,1,0,3,12]输出: [1,3,12,0,0]

示例 2:

复制代码

输入: nums = [0]输出: [0]

提示:

  • 1 <= nums.length <= 10(4)

  • -2(31) <= nums[i] <= 2(31) - 1

**进阶:**你能尽量减少完成的操作次数吗?

复制代码
cpp 复制代码
class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        for (int dest = -1, cur = 0; cur < nums.size(); cur++)
            if (nums[cur])
                swap(nums[++dest], nums[cur]);
    }
};

我们利用destcur将数组还分成三个部分,[0,dest][dest+1,cur-1][cur,n-1],赋予这三个部分实际的意义,[0,dest]部分全都是非零元素,[dest+1]全都是0元素,[cur,n-1]是还没有处理的元素。

我们遍历所有元素,一直维护我们赋予的意义。到最后cur=n,数组被dest划分成两个部分,前者全都是非零元素,后者全都是零元素。接着只需要控制维护的代码,使得前者元素相对位置不发生改变即可。

如果nums[cur]==0,我们只需要cur++即可,这样nums[cur]直接进入到[dest+1,cur-1]区间中。

如果nums[cur]!=0,我们需要将这个元素加入到[0,dest]区间中,并且不改变相对位置,也就是需要加入到[0,dest]区间末尾。我们可以先dest++,然后交换nums[dest],nums[cur]元素,cur++,这样[0,dest]区间维护成功,并且[dest+1,cur-1]全都是零元素。全部都维护成功。

1089. 复写零

给你一个长度固定的整数数组 arr ,请你将该数组中出现的每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地进行上述修改,不要从函数返回任何东西。

示例 1:

输入: arr = [1,0,2,3,0,4,5,0] 输出: [1,0,0,2,3,0,0,4] **解释:**调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]

示例 2:

输入: arr = [1,2,3] 输出: [1,2,3] **解释:**调用函数后,输入的数组将被修改为:[1,2,3]

提示:

  • 1 <= arr.length <= 10(4)

  • 0 <= arr[i] <= 9

复制代码
cpp 复制代码
class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        int cur = 0, dest = -1,
            n = arr.size(); // cur dest对应对cur复写操作后 cur对应的位置
        while (cur < n) {
            if (arr[cur])
                dest++;
            else
                dest += 2;
            if (dest >= n - 1)
                break;
            cur++;
        }

        if (dest == n) {
            arr[n - 1] = 0;
            cur--;
            dest -= 2;
        }

        while (cur >= 0) {
            if (arr[cur])
                arr[dest--] = arr[cur--];
            else {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    }
};

(图片思路里面的dist写错了,实际上是dest。)

这段代码目的是在给定的整数数组 arr 中,每遇到一个0就在其后面插入一个额外的0,同时保持数组的大小不变,这意味着数组末尾的元素会被推出数组外。

int cur = 0, dest = -1, n = arr.size();这一行代码初始化了三个变量:curdestn[0,cur]表示待复写的数字,dest对应cur还没有复写的位置,也就是cur对应复写的位置。一直维护这个意义。

while (cur < n) {这个循环用于确定在不实际插入0的情况下,数组中的每个元素在扩展后的数组中的位置。[0,cur]表示已经复写过后的数字,dest对应[0,cur]复写过后的最后一个元素的位置。为了寻找最后一个复写数字的位置。

if (arr[cur]) dest++; else dest += 2;对于当前元素arr[cur],如果它不是0,dest只需增加1(因为当前元素不需要额外空间);如果它是0,则dest需要增加2,因为我们要在数组中为这个0额外插入一个0。

if (dest >= n - 1) break;这个条件判断是为了结束循环,当dest的值表明数组已经填充到最后或者要超出其原始大小时。

cur++;移动到下一个要处理的元素。

if (dest == n) { arr[n - 1] = 0; cur--; dest -= 2; }这一段特殊处理是为了应对dest走出数组界限的情况。

while (cur >= 0) {这个循环从数组的末尾开始,根据之前计算的dest位置,反向复制元素,以便在遇到0时能够正确地插入额外的0。[0,cur]表示待复写的数字,dest对应cur还没有复写的位置,也就是cur对应复写的位置。一直维护这个意义。

if (arr[cur]) arr[dest--] = arr[cur--]; else { arr[dest--] = 0; arr[dest--] = 0; cur--; }这里检查当前元素arr[cur],如果它不是0,就简单地将其复制到dest指示的位置,并同时更新destcur。如果当前元素是0,则在dest指示的位置设置两个0,并只更新cur一次。

时间复杂度分析:

第一个循环的时间复杂度为O(n),因为它至多遍历一次数组。

第二个循环也是O(n),因为它最多从数组的末尾遍历到开头。

因此,总的时间复杂度是O(n)。

空间复杂度分析:

这个算法只使用了几个变量(cur, dest, n)来追踪索引和数组的大小,没有使用额外的数据结构,因此空间复杂度为O(1)。

202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。

  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。

  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

  • 输入: n = 19 输出: true 解释: 1(2)+9(2) = 82 8(2)+2(2) = 68 6(2)+8(2) = 100 1(2)+0(2)+0(2) = 1

示例 2:

输入: n = 2 **输出:**false

提示:

  • 1 <= n <= 2(31)-1
复制代码
cpp 复制代码
class Solution {
public:
    int bitsum(int n) {
        int sum = 0;
        while (n) {
            int t = n % 10;
            sum += t * t;
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
        int slow = n, fast = bitsum(n);
        while (slow != fast) {
            slow = bitsum(slow);
            fast = bitsum(bitsum(fast));
        }
        return slow == 1;
    }
};

先通过题目给的两个实例进行模拟,寻找规律与发现。

n=191^2+9^2=82,8^2+2^2=68,6^2+8^2=100,1^2+0+0=1,是快乐数。

n=2,2^2=4,4^2+16,1^2+6^2=37,3^2+7^2=58,5^2+8^2=89,8^2+9^2=145,1^2+4^2+5^2=42,4^2+2^2=20,2^2+0=4。我们发现又出现了4这个数,因此会无限的循环下去。

那我们思考,是否存在一个数,他不是快乐数,但是一直变化的数是不会发生循环?

我们可以计算一下n的范围,2^31-1=2147483647≈2.1x10^9,也就是这个最大数是10位数,如果有一个10位数全都是9的额数,9999999999,我们很容易得到,任意一个n进行一个操作,都会小于9999999999进行一次操作,而109操作的结果是9^2=10=810,所以任意一个n进行一次操作的结果一定在[1,810]之间,得到的结果进行一次操作也一定在[1,810]之间。对于一个n对其进行811次操作,那么就一定至少有一次数进行重复出现,所以不可能出现一直变化但是不重复的情况。

鸽巢原理:n个巢,有n+1个鸽,那么就一定有一个巢的鸽子数是2

所以题目的情况就一定是两种情况,要么是快乐数,要么会重复出现一个数字然后无限循环。

我们可以想象成两种情况,第一种情况会出现环,但是这个环里的数全是1,这是快乐数。第二种情况也会出现环,但是环内的数没有1。因此我们可以使用快慢指针的方法解决这道问题。

快指针走两步,相当于计算两次,慢指针走一步,相当于计算一次,快慢指针一定会相遇,判断相遇时他们的值是多少,如果是1则是快乐数,如果不是1则不是快乐数。

结尾

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

相关推荐
荒古前1 分钟前
龟兔赛跑 PTA
c语言·算法
Colinnian4 分钟前
Codeforces Round 994 (Div. 2)-D题
算法·动态规划
用户00993831430110 分钟前
代码随想录算法训练营第十三天 | 二叉树part01
数据结构·算法
shinelord明14 分钟前
【再谈设计模式】享元模式~对象共享的优化妙手
开发语言·数据结构·算法·设计模式·软件工程
დ旧言~20 分钟前
专题八:背包问题
算法·leetcode·动态规划·推荐算法
_WndProc38 分钟前
C++ 日志输出
开发语言·c++·算法
薄荷故人_39 分钟前
从零开始的C++之旅——红黑树及其实现
数据结构·c++
努力学习编程的伍大侠1 小时前
基础排序算法
数据结构·c++·算法
XiaoLeisj1 小时前
【递归,搜索与回溯算法 & 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)
数据结构·算法·leetcode·决策树·深度优先·剪枝