动态规划
做题步骤:
1.确定好dp数组及其下标含义
2.确定递推公式
3.dp数组如何初始化
4.确定遍历顺序
5.举例推导dp数组
实例表现:
最长回文子序列
解题思路
dp[i][j]含义:表示 s 的第 i 个字符到第 j 个字符组成的子串中,最长的回文序列长度是多少。
状态转移方程:
->子序列:元素可以不连续
如果 s 的第 i 个字符和第 j 个字符相同的话,那么dp[i][j] = dp[i+1][j-1] + 2;
如果 s 的第 i 个字符和第 j 个字符不同的话,那么dp[i][j] = max(dp[i+1][j],dp[i][j-1]
遍历顺序:i 从最后一个字符开始往前遍历,j 从 i + 1 开始往后遍历,这样可以保证每个子问题都已经算好了。
初始化:i == j 时dp[i][j] =1(单个字符的最长回文序列是 1)
代码实现
int longestPalindromeSubseq(char * s){
int n,i,j;
n = strlen(s);
if(n == 1){
return n;
}
int max = 1;
int dp[1001][1001]={0};
//dp[i][j]表示第i个字符到第j个字符之间的回文子序列的长度
for(i = 1;i < n+1;i ++){
dp[i][i] = 1;
}
/**dp含义、递推公式、初始化、遍历顺序
s[i] == s[j]时,那么dp[i][j] = dp[i+1][j-1] + 2;
s[i] != s[j]时,dp[i][j] = max(dp[i+1][j],dp[i][j-1]
*/
for(i = n;i >= 1;i--){
for(j = i+1;j <= n;j++){
if(s[i-1] == s[j-1]){
dp[i][j] = dp[i+1][j-1] + 2;
}else{
// dp[i][j] = fmax(dp[i+1][j],dp[i][j-1]);
if(dp[i+1][j] > dp[i][j-1]){
dp[i][j] = dp[i+1][j];
}else{
dp[i][j] = dp[i][j-1];
}
}
if(max < dp[i][j]){
max = dp[i][j];
}
}
// max = fmax(max,dp[i][j]);
}
return max;
}
打家劫舍_1
解题思路:
i 的含义:表示前i步能偷的最大金额(第i家是否必偷)
状态转移方程:
偷?dp[i-2]+nums[i]
不偷?dp[i-1]->不一定非要偷,dp[i-1]不一定偷nums[i-1]
->取最大值
每家必偷,再加上上一次偷的最大值
dp[i] = max(dp[i-2],dp[i-3])+ nums[i];
初始化:
dp[0] = nums[0],
dp[1] = nums[1],
dp[2] = nums[2] + nums[0];
代码实现:
class Solution {
int max(int a,int b){
if(a > b)return a;
else return b;
}
public int rob(int[] nums) {
int[] dp = new int [1001];
int m = 0;
int n = nums.length;
dp[0] = nums[0];
m = max(dp[0],m);
if(n == 1){
return m;
}
dp[1] = nums[1];
m = max(dp[1],m);
if(n == 2){
return m;
}
dp[2] = nums[2] + nums[0];
m = max(dp[2],m);
if(n == 3){
return m;
}
for(int i = 3;i < n;i++){
dp[i] = max(dp[i-2],dp[i-3])+nums[i];
m = max(dp[i],m);
}
return m;
}
}
打家劫舍_2
在打家劫舍_1的基础上添加了限制条件,nums[0],nums[n-1]相邻
解题思路
改进点:当偷第一家时,dp数组在n-2就结束循环;
不偷第一家时,dp数组从dp[1]开始初始化,在dp[n-1]结束循环
找出最大的金额
边界处理:当数组只有三个时,取出最大值即可
代码实现
class Solution {
int max(int a,int b){
a = a > b ? a : b;
return a;
}
public int rob(int[] nums) {
int[] dp = new int [1001];
int n = nums.length;
int m;
if(n == 1){
return nums[0];
}
m = max(nums[0],nums[1]);
if(n == 2){
return m;
}
if(n == 3){
return max(nums[2],m);
}
dp[0] = nums[0];
dp[1] = nums[1];
dp[2] = max ,nums[1]);
m = max(dp[2],m);
for(int i = 3;i < n-1;i++){
dp [i] = max(dp[i-2],dp[i-3])+nums[i];
m = max(m,dp[i]);
}
int s;
dp[1] = nums[1];
dp[2] = nums[2];
s = max(nums[1],nums[2]);
dp[3] = max(nums[1] + nums[3],nums[2]);
s = max(dp[3],s);
for(int i=4;i<n;i++){
dp [i] = max(dp[i-2],dp[i-3])+nums[i];
s = max(s,dp[i]);
}
m = max(m,s);
return m;
}
}
930. 和相同的二元子数组 - 力扣(LeetCode)中等
int numSubarraysWithSum(int* nums, int numsSize, int goal){
int hash[60010];
int i;
int ans=0;
for(i=1;i<numsSize;i++){
nums[i] += nums[i-1];
}
memset(hash,0,sizeof(hash));
hash[goal]=1;
for(i=0;i<numsSize;i++){
ans += hash[nums[i]];
hash[nums[i]+goal]++;
}
return ans;
}
31. 下一个排列 - 力扣(LeetCode)
1.第一个非递减的->记录下标i
2.i后排序,找到第一个比他大的交换
class Solution {
public void nextPermutation(int[] nums) {
int q = nums.length-1,p = nums.length-2,cur;
int temp = 0,i,j,flag=0;
//->找到不是递减的地方
for(q = nums.length-1,p = nums.length-2;p >= 0;p--,q--){
if(nums[p] < nums[q]){//i = 0,p=0,q=1,1<3
flag=1;
break;
}
}
//交换:nums[p]后排序->找到递增序列第一个大于nums[p]的数->交换
if(flag == 1){
Arrays.sort (nums,p+1,nums.length);
for(j=p+1;j<nums.length;j++){
if(nums[j] > nums[p]){
break;
}
}
temp = nums[p];
nums[p] = nums[j];
nums[j] = temp;
}else{
Arrays.sort (nums,0,nums.length);
}
}
}