回文子串
首先回文子串问题涉及到的都是单个字符串,所以如果是单个字符串用动态规划的基本都是二维的,i-j
其次,回文字符串,都是从后往前遍历的,这个要记住。因为dp的状态转移方程特性决定的
647. 回文子串
还是用动态规划吧,中心扩散没太看懂
参考链接
说一下自己的想法,因为就单个字符串,因此我之前说过单个字符串的话是要有范围的。
为什么外循环会从len-1开始,对于字符串“cabac来说”,如果s[0]的c=s[4]的c,那么只需要看“aba”即可,如果你的for循环从0开始的换,你都从0过来了还看什么aba,只有从后往前,才能看aba吧
int countSubstrings(string s) {int len = s.size();vector<vector<bool>> dp(len, vector<bool>(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;
}
516. 最长回文子序列
没思路看这个
思路:这道题一定要和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<vector<int>> dp(len+1, vector<int>(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];}
1312. 让字符串成为回文串的最少插入次数
其他应用题
另类的DP套路!
887. 鸡蛋掉落
参考链接
最重要的是下面的这张图:
- 注意事项,一定要注意两层是 d p [ i ] [ j ] = a dp[i][j]=a dp[i][j]=a,三层的话肯定是最里面一层for有个变量temp,然后跳开里面这层才是dp赋值
int superEggDrop(int k, int n) {//dp数组的含义是到第n层有k个鸡蛋可以进行的最小的操作次数vector<vector<int>> dp(n+1, vector<int>(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<vector<int>> dp(n+1, vector<int>(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;}
312. 戳气球
-
思路分析,其实这道题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<int>& nums) {int n = nums.size();vector<int> 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<vector<int>> dp(n+2, vector<int>(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];}
贪心算法
<思想>
寻找最优解问题,一般将求解过程分成若干个步骤,每个步骤都应用贪心原则,当前(局部)最优的选择,从局部最优策略扩展到全局的最优解。基本步骤如下:
- 从某个初始解出发
- 采用迭代的过程,当可以向目标前进一步时,根据局部最优策略,得到一部分解然后缩小问题规模
- 将所有解综合起来。
<简单题>
455. 分发饼干
- 简单的贪心算法,排序+双指针
int findContentChildren(vector<int>& g, vector<int>& s) {sort(g.begin(), g.end());sort(s.begin(), s.end());cout<<g[0]<<endl;int i = 0;int j = 0;int g_size = g.size();int s_size = s.size();int count = 0;while(i < g_size && j <s_size){if(g[i] <= s[j]){i++;j++;count++;}else{j++;}}return count;}
1005. K 次取反后最大化的数组和
-
刚开始想着从小到大排序然后把小的变成负的就行,但是这样是有问题的,因为没有考虑负数的问题。
因此思路变成,计算一个数组中负数的个数,然后和k次比较,如果k大于负数的个数m,则将所有负数变成整数然后重新排序。
如果k小于负数的个数m,则最小的负数变成正数就行
int largestSumAfterKNegations(vector<int>& 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;}
860. 柠檬水找零
bool lemonadeChange(vector<int>& 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;}
<中等偏上>
376. ❤摆动序列
-
贪心算法: 局部最优然后达到全局最优
这道题想到错了这么多次
首先要注意,峰值最右边的永远有1个,因此count初值=1,但是这道题为什么卡这么久,因为首先越界不报错,我真是服了,第二就是判定条件,一定要看仔细!!!!
int wiggleMaxLength(vector<int>& 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;}
738. 单调递增的数字
-
思路:如果说是暴力破解的话,肯定是不可以的。
可以考虑局部最优,单调递增就意味着最后一位最大为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]:"<<str_num[i]<<endl;cout<<"str_num[i-1]:"<<str_num[i-1]<<endl;str_num[i] = '9';cout<<"str_num[i]:"<<str_num[i]<<endl;cout<<"str_num[i-1]:"<<str_num[i-1]<<endl;str_num[i-1]--;}}return stoi(str_num);
这样写是错误的,遇见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);}
135. 分发糖果
-
思路
“相邻的孩子中评分高的孩子必须获得更多的糖果”这句话拆分成为了两个规则:
-
从数组左边开始遍历,当
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<int>& ratings) {int n = ratings.size();if(n <= 1){return 1;}vector<int> left(n, 1);vector<int> 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;}
406. 根据身高重建队列
-
思路:
这道题就是排序,让数组变得有意义起来
思路很简单,首先根据第一个值倒序排序,因为第二个值的含义是前面有多少个大于等于第一个值的人,因此我们肯定先倒序。然后看第二个值,依次遍历数组,第二个值和索引比较,大于等于索引的就push_back,小于的就insert。
-
代码:
//加static是因为my_function函数其实有三个形参,第三个是this指针,但是sort中只用到了两个参数,参数不匹配//所以要加static,因为static成员函数没有this指针static bool my_function(vector<int>& vec1, vector<int>& vec2){return vec1[0] > vec2[0] || (vec1[0] == vec2[0] && vec1[1] < vec2[1]);}vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {sort(people.begin(), people.end(), my_function);vector<vector<int>> 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)类似类型的时候如何比较大小?我写到了代码集合里面
55. 跳跃游戏
-
思路
其实这道题应该换个问法,即通过下面数组的跳跃规则,最多可以跳多远?这样如果跳出去最远超过了数组长度,直接返回true就好了,小于数组长度说明跳不到最后一个格子。
-
代码
bool canJump(vector<int>& 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
能否到达。
45. 跳跃游戏 II
-
思路
贪婪贪婪,选择一个能调的最远的往下走,肯定就是最小值了
-
代码
int jump(vector<int>& 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
,因为题目上说了“假设你总是可以到达数组的最后一个位置”,因此我总能到达最后一个位置,不用考虑越界的情况