【LeetCode刷题笔记-3 514:自由之路】

今天这题用到了动态规划,对于我来说有一丢丢难度,不过还是弄懂了。
一开始我是使用了贪心算法去做的:(此方法无法得到最优解)

思路是使用两个标识,一个顺时针查找,一个逆时针查找,选取查找到相应字母所用步数的最小值。

class Solution {
public:
    int findRotateSteps(string ring, string key) {
        int z1=0;//顺时针查找标识 
		int z2=0;//逆时针查找标识 
		int sum=0;//次数计算 
		for(int i=0;i<key.size();i++)//针对每一个key里面的字符 
		{
			while(ring[z1]!=key[i]&&ring[z2]!=key[i])//寻找匹配字符,因为题目说了一定会有匹配 
			{
				z2 = z2-1;
				z1 = z1+1;
				sum = sum+1;
				if(z2<0)
				{
					z2 = ring.size()-1;//越界重置位置 
				}
				if(z1==ring.size())
				{
					z1 = 0;//越界重置位置 
				}
			}
			if(ring[z1]==key[i])
			{
				z2=z1;//查找到了则将标识位置重置 
			}
			else
			{
				z1=z2;//查找到了则将标识位置重置  
			}
		}
		sum = sum + key.size();//需要加上按下按钮次数,即key字符个数 
		return sum;
    }
};

之后,会有一个解答错误的提示:
在这里插入图片描述
然后我就悟了,因为在Y之后选取N的时候,会涉及到一个选哪个N的问题,这个选N可能在当下是最优解,但是再到l的时候,会变成局部最优解而不是全局最优解(贪心算法的不足之一)。
比如你选了第三个N,那么再让字符旋转到 l 时,就会慢于第一个n。

于是,就有了官方题解的动态规划法:
记录每一种选法的路径值最后相加
官方的方法就类似于寻找最短路径或者是最小生成树的算法。

1.先用vector pos【26】数组记录每个字母出现在Ring中的位置

2.用一个二维数组记录当前状态下Key【i】字符到对应Ring【j】字符的距离的最小值,即是选择逆时针旋转还是顺时针旋转(也就是dp【i】【j】,这里dp【i】【j】矩阵的初始值要“无穷大”,因为需要最后计算总距离)

3.最后统计每列的值,最小的即为最短距离。

以下是算法带测试

#include<iostream>
#include<string>
#include<string.h>//memset()
#include<algorithm>//min,max
#include<cmath>
#include<vector>
using namespace std;

class Solution {
public:
    int findRotateSteps(string ring, string key) {
		int n = ring.size(), m = key.size();
        vector<int> pos[26];//pos中有26个元素,每一个都是vector<int>类型 
        for (int i = 0; i < n; ++i) {
            pos[ring[i] - 'a'].push_back(i);//记录ring中相同字母出现的位置 
        }
        int dp[m][n];
        memset(dp, 0x3f3f3f3f, sizeof(dp));//将数组都初始化为无穷大 
        for (auto& i: pos[key[0] - 'a']) {//对POS中每一个字母进行遍历 
            dp[0][i] = min(i, n - i) + 1;
        }
        for (int i = 1; i < m; ++i) {//对Key的每个字母循环 
            for (auto& j: pos[key[i] - 'a']) {//对当前比对循环 
                for (auto& k: pos[key[i - 1] - 'a']) {//对上一个Key值所在ring中的位置循环 
                    dp[i][j] = min(dp[i][j], dp[i - 1][k] + min(abs(j - k), n - abs(j - k)) + 1);//其中 min(abs(j - k), n - abs(j - k)) + 1表示在当前第 k个字符与 12:00方向对齐时第 j 个字符旋转到 12:00方向并按下拼写的最少步数。
					//dp[i][j] 表示从前往后拼写出 key 的第 i个字符,ring 的第 j 个字符与 12:00方向对齐的最少步数
                }
            }
        }
        return *min_element(dp[m - 1], dp[m - 1] + n);//最后把对应列加在一起,找出最小值 
    }
};

int main()
{
	string ring = "godding";
	string key = "gd";
	Solution s;
	int sum = s.findRotateSteps(ring,key);
	cout<<sum;
 } 

最后,附上这次代码中一些解析:

1.auto是自动识别变量的类型,就是根据后面的值来决定自己是什么,太久不用都快忘了。
有以下特性:
(1)auto不能作为函数参数
(2)auto不能直接声明数组
(3)为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
(4)auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。
(5)auto不能定义类的非静态成员变量
(6)实例化模板时不能使用auto作为模板参数
auto详情请见

2.关于无穷大0x3f3f3f的一些知识

(1)0x3f3f3f3f的十进制是1061109567,也就是109级别的(和0x7fffffff一个数量级),而一般场合下的数据都是小于109的,所以它可以作为无穷大使用而不致出现数据大于无穷大的情形。

(2)另一方面,由于一般的数据都不会大于10^9,所以当我们把无穷大加上一个数据时,它并不会溢出(这就满足了“无穷大加一个有穷的数依然是无穷大”),事实上0x3f3f3f3f+0x3f3f3f3f=2122219134,这非常大但却没有超过32-bit int的表示范围,所以0x3f3f3f3f还满足了我们“无穷大加无穷大还是无穷大”的需求。

(3)最后,0x3f3f3f3f还能给我们带来一个意想不到的额外好处:如果我们想要将某个数组清零,我们通常会使用memset(a,0,sizeof(a))这样的代码来实现(方便而高效),但是当我们想将某个数组全部赋值为无穷大时(例如解决图论问题时邻接矩阵的初始化),就不能使用memset函数而得自己写循环了(写这些不重要的代码真的很痛苦),我们知道这是因为memset是按字节操作的,它能够对数组清零是因为0的每个字节都是0,现在好了,如果我们将无穷大设为0x3f3f3f3f,那么奇迹就发生了,0x3f3f3f3f的每个字节都是0x3f!所以要把一段内存全部置为无穷大,我们只需要memset(a,0x3f,sizeof(a))。 所以在通常的场合下,const int INF = 0x3f3f3f3f;真的是一个非常棒的选择。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值