【优选算法】Pointer-Slice:双指针的算法切片(上)

文章目录

  • 1.概念解析
  • 2.移动零
  • 3.复写零
  • 4.快乐数
  • 5.盛最多水的容器
  • 希望读者们多多三连支持
  • 小编会继续更新
  • 你们的鼓励就是我前进的动力!

本篇是优选算法之双指针算法,该算法主要用于实现特定的算法逻辑,比如查找比较排序合并等操作,降低时间复杂度,减少空间复杂度,提高程序效率🚀

1.概念解析

🚩什么是双指针算法?

在这里插入图片描述

双指针算法使用两个索引来遍历数据结构,可以根据问题的要求,以不同的方式移动,如同向移动相向移动快慢不同的速度移动

2.移动零

✏️题目描述:

在这里插入图片描述

✏️示例:

在这里插入图片描述

传送门:移动零

题解:
💻第一步:

在这里插入图片描述

有两个索引 destcur
dest 表示在已处理的区间内非零元素的最后一个位置
cur 表示从左往右扫描数组遍历数组

在这里插入图片描述

把整个区间划分为三个部分,从前往后分别是非零区间0区间待处理区间

💻第二步:

根据题意我们要把 0 都移到数组末尾,所以是要注意是移动,而不是覆盖

在这里插入图片描述

刚开始dest 指向 -1的位置,表示非0区间还不存在,然后 cur 先向右移动,如果为 0 就继续向后;如果为非0,就让 dest 向后一位,然后和 cur 交换(因为 cur 遇到 0 不会改变其位置,所以在 dest 后面必定至少有一个 0,通过交换就能一直把 0 向后移)

在这里插入图片描述

总结后的代码如下:

在这里插入图片描述

💻代码实现:

#include <vector>
#include <iostream>
using namespace std;

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

3.复写零

✏️题目描述:

在这里插入图片描述

✏️示例:

在这里插入图片描述

传送门:复写零

题解:

双指针问题在解题通常要求就地操作,但往往很难立马想出来,所以可以先进行异地操作拓展思路。本题的异地操作就是额外创建一个数组,然后据题意操作即可,很简单就不过多讲解

💻第一步: 先找到最后一个复写的数

从前往后复写会发现会出现前一个数把后一个数覆盖的情况,所以我们尝试从后往前复写,发现是可行的,所以唯一的要点就是找到那个开始复写的数

请添加图片描述

如图为示例 1找到最后一个复写的数,那么是如何找到的呢?没有过多的技巧,就是要通过不断地画图尝试找到规律

cur 表示最后一个复写的数
dest 表示是否为最后一个数

在这里插入图片描述

如果 cur 的值为 0dest 向后两位如果 cur 的值为非 0dest 向后一位。那么就延伸出另一个问题,要是 dest 越界了怎么办?

💻第二步: 处理越界情况并从后往前复写

如果越界了,那么 dest 所在的位置一般默认为 0 ,但是在平台上越界就会报错,且这种情况的时候一定是因为 cur 最后一个复写数为 0 导致的

请添加图片描述

所以我们只需将 n-1 处赋为 0dest -= 2cur-- 即可回到最后一个复写数为非0的情况

在这里插入图片描述

接着再完成从后向前复写的操作即可

💻代码实现:

#include <iostream>
#include <vector>
using namespace std;

class Solution 
{
public:
    void duplicateZeros(vector<int>& arr)
    {
        //1.先找到最后一个数
        int cur = 0, dest = -1, n = arr.size();
        while (cur < n)
        {
            if (arr[cur])
            {
                dest++;
            }
            else
            {
                dest += 2;
            }

            if (dest >= n - 1)
            {
                break;
            }

            cur++;
        }
        //2.处理边界情况
        if (dest == n)
        {
            arr[n - 1] = 0;
            dest -= 2;
            cur--;
        }
        //3.从后向前完成复写操作
        while (cur >= 0)
        {
            if (arr[cur])
            {
                arr[dest--] = arr[cur--];
            }
            else
            {
                arr[dest--] = 0;
                arr[dest--] = 0;
                cur--;
            }
        }
    }
};

4.快乐数

✏️题目描述:

在这里插入图片描述

✏️示例:

在这里插入图片描述

传送门:快乐数

题解:
💻解读题意:

据题意快乐数的判断分为两种情况

是快乐数 ,以 1 循环(以示例 1 为例)

在这里插入图片描述

不是快乐数,自循环(以示例 2 为例)

在这里插入图片描述

看到这里显然需要我们判断是否成环,在链表部分了解过,应该使用快慢指针

在这里插入图片描述

💻细节问题:

如果题目没有说明只有两种情况,那是不是可能会出现第三种情况:线性死循环
答案是不会的,以下是一些简单的证明,不影响本题,作了解即可

在这里插入图片描述

我们假设 n 可取的最大值为 9 × 10⁹ ,那么经过快乐数操作的数为 810,接下来无论进行多少次操作都是在[1,810]里的数,那么在经过大于 810 次操作后,根据鸽巢原理,必然会有重复,也就是成环,所以第三种情况不可能存在

💻代码实现:

#include <iostream>
using namespace std;

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;
    }
};

5.盛最多水的容器

✏️题目描述:

在这里插入图片描述

✏️示例:

在这里插入图片描述

传送门:盛最多水的容器

题解:

看到这道题一般最先想到的是用两层for循环暴力枚举,但在本题会超时,时间复杂度为 O(n²),所以本题的思路是尽量把时间复杂度降为O(n)

尝试减少枚举数量来降低时间复杂度,本题求的是体积,所以我们可以在标记开头和结尾的下标为 left 和 right

在这里插入图片描述

v 为体积h 为高度w 为宽度,可以发现在取两边的数计算宽度时,先固定一个不动,然后另一个逐渐缩小宽度,小的那个数缩小之后算出来的体积永远是小的,所以我们可以通过不断舍掉小的那个数缩小宽度,然后得出多个体积数找出里面最大的那个

在这里插入图片描述

通过这种方式减少了不必要的枚举,降低了时间复杂度,只需遍历一遍数组就能得出最大的体积

💻代码实现:

#include <iostream>
#include <vector>
using namespace std;

class Solution 
{
public:
    int maxArea(vector<int>& height) 
    {
        int left = 0, right = height.size() - 1, ret = 0;
        while (left < right)
        {
            int v = (right - left) * min(height[left], height[right]);
            ret = max(v, ret);
            if (height[left] > height[right])
            {
                right--;
            }
            else
            {
                left++;
            }
        }
        return ret;
    }
};

希望读者们多多三连支持

小编会继续更新

你们的鼓励就是我前进的动力!

请添加图片描述

<think>嗯,用户想了解Golang中不同数据类型的定义以及它们在内存中的占用空间。首先,我需要回忆Golang的基本数据类型,比如整数、浮点数、布尔、字符串这些,还有复合类型如数组、切片、结构体、map和指针等。接下来,我得确定每种类型在内存中的大小,可能涉及到不同平台下的差异,比如int在32位和64位系统中的区别。 记得之前看过一些资料,比如引用[3]中提到,英文字符串每个字符占1字节,中文每个字符占3字节,这可能和UTF-8编码有关。字符串类型除了字符数据,还有指向底层数组的指针和长度信息,所以总大小应该是固定的头部加上字符数据本身的大小。比如,一个字符串变量可能占用16字节(64位系统)的固定部分,再加上字符长度乘以每个字符的字节数。 基本类型方面,像int8是1字节,int16是2字节,int32和rune是4字节,int64是8字节。uint同理。浮点数float32和float64分别占4和8字节。布尔类型bool是1字节,虽然理论上只需要1位,但内存对齐会让它占用1字节。指针的大小应该和系统架构相关,32位系统4字节,64位系统8字节。 复合类型的话,数组是连续存储的,所以总大小是元素类型大小乘以元素数量。比如[3]int32在64位系统下,每个int32是4字节,3个就是12字节。结构体需要考虑内存对齐,比如struct { a bool; b int32 },bool占1字节,可能需要填充3字节来让int32对齐到4字节,所以总大小是8字节。 切片的话,引用[1]提到切片由指针、长度和容量组成,每个部分在64位系统下是8字节,所以总共有24字节。map和通道的底层实现比较复杂,可能是指针或者引用类型,所以它们本身占用的空间可能是一个指针的大小,比如8字节,但实际数据存储在堆里,需要额外内存管理。 还有接口类型,比如interface{},包含类型指针和数据指针,各占8字节,所以总共有16字节。空结构体struct{}不占空间,常用于占位符。 需要注意不同平台下的差异,比如int和uint在32位和64位系统下的大小不同。此外,内存对齐对结构体和数组的影响,可能会增加填充字节,导致实际占用空间比预期大。 用户可能还想知道如何优化内存使用,比如选择合适的数据类型,避免不必要的内存浪费。比如,如果知道数值范围较小,使用int8而不是int。结构体字段的顺序调整可以减少填充,节省空间。对于字符串,拼接操作可能产生临时变量,需要注意。 最后,用户的问题可能涉及到性能优化,所以需要结合内存占用和算法选择,比如引用[2]提到的堆和散列表的应用场景。需要确保提供的信息准确,并引用相关参考资料来支持回答。</think>### Golang数据类型内存占用详解 Golang的数据类型内存占用与其底层存储结构直接相关,以下是关键分类及内存占用分析: --- #### 一、基本数据类型 1. **整型** - `int8/uint8`(byte):$1$字节 - `int16/uint16`:$2$字节 - `int32`(rune)/`uint32`:$4$字节 - `int64/uint64`:$8$字节 - `int/uint`:32位系统$4$字节,64位系统$8$字节[^1] 2. **浮点型** - `float32`:$4$字节(IEEE-754单精度) - `float64`:$8$字节(IEEE-754双精度) 3. **布尔型** - `bool`:$1$字节(最小寻址单位限制) 4. **指针** - 32位系统$4$字节,64位系统$8$字节 --- #### 二、复合数据类型 1. **字符串(string)** ```go type string struct { ptr *byte len int } ``` - 固定占用$16$字节(64位系统) - 数据部分:英文$1$字节/字符,中文$3$字节/字符(UTF-8编码)[^3] 2. **数组** - 内存连续分配 - 示例:`[3]int32`占用$3 \times 4 = 12$字节 3. **切片slice)** ```go type slice struct { ptr *T len int cap int } ``` - 固定占用$24$字节(64位系统)[^1] 4. **结构体(struct)** ```go type Example struct { a bool // 1字节 b int32 // 4字节 } // 总占用8字节(含3字节填充) ``` - 内存对齐规则可能导致填充字节 --- #### 三、引用类型 1. **map** - 底层哈希表实现 - 变量本身占用$8$字节(指针) 2. **通道(chan)** - 底层环形队列 - 变量本身占用$8$字节(指针) --- #### 四、特殊类型 1. **接口(interface)** ```go type iface struct { tab *itab data unsafe.Pointer } ``` - 固定占用$16$字节(64位系统) 2. **空结构体** - `struct{}`:$0$字节(用于信号传递) --- ### 内存优化技巧 1. 数值类型选择:根据范围选择`int8/int16` 2. 结构体字段排序:将同类型字段相邻存放减少填充 3. 字符串处理:避免频繁拼接(产生临时对象) ---
评论 176
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值