赞
踩
基于对C++语法的学习,我觉得算法对现在来说也是非常重要的,以此写下博客来记录每日的算法学习,从基础的开始,有更优秀的解法欢迎各位佬们在评论区分享自己优秀的解法。
常⻅的双指针有两种形式,⼀种是对撞指针,⼀种是左右指针。
⼀般⽤于顺序结构中,也称左右指针。
• 对撞指针从两端向中间移动。⼀个指针从最左端开始,另⼀个从最右端开始,然后逐渐往中间逼 近。
• 对撞指针的终⽌条件⼀般是两个指针相遇或者错开(也可能在循环内部找到结果直接跳出循 环),也就是:
◦ left == right (两个指针指向同⼀个位置)
◦ l eft > right (两个指针错开)
⼜称为⻳兔赛跑算法,其基本思想就是使⽤两个移动速度不同的指针在数组或链表等序列 结构上移动。 这种⽅法对于处理环形链表或数组⾮常有⽤。 其实不单单是环形链表或者是数组,如果我们要研究的问题出现循环往复的情况时,均可考虑使⽤快 慢指针的思想。 快慢指针的实现⽅式有很多种,最常⽤的⼀种就是:
• 在⼀次循环中,每次让慢的指针向后移动⼀位,⽽快的指针往后移动两位,实现⼀快⼀慢。
算法思路:
在本题中,我们可以⽤⼀个 的最后⼀个位置。根据 cur 指针来扫描整个数组,另⼀个 dest 指针⽤来记录⾮零数序列 cur 在扫描的过程中,遇到的不同情况,分类处理,实现数组的划分。 在 cur 遍历期间,使 [0, dest] 的元素全部都是⾮零元素, [dest + 1, cur - 1] 的 元素全是零。
算法流程:
a. 初始化 cur = 0 (⽤来遍历数组), dest = -1 (指向⾮零元素序列的最后⼀个位置。 因为刚开始我们不知道最后⼀个⾮零元素在什么位置,因此初始化为
b. cur 依次往后遍历每个元素,遍历到的元素会有下⾯两种情况:
i. 遇到的元素是 0 ,-1 ) cur 直接 ++ 。因为我们的⽬标是让 的元素全都是零,因此当 [dest + 1, cur - 1] 内 cur 遇到 0 的时候,直接 ++ ,就可以让 0 在 cur - 1 的位置上,从⽽在 [dest + 1, cur - 1] 内;
ii. 遇到的元素不是 0 , dest++ ,并且交换 cur++ ,扫描下⼀个元素。 cur 位置和 dest 位置的元素,之后让
• 因为 dest 指向的位置是⾮零元素区间的最后⼀个位置,如果扫描到⼀个新的⾮零元 素,那么它的位置应该在 dest + 1 的位置上,因此 dest 先⾃增 1 ;
• de st++ 之后,指向的元素就是 0 元素(因为⾮零元素区间末尾的后⼀个元素就是 0 ),因此可以交换到 cur 所处的位置上,实现 元素, [dest + 1, cur - 1] 的元素全是零。
代码实现:
- 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]);
- }
- };
贪心算法是一种常见的算法思想,它在每一步选择都采取当前状态下最优的选择,以期望最终能够得到全局最优解。贪心算法通常适用于求解最优化问题,但并不保证一定能够得到最优解。
贪心算法的基本思路是通过局部最优解来推导全局最优解。在每一步选择中,贪心算法会根据某种规则或者评价函数,选择当前状态下的最优解,并将其作为下一步的起点。这样不断地进行下去,直到达到最终的目标。
贪心算法的优点是简单、高效,适用于一些特定的问题。但是,由于贪心算法只考虑当前状态下的最优解,并没有考虑全局的情况,所以不能保证一定能够得到最优解。在某些情况下,贪心算法可能会得到次优解或者错误的结果。
总结一下,贪心算法是一种通过每一步选择当前最优解来推导全局最优解的算法思想。它简单高效,但不能保证一定能够得到最优解。
- class Solution
- {
- public:
- bool lemonadeChange(vector<int>& bills)
- {
- int five = 0, ten = 0;
- for(auto x : bills)
- {
- if(x == 5) five++; // 5元,直接收下
-
- else if(x == 10) // 10 元:找零5元
- {
- if(five == 0) return false;
- five--; ten++;
- }
- else // 20 元:分情况讨论
- {
- if(ten && five) // 贪心
- {
- ten--; five--;
- }
- else if(five >= 3)
- {
- five -= 3;
- }
- else return false;
- }
- }
- return true;
- }
- };
动态规划是一种常用的优化问题求解方法,它通过将原问题分解为若干子问题,并保存子问题的解来避免重复计算,从而实现高效的求解。动态规划通常适用于具有重叠子问题和最优子结构性质的问题。
动态规划算法的基本思想是:将原问题划分为若干个子问题,先求解子问题的解,再根据子问题的解推导出原问题的解。为了避免重复计算,动态规划算法使用一个表格(通常是二维数组)来保存子问题的解,这样可以在需要时直接查表获取已经计算过的结果。
动态规划算法的步骤如下:
1. 定义状态:将原问题划分为若干个子问题,并定义状态表示子问题的解。
2. 确定状态转移方程:根据子问题之间的关系,确定状态之间的转移方程,即如何通过已知状态推导出未知状态。
3. 初始化:初始化表格中的一些特殊状态,通常是边界条件。
4. 递推计算:按照状态转移方程,从小规模的子问题开始逐步计算出大规模问题的解,并将结果保存在表格中。
5. 求解原问题:根据表格中保存的结果,得到原问题的解。
动态规划算法的时间复杂度通常是子问题个数乘以求解一个子问题的时间复杂度,即O(nk),其中n是问题规模,k是子问题的个数。动态规划算法的空间复杂度通常是O(n),需要额外的空间来保存子问题的解。
1. 状态表⽰: 这道题可以「根据题⽬的要求」直接定义出状态表⽰: dp[i] 表⽰:第 i 个泰波那契数的值。
2.状态转移方程:
dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3](题目贴心的告诉了我们,后面的题需要我们自己一步一步的推)
3.初始化:
从我们的递推公式可以看出, 为 dp [-2] 或 dp[i] 在 i = 0 以及 i = 1 的时候是没有办法进⾏推导的,因 dp[-1] 不是⼀个有效的数据。因此在填表之前,我们需要将0,1,2,位置的值填上,题目告诉我们dp[0]=0 , do[1] = 1,dp[2] = 1.
4.填表顺序:
毫无疑问,从左到右填表
5.返回值: 返回dp[n] 的值。
1.一维数组填表:
- class Solution {
- public:
- int tribonacci(int n)
- {
- if(n==1||n==0) return n;
- vector<int> dp(n+1);//dp[i]表示第i个泰波那契数的值
- dp[0]=0,dp[1]=1,dp[2]=1;//初始化
- //从左往右填表
- for(int i=3;i<=n;i++)
- dp[i] = dp[i-3] + dp[i-2] + dp[i-1];
- return dp[n];
- }
- };
2:使用滚动数组填表:
- class Solution
- {
- public:
- int tribonacci(int n)
- {
- if(n == 0) return 0;
- if(n == 1 || n == 2) return 1;
- int a = 0, b = 1, c = 1, d = 0;
- for(int i = 3; i <= n; i++)
- {
- d = a + b + c;
- a = b; b = c; c = d;
- }
- return d;
- }
- };
每日三题,如果有更好的解法,希望各位佬在评论区给出建议。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。