算法学习了一段时间了,算法题有数学题,有找规律的题像第二个子数组题和约瑟夫问题,有递归动态规划这种猛找所有可能性的题,不过所有题都必须扣清楚边界,不然就有可能出错,算法题考逻辑思维,考细致性,我差远了。
一个数组中全是正数,求子数组和等于aim的最长子数组长度
int maxlen(vector<int> arr,int aim)
{
int i = 0; int j = 0;
int len = 0,sum=0;
while (j < arr.size())//当右指针到达数组右边界的下一个位置时就说明已经没有更长的子数组了
{
if (sum <= aim)
sum += arr[j++];//右指针右移,则sum增加
else
sum -= arr[i++];//左指针右移,则sum减小,因为所有数都是正数,所以可以通过左右指针移动来找到等于aim的左右边界
if (sum == aim)
len = len<(j - i)?j-i:len;
}
return len;
}
给定一个元素为实数的数组,求该数组中长度小于aim的子数组的最长长度?要求时间复杂度为O(n)
int maxlen2(vector<int> arr, int aim)
{
if(arr.empty())
return 0;
vector<int> m_s(arr.size(), 0);
vector<int> m_s_ind(arr.size(), 0);
m_s[arr.size() - 1] = arr.back();
m_s_ind[arr.size() - 1] = arr.size() - 1;
//以某一个位置开始的子数组中,他的最小子数组和的右边界是多少?这个问题可以根据以该位置之前一个位置为起点的最小子数组和为多少还有它的右边界来决定他的最小子数组的右边界,为正则右边界为它本身,为负则右边界为以下一个位置为起点的最小子数组和的右边界。所以我们可以从最后一个元素开始倒着往前推
for (int i = arr.size() - 2; i >= 0; i--)
{
if (m_s[i + 1] <= 0)
{
m_s[i] = arr[i] + m_s[i + 1];
m_s_ind[i] = m_s_ind[i + 1];
}
else
{
m_s[i] = arr[i];
m_s_ind[i] = i;
}
}
int res = 0;
int j = 0; int sum = 0;
for (int i = 0; i < arr.size(); i++)
{
//得出以每个位置为起点的最小子数组和数组和其右边界数组后,只需要遍历一次该数组我们就可以得出以该第i个位置为起点的子数组和小于的aim的子数组的最长长度为多少
while (j<arr.size()&&sum + m_s[j] <= aim)//如果sum加上下一个子数组和大于aim了就说明当前的j就是以i位置为起点的小于aim的最长子数组的右边界的下一个位置
{
sum += m_s[j];
j = m_s_ind[j] + 1;
}
res = max(res, j - i);
//m_s数组只需要遍历一次即可,每次i变化时只需要从当前遍历到的位置重新开始,并将sum的值减去移走的数字。
if (j == i)//i==j说明以第i的位置为起点的子数组的最长长度就是0,因为arr[i]>aim
{
j++;
sum = 0;//需要把sum置为0,因为sum可能保留着当前位置的值,只有i为起始位置时s原来的sum才为0
}
else
sum -= arr[i];//i!=j说明当前sum值大于0,以i开始的子数组的右边界与以i+1开始的子数组的右边界相同。
}
return res;
}
//约瑟夫问题,通过新编号来逆推旧编号的公式,找规律问题,贼难
//链表递减过程中长度是会改变的,要杀的序号可能大于链表的编号,怎么办?在坐标系中画图后可以得出,可以找出报数m和最大编号i之间的关系得出要去掉的结点,要去掉的编号-1 = (m-1)%i
//杀掉一个编号后,编号会产生变化,在坐标系中画图后可以得出新旧编号之间的关系
int getsurieve(int i, int m)
{
if (i == 1)
return 1;
return (getsurieve(i - 1, m) + m - 1) % i + 1;//报数和编号之间的关系为:编号=(报数-1)%最大编号+1;新编号和旧编号之间的关系:旧编号=(新编号+要杀的编号-1)%最大编号+1;合并求解之后得出这个return 公式
}
//正则表达式,表达式由*,.字符组成,其中*表示*之前的字符可以有无限多个,.可以表示任意字符,并且不能有两个连续的*
bool ifmatch(string& obj, string& exp, int i, int j)//这里的i,j有重复出现的可能性,所有可以用动态规划
{
//只能逐个字符串匹配,而当前字符之后一个元素是*时,我们可以跳过这两个字符,也可以在有任意多个字符都和它相等时,观察0到任意多个字符每个的下一次循环匹配的情况,这里循环里有多个循环,且子过程都一样,使用递归会方便很多
if (j == exp.size())//j到达末尾
return i == obj.size();//baseline情况是只有表达式和需要判断的字符都到末尾时才为true,否则exp先到达末尾则为false,i先到达末尾,则需要看exp的最后几组符号有几组是符号+*的模式
if (j + 1 == exp.size() || exp[j + 1] != '*')//j是末尾字符时,该字符不可能为*,且只有i也是最后一个字符的时候才有可能为1
{
if (i != obj.size() && exp[j] == obj[i] || exp[j] == '.')
return ifmatch(obj, exp, i + 1, j + 1);
else
return false;//i到达末尾且j没有到达末尾或者当前两个字符不相等
}
while (i != obj.size() && exp[j] == obj[i] || exp[j] == '.')
{
if (ifmatch(obj, exp, i, j + 2))
return true;
i++;
}
return ifmatch(obj, exp, i, j + 2);//如果exp[j]!=obj[i]或者i到达边界,则跳过当前两个
//i先到达末尾时,只有最后几组都是符号+*的模式才能返回true字符
}
//这个动态规划总结了一天,我才勉强总结出来,qnmdb吧
vector<vector<bool>> initdp(string str,string exp)
{
vector <vector<bool>> dp(str.size()+1, vector<bool>(exp.size()+1));
dp[str.size()][exp.size()] == 1;//baseline情况
if (str.size() > 0 && exp.size() > 0&&str[str.size() - 1]==exp[exp.size() - 1])
dp[str.size() - 1][exp.size() - 1] = 1;//i最后一个字符且j也为最后一个字符才为true
for (int i = exp.size() - 2; i >= 0; i-=2)
{
if (exp[i] != '*'&&exp[i + 1] == '*')
dp[str.size()][i] = 1;//i到达末尾时,只有exp的最后几组都是符号+*的模式才为true
else
break;
}
return dp;
}
bool dpmatch(string str, string exp)
{
vector <vector<bool>> dp = initdp(str,exp);//bool初始化为0,即false;
for (int i = str.size() - 1; i >= 0; i--)
{
for (int j = exp.size() - 2; j > -1; j--)
{
if (exp[j + 1] != '*')
{
if (exp[j] == str[i] || exp[j] == '.')
dp[i][j] = dp[i + 1][j + 1];//这种情况依赖于i+1,j+1位置
}
else//为了模仿递归版本的依赖过程,应该加个else
{
int temp = i;//这种情况依赖j+1列的某些行,
while (temp < str.size() && exp[j] == str[i] || exp[j] == '.')
{
if (dp[temp][j + 2])
{
dp[i][j] = 1;
break;
}
else
temp++;
}
if (!dp[i][j])//为了模仿递归版本,需要判断当前布尔值是否被改变过;
dp[i][j] = dp[i][j + 2];//这种情况依赖i,j+2位置
}
}
}
return dp[0][0];
}