双指针+动态规划(上)力扣解析

目录

1:对撞指针

思路解析:

1:反转字符串

2:反转字符串中的单词|||

 思路解析:

边界问题:

3:两数之和||-输入有序数组

2:快慢指针

1:移除元素

2:最大连续1的个数

3:删除排序数组中的重复项

4:移动零

3:动态规划

1:斐波那契数

2:第N个泰波那契数

3:跳台阶


1:对撞指针

思路解析:

初始化:首指针指向头,尾指针指向尾

迭代过程:当满足条件A时,首指针向右逐步移动;当满足条件B时,尾指针逐步向左移动

迭代条件:首指针<尾指针

例题解析:模板就是下一题= =

1:反转字符串

思路解析:原地修改数组->我们不能再去开一个新的空间,让这个空间倒置存入元素

本题时典型的双指针问题:

初始化:首指针指向头,尾指针指向尾

迭代过程:当满足条件A时,首指针向右逐步移动;当满足条件B时,尾指针逐步向左移动

迭代条件:首指针<尾指针

代码实现:

void Swap(char* ch1,char* ch2)
{
	char t = * ch1;
	*ch1 = *ch2;
	*ch2 = t;
}
void reverseString(char* s, int sSize)
{
	int left = 0;
	int right = sSize - 1;
	while (left <= right)
	{
		Swap(&s[left], &s[right]);
		left++;
		right--;
	}
}

2:反转字符串中的单词|||

 思路解析:

本题和上题反转字符串一样,只不过需要注意边界问题,本题时是反转单词,由于英语单词之间有空格,字符串的末尾是‘\0’,我们把这两个之一作为右边界,左边界为第一个字母或是空格后的第一个字母之一,剩下的就是让头尾指针再合适的时候++,--即可

边界问题:

一定要注意顺序,在这里我们先遍历字符串,当s[i]为空格,我们就让右指针指向空格前的一个字母,此时左指针初始化为0,二者像回文串那节一样交换,然后left++,right--即可,注意跳出回文串的交换循环后,让left=i+1,即空格后的第一个字母,即第二个单词的首字母,之后就是循环了

代码实现:

void Swap(char *ch1, char *ch2)
{
	char t = *ch1;
	*ch1 = *ch2;
	*ch2 = t;
}
char* reverseWords(char* s) 
{
	int left = 0;
	int right = 0;
	for (int i = 0; i <= strlen(s); i++)//遍历
	{
		if (s[i] == ' ' || s[i]== '\0')//如果遍历到的位置为空格或是字符串的尾端
		{
			right = i-1;//右指针指向空格前的一个字符
			while(left<=right)//双指针交换反转
			{
				Swap(&s[left],&s[right]);//交换
				left++;
				right--;
			}
			left = i + 1;//左指针=空格后的一个位置,也就是第二个单词的第一个字符
		}
	}
	return s;
}

3:两数之和||-输入有序数组

 

 思路分析:由于次数组为递增序列,我们就可以使用二分查找或是双指针了,但是很明显双指针的效率更高,二分查找需要遍历一遍+二分查找,时间复杂度为O(N*logN),双指针只需遍历一遍,时间复杂度为:O(N)

初始化:首指针指向头,尾指针指向尾

迭代过程:当满足条件A时,首指针向右逐步移动;当满足条件B时,尾指针逐步向左移动

迭代条件:首指针<尾指针

 一样的模板一样的套路

代码实现:

#include<malloc.h>
int* twoSum(int* numbers, int numbersSize, int target, int* returnSize)
{
    int* ans = (int*)malloc(sizeof(int) * 2);//返回的是对应元素组成的下标+1数组
    *returnSize = 2;
    
    int left = 0;
    int right = numbersSize - 1;
    while (left < right)
    {
        int sum = numbers[left] + numbers[right];
        if (sum == target)
        {
            ans[0] = left + 1;
            ans[1] = right + 1;
            return ans;
        }
        else if (sum < target)//当sum小于target说明值小了,右值无法增大,只能左值右移增大
        {
            left++;
        }
        else//当sum大于target说明值大了,左值无法减小,只能右值左移减小
        {
            right--;
        }
    }
    ans[0] = -1;
    ans[1] = -1;
    return ans;
}

2:快慢指针

 力扣icon-default.png?t=M0H8https://leetcode-cn.com/leetbook/read/array-and-string/cv3bv/思路解析:

初始化:有时是将快慢指针都赋值为0,有时是将慢指针赋值为0,快指针为1

迭代过程:满足条件A,快指针覆盖慢指针所指的值;不满足条件A,快指针++,慢指针不动

迭代条件:快指针没有走到尾

1:移除元素

 思路解析:看到这种有对于数组而言有两种不同的条件的可以联想到快慢指针,对于数组元素而言要么满足条件值,要么不满足条件值

初始化:有时是将快慢指针都赋值为0,有时是将慢指针赋值为0,快指针为1

迭代过程:满足条件A,快指针覆盖慢指针所指的值;不满足条件A,快指针++,慢指针不动

迭代条件:快指针没有走到尾

 代码实现:

int removeElement(int* nums, int numsSize, int val) 
{
	int slow = 0;
	int fast = 0;
	for (fast=0; fast < numsSize; fast++)
	{
		if (nums[fast] != val)
		{
			nums[slow] = nums[fast];
			slow++;
		}
	}
	return slow;
}

特殊说明:

 所以一开始肯定存在这样的疑问

为什么,明明后面的数只是拷贝过去,实际还是存在的,答案就在这里了= =


2:最大连续1的个数

 思路解析:但凡看到最大最小,数组中又有多个结果的,绝大多数都是动态规划+快慢指针

 初始化:count=0,maxCount=0

迭代过程:当nums[count]==1时 -> count++

                  当nums[count]!=1时,刷新最大值,maxCount=fmax(count,maxCount)

                   并将count=0,让它重新开始计数

迭代条件:当count指向尾端时

 代码实现:

int findMaxConsecutiveOnes(int* nums, int numsSize) 
{
    int maxCount = 0, count = 0;
    for (int i = 0; i < numsSize; i++) 
    {
        if (nums[i] == 1)
        {
            count++;
        }
        else 
        {
            maxCount = fmax(maxCount, count);
            count = 0;
        }
    }
    maxCount = fmax(maxCount, count);
    return maxCount;
}

注意:

当count=0刷新过后,直到尾端都没有再遇到0,那么此时就有可能count的值比maxcount大

所以最后一步还是需要在两者中取最大值


3:删除排序数组中的重复项

 思路解析:很经典的快慢指针,唯一需要注意的就是初始化问题,题目要求的时删除重复项,且该数组有序,所以第一个元素一定不要删除,所以初始化slow=1,fast=1;剩下的就是套路了

int removeDuplicates(int* nums, int numsSize)
 {
    if (numsSize == 0) 
    {
        return 0;
    }
    int fast = 1, slow = 1;
    while (fast < numsSize) 
    {
        if (nums[fast] != nums[fast - 1]) 
        {
            nums[slow++] = nums[fast];
        }
        fast++;
    }
    return slow;
}

注意:返回的slow,这个时候你应该不会意外了吧^ ^,返回的是有效数组的长度


4:移动零

 思路解析:由于没什么特殊的,初始化slow,fast均从0开始,其实快指针可以从1开始,但慢指针必须从0开始,因为要考虑第一位数是0的情况,然后由于不是返回的数组的有效长度,所以我们需要在原来的基础上进行简单的补0操作即可

void moveZeroes(int* nums, int numsSize) 
{
	int slow = 0;
	int fast = 0;
	for (fast; fast < numsSize; fast++)
	{
		if (nums[fast] != 0)
		{
			nums[slow++] = nums[fast];
		}
	}
	for (int i = numsSize-1; i >= slow; i--)
	{
		nums[i] = 0;
	}
}

3:动态规划

 

 

 我的理解就是:有很多的结果,但是要求的是最优解

1:斐波那契数

 解析:用递归的话太废时间和空间了,运行效率低下,因为每一次寻找都要再次寻找它的前面两个数:图解为:

优化:

如果我们能把对应的数字存储起来,我们就不用再花时间去寻找了,直接返回即可

class Solution {
public:
    int fib(int n) 
    {
        if(n==0)
        {
            return 0;
        }
        if(n==1)
        {
            return 1;
        }
        vector<int> dp(n+1);
        dp[1]=1;
        dp[2]=1;
        for(int i=3;i<n+1;i++)
        {
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

问:为什么要创建n+1的大小的vector?

答:因为要用到n这个下标,即返回dp[n]


2:第N个泰波那契数

解析:与上一题一样

class Solution {
public:
    int tribonacci(int n) {
    if (n == 0)
	{
		return 0;
	}
	if (n == 1)
	{
		return 1;
	}
	if (n == 2)
	{
		return 1;
	}
	vector<int> dp(n+1, 0);
    dp[0]=0;
    dp[1]=1;
    dp[2]=1;
	for (int i = 3; i < n+1; i++)
	{
		dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
	}
	return dp[n];
    }
};

3:跳台阶

 解析:这道题你可以尝试用列多种情况来找出规律,也可以凭数学直觉

比如如何爬上第三层楼呢?

可以从第(n-1)层跳1层

也可以从第(n-2)层跳2层

规律就出来了^ ^

f(n)=f(n-1)+f(n-2)

代码实现:

class Solution {
public:
    int climbStairs(int n) {
        if (n == 1)
	{
		return 1;
	}
	if (n == 2)
	{
		return 2;
	}
	vector<int> dp(n + 1,0);
	dp[0] = 0;
	dp[1] = 1;
    dp[2]=2;
	for (int i = 3; i < n + 1; i++)
	{
		dp[i] = dp[i - 1] + dp[i - 2];
	}
	return dp[n];
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值