首先回文子串问题涉及到的都是单个字符串,所以如果是单个字符串用动态规划的基本都是二维的,i-j
其次,回文字符串,都是从后往前遍历的,这个要记住。因为dp的状态转移方程特性决定的
还是用动态规划吧,中心扩散没太看懂
参考链接
说一下自己的想法,因为就单个字符串,因此我之前说过单个字符串的话是要有范围的。
为什么外循环会从len-1开始,对于字符串“cabac来说”,如果s[0]的c=s[4]的c,那么只需要看“aba”即可,如果你的for循环从0开始的换,你都从0过来了还看什么aba,只有从后往前,才能看aba吧
int countSubstrings(string s) {
int len = s.size();
vector> dp(len, vector(len, false));
int count = 0;
for(int i = len - 1; i >= 0; i--){
for(int j = i; j < len; j++){
if(s[i] == s[j] ){
if(j-i <= 1){
count++;
dp[i][j] = true;
}
else if(dp[i + 1][j - 1]){
count++;
dp[i][j] = true;
}
}
}
}
return count;
}
没思路看这个
思路:这道题一定要和647放在一起看,这两道题是一模一样的类型。
这道题的难点主要在于dp的状态转移过程,来分析一下
如果s[i]与s[j]相同,那么 d p [ i ] [ j ] = d p [ i + 1 ] [ j − 1 ] + 2 dp[i][j] = dp[i + 1][j - 1] + 2 dp[i][j]=dp[i+1][j−1]+2,因为回文串的个数嘛,+2
如果s[i]与s[j]不相同,说明s[i]和s[j]的同时加入 并不能增加[i,j]区间回文子串的长度,那么分别加入s[i]、s[j]看看哪一个可以组成最长的回文子序列。
加入s[j]的回文子序列长度为 d p [ i + 1 ] [ j ] dp[i + 1][j] dp[i+1][j]
加入s[i]的回文子序列长度为 d p [ i ] [ j − 1 ] dp[i][j - 1] dp[i][j−1]
那么dp[i][j]一定是取最大的,即: d p [ i ] [ j ] = m a x ( d p [ i + 1 ] [ j ] , d p [ i ] [ j − 1 ] ) dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]) dp[i][j]=max(dp[i+1][j],dp[i][j−1])
int longestPalindromeSubseq(string s) {
int len = s.size();
vector> dp(len+1, vector(len+1, 0));
for(int i = 1; i < len + 1; i++ ){
dp[i][i] = 1;
}
for(int i = len ; i >= 1; i--){
//这里j从i+1开始,和上一题不一样,因为不考虑本身
for(int j = i + 1; j < len+1; j++){
if(s[i-1] == s[j-1]){
dp[i][j] = dp[i+1][j-1] + 2;
}else{
//不构成回文串了,不能+1了
dp[i][j] = max(dp[i][j-1], dp[i+1][j]);
}
}
}
return dp[1][len];
}
另类的DP套路!
参考链接
最重要的是下面的这张图:
int superEggDrop(int k, int n) {
//dp数组的含义是到第n层有k个鸡蛋可以进行的最小的操作次数
vector> dp(n+1, vector(k+1, 0));
for(int i = 1; i < k + 1; i++){
dp[1][i] = 1;
}
//一个鸡蛋扔肯定每楼扔一次
for(int i = 1; i < n + 1; i++){
dp[i][1] = i;
}
for(int i = 2; i < n + 1; i++){
for(int j = 2;j < k + 1; j++){
//楼层区间
int temp = INT_MAX;
for(int m = 1; m <= i; m++){
//最坏就是最大
temp = min(temp, max(dp[m-1][j-1], dp[i-m][j])+1);
}
dp[i][j] = temp;
}
}
return dp[n][k];
}
这样子写会超时!不过重要的是思路
第二种思路!!!!
其实第二种思路最主要的就是对于dp数组的设计了,dp设计好了一道题也就自然而然的解开了
我们把dp设计成 d p [ i ] [ j ] dp[i][j] dp[i][j]表示有i个鸡蛋,走了m步能到的层数,因此 d p [ i ] [ j ] dp[i][j] dp[i][j]就是层数,只要层数大于等于n,就可以返回j
状态转移方程这样理解:
当我们扔鸡蛋的时候,都是两种情况,碎或者不碎,不管碎没碎,都用掉了一步(+1),
无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上。
无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)
因此 d p [ i ] [ j ] dp[i][j] dp[i][j]的i是次数,不管碎没碎总是i-1!!
至于为啥初始化dp数组的时候要用k+1和n+1是因为最多次数不可能超过楼层数吧!!!
int superEggDrop(int k, int n) {
vector> dp(n+1, vector(k+1, 0));
for(int i = 1; i < n + 1; i++){
for(int j = 1; j < k + 1; j++){
dp[i][j] = dp[i-1][j]/*鸡蛋没碎,注意这里面i是次数!*/ + dp[i-1][j-1]/*鸡蛋碎了*/ + 1;
if(dp[i][j] >= n){
return i;
}
}
}
return n;
}
思路分析,其实这道题dp应该这样定义,即 d p [ i ] [ j ] dp[i][j] dp[i][j]指的是区间i到j中,所得到的气球的最大值。
那么经过前面的洗礼,我们很自然而然的就会想到在i和j之间用一个参数k来分割,因此自然而然是三个for循环,然后
d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , 状态转移 ) dp[i][j] = max(dp[i][j], 状态转移) dp[i][j]=max(dp[i][j],状态转移)
很自然而然
但是这道题很经典在于,k这个气球是最后一个被戳爆的,一定要记住!!!
根据状态转移方程可以画一下图,看看求 d p [ i ] [ j ] dp[i][j] dp[i][j]需要先求那一行一列,很清楚明白
参考链接
int maxCoins(vector& nums) {
int n = nums.size();
vector temp_nums(n+2);
temp_nums[0] = 1;
for(int i = 0; i < n ; i++){
temp_nums[i+1] = nums[i];
}
temp_nums[n+1] = 1;
vector> dp(n+2, vector(n+2, 0));
for(int i = n; i >= 0; i--){
for(int j = i + 1; j < n + 2; j++){
for(int k = i + 1; k < j; k++){
dp[i][j] = max(dp[i][j], dp[i][k]+dp[k][j]+(temp_nums[k]* temp_nums[i]* temp_nums[j]));
}
}
}
return dp[0][n+1];
}
寻找最优解问题,一般将求解过程分成若干个步骤,每个步骤都应用贪心原则,当前(局部)最优的选择,从局部最优策略扩展到全局的最优解。基本步骤如下:
int findContentChildren(vector& g, vector& s) {
sort(g.begin(), g.end());
sort(s.begin(), s.end());
cout<
刚开始想着从小到大排序然后把小的变成负的就行,但是这样是有问题的,因为没有考虑负数的问题。
因此思路变成,计算一个数组中负数的个数,然后和k次比较,如果k大于负数的个数m,则将所有负数变成整数然后重新排序。
如果k小于负数的个数m,则最小的负数变成正数就行
int largestSumAfterKNegations(vector& nums, int k) {
int neg_num = 0;
int len = nums.size();
int sum = 0;
for(int i = 0; i < len; i++){
if(nums[i] < 0){
neg_num++;
}
}
sort(nums.begin(), nums.end());
if(k > neg_num){
//先把负数变成正数
for(int i = 0; i < neg_num; i++){
nums[i] = -nums[i];
}
k = (k - neg_num) % 2;
//接下来全部变成正数数组
sort(nums.begin(), nums.end());
for(int i = 0; i < k; i++){
nums[i] = -nums[i];
}
for(int i = 0; i < len; i++){
sum+=nums[i];
}
}else{
for(int i = 0; i < k; i++){
nums[i] = -nums[i];
}
for(int i = 0; i < len; i++){
sum+=nums[i];
}
}
return sum;
}
bool lemonadeChange(vector& bills) {
int len = bills.size();
if(bills[0] != 5){
return false;
}
//注意只有5,10,20的面值
int five = 0;
int ten = 0;
int twenty = 0;
int temp = 0;
for(int i = 0; i < len; i++){
if(bills[i] == 5){
five++;
}
else if(bills[i] == 10){
ten++;
}
else if(bills[i] == 20){
twenty++;
}
temp = bills[i] - 5;
if(temp == 5){
five--;
}
else if(temp == 15){
//更倾向于10+5这种方式找零
if(ten > 0 ){
ten--;
five--;
}else{
five = five - 3;
}
}
if(five < 0 || ten < 0 || twenty <0){
return false;
}
}
return true;
}
贪心算法: 局部最优然后达到全局最优
这道题想到错了这么多次
首先要注意,峰值最右边的永远有1个,因此count初值=1,但是这道题为什么卡这么久,因为首先越界不报错,我真是服了,第二就是判定条件,一定要看仔细!!!!
int wiggleMaxLength(vector& nums) {
//峰值法
int pre = 0;
int now = 0;
int n = nums.size();
if(n <= 1){
return n;
}
int count = 1;
for(int i = 0; i < n-1 ; i++){
now = nums[i+1] - nums[i];
if((pre <= 0 && now > 0) || (pre >= 0 && now < 0)){
count++;
pre = now;
}
}
return count;
}
思路:如果说是暴力破解的话,肯定是不可以的。
可以考虑局部最优,单调递增就意味着最后一位最大为9。
参考链接
本来我没有加flag标志位,代码如下:
int monotoneIncreasingDigits(int n) {
string str_num = to_string(n);
int len = str_num.size();
if(n < 10){
return n;
}
for(int i = len - 1; i > 0; i--){
if(str_num[i] < str_num[i-1]){
cout<<"str_num[i]:"<
这样写是错误的,遇见100这个用例就知道了,进入if的时候i为1,因此要加一个flag标志位,从该标志为往后都设置为9!
代码如下:
int monotoneIncreasingDigits(int n) {
string str_num = to_string(n);
int len = str_num.size();
if(n < 10){
return n;
}
int flag = len;
for(int i = len - 1; i > 0; i--){
if(str_num[i] < str_num[i-1]){
flag = i;
str_num[i-1]--;
}
}
for(int i = flag; i < len; i++){
str_num[i] = '9';
}
return stoi(str_num);
}
思路
“相邻的孩子中评分高的孩子必须获得更多的糖果”这句话拆分成为了两个规则:
从数组左边开始遍历,当ratings[i] > rating[i-1]
时,则必须保证第i
个孩子的糖果比第i-1
个的多
这个是后比较是不完整的,比如说[1,0,2]
这个数组,只比较了0,1
和2,0
,对于0,2
和1,0
这个顺序没有对比
因此还要从数组的右边开始遍历,比对一次,当ratings[i] > ratings[i]+1
的时候,保证第i
个孩子的糖果比第i+1
个的多
因此加入有个数组时[1,0,2]
,左边开始遍历得到数组[1,1,2]
,右边开始遍历的到数组[2,1,1]
,对于两个数组的同一个索引取最大值,即2+1+2=5
。
代码
int candy(vector& ratings) {
int n = ratings.size();
if(n <= 1){
return 1;
}
vector left(n, 1);
vector right(n, 1);
for(int i = 1; i < n; i++){
if(ratings[i] > ratings[i - 1]){
left[i] = left[i - 1] + 1;
}
}
for(int i = n - 2; i >= 0; i-- ){
if(ratings[i] > ratings[i + 1]){
right[i] = right[i + 1] + 1;
}
}
int candy_num = 0;
for(int i = 0; i < n; i++){
candy_num +=max(left[i], right[i]);
}
return candy_num;
}
思路:
这道题就是排序,让数组变得有意义起来
思路很简单,首先根据第一个值倒序排序,因为第二个值的含义是前面有多少个大于等于第一个值的人,因此我们肯定先倒序。然后看第二个值,依次遍历数组,第二个值和索引比较,大于等于索引的就push_back,小于的就insert。
代码:
//加static是因为my_function函数其实有三个形参,第三个是this指针,但是sort中只用到了两个参数,参数不匹配
//所以要加static,因为static成员函数没有this指针
static bool my_function(vector& vec1, vector& vec2){
return vec1[0] > vec2[0] || (vec1[0] == vec2[0] && vec1[1] < vec2[1]);
}
vector> reconstructQueue(vector>& people) {
sort(people.begin(), people.end(), my_function);
vector> temp;
for(int i = 0; i < people.size(); i++){
if(people[i][1] >= i){
temp.push_back(people[i]);
}else{
temp.insert(temp.begin() + people[i][1], people[i]);
}
}
return temp;
}
补充
当数组时(key,value)类似类型的时候如何比较大小?我写到了代码集合里面
思路
其实这道题应该换个问法,即通过下面数组的跳跃规则,最多可以跳多远?这样如果跳出去最远超过了数组长度,直接返回true就好了,小于数组长度说明跳不到最后一个格子。
代码
bool canJump(vector& nums) {
int jump_to_index = 0;
int n = nums.size();
for(int i = 0; i < n ; i++){
if(i > jump_to_index){
return false;
}
jump_to_index = max(jump_to_index, i + nums[i]);
}
return true;
}
首先我觉得这个题用“能跳跃到的索引”表示是最好的,因为 i + nums[i]
指的就是能够跳跃到的最大的索引。
有了上面这个理解下面就好理解很多,之前代码错就是将for里面的判断语句放到了下面,其实应该先判断在计算,先判断就表明对于下一个索引,jump_to_index
能否到达。
思路
贪婪贪婪,选择一个能调的最远的往下走,肯定就是最小值了
代码
int jump(vector& nums) {
int jump_to_index = 0;
int end_index = 0;
int step = 0;
int n = nums.size();
for(int i = 0; i < n-1; i++){
jump_to_index = max(jump_to_index, i+nums[i]);
if(i == end_index){
step++;
end_index = jump_to_index;
}
}
return step;
}
i < n-1
是因为最后一次到达最后一个位置就不用再跳跃了。
为什么是i == end_index
,因为题目上说了“假设你总是可以到达数组的最后一个位置”,因此我总能到达最后一个位置,不用考虑越界的情况